package context import ( "context" "fmt" "testing" ) // --- 模拟实现 --- type mockFileCache struct { files []string } func (m *mockFileCache) RecentFiles(limit int) []string { if limit > 0 && limit < len(m.files) { return m.files[:limit] } return m.files } type mockSkillProvider struct { skills []SkillInfo } func (m *mockSkillProvider) ActiveSkills() []SkillInfo { return m.skills } // --- RestoreManager 测试 --- // TestRestoreManager_Execute_Priority 测试恢复器按优先级执行 func TestRestoreManager_Execute_Priority(t *testing.T) { rm := NewRestoreManager(100000) // 注册两个恢复器,priority 50 先于 100 rm.Register(&mockRestorer{name: "low-priority", priority: 100, items: []RestoreItem{ {Type: "state", Name: "low", Content: "low priority", Tokens: 100}, }}) rm.Register(&mockRestorer{name: "high-priority", priority: 50, items: []RestoreItem{ {Type: "state", Name: "high", Content: "high priority", Tokens: 100}, }}) ctx := context.Background() state := &CompactState{} items, err := rm.Execute(ctx, state) if err != nil { t.Fatalf("Execute 错误: %v", err) } if len(items) != 2 { t.Fatalf("期望 2 个恢复项, 实际 %d", len(items)) } // 高优先级应该先执行 if items[0].Name != "high" { t.Errorf("第一个恢复项应该是 high-priority, 实际 %s", items[0].Name) } if items[1].Name != "low" { t.Errorf("第二个恢复项应该是 low-priority, 实际 %s", items[1].Name) } } // TestRestoreManager_Execute_BudgetControl 测试 token 预算控制 func TestRestoreManager_Execute_BudgetControl(t *testing.T) { rm := NewRestoreManager(150) // 总预算 150 tokens // 第一个恢复器返回 100 tokens rm.Register(&mockRestorer{name: "r1", priority: 10, budget: 200, items: []RestoreItem{ {Type: "state", Name: "a", Content: "aaa", Tokens: 100}, }}) // 第二个恢复器返回 100 tokens(超过剩余预算 50) rm.Register(&mockRestorer{name: "r2", priority: 20, budget: 200, items: []RestoreItem{ {Type: "state", Name: "b", Content: "bbb", Tokens: 100}, }}) ctx := context.Background() state := &CompactState{} items, err := rm.Execute(ctx, state) if err != nil { t.Fatalf("Execute 错误: %v", err) } // 只应该返回第一个(100),第二个(100)超出剩余预算(50) if len(items) != 1 { t.Errorf("期望 1 个恢复项(预算限制), 实际 %d", len(items)) } } // TestRestoreManager_Execute_Empty 测试无恢复器 func TestRestoreManager_Execute_Empty(t *testing.T) { rm := NewRestoreManager(0) ctx := context.Background() state := &CompactState{} items, err := rm.Execute(ctx, state) if err != nil { t.Fatalf("Execute 错误: %v", err) } if len(items) != 0 { t.Errorf("无恢复器应返回空, 实际 %d", len(items)) } } // TestRestoreManager_Execute_RestorerFailure 测试单个恢复器失败不影响其他 func TestRestoreManager_Execute_RestorerFailure(t *testing.T) { rm := NewRestoreManager(100000) rm.Register(&mockRestorer{name: "good1", priority: 10, items: []RestoreItem{ {Type: "state", Name: "a", Content: "aaa", Tokens: 10}, }}) rm.Register(&mockRestorer{name: "bad", priority: 20, err: fmt.Errorf("mock error")}) rm.Register(&mockRestorer{name: "good2", priority: 30, items: []RestoreItem{ {Type: "state", Name: "b", Content: "bbb", Tokens: 10}, }}) ctx := context.Background() state := &CompactState{} items, err := rm.Execute(ctx, state) if err != nil { t.Fatalf("Execute 不应返回错误: %v", err) } if len(items) != 2 { t.Errorf("期望 2 个恢复项(跳过失败的), 实际 %d", len(items)) } } // TestRestoreManager_ContextCancellation 测试上下文取消 func TestRestoreManager_ContextCancellation(t *testing.T) { rm := NewRestoreManager(100000) rm.Register(&mockRestorer{name: "r1", priority: 10, items: []RestoreItem{ {Type: "state", Name: "a", Content: "aaa", Tokens: 10}, }}) rm.Register(&mockRestorer{name: "r2", priority: 20, items: []RestoreItem{ {Type: "state", Name: "b", Content: "bbb", Tokens: 10}, }}) ctx, cancel := context.WithCancel(context.Background()) cancel() // 立即取消 state := &CompactState{} _, err := rm.Execute(ctx, state) if err == nil { // 已取消的上下文,可能返回空结果或错误 // 两种都可接受 } } // --- 内置恢复器测试 --- // TestFileRestorer 测试文件恢复器 func TestFileRestorer(t *testing.T) { fc := &mockFileCache{files: []string{"/a.go", "/b.go", "/c.go"}} r := NewFileRestorer(fc) if r.Name() != "file-restorer" { t.Errorf("Name = %s", r.Name()) } if r.Priority() != 10 { t.Errorf("Priority = %d", r.Priority()) } state := &CompactState{} items, err := r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } // 应从 fileCache 恢复 3 个文件 if len(items) != 3 { t.Errorf("期望 3 个恢复项, 实际 %d", len(items)) } for _, item := range items { if item.Type != "file" { t.Errorf("恢复项类型应为 file, 实际 %s", item.Type) } } } // TestFileRestorer_WithStateFiles 测试从 State 恢复文件 func TestFileRestorer_WithStateFiles(t *testing.T) { r := NewFileRestorer(nil) state := &CompactState{ RecentFiles: []string{"/x.go", "/y.go"}, } items, err := r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) != 2 { t.Errorf("期望 2 个恢复项, 实际 %d", len(items)) } } // TestFileRestorer_MaxFiles 测试文件数量上限 func TestFileRestorer_MaxFiles(t *testing.T) { files := make([]string, 10) for i := range files { files[i] = fmt.Sprintf("/file%d.go", i) } r := NewFileRestorer(nil) state := &CompactState{RecentFiles: files} items, err := r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) > 5 { t.Errorf("最多应恢复 5 个文件, 实际 %d", len(items)) } } // TestSkillRestorer 测试技能恢复器 func TestSkillRestorer(t *testing.T) { sp := &mockSkillProvider{skills: []SkillInfo{ {Name: "commit", Content: "commit skill content"}, {Name: "review", Content: "review skill content"}, }} r := NewSkillRestorer(sp) if r.Name() != "skill-restorer" { t.Errorf("Name = %s", r.Name()) } state := &CompactState{ActiveSkills: []string{"commit"}} items, err := r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) != 1 { t.Errorf("期望 1 个恢复项(只恢复活跃技能), 实际 %d", len(items)) } if items[0].Name != "commit" { t.Errorf("恢复的技能应为 commit, 实际 %s", items[0].Name) } } // TestSkillRestorer_NoProvider 测试无 provider 时只恢复名称 func TestSkillRestorer_NoProvider(t *testing.T) { r := NewSkillRestorer(nil) state := &CompactState{ActiveSkills: []string{"skill-a", "skill-b"}} items, err := r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) != 2 { t.Errorf("期望 2 个恢复项, 实际 %d", len(items)) } } // TestPlanRestorer 测试计划恢复器 func TestPlanRestorer(t *testing.T) { r := &PlanRestorer{} // 无计划 state := &CompactState{} items, err := r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) != 0 { t.Errorf("无计划应返回空, 实际 %d", len(items)) } // 有计划 state.CustomData = map[string]any{ "current_plan": "Step 1: do X\nStep 2: do Y", } items, err = r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) != 1 { t.Errorf("期望 1 个恢复项, 实际 %d", len(items)) } } // TestMCPRestorer 测试 MCP 恢复器 func TestMCPRestorer(t *testing.T) { r := &MCPRestorer{} // 无 MCP 服务器 state := &CompactState{} items, err := r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) != 0 { t.Errorf("无 MCP 应返回空, 实际 %d", len(items)) } // 有 MCP 服务器 state.MCPServers = []string{"server-a", "server-b"} items, err = r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) != 1 { t.Errorf("期望 1 个恢复项, 实际 %d", len(items)) } } // TestAgentRestorer 测试代理恢复器 func TestAgentRestorer(t *testing.T) { r := &AgentRestorer{} // 无代理 state := &CompactState{} items, err := r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) != 0 { t.Errorf("无代理应返回空, 实际 %d", len(items)) } // 有代理 state.ActiveAgents = []string{"agent-1"} items, err = r.Restore(context.Background(), state) if err != nil { t.Fatalf("Restore 错误: %v", err) } if len(items) != 1 { t.Errorf("期望 1 个恢复项, 实际 %d", len(items)) } } // --- 辅助 mock --- type mockRestorer struct { name string priority int budget int items []RestoreItem err error } func (m *mockRestorer) Name() string { return m.name } func (m *mockRestorer) Priority() int { return m.priority } func (m *mockRestorer) TokenBudget() int { if m.budget > 0 { return m.budget } return 10000 } func (m *mockRestorer) Restore(_ context.Context, _ *CompactState) ([]RestoreItem, error) { if m.err != nil { return nil, m.err } return m.items, nil }