Files
agent-bot 28a2c8ca2b
CI / go-vet (pull_request) Successful in 14s
CI / short-tests-report (pull_request) Failing after 36s
CI / backup-restore-drill (pull_request) Successful in 26s
CI / web (pull_request) Successful in 31s
feat(config): instance-settings fifth layer (U10b core 2, KTD16/17)
- <config_root>/instance-settings.json is the final config layer:
  default > env-file(s) > process-env > cli > instance-settings
- LoadForServe wires the CLI mutation hook BEFORE instance settings;
  serve/check/explain all use it; plain Load keeps legacy behavior
- bootstrap denylist (env) fails closed (no self-locating file loop);
  SETTINGS_MODE=ignore skips the layer as the recovery path
- reflection-based typed application: string/int/bool/duration(string or
  seconds)/string-array; unknown fields and type mismatches fail closed
- group/world-writable settings file rejected; absent key = inherit
- sources table gains the instance-settings layer; e2e smoke verified
  (override applies; ignore-mode reverts to default)
2026-09-29 08:17:21 +00:00

73 lines
2.1 KiB
Go

package config
import (
"os"
"path/filepath"
"testing"
)
func TestFieldSourcesLayerAttribution(t *testing.T) {
dir := t.TempDir()
envf := filepath.Join(dir, ".env")
if err := os.WriteFile(envf, []byte("HTTP_HOST=0.0.0.0\nKB_DB_PASSWORD=from-file\nAGENT_BACKEND=stub\n"), 0o600); err != nil {
t.Fatal(err)
}
t.Setenv("KB_DB_PASSWORD", "from-process") // 进程 env 覆盖 env-file
cfg, src, err := LoadWithSources(envf, nil)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.HTTPHost != "0.0.0.0" || cfg.KbDbPassword != "from-process" || cfg.AgentBackend != "stub" {
t.Fatalf("values not applied: host=%s", cfg.HTTPHost)
}
if src["http_host"].Layer != "env-file:"+filepath.ToSlash(envf) {
t.Fatalf("http_host layer=%s", src["http_host"].Layer)
}
if src["kb_db_password"].Layer != "process-env" {
t.Fatalf("kb_db_password layer=%s", src["kb_db_password"].Layer)
}
if src["agent_backend"].Layer != "env-file:"+filepath.ToSlash(envf) {
t.Fatalf("agent_backend layer=%s", src["agent_backend"].Layer)
}
// 未设置字段仍是 default 层
if src["admin_username"].Layer != "default" || src["admin_username"].Set {
t.Fatalf("admin_username layer=%s set=%v", src["admin_username"].Layer, src["admin_username"].Set)
}
}
func TestApplyCLIOverridesAttribution(t *testing.T) {
cfg, src, err := LoadWithSources("", nil)
if err != nil {
t.Fatal(err)
}
src2, err := ApplyCLIOverrides(cfg, src, func(c *Config) {
c.HTTPPort = 9999
c.AgentBackend = "stub"
})
if err != nil {
t.Fatal(err)
}
if src2["http_port"].Layer != "cli" || !src2["http_port"].Set {
t.Fatalf("http_port layer=%s set=%v", src2["http_port"].Layer, src2["http_port"].Set)
}
if src2["agent_backend"].Layer != "cli" {
t.Fatalf("agent_backend layer=%s", src2["agent_backend"].Layer)
}
// 未覆盖字段来源不变
if src2["http_host"].Layer != src["http_host"].Layer {
t.Fatalf("http_host layer changed unexpectedly")
}
}
// Load 行为回归:老入口签名与结果不变。
func TestLoadStillWorks(t *testing.T) {
cfg, err := Load("")
if err != nil {
t.Fatal(err)
}
if cfg.HTTPPort == 0 || cfg.DataDir == "" {
t.Fatal("defaults lost")
}
}