feat: живой цикл M2M с НРД + мастер установки ключа на флешку
Инфраструктура M2M (живой обмен с НРД через ИШ): - обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение ответа; INFO → ждём Decision; идемпотентность поллера - fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO - сырой XML ответа НРД в карточке заявки (для пересылки в ТП) - тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes, 4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта - редирект из теста сразу в карточку заявки Мастер установки ключа Валидаты на флешку (admin/setup/keywizard): - пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник сертификатов (CRL) → перезапуск+проверка ИШ → готово - привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен, bj-server остаётся в песочнице - сохранение структуры профиля архива (spr<N>), перечисление съёмных USB Прочее: - пакет-доказательство для ТП НРД + форма регистрации участника M2M - эталонные образцы робота (DOC/m2m_robot_samples) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
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-ответе")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user