package context import ( "encoding/json" "testing" ) // mockPolicy 是测试用的模拟策略. type mockPolicy struct { name string keywords string score float64 rounds int preprocess func([]CompactMessage) []CompactMessage } func (m *mockPolicy) Name() string { return m.name } func (m *mockPolicy) PreserveKeywords() string { return m.keywords } func (m *mockPolicy) ScoreMessageImportance(role, content string) float64 { return m.score } func (m *mockPolicy) MaxRecentRoundsToKeep() int { return m.rounds } func (m *mockPolicy) PreprocessForCompaction(msgs []CompactMessage) []CompactMessage { if m.preprocess != nil { return m.preprocess(msgs) } return msgs } // TestCompositePolicy_SinglePolicyDegradation 单策略退化测试 func TestCompositePolicy_SinglePolicyDegradation(t *testing.T) { single := &mockPolicy{ name: "mock", keywords: "order numbers, SKU codes", score: 0.7, rounds: 8, } cp := NewCompositePolicy(single) if cp.PreserveKeywords() != "order numbers, SKU codes" { t.Errorf("单策略关键词不匹配: %q", cp.PreserveKeywords()) } if cp.ScoreMessageImportance("user", "test") != 0.7 { t.Errorf("单策略评分不匹配: %.2f", cp.ScoreMessageImportance("user", "test")) } if cp.MaxRecentRoundsToKeep() != 8 { t.Errorf("单策略轮数不匹配: %d", cp.MaxRecentRoundsToKeep()) } } // TestCompositePolicy_DualPolicyKeywordsMerge 双策略关键词合并 func TestCompositePolicy_DualPolicyKeywordsMerge(t *testing.T) { code := &mockPolicy{name: "code", keywords: "file paths, function names, error messages"} warehouse := &mockPolicy{name: "warehouse", keywords: "order numbers, SKU codes, error messages"} cp := NewCompositePolicy(code, warehouse) kw := cp.PreserveKeywords() // 应包含所有关键词 for _, expected := range []string{"file paths", "function names", "order numbers", "SKU codes"} { if !containsStr(kw, expected) { t.Errorf("合并关键词应包含 %q, 实际: %q", expected, kw) } } // "error messages" 应只出现一次(去重) count := 0 for _, seg := range splitAndTrim(kw) { if seg == "error messages" { count++ } } if count != 1 { t.Errorf("'error messages' 应只出现 1 次, 实际 %d 次, 全文: %q", count, kw) } } // TestCompositePolicy_ScoreMaximum 分数取最高值 func TestCompositePolicy_ScoreMaximum(t *testing.T) { low := &mockPolicy{name: "low", score: 0.3} high := &mockPolicy{name: "high", score: 0.9} cp := NewCompositePolicy(low, high) score := cp.ScoreMessageImportance("user", "any content") if score != 0.9 { t.Errorf("分数应取最高值 0.9, 实际 %.2f", score) } } // TestCompositePolicy_ScoreMaximum_ReversedOrder 分数取最高值(反序) func TestCompositePolicy_ScoreMaximum_ReversedOrder(t *testing.T) { high := &mockPolicy{name: "high", score: 0.9} low := &mockPolicy{name: "low", score: 0.3} cp := NewCompositePolicy(high, low) score := cp.ScoreMessageImportance("user", "any content") if score != 0.9 { t.Errorf("分数应取最高值 0.9, 实际 %.2f", score) } } // TestCompositePolicy_MaxRoundsToKeep 轮次取最大值 func TestCompositePolicy_MaxRoundsToKeep(t *testing.T) { short := &mockPolicy{name: "short", rounds: 3} long := &mockPolicy{name: "long", rounds: 10} cp := NewCompositePolicy(short, long) rounds := cp.MaxRecentRoundsToKeep() if rounds != 10 { t.Errorf("轮次应取最大值 10, 实际 %d", rounds) } } // TestCompositePolicy_PreprocessChain 预处理链式执行 func TestCompositePolicy_PreprocessChain(t *testing.T) { // 第一个策略给每条消息内容加前缀 first := &mockPolicy{ name: "first", preprocess: func(msgs []CompactMessage) []CompactMessage { result := make([]CompactMessage, len(msgs)) for i, m := range msgs { result[i] = CompactMessage{ Role: m.Role, Content: json.RawMessage(`"first:` + string(m.Content[1:])), } } return result }, } // 第二个策略给每条消息内容加前缀 second := &mockPolicy{ name: "second", preprocess: func(msgs []CompactMessage) []CompactMessage { result := make([]CompactMessage, len(msgs)) for i, m := range msgs { result[i] = CompactMessage{ Role: m.Role, Content: json.RawMessage(`"second:` + string(m.Content[1:])), } } return result }, } cp := NewCompositePolicy(first, second) input := []CompactMessage{ {Role: "user", Content: json.RawMessage(`"hello"`)}, } output := cp.PreprocessForCompaction(input) // 应该是 second:first:hello(先 first 再 second) var content string if err := json.Unmarshal(output[0].Content, &content); err != nil { t.Fatalf("解析结果失败: %v", err) } if content != "second:first:hello" { t.Errorf("预处理链结果 = %q, 期望 %q", content, "second:first:hello") } } // TestCompositePolicy_DynamicAddRemove 动态添加/移除策略 func TestCompositePolicy_DynamicAddRemove(t *testing.T) { cp := NewCompositePolicy(&mockPolicy{name: "a", score: 0.5, rounds: 3, keywords: "alpha"}) if cp.Len() != 1 { t.Fatalf("初始策略数 = %d, 期望 1", cp.Len()) } // 添加策略 cp.Add(&mockPolicy{name: "b", score: 0.8, rounds: 7, keywords: "beta"}) if cp.Len() != 2 { t.Errorf("添加后策略数 = %d, 期望 2", cp.Len()) } // 叠加效果:分数取最高 if cp.ScoreMessageImportance("user", "x") != 0.8 { t.Errorf("叠加后分数 = %.2f, 期望 0.8", cp.ScoreMessageImportance("user", "x")) } // 移除策略 removed := cp.Remove("b") if !removed { t.Error("移除 'b' 应返回 true") } if cp.Len() != 1 { t.Errorf("移除后策略数 = %d, 期望 1", cp.Len()) } // 移除后回到原始值 if cp.ScoreMessageImportance("user", "x") != 0.5 { t.Errorf("移除后分数 = %.2f, 期望 0.5", cp.ScoreMessageImportance("user", "x")) } // 移除不存在的策略 removed = cp.Remove("nonexistent") if removed { t.Error("移除不存在的策略应返回 false") } } // TestCompositePolicy_EmptyPoliciesFallback 空策略列表兜底 func TestCompositePolicy_EmptyPoliciesFallback(t *testing.T) { cp := NewCompositePolicy() // 应兜底使用 DefaultCodePolicy defaultPolicy := &DefaultCodePolicy{} if cp.PreserveKeywords() != defaultPolicy.PreserveKeywords() { t.Errorf("空策略关键词 = %q, 期望与 DefaultCodePolicy 一致", cp.PreserveKeywords()) } if cp.MaxRecentRoundsToKeep() != defaultPolicy.MaxRecentRoundsToKeep() { t.Errorf("空策略轮数 = %d, 期望 %d", cp.MaxRecentRoundsToKeep(), defaultPolicy.MaxRecentRoundsToKeep()) } } // TestCompositePolicy_NilPoliciesFiltered 空指针策略被过滤 func TestCompositePolicy_NilPoliciesFiltered(t *testing.T) { cp := NewCompositePolicy(nil, &mockPolicy{name: "real", score: 0.5}, nil) if cp.Len() != 1 { t.Errorf("nil 策略应被过滤, 策略数 = %d, 期望 1", cp.Len()) } } // TestCompositePolicy_AddNilIgnored 添加 nil 策略被忽略 func TestCompositePolicy_AddNilIgnored(t *testing.T) { cp := NewCompositePolicy() cp.Add(nil) if cp.Len() != 0 { t.Errorf("添加 nil 后策略数 = %d, 期望 0", cp.Len()) } } // TestCompositePolicy_Name 测试叠加策略名称 func TestCompositePolicy_Name(t *testing.T) { tests := []struct { desc string policies []CompactionPolicy want string }{ { desc: "空策略", policies: nil, want: "composite(empty)", }, { desc: "单策略", policies: []CompactionPolicy{&mockPolicy{name: "code"}}, want: "composite(code)", }, { desc: "多策略", policies: []CompactionPolicy{ &mockPolicy{name: "code"}, &mockPolicy{name: "warehouse"}, }, want: "composite(code+warehouse)", }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { cp := NewCompositePolicy(tt.policies...) if cp.Name() != tt.want { t.Errorf("Name() = %q, 期望 %q", cp.Name(), tt.want) } }) } } // TestCompositePolicy_ImplementsInterface 确认实现 CompactionPolicy 接口 func TestCompositePolicy_ImplementsInterface(t *testing.T) { var _ CompactionPolicy = (*CompositePolicy)(nil) } // splitAndTrim 按逗号分割并去除空白(测试辅助). func splitAndTrim(s string) []string { var result []string for _, part := range splitByComma(s) { trimmed := trimSpace(part) if trimmed != "" { result = append(result, trimmed) } } return result } func splitByComma(s string) []string { var parts []string start := 0 for i := 0; i < len(s); i++ { if s[i] == ',' { parts = append(parts, s[start:i]) start = i + 1 } } parts = append(parts, s[start:]) return parts } func trimSpace(s string) string { start := 0 for start < len(s) && (s[start] == ' ' || s[start] == '\t' || s[start] == '\n') { start++ } end := len(s) for end > start && (s[end-1] == ' ' || s[end-1] == '\t' || s[end-1] == '\n') { end-- } return s[start:end] }