package m2mcore_test import ( "context" "testing" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore" ) // makeResponse строит M2MTransferResponse сервиса МОСТ с заданным статусом и // одним кодом/текстом (как в реальном ответе НРД, напр. M2M14). func makeResponse(guid m2m.UUID, status m2m.StatusCode, code, text string) *m2m.M2MTransferResponse { return &m2m.M2MTransferResponse{ GUID: guid, StatusCode: status, Responses: []m2m.Response{{Code: code, Text: text}}, } } // TestReceiveServiceResponseError — ERROR (отказ сервиса, напр. M2M14) // переводит сделку в Rejected и сохраняет ответ для отображения в карточке. func TestReceiveServiceResponseError(t *testing.T) { d := newTestDeal(t) resp := makeResponse(d.GUID, m2m.StatusError, "M2M14", "Код ЭДО НРД отправителя отсутствует в справочнике участников M2M") if err := d.ReceiveServiceResponse(context.Background(), resp, nil); err != nil { t.Fatalf("ReceiveServiceResponse(ERROR): %v", err) } if d.State != m2mcore.StateRejected { t.Errorf("состояние %s, ожидалось %s", d.State, m2mcore.StateRejected) } if d.Response == nil || d.Response.StatusCode != m2m.StatusError { t.Errorf("ответ НРД не сохранён в сделке: %+v", d.Response) } if len(d.Response.Responses) == 0 || d.Response.Responses[0].Code != "M2M14" { t.Errorf("код ответа не сохранён: %+v", d.Response) } } // TestReceiveServiceResponseInfo — INFO (принято в обработку) сохраняет ответ, // но НЕ меняет состояние: ждём M2MTransferDecision контрагента. func TestReceiveServiceResponseInfo(t *testing.T) { d := newTestDeal(t) before := d.State resp := makeResponse(d.GUID, m2m.StatusInfo, "01", "Запрос на перевод принят в обработку") if err := d.ReceiveServiceResponse(context.Background(), resp, nil); err != nil { t.Fatalf("ReceiveServiceResponse(INFO): %v", err) } if d.State != before { t.Errorf("состояние изменилось на %s, ожидалось без изменений (%s)", d.State, before) } if d.Response == nil || d.Response.StatusCode != m2m.StatusInfo { t.Errorf("ответ НРД не сохранён: %+v", d.Response) } } // TestReceiveServiceResponseTerminalIdempotent — повторный ERROR на уже // терминальной (Rejected) сделке не валит FSM, ответ обновляется. func TestReceiveServiceResponseTerminalIdempotent(t *testing.T) { d := newTestDeal(t) first := makeResponse(d.GUID, m2m.StatusError, "M2M14", "первый отказ") if err := d.ReceiveServiceResponse(context.Background(), first, nil); err != nil { t.Fatalf("первый ERROR: %v", err) } // Поллер ИШ может прислать тот же пакет повторно — не должно паниковать // и не должно ломать состояние недопустимым переходом Rejected→Rejected. second := makeResponse(d.GUID, m2m.StatusError, "M2M14", "повторный отказ") if err := d.ReceiveServiceResponse(context.Background(), second, nil); err != nil { t.Fatalf("повторный ERROR на терминальной сделке: %v", err) } if d.State != m2mcore.StateRejected { t.Errorf("состояние %s, ожидалось %s", d.State, m2mcore.StateRejected) } } // TestReceiveServiceResponseNil — защита от nil. func TestReceiveServiceResponseNil(t *testing.T) { d := newTestDeal(t) if err := d.ReceiveServiceResponse(context.Background(), nil, nil); err == nil { t.Error("ожидалась ошибка на nil-ответе") } }