Files
Agent-System/internal/sysadmin/service_test.go
T
weichenandCursor db1706cd24 feat(docrevision): send clipped diff text to the change-report narrator
The narrator only received counts, so it could not describe the actual change. Caps keep the full document out and leave the input digest unchanged; the flag stays off.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 05:03:31 +00:00

195 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 != "shadow" || byKey["PI_CHANGE_REPORT_NARRATIVE"].AdapterReadiness != "degraded" {
t.Fatalf("narrative readiness must stay degraded while the flag is off and the live runner is not --no-tools: %+v", byKey["PI_CHANGE_REPORT_NARRATIVE"])
}
}
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",
}
},
})
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"]
if replace.Value != "true" || replace.ImplementationState != "placeholder" || replace.AdapterReadiness != "unavailable" {
t.Fatalf("replace=true must stay unwired: %+v", replace)
}
tier := byKey["MULTI_INTENT_PLAN_TIER"]
if tier.Value != "confirm" || tier.ImplementationState != "dry_run" || tier.AdapterReadiness != "unavailable" {
t.Fatalf("confirm tier must not look production-ready: %+v", tier)
}
u9 := byKey["MULTI_INTENT_U9_SEGMENT"]
if u9.Value != "true" || u9.AdapterReadiness != "unavailable" {
t.Fatalf("U9 flag must not imply a live segmenter: %+v", u9)
}
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"])
}
}