Первичная настройка: ключ спрашивается один раз, затем обновления, модули и «Время и сеть»; DEPO одним шагом
- Первая страница — только администратор (и восстановление из копии); ключ продукта — после создания администратора, на шаге «Лицензия». Консоль первой загрузки ключ больше не спрашивает (для образа с комплектом bj). - «Подготовка модулей» начинает с проверки обновлений: есть — ставит (bj перезапускается, страница ждёт и продолжает), нет — дальше; выпуск, который уже ставили, по кругу не ставится. Затем модули лицензии и переход на «Время и сеть» (шаг 12) — часовой пояс клиент выбирает сам, шаг больше не проскакивает. - Учёт DEPO одним шагом: bj сам находит базу Fancy DEPO на сервере (по таблицам OD_FACES/OD_RESTS), имя базы не спрашивается. Один скрипт создаёт логин bj, таблицы обмена и права; по выбору — задание обработки в SQL Agent или обработка на сервере bj. Выполнить сразу (логин администратора СУБД не сохраняется), скачать скрипт для администратора или подключиться готовым логином. - Восстановление из копии — только на первой странице, в мастере его больше нет. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -307,14 +307,22 @@ func TestPrepareScreen(t *testing.T) {
|
||||
t.Fatalf("нет %q", want)
|
||||
}
|
||||
}
|
||||
for in, want := range map[string]string{"": "/admin/wizard", "/admin/setup?x=1": "/admin/setup?x=1", "https://evil": "/admin/wizard", "//evil/admin/": "/admin/wizard", "/admin/wizard\r\nX: y": "/admin/wizard"} {
|
||||
const def = "/admin/wizard?step=12" // после ключа и модулей — «Время и сеть»
|
||||
for in, want := range map[string]string{"": def, "/admin/setup?x=1": "/admin/setup?x=1", "https://evil": def, "//evil/admin/": def, "/admin/wizard\r\nX: y": def} {
|
||||
if got := safeNext(in); got != want {
|
||||
t.Errorf("safeNext(%q) = %q, ожидалось %q", in, got, want)
|
||||
}
|
||||
}
|
||||
rc, _ := NewRuntimeConfig(t.TempDir() + "/setup.json")
|
||||
if items := prepareItems(rc, false, false, ""); len(items) != 1 || items[0].State != "ok" {
|
||||
t.Fatalf("без лицензии — только ядро: %+v", items)
|
||||
if items := prepareItems(rc, false, false, ""); len(items) != 2 || items[0].Title != "Обновление bj" || items[0].State != "wait" || items[1].State != "ok" {
|
||||
t.Fatalf("без лицензии — обновление (ещё не проверялось) и ядро: %+v", items)
|
||||
}
|
||||
// выпуск, который подготовка уже ставила, после перезапуска bj не ставится по кругу
|
||||
oldTried := prepareTriedFile
|
||||
prepareTriedFile = t.TempDir() + "/tried"
|
||||
defer func() { prepareTriedFile = oldTried }()
|
||||
if prepareTried("2026.10.06.1") || !prepareTried("2026.10.06.1") || prepareTried("2026.10.06.2") {
|
||||
t.Fatal("prepareTried: первый раз — ставить, второй раз тот же выпуск — нет, новый выпуск — ставить")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,3 +346,34 @@ func TestFromLocalBundle(t *testing.T) {
|
||||
t.Fatal("другой выпуск — качать с сервера, а не брать из образа")
|
||||
}
|
||||
}
|
||||
|
||||
// Один скрипт подготовки базы DEPO: сам ищет базу, создаёт логин, таблицы, права и (по выбору) задание.
|
||||
func TestDepoSetupScript(t *testing.T) {
|
||||
f := FancyMSSQLSettings{}
|
||||
s := depoSetupScript(f, "", "bj_user", "Pass1word", true)
|
||||
for _, want := range []string{"DECLARE @db sysname = N'';", "OD_FACES", "CREATE LOGIN [bj_user] WITH PASSWORD = N''Pass1word''", "FTV_M2M_EVENTS", "db_datareader", "sp_add_job @job_name = N'bj2depo'", "@database_name = @db", "SQLAgentReaderRole"} {
|
||||
if !strings.Contains(s, want) {
|
||||
t.Fatalf("в скрипте нет %q", want)
|
||||
}
|
||||
}
|
||||
if strings.Contains(s, "BJ_DEPO_DATA") || strings.Contains(s, "\nGO") {
|
||||
t.Fatal("в скрипте — имя нашей базы или GO (переменные @db между пакетами не живут)")
|
||||
}
|
||||
// каждая строка EXEC(@in + N'…') — закрытый литерал: чётное число кавычек
|
||||
for _, line := range strings.Split(s, "\n") {
|
||||
if strings.HasPrefix(line, "EXEC(") && strings.HasSuffix(line, "');") && strings.Count(line, "'")%2 != 0 {
|
||||
t.Fatalf("нечётные кавычки: %s", line)
|
||||
}
|
||||
}
|
||||
bj := depoSetupScript(f, "DEPO_X", "bj_user", "Pass1word", false)
|
||||
if strings.Contains(bj, "sp_add_job") || !strings.Contains(bj, "GRANT EXECUTE ON dbo.FTV_M2M_JOB") || !strings.Contains(bj, "N'DEPO_X'") {
|
||||
t.Fatal("обработка силами bj: без задания, с правом на процедуру и указанной базой")
|
||||
}
|
||||
pg := depoSetupScript(FancyMSSQLSettings{DBType: "postgres"}, "depo", "bj_user", "p'w", false)
|
||||
if !strings.Contains(pg, `\c "depo"`) || !strings.Contains(pg, "PASSWORD 'p''w'") {
|
||||
t.Fatal("PostgreSQL: подключение к базе и экранирование пароля")
|
||||
}
|
||||
if p := depoGenPassword(); len(p) != 24 || !strings.ContainsAny(p, "ABCDEFGHJKLMNPQRSTUVWXYZ") || !strings.ContainsAny(p, "23456789") {
|
||||
t.Fatalf("пароль: %q", p)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user