Files
Bridge-and-Join-s/internal/lkgateway/lkemulate_test.go
T
fontvielle cd6e2c424f Эмуляция НРД: один переключатель Выключена/Подмена/Полная имитация; контур отдельно
Было: правило «боевой ЛК + тестовый профиль → принудительно имитатор» не давало выключить
эмуляцию, а подмена личности жила отдельным тумблером на другой странице — пользователь не мог
понять состояние и выключить.

Стало — явная модель (контур НРД и режим эмуляции независимы):
- «Выключена» — заявки идут в выбранный контур (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>
2026-10-13 16:02:06 +03:00

110 lines
4.3 KiB
Go

package lkgateway
import (
"context"
"path/filepath"
"strings"
"testing"
"time"
)
func TestLKDepoRejectText(t *testing.T) {
res := depoResult{Ran: true, Recommend: "reject", Secs: []depoSecResult{{
Security: "RU0009029540",
Checks: []depoCheck{
{Name: "Российский выпуск (RU-ISIN)", OK: true, Code: "M2M01"},
{Name: "Остаток ≥ количеству (98070.04)", OK: false, Code: "QTY", Detail: "на торговом счёте ВТНКЦ00000001: 0 шт., списываем 10 — недостаточно"},
{Name: "Задолженность по комиссиям", OK: true, Warn: true},
},
}}}
code, short, text := lkDepoRejectText(res)
if code != "QTY" {
t.Errorf("код %q, ожидали QTY", code)
}
if short != "Отказ по результатам проверки депозитария: "+m2mCodeLabel("QTY") || strings.Contains(short, "Причина") {
t.Errorf("короткий текст для ЛК: %q", short)
}
for _, want := range []string{"Отказ по результатам проверки депозитария", "(QTY)", "Причина: Остаток ≥ количеству (98070.04): на торговом счёте"} {
if !strings.Contains(text, want) {
t.Errorf("в тексте нет %q: %s", want, text)
}
}
if strings.Contains(text, "Российский выпуск") || strings.Contains(text, "комиссиям") {
t.Errorf("пройденные проверки не должны попадать в причину: %s", text)
}
}
func TestLKEmulateNSD(t *testing.T) {
rc, err := NewRuntimeConfig(filepath.Join(t.TempDir(), "setup.json"))
if err != nil {
t.Fatal(err)
}
s := rc.Snapshot()
// Контур test3 сам по себе эмуляцию НЕ включает — режим выбирается явно.
s.LK.ActiveEnv, s.NSD.Profile = "prod", "test3-gost"
if err := rc.UpdateLK(s.LK); err != nil {
t.Fatal(err)
}
if err := rc.UpdateNSD(s.NSD); err != nil {
t.Fatal(err)
}
if on, _ := lkEmulateNSD(rc); on {
t.Error("тестовый контур без режима: эмуляции быть не должно")
}
if m := nsdEmuMode(rc); m != "off" {
t.Errorf("режим по умолчанию: %q, ожидали off", m)
}
// Полная имитация.
s = rc.Snapshot()
s.LK.EmulateNSD = true
_ = rc.UpdateLK(s.LK)
if on, why := lkEmulateNSD(rc); !on || !strings.Contains(why, "имитаци") {
t.Errorf("полная имитация: %v %q", on, why)
}
if m := nsdEmuMode(rc); m != "imitate" {
t.Errorf("режим: %q, ожидали imitate", m)
}
// Подмена личности (взаимоисключающа с имитацией).
s = rc.Snapshot()
s.LK.EmulateNSD = false
s.LK.RealNSDTest.Enabled = true
_ = rc.UpdateLK(s.LK)
if on, why := lkEmulateNSD(rc); !on || !strings.Contains(why, "подмена") {
t.Errorf("подмена: %v %q", on, why)
}
if m := nsdEmuMode(rc); m != "substitute" {
t.Errorf("режим: %q, ожидали substitute", m)
}
}
// applyNSDEmuSettings переносит настройки со страницы «Эмуляторы» в конфиг работающего
// сендера: исход и код отказа меняются без пересоздания (канал решений тот же).
func TestApplyNSDEmuSettings(t *testing.T) {
old := lkEmuSender
lkEmuSender = newLKEmuSender()
defer func() { lkEmuSender.Stop(); lkEmuSender = old }()
rc, err := NewRuntimeConfig(filepath.Join(t.TempDir(), "setup.json"))
if err != nil {
t.Fatal(err)
}
if err := rc.UpdateNSDEmu(NSDEmuSettings{DelaySec: 1, Outcome: "reject", RejectCode: "18"}); err != nil {
t.Fatal(err)
}
applyNSDEmuSettings(rc)
if _, err := lkEmuSender.Send(context.Background(), sendFailRequest()); err != nil {
t.Fatal(err)
}
select {
case d := <-lkEmuSender.Decisions():
rej := d.Data.Securities[0].TransferDecision.Rejection
if rej == nil {
t.Fatal("ожидался отказ (outcome=reject)")
}
if len(rej.Codes) != 1 || rej.Codes[0] != "18" {
t.Errorf("код отказа %v, ожидался 18", rej.Codes)
}
case <-time.After(3 * time.Second):
t.Fatal("решение не пришло — задержка из настроек не применилась")
}
}