13832fefad
Страница /admin/setup/client-profile: скачать эталон текущей конфигурации (секреты затёрты) и — для стенда — применить профиль клиента вручную. На боевом профиль приходит с сервера лицензий (поле config_profile в ответе активации/продления) и предзаполняет ещё НЕ настроенные разделы мастера, ничего не перетирая. Локальный фолбэк — файл client-profile.json рядом с setup.json. Администратор правит профиль под клиента на lic (отличия от стандарта). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XG3zNMMf2LMoDpXo22mwMX
86 lines
2.9 KiB
Go
86 lines
2.9 KiB
Go
package lkgateway
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRedactSecrets(t *testing.T) {
|
|
m := map[string]any{
|
|
"host": "db",
|
|
"password": "p@ss",
|
|
"nested": map[string]any{
|
|
"dsn": "postgres://u:pw@h/db",
|
|
"keep": "ok",
|
|
"token": "abc",
|
|
},
|
|
"list": []any{map[string]any{"api_key": "x", "name": "n"}},
|
|
}
|
|
redactSecrets(m)
|
|
if m["password"] != "" {
|
|
t.Errorf("password должен быть затёрт, получили %v", m["password"])
|
|
}
|
|
n := m["nested"].(map[string]any)
|
|
if n["dsn"] != "" || n["token"] != "" {
|
|
t.Errorf("dsn/token должны быть затёрты: %v", n)
|
|
}
|
|
if n["keep"] != "ok" {
|
|
t.Errorf("несекретное поле потеряно: %v", n["keep"])
|
|
}
|
|
if m["list"].([]any)[0].(map[string]any)["api_key"] != "" {
|
|
t.Errorf("api_key в массиве должен быть затёрт")
|
|
}
|
|
}
|
|
|
|
func TestMergeEmptySections(t *testing.T) {
|
|
// Пустой раздел заполняется из профиля.
|
|
dst := Settings{}
|
|
prof := Settings{Postgres: PostgresSettings{DSN: "postgres://x"}}
|
|
filled := mergeEmptySections(&dst, prof)
|
|
if dst.Postgres.DSN != "postgres://x" {
|
|
t.Fatalf("пустой раздел должен заполниться из профиля, got %q", dst.Postgres.DSN)
|
|
}
|
|
if len(filled) != 1 || filled[0] != "Postgres" {
|
|
t.Fatalf("ожидали filled=[Postgres], got %v", filled)
|
|
}
|
|
// Настроенный раздел профиль не перетирает.
|
|
dst2 := Settings{Postgres: PostgresSettings{DSN: "mine"}}
|
|
prof2 := Settings{Postgres: PostgresSettings{DSN: "theirs"}}
|
|
filled2 := mergeEmptySections(&dst2, prof2)
|
|
if dst2.Postgres.DSN != "mine" {
|
|
t.Fatalf("настроенный раздел не должен перетираться, got %q", dst2.Postgres.DSN)
|
|
}
|
|
if len(filled2) != 0 {
|
|
t.Fatalf("ничего не должно заполниться, got %v", filled2)
|
|
}
|
|
// Лицензия не трогается профилем.
|
|
dst3 := Settings{}
|
|
prof3 := Settings{License: LicenseSettings{InstallID: "zzz"}}
|
|
mergeEmptySections(&dst3, prof3)
|
|
if dst3.License.InstallID != "" {
|
|
t.Fatalf("лицензия не должна заполняться из профиля, got %q", dst3.License.InstallID)
|
|
}
|
|
}
|
|
|
|
func TestExportClientProfileRedacts(t *testing.T) {
|
|
rc := &RuntimeConfig{path: t.TempDir() + "/setup.json"}
|
|
rc.data = Settings{Postgres: PostgresSettings{DSN: "postgres://u:secret@h/db"}}
|
|
body, err := exportClientProfile(rc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var m map[string]any
|
|
if err := json.Unmarshal(body, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := m["settings"].(map[string]any)
|
|
pg := s["postgres"].(map[string]any)
|
|
if pg["dsn"] != "" {
|
|
t.Errorf("DSN должен быть затёрт в эталоне, got %v", pg["dsn"])
|
|
}
|
|
if !strings.Contains(string(body), `"settings"`) {
|
|
t.Error("эталон должен содержать settings")
|
|
}
|
|
}
|