Files
Agent-System-Backend/internal/config/config_test.go
T
weichen 66b4bbf55a Add the startup configuration pipe with file and env layers plus explain
Why: startup had only defaults and CLI flags; dev/test/prod operation needs
the standard baseline (explicit file, environment, explicit-only CLI) and a
read-only diagnostic.
Evidence: internal/config unit tests cover precedence, unset-CLI no-override,
presence-based file semantics, unknown-key/missing-file errors, env bool
parsing, explain source labelling and DSN redaction; full suite 12 packages
pass; live run of config explain with envs/prod.json shows correct layer
attribution and redaction.
Impact: serve gains --config and AGENT_SYSTEM_* env overrides (behavior
unchanged when neither is used); new config explain subcommand; envs/
examples and PROJECT.md configuration section.
Refs: documents docs/proposals/2026-09-30-configuration-surface.md; standards/configuration.md
2026-09-30 04:19:11 +00:00

151 lines
4.0 KiB
Go

package config
import (
"bytes"
"os"
"path/filepath"
"testing"
)
func noEnv(string) (string, bool) { return "", false }
func fileWith(t *testing.T, content string) string {
t.Helper()
p := filepath.Join(t.TempDir(), "c.json")
if err := os.WriteFile(p, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
return p
}
// 基线:无文件无环境无 CLI → 全默认。
func TestDefaults(t *testing.T) {
r, err := Load("", noEnv, Config{}, nil)
if err != nil {
t.Fatal(err)
}
if r.Addr != "127.0.0.1:8787" || r.DataDir != "./data" || r.AuthMode != "auto" || r.Mock {
t.Fatalf("defaults wrong: %+v", r.Config)
}
if r.SourceOf("addr") != SrcDefault {
t.Fatalf("addr source = %v", r.SourceOf("addr"))
}
}
// 四源优先级:文件 < 环境 < 显式 CLI。
func TestPrecedence(t *testing.T) {
f := fileWith(t, `{"addr":"127.0.0.1:5000","auth":"off"}`)
env := func(k string) (string, bool) {
if k == "AGENT_SYSTEM_ADDR" {
return "127.0.0.1:6000", true
}
return "", false
}
r, err := Load(f, env, Config{Addr: "127.0.0.1:7000", AuthMode: "on"}, map[string]bool{"addr": true, "auth": true})
if err != nil {
t.Fatal(err)
}
if r.Addr != "127.0.0.1:7000" || r.SourceOf("addr") != SrcCLI {
t.Fatalf("addr should be CLI: %q %v", r.Addr, r.SourceOf("addr"))
}
if r.AuthMode != "on" || r.SourceOf("auth") != SrcCLI {
t.Fatalf("auth should be CLI: %q %v", r.AuthMode, r.SourceOf("auth"))
}
}
// 未显式传入的 CLI 字段不参与覆盖(区分"未设置"与零值)。
func TestUnsetCLIDoesNotOverride(t *testing.T) {
env := func(k string) (string, bool) {
if k == "AGENT_SYSTEM_ADDR" {
return "127.0.0.1:6000", true
}
return "", false
}
// cli.Addr 是零值 "" 但未在 cliSet 中:不得覆盖环境值。
r, err := Load("", env, Config{}, nil)
if err != nil {
t.Fatal(err)
}
if r.Addr != "127.0.0.1:6000" || r.SourceOf("addr") != SrcEnv {
t.Fatalf("addr should stay env: %q %v", r.Addr, r.SourceOf("addr"))
}
}
// 文件层按键存在覆盖:mock:false 显式出现也生效。
func TestFilePresenceSemantics(t *testing.T) {
f := fileWith(t, `{"mock":true,"data":"./d2"}`)
r, err := Load(f, noEnv, Config{}, nil)
if err != nil {
t.Fatal(err)
}
if !r.Mock || r.DataDir != "./d2" || r.SourceOf("mock") != SrcFile {
t.Fatalf("file layer wrong: %+v mock_src=%v", r.Config, r.SourceOf("mock"))
}
}
// 未知键:错误须定位字段名。
func TestUnknownKey(t *testing.T) {
f := fileWith(t, `{"bogus":1}`)
_, err := Load(f, noEnv, Config{}, nil)
if err == nil || !bytes.Contains([]byte(err.Error()), []byte(`unknown key "bogus"`)) {
t.Fatalf("want unknown-key error, got %v", err)
}
}
// 显式传入而缺失:报错。
func TestMissingFile(t *testing.T) {
_, err := Load("/nonexistent/c.json", noEnv, Config{}, nil)
if err == nil {
t.Fatal("missing file must error")
}
}
// 布尔环境变量:非法值报错,true/false/1/0 合法。
func TestEnvBool(t *testing.T) {
env := func(k string) (string, bool) {
if k == "AGENT_SYSTEM_MOCK" {
return "true", true
}
return "", false
}
r, err := Load("", env, Config{}, nil)
if err != nil || !r.Mock {
t.Fatalf("env bool: %v %+v", err, r.Config)
}
bad := func(k string) (string, bool) {
if k == "AGENT_SYSTEM_MOCK" {
return "yes", true
}
return "", false
}
if _, err := Load("", bad, Config{}, nil); err == nil {
t.Fatal("invalid bool must error")
}
}
// explain:来源标注 + DSN 脱敏;只读(不落任何盘外文件)。
func TestExplainRedactsDSN(t *testing.T) {
env := func(k string) (string, bool) {
if k == "AGENT_SYSTEM_DSN" {
return "postgres://u:secret@h:5432/db", true
}
return "", false
}
r, err := Load("", env, Config{}, nil)
if err != nil {
t.Fatal(err)
}
var b bytes.Buffer
r.Explain(&b, "")
out := b.String()
if bytes.Contains([]byte(out), []byte("secret")) {
t.Fatalf("dsn not redacted: %s", out)
}
if !bytes.Contains([]byte(out), []byte("u:***@h")) {
t.Fatalf("redact form wrong: %s", out)
}
if !bytes.Contains([]byte(out), []byte("env")) {
t.Fatalf("source column missing: %s", out)
}
}