Эмуляция НРД: один переключатель Выключена/Подмена/Полная имитация; контур отдельно

Было: правило «боевой ЛК + тестовый профиль → принудительно имитатор» не давало выключить
эмуляцию, а подмена личности жила отдельным тумблером на другой странице — пользователь не мог
понять состояние и выключить.

Стало — явная модель (контур НРД и режим эмуляции независимы):
- «Выключена» — заявки идут в выбранный контур (NSD.Profile) с реальными данными клиента;
- «Подмена личности» — уходят в контур, но у клиентов из списка блок инвестора заменяется на
  тест-инвестора (Бубликов/Баранкин); DEPO и QUIK — по реальному клиенту;
- «Полная имитация» — в НРД ничего не уходит, bj сам отвечает (внутренний имитатор).
Режимы взаимоисключающие (ведутся флагами EmulateNSD/RealNSDTest.Enabled), принудительное
правило убрано. Единый 3-радио на /admin/emulators (как у QUIK), с показом контура, тест-инвестора
и списка паспортов; карточка на /admin/m2m-testing — статус + ссылка на «Эмуляторы».
Тесты обновлены под новую модель.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
fontvielle
2026-10-13 16:02:06 +03:00
parent 6ca556c2fc
commit cd6e2c424f
7 changed files with 140 additions and 91 deletions
+43 -17
View File
@@ -33,14 +33,28 @@ const lkEmuNote = " (тестовый режим: в НРД не отправл
// lkEmuSender — имитатор НРД для заявок ЛК; создаётся в Server.Run.
var lkEmuSender *mock.Sender
// lkEmulateNSD — идут ли заявки ЛК через имитатор НРД и почему.
func lkEmulateNSD(rc *RuntimeConfig) (bool, string) {
// nsdEmuMode — режим эмуляции НРД: "off" | "substitute" | "imitate". Выбор контура НРД
// (NSD.Profile — test3 / боевой) от режима НЕ зависит: при "off" заявка уходит в выбранный
// контур как есть. Режимы взаимоисключающие (ведутся флагами EmulateNSD / RealNSDTest.Enabled).
func nsdEmuMode(rc *RuntimeConfig) string {
s := rc.Snapshot()
if s.LK.EmulateNSD && devMode(rc) {
return true, "включена в настройках"
switch {
case s.LK.EmulateNSD:
return "imitate"
case s.LK.RealNSDTest.Enabled:
return "substitute"
}
if s.LK.ActiveEnv == "prod" && strings.HasPrefix(strings.ToLower(s.NSD.Profile), "test") {
return true, "боевой ЛК при тестовом контуре НРД (профиль ИШ " + s.NSD.Profile + ")"
return "off"
}
// lkEmulateNSD — обрабатывается ли заявка ЛК слоем эмуляции (имитация или подмена личности).
// "off" — заявка идёт в выбранный контур НРД с реальными данными клиента (обычный путь).
func lkEmulateNSD(rc *RuntimeConfig) (bool, string) {
switch nsdEmuMode(rc) {
case "imitate":
return true, "полная имитация — в НРД ничего не отправляется, ответ формирует bj"
case "substitute":
return true, "подмена личности — в НРД уходит тест-инвестор вместо клиента"
}
return false, ""
}
@@ -95,17 +109,29 @@ func lkProcessEmulated(ctx context.Context, rc *RuntimeConfig, svc *Service, ep
globalLKFlows.step(guid, id, "crane", "Проверка шлюза bj: можно исполнить",
fmt.Sprintf("Сверка с DEPO: %s (инвестор %s, счёт %s).", res.Summary, pick(res.Investor, "—"), pick(res.Account, "—")))
// Тест «боевой ЛК → TEST3» (realtest.go): клиент из списка — в настоящий НРД с подменой инвестора.
sender := m2mcore.NSDSender(lkEmuSender)
realNSD := realTestMatch(rc, inv.Document.Series, inv.Document.Number) && svc.sender != nil
if realNSD {
asInv, asText := realTestInvestor(rc, req.Data.InvestorInformation.IdentityDocument.DocumentType)
realTestPut(RealTestRec{GUID: guid, ClaimID: id, At: time.Now(), As: asText,
Client: strings.TrimSpace(fmt.Sprintf("%s %s %s, %s %s", inv.LastName, inv.FirstName, inv.MiddleName, inv.Document.Series, inv.Document.Number))})
sender = substituteSender{real: svc.sender, inv: asInv}
globalLKFlows.step(guid, id, "gateway", "Тест: в НРД (TEST3) — с данными тест-инвестора",
"У нас заявка проходит как клиент ("+inv.LastName+"): DEPO, QUIK МНП (боевая запись), статусы в ЛК. В НРД блок инвестора заменён на "+asText+"; GUID и референсы — настоящие.")
log.Printf("lk-gateway: ЛК #%d (%s): тест «боевой ЛК → TEST3» — в НРД как %s", id, shortGUID(guid), asText)
// Выбор отправителя по режиму эмуляции НРД:
// imitate — внутренний имитатор (в НРД ничего не уходит, ответ формирует bj);
// substitute — реальная отправка в выбранный контур НРД; клиент из списка идёт с подменой
// блока инвестора на тест-инвестора, остальные — с настоящими данными.
sender := m2mcore.NSDSender(lkEmuSender) // imitate (по умолчанию в этом пути)
realNSD := false
if nsdEmuMode(rc) == "substitute" && svc.sender != nil {
realNSD = true
if realTestMatch(rc, inv.Document.Series, inv.Document.Number) {
asInv, asText := realTestInvestor(rc, req.Data.InvestorInformation.IdentityDocument.DocumentType)
realTestPut(RealTestRec{GUID: guid, ClaimID: id, At: time.Now(), As: asText,
Client: strings.TrimSpace(fmt.Sprintf("%s %s %s, %s %s", inv.LastName, inv.FirstName, inv.MiddleName, inv.Document.Series, inv.Document.Number))})
sender = substituteSender{real: svc.sender, inv: asInv}
globalLKFlows.step(guid, id, "gateway", "Подмена личности: в НРД ("+rc.Snapshot().NSD.Profile+") с данными тест-инвестора",
"У нас заявка проходит как клиент ("+inv.LastName+"): DEPO, QUIK МНП (боевая запись), статусы в ЛК. В НРД блок инвестора заменён на "+asText+"; GUID и референсы — настоящие.")
log.Printf("lk-gateway: ЛК #%d (%s): подмена личности — в НРД как %s", id, shortGUID(guid), asText)
} else {
// подмена включена, но клиент не в списке — отправляем в НРД с настоящими данными
sender = svc.sender
globalLKFlows.step(guid, id, "gateway", "В НРД ("+rc.Snapshot().NSD.Profile+") с настоящими данными",
"Подмена включена, но этот клиент не в списке — запрос уходит в НРД как есть.")
log.Printf("lk-gateway: ЛК #%d (%s): отправка в НРД с настоящими данными (вне списка подмены)", id, shortGUID(guid))
}
}
if _, err := svc.SendBuiltRequestVia(ctx, sender, req, inv.ID, nil); err != nil {
if strings.Contains(err.Error(), "QUIK") {