feat(admin): dev-страница «Эмуляторы» — переключатели и настройки эмуляции НРД и МНП QUIK
В режиме разработки новая страница /admin/emulators с двумя клавишами: - «Эмулятор НРД» (флаг LK.EmulateNSD): заявки ЛК и ручного ввода идут через внутренний имитатор, в НРД ничего не отправляется. Настройки: задержка решения (0–300 с), исход (подтвердить/отклонить/не отвечать), код отказа M2M01–M2M09/18. Сендер перенастраивается в рантайме через потокобезопасный mock.Sender.Reconfigure — без перезапуска. - «Эмулятор МНП QUIK» (флаг QuikMNP.Emulate): шаги цикла без записи в боевую БД МНП. Настройки: задержка шага (0–60 с), точечные отказы по шагам (регистрация/подпись/отказ/зачисление) — эмулированный отказ ведёт себя как боевой: шаг OK=false, состояние не продвигается, повтор на следующем проходе. Настройки хранятся в setup.json (nsd_emu/mnp_emu), обратная совместимость — дефолты. Баннер «Режим эмуляции» показывается и при ручном включении эмуляторов (раньше зависел только от отсутствия ИШ). Пункт меню «Эмуляторы» и две плитки на дашборде — только в dev. Пути занесены в devOnlyPath. Попутно: исправлен race в TestLicenseActivateAndRenew (cleanup восстанавливал DefaultLicensePublicKey без moduleEnforceMu — падал под -race и на чистом HEAD).
This commit is contained in:
@@ -45,6 +45,8 @@ type Settings struct {
|
||||
Time TimeSettings `json:"time,omitempty"`
|
||||
WorkHours WorkHoursSettings `json:"work_hours,omitempty"` // рабочее время: запросы M2M вне него — отказ M2M06
|
||||
OneC OneCSettings `json:"onec,omitempty"` // база 1С бэк-офиса (модуль onec-back)
|
||||
NSDEmu NSDEmuSettings `json:"nsd_emu,omitempty"` // эмулятор НРД (страница «Эмуляторы», только режим разработки)
|
||||
MNPEmu MNPEmuSettings `json:"mnp_emu,omitempty"` // эмулятор МНП QUIK (страница «Эмуляторы», только режим разработки)
|
||||
LastTest *TestRunResult `json:"last_test,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -345,6 +347,90 @@ type LKEndpoint struct {
|
||||
LastAt time.Time `json:"last_at"` // время последней проверки
|
||||
}
|
||||
|
||||
// NSDEmuSettings — настройки эмулятора НРД (действуют при LK.EmulateNSD в режиме
|
||||
// разработки, страница «Эмуляторы»): каким решением и через сколько отвечает
|
||||
// имитатор контрагента. Нулевые поля (старый setup.json) = дефолты.
|
||||
type NSDEmuSettings struct {
|
||||
DelaySec int `json:"delay_sec,omitempty"` // задержка решения контрагента, сек (0..300; 0 = дефолт 30)
|
||||
Outcome string `json:"outcome,omitempty"` // confirm | reject | timeout (пусто = confirm)
|
||||
RejectCode string `json:"reject_code,omitempty"` // код отказа M2Mxx для outcome=reject (пусто = 07)
|
||||
}
|
||||
|
||||
// Defaults подставляет дефолты вместо нулевых полей (обратная совместимость).
|
||||
func (s NSDEmuSettings) Defaults() NSDEmuSettings {
|
||||
if s.DelaySec <= 0 {
|
||||
s.DelaySec = int(lkEmuDecisionDelay / time.Second)
|
||||
}
|
||||
if s.Outcome == "" {
|
||||
s.Outcome = "confirm"
|
||||
}
|
||||
if s.RejectCode == "" {
|
||||
s.RejectCode = "07"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// MNPEmuSettings — настройки эмулятора МНП QUIK (действуют при QuikMNP.Emulate):
|
||||
// задержка каждого шага цикла и точечные отказы по шагам — для отработки
|
||||
// сценариев «МНП отказал» без боевой базы.
|
||||
type MNPEmuSettings struct {
|
||||
StepDelaySec int `json:"step_delay_sec,omitempty"` // задержка шага, сек (0..60; 0 = без задержки)
|
||||
FailRegister bool `json:"fail_register,omitempty"` // регистрация завершается отказом
|
||||
FailSign bool `json:"fail_sign,omitempty"` // подпись (исполнение) завершается отказом
|
||||
FailDeny bool `json:"fail_deny,omitempty"` // снятие поручения завершается отказом
|
||||
FailDeposit bool `json:"fail_deposit,omitempty"` // зачисление (входящие) завершается отказом
|
||||
}
|
||||
|
||||
// FailFor — для шага настроен отказ (все шаги успешны, если галочки сняты).
|
||||
func (s MNPEmuSettings) FailFor(action string) bool {
|
||||
switch action {
|
||||
case "register":
|
||||
return s.FailRegister
|
||||
case "sign":
|
||||
return s.FailSign
|
||||
case "deny":
|
||||
return s.FailDeny
|
||||
case "deposit":
|
||||
return s.FailDeposit
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// nsdEmuRejectCodes — допустимые коды отказа принимающей стороны (M2M).
|
||||
var nsdEmuRejectCodes = []string{"01", "02", "03", "04", "05", "06", "07", "08", "09", "18"}
|
||||
|
||||
// UpdateNSDEmu сохраняет настройки эмулятора НРД (с валидацией).
|
||||
func (r *RuntimeConfig) UpdateNSDEmu(s NSDEmuSettings) error {
|
||||
if s.DelaySec < 0 || s.DelaySec > 300 {
|
||||
return errors.New("задержка решения — от 0 до 300 секунд")
|
||||
}
|
||||
switch s.Outcome {
|
||||
case "", "confirm", "reject", "timeout":
|
||||
default:
|
||||
return errors.New("исход — confirm, reject или timeout")
|
||||
}
|
||||
if s.RejectCode != "" && !containsStr(nsdEmuRejectCodes, s.RejectCode) {
|
||||
return errors.New("код отказа — M2M01..M2M09 или M2M18")
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.data.NSDEmu = s
|
||||
r.data.UpdatedAt = time.Now().UTC()
|
||||
r.mu.Unlock()
|
||||
return r.save()
|
||||
}
|
||||
|
||||
// UpdateMNPEmu сохраняет настройки эмулятора МНП QUIK (с валидацией).
|
||||
func (r *RuntimeConfig) UpdateMNPEmu(s MNPEmuSettings) error {
|
||||
if s.StepDelaySec < 0 || s.StepDelaySec > 60 {
|
||||
return errors.New("задержка шага — от 0 до 60 секунд")
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.data.MNPEmu = s
|
||||
r.data.UpdatedAt = time.Now().UTC()
|
||||
r.mu.Unlock()
|
||||
return r.save()
|
||||
}
|
||||
|
||||
// TestRunResult — результат последнего тестового прогона.
|
||||
type TestRunResult struct {
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
|
||||
Reference in New Issue
Block a user