44f00a006a
- bj-igate-admin: пустые строки в числовых/интервальных параметрах канала ИШ разбирал с ошибкой и не загружал канал («There are no active channels», API 500 «more than one Channel…»); теперь NULL, как пишет интерфейс ИШ; старые каналы чинятся при «Создать канал»; папка архивов 1042S — по имени - проверка «Шлюз НРД»: канал контура действительно загружен и включён (api/admin/channels/wsl) - «Учёт DEPO»: нет процедур bj2depo — жёлтое «не забудьте доустановить», не блокирует - HTTPS: перенаправление с http:// включено по умолчанию, только когда HTTPS реально слушает порт - мастер: шаг «Учётные записи» (роли, создание, «Придумать пароль») перед проверкой; письма с проверки убраны; форма «создать» не меняет существующую запись с тем же логином Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
187 lines
8.6 KiB
Go
187 lines
8.6 KiB
Go
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("один администратор — шаг не выполнен")
|
|
}
|
|
}
|