package sysadmin import ( "context" "encoding/json" "strings" "testing" "chat-agent-system/internal/config" ) type fakeStatus struct { ready bool version int64 } func (f fakeStatus) DatabaseReady() bool { return f.ready } func (f fakeStatus) SchemaVersion() int64 { return f.version } func (f fakeStatus) LiveSchemaVersion(_ context.Context) int64 { return f.version + 1 } type fakeKB struct{ online bool } func (f fakeKB) Online() bool { return f.online } func testConfig() *config.Config { return &config.Config{ Env: "test", AgentBackend: "stub", AllowInMemory: true, KbDbHost: "kb.internal", KbDbPort: 5432, KbDbUser: "regkb", KbDbPassword: "super-secret", KbDbName: "regulations", } } func TestOverview_AggregatesSources(t *testing.T) { svc := NewService(testConfig(), Options{ Status: fakeStatus{ready: true, version: 48}, KB: fakeKB{online: true}, TaskStoreReady: func(context.Context) bool { return true }, }) ov := svc.Overview(context.Background()) if ov.Status != "ok" { t.Fatalf("expected ok, got %s", ov.Status) } if ov.Env != "test" || ov.AgentBackend != "stub" { t.Fatalf("unexpected env/backend: %+v", ov) } if !ov.DatabaseReady || !ov.TaskStoreReady || !ov.KBOnline { t.Fatalf("expected all ready: %+v", ov) } // dbReady 时使用 LiveSchemaVersion(fake 返回 version+1) if ov.SchemaVersion != 49 { t.Fatalf("expected live schema version 49, got %d", ov.SchemaVersion) } if ov.UptimeSeconds < 0 { t.Fatalf("uptime must be >= 0, got %d", ov.UptimeSeconds) } } func TestOverview_DegradedWhenDBDownAndNotInMemory(t *testing.T) { cfg := testConfig() cfg.AllowInMemory = false cfg.ContextDbHost = "127.0.0.1" svc := NewService(cfg, Options{Status: fakeStatus{ready: false, version: 48}}) ov := svc.Overview(context.Background()) if ov.Status != "degraded" { t.Fatalf("expected degraded, got %s", ov.Status) } // 未就绪时回退启动缓存版本 if ov.SchemaVersion != 48 { t.Fatalf("expected cached schema version 48, got %d", ov.SchemaVersion) } if ov.TaskStoreReady || ov.KBOnline { t.Fatalf("expected nil probes to report not-ready: %+v", ov) } } func TestKBInfo_NeverLeaksSecrets(t *testing.T) { svc := NewService(testConfig(), Options{KB: fakeKB{online: true}}) info := svc.KBInfo() if !info.Online { t.Fatal("expected online") } if info.Host != "kb.internal" || info.Port != 5432 || info.Database != "regulations" || info.User != "regkb" { t.Fatalf("unexpected connection view: %+v", info) } if info.SSLMode != "disable" { t.Fatalf("expected sslMode=disable (KbDSN fixed), got %s", info.SSLMode) } if info.RerankerEnabled { t.Fatal("reranker endpoint empty must report disabled") } // 结构体无密码字段;序列化结果也不得出现密码值 raw, err := json.Marshal(info) if err != nil { t.Fatalf("marshal: %v", err) } if s := string(raw); strings.Contains(s, "super-secret") || strings.Contains(s, "assword") { t.Fatalf("KBInfo leaks secret material: %s", s) } } func TestFeatures_CoversCatalogAndInjectedValues(t *testing.T) { svc := NewService(testConfig(), Options{ FeatureValues: func() map[string]string { return map[string]string{ "AGENT_BACKEND": "stub", "FAV_PUBLISHER_ENABLED": "true", "MULTI_INTENT_PLAN_TIER": "shadow", } }, }) features := svc.Features() if len(features) != len(featureCatalog) { t.Fatalf("expected %d features, got %d", len(featureCatalog), len(features)) } byKey := map[string]Feature{} for _, f := range features { byKey[f.Key] = f if f.Source != "env" || !f.RequiresRestart { t.Fatalf("feature %s must be env-sourced and restart-bound: %+v", f.Key, f) } if f.Label == "" || f.Group == "" { t.Fatalf("feature %s missing label/group: %+v", f.Key, f) } } if byKey["FAV_PUBLISHER_ENABLED"].Value != "true" { t.Fatalf("expected injected FAV value, got %+v", byKey["FAV_PUBLISHER_ENABLED"]) } if byKey["FAV_PUBLISHER_ENABLED"].Group != "artifact-vault" { t.Fatalf("unexpected group: %+v", byKey["FAV_PUBLISHER_ENABLED"]) } if byKey["MULTI_INTENT_PLAN_TIER"].Value != "shadow" { t.Fatalf("expected injected plan tier, got %+v", byKey["MULTI_INTENT_PLAN_TIER"]) } if byKey["INTENT_V2_SHADOW"].Value != "false" { t.Fatalf("expected catalog fallback false, got %+v", byKey["INTENT_V2_SHADOW"]) } for _, key := range []string{"AGENT_BACKEND", "AUTH_REGISTRATION_ENABLED", "FAV_DEGRADED_READ", "SEMANTIC_SUMMARY_SHADOW", "PI_CHANGE_REPORT_NARRATIVE"} { if _, ok := byKey[key]; !ok { t.Fatalf("catalog must cover %s", key) } } if byKey["PI_CHANGE_REPORT_NARRATIVE"].ImplementationState != "live" || byKey["PI_CHANGE_REPORT_NARRATIVE"].AdapterReadiness != "ready" { t.Fatalf("narrative must read live+ready after the 2026-09-26 dev rollout (real MiniMax retest PASS; dev flag on): %+v", byKey["PI_CHANGE_REPORT_NARRATIVE"]) } if !strings.Contains(byKey["SEMANTIC_SUMMARY_SHADOW"].AdapterReason, "chat_summaries") { t.Fatalf("semantic summary reason must reflect schema 61 chat_summaries: %+v", byKey["SEMANTIC_SUMMARY_SHADOW"]) } if strings.Contains(byKey["SEMANTIC_SUMMARY_SHADOW"].AdapterReason, "尚未在运行环境执行") || strings.Contains(byKey["PI_CHANGE_REPORT_NARRATIVE"].AdapterReason, "尚未切到") { t.Fatalf("readiness reasons must not claim missing migration or missing --no-tools: summary=%+v narrative=%+v", byKey["SEMANTIC_SUMMARY_SHADOW"], byKey["PI_CHANGE_REPORT_NARRATIVE"]) } if !strings.Contains(byKey["PI_CHANGE_REPORT_NARRATIVE"].AdapterReason, "2026-09-26") { t.Fatalf("narrative reason must note the dev rollout date: %+v", byKey["PI_CHANGE_REPORT_NARRATIVE"]) } for _, key := range []string{"MULTI_INTENT_PLAN_TIER", "MULTI_INTENT_U11_DOCGOV", "MULTI_INTENT_U12_RUN_CONTROL", "SEMANTIC_SUMMARY_SHADOW", "SEMANTIC_SUMMARY_REPLACE"} { if byKey[key].AdapterReadiness != "ready" || byKey[key].ImplementationState != "live" { t.Fatalf("%s must read live+ready after I2/I3/P8 wiring and the 2026-09-26 dev rollout: %+v", key, byKey[key]) } } if !strings.Contains(byKey["MULTI_INTENT_U11_DOCGOV"].AdapterReason, "逐键") || !strings.Contains(byKey["MULTI_INTENT_U12_RUN_CONTROL"].AdapterReason, "逐键") { t.Fatalf("U11/U12 reasons must keep the per-key rollout gate visible: %+v %+v", byKey["MULTI_INTENT_U11_DOCGOV"], byKey["MULTI_INTENT_U12_RUN_CONTROL"]) } } func TestFeatures_ConfiguredValueDoesNotImplyReadiness(t *testing.T) { svc := NewService(testConfig(), Options{ FeatureValues: func() map[string]string { return map[string]string{ "SEMANTIC_SUMMARY_REPLACE": "true", "MULTI_INTENT_PLAN_TIER": "confirm", "MULTI_INTENT_U9_SEGMENT": "true", "FAV_PUBLISHER_ENABLED": "true", } }, }) byKey := map[string]Feature{} for _, f := range svc.Features() { byKey[f.Key] = f if f.ConfiguredValue != f.Value || f.EffectiveValue != f.Value { t.Fatalf("env-only flag %s must report the same configured and effective value: %+v", f.Key, f) } if f.RolloutScope != "environment" { t.Fatalf("flag %s rollout scope = %q", f.Key, f.RolloutScope) } if f.ImplementationState == "" || f.AdapterReadiness == "" { t.Fatalf("flag %s missing readiness: %+v", f.Key, f) } } replace := byKey["SEMANTIC_SUMMARY_REPLACE"] // 2026-09-26 fail-closed gray wiring: the master bit alone replaces // nothing (session allowlist required); after the dev rollout the catalog // reads live+ready while the empty SESSIONS value still shows the // defense-in-depth default (replaces nothing until an allowlist is set). if replace.Value != "true" || replace.ImplementationState != "live" || replace.AdapterReadiness != "ready" { t.Fatalf("replace must read live+ready after full fail-closed wiring: %+v", replace) } if !strings.Contains(replace.Description, "白名单") { t.Fatalf("replace description must surface the allowlist requirement: %+v", replace) } allow := byKey["SEMANTIC_SUMMARY_REPLACE_SESSIONS"] if allow.Value != "" { t.Fatalf("replace sessions allowlist must default to empty (replaces nothing): %+v", allow) } // Static wiring facts must not flip with configured values: FAV stays // dry_run/unavailable even when configured true (red line, no live yet). fav := byKey["FAV_PUBLISHER_ENABLED"] if fav.Value != "true" || fav.ImplementationState != "dry_run" || fav.AdapterReadiness != "unavailable" { t.Fatalf("FAV must keep dry_run/unavailable regardless of configured value: %+v", fav) } tier := byKey["MULTI_INTENT_PLAN_TIER"] if tier.Value != "confirm" || tier.ImplementationState != "live" || tier.AdapterReadiness != "ready" { t.Fatalf("confirm tier value with I2/I3/P8 wiring must read live+ready: %+v", tier) } u9 := byKey["MULTI_INTENT_U9_SEGMENT"] if u9.Value != "true" || u9.ImplementationState != "live" || u9.AdapterReadiness != "ready" { t.Fatalf("U9 flag on must reflect live HandleTurn wiring: %+v", u9) } u10 := byKey["MULTI_INTENT_U10_MUTATE"] if u10.ImplementationState != "live" || u10.AdapterReadiness != "ready" { t.Fatalf("U10 live mutate adapter: %+v", u10) } if byKey["MULTI_INTENT_ENABLED"].ImplementationState != "live" || byKey["MULTI_INTENT_ENABLED"].AdapterReadiness != "ready" { t.Fatalf("legacy multi-intent remains the live path: %+v", byKey["MULTI_INTENT_ENABLED"]) } }