package engine import ( "context" "testing" agentctx "git.flytoex.net/yuanwei/flyto-agent/pkg/context" "git.flytoex.net/yuanwei/flyto-agent/pkg/permission" ) // --- resolvePolicy 测试 --- // TestResolvePolicy_Empty 空策略列表返回 CompositePolicy(内部 fallback 到 DefaultCodePolicy) func TestResolvePolicy_Empty(t *testing.T) { cfg := &Config{} policy := cfg.resolvePolicy() // 升华改进(ELEVATED): 总是返回 CompositePolicy,支持运行时动态 Add cp, ok := policy.(*agentctx.CompositePolicy) if !ok { t.Fatalf("空策略应返回 CompositePolicy, 实际类型 %T", policy) } // 内部无策略,fallback 到 DefaultCodePolicy 的关键词 kw := cp.PreserveKeywords() if kw == "" { t.Error("空 CompositePolicy 应 fallback 到 DefaultCodePolicy 的关键词") } } // TestResolvePolicy_SinglePolicy 单策略也包装为 CompositePolicy(保持动态 Add 能力) func TestResolvePolicy_SinglePolicy(t *testing.T) { mock := &testPolicy{name: "warehouse"} cfg := &Config{ CompactionPolicies: []agentctx.CompactionPolicy{mock}, } policy := cfg.resolvePolicy() // 升华改进(ELEVATED): 即使单策略也返回 CompositePolicy cp, ok := policy.(*agentctx.CompositePolicy) if !ok { t.Fatalf("单策略应包装为 CompositePolicy, 实际类型 %T", policy) } if cp.Len() != 1 { t.Errorf("CompositePolicy 策略数 = %d, 期望 1", cp.Len()) } } // TestResolvePolicy_MultiplePolicies 多策略叠加为 CompositePolicy func TestResolvePolicy_MultiplePolicies(t *testing.T) { code := &testPolicy{name: "code"} warehouse := &testPolicy{name: "warehouse"} cfg := &Config{ CompactionPolicies: []agentctx.CompactionPolicy{code, warehouse}, } policy := cfg.resolvePolicy() cp, ok := policy.(*agentctx.CompositePolicy) if !ok { t.Fatalf("多策略应返回 CompositePolicy, 实际类型 %T", policy) } if cp.Len() != 2 { t.Errorf("CompositePolicy 策略数 = %d, 期望 2", cp.Len()) } } // --- resolvePermissionHandler 测试 --- // TestResolvePermissionHandler_Empty 都为空返回 nil func TestResolvePermissionHandler_Empty(t *testing.T) { cfg := &Config{} handler := cfg.resolvePermissionHandler() if handler != nil { t.Error("空配置应返回 nil handler") } } // TestResolvePermissionHandler_LegacySingle 向后兼容单 Handler func TestResolvePermissionHandler_LegacySingle(t *testing.T) { called := false cfg := &Config{ PermissionHandler: func(ctx context.Context, req *permission.Request) (*permission.Response, error) { called = true return &permission.Response{Decision: permission.DecisionAllow}, nil }, } handler := cfg.resolvePermissionHandler() if handler == nil { t.Fatal("有 PermissionHandler 时不应返回 nil") } resp, err := handler(context.Background(), &permission.Request{ToolName: "Bash"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if !called { t.Error("PermissionHandler 应被调用") } if resp.Decision != permission.DecisionAllow { t.Errorf("Decision = %q, want allow", resp.Decision) } } // TestResolvePermissionHandler_NamedHandlers 使用 PermissionHandlers 列表 func TestResolvePermissionHandler_NamedHandlers(t *testing.T) { var decisionCalled, auditCalled bool cfg := &Config{ // 旧字段也设了值,但 PermissionHandlers 优先 PermissionHandler: func(ctx context.Context, req *permission.Request) (*permission.Response, error) { t.Error("旧 PermissionHandler 不应被调用") return nil, nil }, PermissionHandlers: []permission.NamedHandler{ { Name: "cli", Handler: func(ctx context.Context, req *permission.Request) (*permission.Response, error) { decisionCalled = true return &permission.Response{Decision: permission.DecisionAllow, Reason: "cli"}, nil }, IsDecisionMaker: true, }, { Name: "audit", Handler: func(ctx context.Context, req *permission.Request) (*permission.Response, error) { auditCalled = true return nil, nil }, IsDecisionMaker: false, }, }, } handler := cfg.resolvePermissionHandler() if handler == nil { t.Fatal("有 PermissionHandlers 时不应返回 nil") } resp, err := handler(context.Background(), &permission.Request{ToolName: "Bash"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if !decisionCalled { t.Error("决策处理器应被调用") } if !auditCalled { t.Error("审计处理器应被调用") } if resp.Decision != permission.DecisionAllow { t.Errorf("Decision = %q, want allow", resp.Decision) } } // testPolicy 是测试用的 CompactionPolicy 实现. type testPolicy struct { name string } func (p *testPolicy) Name() string { return p.name } func (p *testPolicy) PreserveKeywords() string { return p.name + " keywords" } func (p *testPolicy) ScoreMessageImportance(role, content string) float64 { return 0.5 } func (p *testPolicy) MaxRecentRoundsToKeep() int { return 5 } func (p *testPolicy) PreprocessForCompaction(msgs []agentctx.CompactMessage) []agentctx.CompactMessage { return msgs }