package lkgateway import ( "net/http" "net/http/httptest" "path/filepath" "strings" "testing" ) // Сбои и обновления — отдельные типы (по умолчанию — администратору системы), деловые — отделам. func TestAlertKindAdmin(t *testing.T) { cases := map[string]string{ "ИШ НРД недоступен": "infra", "Перезапуск ИШ НРД не помог (попытка 1 из 3)": "infra", "НРД не отвечает шлюзу": "infra", "Компонент не отвечает: poll-incoming": "infra", "Служба «Подпись» не работает": "infra", "DEPO: вход логином bj_user не удался": "infra", "Доступно обновление bj 2026.10.06.24": "updates", "ИШ обновлён до 107.0.930": "updates", "Лицензия: сервер лицензий отказал в продлении": "updates", "Поддержка bj заканчивается через 10 дней": "updates", "Сертификат ключа организации истекает через 5": "updates", "НРД: депо-поручение не исполнено в срок 5 минут": "orders", "QUIK МНП: не удалось зачислить бумаги": "quik", "Перевод не доставлен в НРД": "system", "DEPO: джоба не обработала перевод ab12": "system", } for title, want := range cases { if got := alertKind(title); got != want { t.Errorf("%q: %s, ожидалось %s", title, got, want) } } } // Существующая установка: администратор (адрес из «Почты») получает всё, что получал раньше; выключенные // уведомления о переводах — пустой маршрут. Новая установка — маршруты по умолчанию. func TestNotifyRoutesMigration(t *testing.T) { var old Settings old.Alerts.SMTP.To = "it@example.ru" old.Incoming.Emails = "depo@example.ru" migrateNotifyRoutes(&old) r := old.Incoming.Routes for _, k := range []string{"orders", "quik", "infra", "updates", "system"} { if !old.Incoming.RouteHas(k, "admin") { t.Errorf("%s: администратор должен остаться получателем (%q)", k, r[k]) } } if r["incoming"] != "" || old.Incoming.RouteHas("incoming", "depo") || !old.Incoming.Enabled || old.Incoming.RoutesV != 2 { t.Errorf("выключенные уведомления о переводах: %+v", old.Incoming) } if !old.Incoming.RouteHas("infra", "back") || !old.Incoming.RouteHas("orders", "depo") { t.Errorf("сбои — тем же отделам, что «прочие» раньше: %v", r) } if got := notifyRecipients(old, "infra"); got != "it@example.ru" { t.Errorf("сбои: %q", got) } migrateNotifyRoutes(&old) // повторно — без изменений if old.Incoming.Routes["orders"] != r["orders"] || strings.Count(old.Incoming.Routes["orders"], "admin") != 1 { t.Errorf("повторная миграция: %v", old.Incoming.Routes) } var fresh Settings migrateNotifyRoutes(&fresh) fresh.Alerts.SMTP.To = "it@example.ru" fresh.Incoming.Emails, fresh.Incoming.EmailsBack = "depo@example.ru", "back@example.ru" if fresh.Incoming.Routes != nil || !fresh.Incoming.Enabled { t.Fatalf("новая установка: %+v", fresh.Incoming) } want := map[string]string{ "incoming": "depo@example.ru, back@example.ru", "orders": "depo@example.ru, back@example.ru", "infra": "it@example.ru", "updates": "it@example.ru", "system": "it@example.ru, back@example.ru", } for k, w := range want { if got := notifyRecipients(fresh, k); got != w { t.Errorf("%s: %q, ожидалось %q", k, got, w) } } } // Служба пишет администратору, только пролежав две проверки подряд, один раз; поднялась — второе письмо. func TestUnitsWatchTick(t *testing.T) { rc, err := NewRuntimeConfig(filepath.Join(t.TempDir(), "setup.json")) if err != nil { t.Fatal(err) } unitsWatch.Lock() unitsWatch.down, unitsWatch.alerted = map[string]int{}, map[string]bool{} unitsWatch.Unlock() down := []watchedUnit{{Key: "bj-npr", Title: "Риск-монитор", Unit: "bj-npr", State: "failed/failed"}} up := []watchedUnit{{Key: "bj-npr", Title: "Риск-монитор", Unit: "bj-npr", OK: true}} count := func(sub string) int { n := 0 for _, it := range rc.Snapshot().News.Items { if strings.Contains(it.Title, sub) { n++ } } return n } unitsWatchTick(rc, down) if count("не работает") != 0 { t.Fatal("после одной проверки письма быть не должно") } unitsWatchTick(rc, down) unitsWatchTick(rc, down) if count("не работает") != 1 { t.Fatalf("оповещений о сбое: %d", count("не работает")) } unitsWatchTick(rc, up) if count("снова работает") != 1 { t.Fatalf("оповещений о восстановлении: %d", count("снова работает")) } } // Адрес хранилища копий — как его вводят люди: в проводнике \\сервер\папка, или сервер и общая папка по разным полям. func TestSMBLocation(t *testing.T) { cases := []struct{ share, dir, wantShare, wantDir string }{ {`\\10.10.10.11\`, "backup_test", "//10.10.10.11/backup_test", ""}, {`\\10.10.10.11\backup_test`, "", "//10.10.10.11/backup_test", ""}, {`\\nas\backup\bj\copies`, "x", "//nas/backup", "bj/copies/x"}, {"//nas/backup", "bj-backup", "//nas/backup", "bj-backup"}, {"smb://nas/backup/", "", "//nas/backup", ""}, {"nas", "", "//nas", ""}, {"", "bj", "", "bj"}, } for _, c := range cases { s, d := smbLocation(c.share, c.dir) if s != c.wantShare || d != c.wantDir { t.Errorf("%q + %q → %q %q, ожидалось %q %q", c.share, c.dir, s, d, c.wantShare, c.wantDir) } } if smbShareOK("//nas") || !smbShareOK("//nas/backup") || smbWin("//nas/backup") != `\\nas\backup` { t.Error("smbShareOK/smbWin") } } // Поле «Проверочное письмо — кому»: адрес администратора по умолчанию не дублируется, свой — запоминается. func TestSMTPTestTo(t *testing.T) { s := SMTPSettings{To: "it@example.ru", TestTo: "old@example.ru"} req := func(body string) *http.Request { r := httptest.NewRequest(http.MethodPost, "/admin/setup/alerts-smtp-test", strings.NewReader(body)) r.Header.Set("Content-Type", "application/x-www-form-urlencoded") return r } if got := smtpTestTo(req("test_to=me%40example.ru"), s); got != "me@example.ru" { t.Errorf("свой адрес: %q", got) } if got := smtpTestTo(req("test_to=it%40example.ru"), s); got != "" { t.Errorf("адрес администратора не хранится отдельно: %q", got) } if got := smtpTestTo(req("host=x"), s); got != "old@example.ru" { t.Errorf("без поля — прежний: %q", got) } } // Форма «Создать учётную запись» из мастера не меняет существующего пользователя с тем же логином. func TestCreateUserLoginTaken(t *testing.T) { old := usersFile usersFile = filepath.Join(t.TempDir(), "users.json") defer func() { usersFile = old }() prev := globalUsers globalUsers = &userStore{users: map[string]*User{}} defer func() { globalUsers = prev }() if err := globalUsers.Put(&User{Login: "admin", Role: "admin", Source: "local", Active: true}); err != nil { t.Fatal(err) } h := &setupHandlers{} r := httptest.NewRequest(http.MethodPost, "/admin/setup/users", strings.NewReader("login=admin&role=viewer&create=1&password=12345678&return_to=%2Fadmin%2Fwizard%3Fstep%3D13")) r.Header.Set("Content-Type", "application/x-www-form-urlencoded") w := httptest.NewRecorder() h.saveUser(w, r) if u := globalUsers.Get("admin"); u == nil || u.Role != "admin" { t.Fatalf("существующий администратор изменён: %+v", u) } if loc := w.Header().Get("Location"); !strings.HasPrefix(loc, "/admin/wizard?step=13") || !strings.Contains(loc, "flash=") { t.Fatalf("возврат в мастер: %q", loc) } if doneUsers() { t.Fatal("один администратор — шаг не выполнен") } }