// extractor_test.go - DefaultCodeExtractor 单元测试. package memory import ( "strings" "testing" ) // ───────────────────────────────────────────────────────────────────────────── // ShouldExtract // ───────────────────────────────────────────────────────────────────────────── func TestDefaultCodeExtractor_ShouldExtract_TooFewTurns(t *testing.T) { e := &DefaultCodeExtractor{} for turns := 0; turns < 5; turns++ { if e.ShouldExtract(turns, 0) { t.Errorf("ShouldExtract(%d, 0) should be false (< 5 turns)", turns) } } } func TestDefaultCodeExtractor_ShouldExtract_AtThreshold(t *testing.T) { e := &DefaultCodeExtractor{} // 第 5 轮,lastExtractTurn=0,差值=5,刚好触发 if !e.ShouldExtract(5, 0) { t.Error("ShouldExtract(5, 0) should be true") } } func TestDefaultCodeExtractor_ShouldExtract_NotYet(t *testing.T) { e := &DefaultCodeExtractor{} // 第 8 轮,lastExtractTurn=5,差值=3,未到 5 if e.ShouldExtract(8, 5) { t.Error("ShouldExtract(8, 5) should be false (only 3 turns since last)") } } func TestDefaultCodeExtractor_ShouldExtract_Again(t *testing.T) { e := &DefaultCodeExtractor{} // 第 10 轮,lastExtractTurn=5,差值=5,再次触发 if !e.ShouldExtract(10, 5) { t.Error("ShouldExtract(10, 5) should be true") } } // ───────────────────────────────────────────────────────────────────────────── // BuildPrompt // ───────────────────────────────────────────────────────────────────────────── func TestDefaultCodeExtractor_BuildPrompt_ContainsMessageCount(t *testing.T) { e := &DefaultCodeExtractor{} prompt := e.BuildPrompt(nil, 42) if !strings.Contains(prompt, "42") { t.Error("prompt should mention the newMessageCount (42)") } if !strings.Contains(prompt, "most recent") { t.Error("prompt should mention 'most recent'") } } func TestDefaultCodeExtractor_BuildPrompt_ZeroCountFallback(t *testing.T) { e := &DefaultCodeExtractor{} prompt := e.BuildPrompt(nil, 0) // newMessageCount=0 时退回 "full conversation history" if !strings.Contains(prompt, "full conversation history") { t.Errorf("prompt with count=0 should say 'full conversation history', got: %s", prompt[:200]) } } func TestDefaultCodeExtractor_BuildPrompt_ParallelStrategy(t *testing.T) { e := &DefaultCodeExtractor{} prompt := e.BuildPrompt(nil, 10) // 应包含并行读写策略说明 if !strings.Contains(prompt, "Turn 1") { t.Error("prompt should mention 'Turn 1' parallel read strategy") } if !strings.Contains(prompt, "Turn 2") { t.Error("prompt should mention 'Turn 2' parallel write strategy") } } func TestDefaultCodeExtractor_BuildPrompt_ListsExistingMemories(t *testing.T) { e := &DefaultCodeExtractor{} existing := []*Entry{ {Name: "project-structure", Type: TypeProject, Description: "directory layout"}, {Name: "code-conventions", Type: TypeFeedback, Description: "naming conventions"}, } prompt := e.BuildPrompt(existing, 5) if !strings.Contains(prompt, "project-structure") { t.Error("prompt should list existing memory 'project-structure'") } if !strings.Contains(prompt, "code-conventions") { t.Error("prompt should list existing memory 'code-conventions'") } if !strings.Contains(prompt, "do NOT duplicate") { t.Error("prompt should warn not to duplicate existing memories") } } func TestDefaultCodeExtractor_BuildPrompt_NoExistingMemoriesSection(t *testing.T) { e := &DefaultCodeExtractor{} prompt := e.BuildPrompt(nil, 5) // 没有现有记忆时不应有"Existing memories"节 if strings.Contains(prompt, "Existing memories") { t.Error("prompt should not have 'Existing memories' section when list is empty") } } // ───────────────────────────────────────────────────────────────────────────── // AllowedTools / MaxTurns / Name // ───────────────────────────────────────────────────────────────────────────── func TestDefaultCodeExtractor_AllowedTools(t *testing.T) { e := &DefaultCodeExtractor{} toolList := e.AllowedTools() required := map[string]bool{"Read": false, "Grep": false, "Glob": false, "Edit": false, "Write": false} for _, name := range toolList { required[name] = true } for name, found := range required { if !found { t.Errorf("AllowedTools() missing required tool %q", name) } } // 不应包含 Bash 或 Agent for _, name := range toolList { if name == "Bash" || name == "Agent" { t.Errorf("AllowedTools() should not include %q (too powerful for extraction)", name) } } } func TestDefaultCodeExtractor_MaxTurns(t *testing.T) { e := &DefaultCodeExtractor{} if e.MaxTurns() <= 0 { t.Error("MaxTurns() should be positive") } } func TestDefaultCodeExtractor_Name(t *testing.T) { e := &DefaultCodeExtractor{} if e.Name() == "" { t.Error("Name() should not be empty") } }