Учёт DEPO: скрипт снова выполняется; логин bj виден в обоих способах; новые логин и пароль — только после проверки входа

- EXEC(...) в T-SQL склеивает только строки и переменные: REPLACE/QUOTENAME внутри
  давали ошибку 102, и SQL Server отклонял весь скрипт (.06.4–.06.6). Команда входа
  собирается в @sql заранее; тест ловит вызовы функций внутри EXEC; живая проверка
  синтаксиса на SQL Server без выполнения (SET PARSEONLY) — TestDepoSetupScriptParseLive.
- «Логин bj / Пароль bj» и в способе «Дать логин администратора»: на общем сервере
  bj_user может быть занят другим bj — подготовка сменила бы ему пароль. Предупреждение.
- Показ скрипта на странице ничего не сохраняет. Логин и пароль из выданного скрипта
  и из «Проверить подключение» хранятся черновиком (DepoNewUser/DepoNewPass) и
  становятся рабочими только после успешного входа: службы bj с неверным паролем
  за минуты блокировали логин на сервере (CHECK_POLICY) — у всех, кто им пользуется.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-10-06 15:35:36 +03:00
parent 13c252ae3d
commit 7af6cdf2f6
7 changed files with 190 additions and 27 deletions
+63 -8
View File
@@ -366,6 +366,15 @@ func TestDepoSetupScript(t *testing.T) {
t.Fatalf("нечётные кавычки: %s", line)
}
}
// EXEC(...) в T-SQL склеивает только литералы и переменные: вызов функции внутри (REPLACE, QUOTENAME…) —
// ошибка 102 «Неправильный синтаксис», и весь скрипт не выполняется (так было в .06.4–.06.6)
for _, sc := range []string{s, depoSetupScript(f, "DEPO_X", "bj_user", "Pass1word", false)} {
for _, line := range strings.Split(sc, "\n") {
if ln := strings.TrimSpace(line); strings.HasPrefix(ln, "EXEC(") && strings.Contains(tsqlOutsideLiterals(ln[len("EXEC("):]), "(") {
t.Fatalf("в EXEC(...) вызов функции — SQL Server не примет: %s", ln)
}
}
}
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: без задания, с правом на процедуру и указанной базой")
@@ -460,8 +469,9 @@ func TestDumpFirstRunPage(t *testing.T) {
_ = os.WriteFile(filepath.Join(dir, "first-run.html"), []byte(b.String()), 0o644)
}
// «Отдать скрипт администратору»: скрипт показывается на странице с базой и паролем с экрана, пароль
// запоминается (с ним bj потом проверяет подключение); короткий пароль не принимается.
// «Получить скрипт»: скрипт показывается на странице с базой и паролем с экрана и при показе ничего не
// сохраняет; файлом — логин и пароль запоминаются черновиком, рабочие (с ними ходят службы bj) не меняются
// до проверки входа: неверный пароль от служб блокирует логин на сервере. Короткий пароль не принимается.
func TestDepoScriptView(t *testing.T) {
priv := testLicenseKey(t)
rc := testRC(t)
@@ -488,22 +498,46 @@ func TestDepoScriptView(t *testing.T) {
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "DECLARE @db sysname = N'DEPO_X';") || !strings.Contains(w.Body.String(), "N'Secret123x';") {
t.Fatalf("показ скрипта: %d %s", w.Code, w.Body.String()[:min(300, w.Body.Len())])
}
if f := rc.Snapshot().Fancy; f.User != "bj_user" || f.Password != "Secret123x" || f.DepoTablesMode != "script" {
t.Fatalf("логин и пароль bj не запомнились: %+v", f.User)
if f := rc.Snapshot().Fancy; f.Server != "" || f.User != "" || f.DepoNewUser != "" {
t.Fatalf("показ скрипта не должен ничего сохранять: %q %q %q", f.Server, f.User, f.DepoNewUser)
}
// рабочий логин уже есть (другой bj на том же сервере или прежняя настройка) — файл его не трогает
cur := rc.Snapshot().Fancy
cur.User, cur.Password = "bj_user", "Working1x"
if err := rc.UpdateFancy(cur); err != nil {
t.Fatal(err)
}
form.Del("view")
if w := post(form); w.Code != http.StatusOK || !strings.Contains(w.Header().Get("Content-Disposition"), "attachment") {
t.Fatalf("скрипт файлом: %d %s", w.Code, w.Body.String())
}
if f := rc.Snapshot().Fancy; f.User != "bj_user" || f.Password != "Working1x" || f.DepoNewUser != "bj_user" || f.DepoNewPass != "Secret123x" || f.Server != "10.0.0.5" || f.DepoTablesMode != "script" {
t.Fatalf("файл: рабочие логин/пароль должны остаться, новые — черновиком: %q/%q, черновик %q", f.User, f.Password, f.DepoNewUser)
}
form.Set("view", "1")
form.Set("bj_password", "short")
if w := post(form); w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), "8 знаков") {
t.Fatalf("короткий пароль: %d %s", w.Code, w.Body.String())
}
// экран пересобирает скрипт на ходу — и до того, как вписан адрес сервера; без адреса ничего не сохраняется
// «Проверить подключение», а войти не удалось — рабочими остаются прежние логин и пароль
chk := url.Values{"server": {"127.0.0.1"}, "port": {"1"}, "db_type": {"mssql"}, "depo_database": {"DEPO_X"},
"mode": {"script"}, "bj_user": {"bj_new"}, "bj_password": {"Newpass12"}, "depo_job": {"bj"}}
r := httptest.NewRequest(http.MethodPost, "/admin/setup/wizard-depo/setup", strings.NewReader(chk.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
cw := httptest.NewRecorder()
h.wizardDepoSetup(cw, r)
if loc := cw.Header().Get("Location"); cw.Code != http.StatusSeeOther || !strings.Contains(loc, "flash=") {
t.Fatalf("проверка без сервера: %d %s", cw.Code, loc)
}
if f := rc.Snapshot().Fancy; f.User != "bj_user" || f.Password != "Working1x" || f.DepoNewUser != "bj_new" || f.DepoNewPass != "Newpass12" {
t.Fatalf("вход не удался — рабочие логин/пароль не должны меняться: %q/%q, черновик %q", f.User, f.Password, f.DepoNewUser)
}
// экран пересобирает скрипт на ходу — и до того, как вписан адрес сервера
form.Set("bj_password", "Other123x")
form.Set("server", "")
if w := post(form); w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "N'Other123x';") {
t.Fatalf("скрипт без адреса сервера: %d %s", w.Code, w.Body.String())
}
if f := rc.Snapshot().Fancy; f.Server != "10.0.0.5" || f.Password != "Secret123x" {
t.Fatalf("без адреса сервера настройки не должны меняться: %q", f.Server)
}
// имя базы обязательно (bj по серверу не ищет) — без него вместо скрипта понятная причина
form.Set("depo_database", "")
if w := post(form); w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), "имя базы") {
@@ -518,3 +552,24 @@ func TestDepoScriptView(t *testing.T) {
t.Fatalf("PostgreSQL: %d %s", w.Code, w.Body.String())
}
}
// tsqlOutsideLiterals — текст T-SQL без строковых литералов ('…', внутри ” — экранированная кавычка).
func tsqlOutsideLiterals(s string) string {
var b strings.Builder
for i := 0; i < len(s); i++ {
if s[i] != '\'' {
b.WriteByte(s[i])
continue
}
for i++; i < len(s); i++ {
if s[i] == '\'' {
if i+1 < len(s) && s[i+1] == '\'' {
i++
continue
}
break
}
}
}
return b.String()
}