feat(admin): эмулятор МНП — режим реальной записи в МНП на подставного клиента
Третий режим МНП (между «внутренняя имитация» и «боевая запись»): в bj цикл идёт по обычному пользователю, а поручение в QUIK МНП уходит РЕАЛЬНО, но с кодом другого, реально существующего в QUIK клиента (обобщение ручного теста realtest «боевой ЛК → TEST3»). - QuikMNPSettings: RealWrite + RealClientCode (валидация формата кода). - Подмена кода — единая точка quikClientCodeFromPort (после ClientCodeMap); портфель не трогаем. quikActive: RealWrite только при devMode и непустом коде; Emulate (внутренняя имитация) в приоритете. - Шаги боевого пути помечаются «[на подставного клиента <код>]» — видно в «Поручениях QUIK». - Карточка МНП на /admin/emulators — три взаимоисключающих режима (выключено / имитация / реальная запись), поле кода, крупное предупреждение; отдельный красный баннер «Реальные поручения в МНП на клиента <код> (режим разработки)» — не смешиваем с баннером эмуляции НРД. - disableEmulatorsIfDevOff сбрасывает и RealWrite (код остаётся, инертен), новость явно фиксирует прекращение реальных поручений.
This commit is contained in:
@@ -116,14 +116,14 @@ func TestEmulatorsSave(t *testing.T) {
|
||||
}
|
||||
|
||||
rec = post("/admin/emulators/quik", url.Values{
|
||||
"emulate": {"1"}, "step_delay_sec": {"7"}, "fail_register": {"1"}, "fail_deposit": {"1"},
|
||||
"mode": {"emulate"}, "step_delay_sec": {"7"}, "fail_register": {"1"}, "fail_deposit": {"1"},
|
||||
})
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("quik: %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
s = rc.Snapshot()
|
||||
if !s.Quik.Emulate || s.MNPEmu.StepDelaySec != 7 || !s.MNPEmu.FailRegister || !s.MNPEmu.FailDeposit || s.MNPEmu.FailSign {
|
||||
t.Errorf("после сохранения МНП: emulate=%v emu=%+v", s.Quik.Emulate, s.MNPEmu)
|
||||
if !s.Quik.Emulate || s.Quik.RealWrite || s.MNPEmu.StepDelaySec != 7 || !s.MNPEmu.FailRegister || !s.MNPEmu.FailDeposit || s.MNPEmu.FailSign {
|
||||
t.Errorf("после сохранения МНП: emulate=%v real=%v emu=%+v", s.Quik.Emulate, s.Quik.RealWrite, s.MNPEmu)
|
||||
}
|
||||
|
||||
// Валидация на хендлере: плохой код отказа — 400, настройки не тронуты.
|
||||
@@ -301,3 +301,116 @@ func TestDisableEmulatorsIfDevOffNoOp(t *testing.T) {
|
||||
t.Error("no-op не должен добавлять новость")
|
||||
}
|
||||
}
|
||||
|
||||
// Режимы МНП взаимоисключающие: хендлер выставляет Emulate/RealWrite по выбранному режиму.
|
||||
func TestEmulatorsQuikModes(t *testing.T) {
|
||||
devModeForced = true
|
||||
defer func() { devModeForced = false }()
|
||||
rc, err := NewRuntimeConfig(filepath.Join(t.TempDir(), "setup.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
registerEmulators(mux, nil, rc)
|
||||
post := func(form url.Values) *httptest.ResponseRecorder {
|
||||
req := httptest.NewRequest(http.MethodPost, "/admin/emulators/quik", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("X-BJ-Ajax", "1")
|
||||
rec := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rec, req)
|
||||
return rec
|
||||
}
|
||||
|
||||
// Реальная запись на подставного клиента.
|
||||
rec := post(url.Values{"mode": {"real"}, "real_client_code": {"BR859"}})
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("real: %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if s := rc.Snapshot().Quik; !s.RealWrite || s.Emulate || s.RealClientCode != "BR859" {
|
||||
t.Fatalf("режим real: %+v", s)
|
||||
}
|
||||
// Внутренняя имитация — наоборот, код сохраняется (инертен без флага).
|
||||
rec = post(url.Values{"mode": {"emulate"}, "real_client_code": {"BR859"}})
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("emulate: %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if s := rc.Snapshot().Quik; !s.Emulate || s.RealWrite {
|
||||
t.Fatalf("режим emulate: %+v", s)
|
||||
}
|
||||
// real без кода — 400.
|
||||
if rec := post(url.Values{"mode": {"real"}}); rec.Code != http.StatusBadRequest {
|
||||
t.Errorf("real без кода: %d, ожидали 400", rec.Code)
|
||||
}
|
||||
// Код с недопустимыми символами — 400 (валидация UpdateQuik).
|
||||
if rec := post(url.Values{"mode": {"real"}, "real_client_code": {"БР 859!"}}); rec.Code != http.StatusBadRequest {
|
||||
t.Errorf("плохой код: %d, ожидали 400", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// Подмена кода клиента QUIK в режиме реальной записи — после обычной логики (ClientCodeMap не мешает).
|
||||
func TestQuikClientCodeRealWrite(t *testing.T) {
|
||||
s := QuikMNPSettings{ClientCodeMap: map[string]string{"БР-7-7": "BR777"}}
|
||||
if c := quikClientCodeFromPort(s, "БР-859 Баранкин"); c != "BR859" {
|
||||
t.Errorf("обычный режим: %q", c)
|
||||
}
|
||||
if c := quikClientCodeFromPort(s, "БР-7-7 Погонщиков"); c != "BR777" {
|
||||
t.Errorf("ClientCodeMap: %q", c)
|
||||
}
|
||||
s.RealWrite, s.RealClientCode = true, "BR859"
|
||||
if c := quikClientCodeFromPort(s, "БР-7-7 Погонщиков"); c != "BR859" {
|
||||
t.Errorf("подмена имеет приоритет над ClientCodeMap: %q", c)
|
||||
}
|
||||
if c := quikClientCodeFromPort(s, "БР-859 Баранкин"); c != "BR859" {
|
||||
t.Errorf("подмена: %q", c)
|
||||
}
|
||||
// Флаг включён, но код не задан — обычная логика.
|
||||
s.RealClientCode = ""
|
||||
if c := quikClientCodeFromPort(s, "БР-7-7 Погонщиков"); c != "BR777" {
|
||||
t.Errorf("real без кода: %q", c)
|
||||
}
|
||||
}
|
||||
|
||||
// Цикл МНП активен в режиме реальной записи только в dev и с указанным кодом.
|
||||
func TestQuikActiveRealWrite(t *testing.T) {
|
||||
if quikActive(QuikMNPSettings{RealWrite: true, RealClientCode: "BR859"}) {
|
||||
t.Error("вне dev-режима цикл стоит")
|
||||
}
|
||||
devModeForced = true
|
||||
defer func() { devModeForced = false }()
|
||||
if !quikActive(QuikMNPSettings{RealWrite: true, RealClientCode: "BR859"}) {
|
||||
t.Error("dev + код: цикл должен работать")
|
||||
}
|
||||
if quikActive(QuikMNPSettings{RealWrite: true}) {
|
||||
t.Error("dev без кода: цикл стоит")
|
||||
}
|
||||
// Имитация имеет приоритет над real-записью.
|
||||
if !quikActive(QuikMNPSettings{Emulate: true, RealWrite: true, RealClientCode: "BR859"}) {
|
||||
t.Error("emulate + real: работает имитация (цикл активен)")
|
||||
}
|
||||
}
|
||||
|
||||
// Авто-сброс при окончании dev-режима гасит и реальную запись на подставного клиента.
|
||||
func TestDisableEmulatorsIfDevOffRealWrite(t *testing.T) {
|
||||
rc := rcWithExpiredDev(t)
|
||||
q := rc.Snapshot().Quik
|
||||
q.RealWrite, q.RealClientCode = true, "BR859"
|
||||
if err := rc.UpdateQuik(q); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
disableEmulatorsIfDevOff(rc)
|
||||
if s := rc.Snapshot().Quik; s.RealWrite {
|
||||
t.Fatal("RealWrite не сброшен")
|
||||
}
|
||||
found := false
|
||||
for _, n := range rc.Snapshot().News.Items {
|
||||
if strings.Contains(n.Title, "Эмуляторы отключены") {
|
||||
found = true
|
||||
if !strings.Contains(n.Body, "подставного клиента BR859") || !strings.Contains(n.Body, "прекращены") {
|
||||
t.Errorf("новость без упоминания реальных поручений: %s", n.Body)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("новость не добавлена")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user