/** * Tests for Context Compactor (Phase 2) * * Covers: token estimation, threshold checking, memory flush, compaction * * Now uses intelligenceClient which delegates to Rust backend. * These tests mock the backend calls for unit testing. */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { intelligenceClient, type CompactableMessage, } from '../../desktop/src/lib/intelligence-client'; // === Mock localStorage === const localStorageMock = (() => { let store: Record = {}; return { getItem: (key: string) => store[key] ?? null, setItem: (key: string, value: string) => { store[key] = value; }, removeItem: (key: string) => { delete store[key]; }, clear: () => { store = {}; }, }; })(); vi.stubGlobal('localStorage', localStorageMock); // === Mock Tauri invoke === vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(async (cmd: string, _args?: unknown) => { // Mock responses for compactor commands if (cmd === 'compactor_check_threshold') { return { should_compact: false, current_tokens: 100, threshold: 15000, urgency: 'none', }; } if (cmd === 'compactor_compact') { return { compacted_messages: _args?.messages?.slice(-4) || [], summary: '压缩摘要:讨论了技术方案', original_count: _args?.messages?.length || 0, retained_count: 4, flushed_memories: 0, tokens_before_compaction: 1000, tokens_after_compaction: 200, }; } return null; }), })); // === Helpers === function makeMessages(count: number, contentLength: number = 100): CompactableMessage[] { const msgs: CompactableMessage[] = []; for (let i = 0; i < count; i++) { msgs.push({ role: i % 2 === 0 ? 'user' : 'assistant', content: '测试消息内容'.repeat(Math.ceil(contentLength / 6)).slice(0, contentLength), id: `msg_${i}`, timestamp: new Date(Date.now() - (count - i) * 60000).toISOString(), }); } return msgs; } // ============================================= // ContextCompactor Tests (via intelligenceClient) // ============================================= describe('ContextCompactor (via intelligenceClient)', () => { beforeEach(() => { localStorageMock.clear(); vi.clearAllMocks(); }); describe('checkThreshold', () => { it('returns check result with expected structure', async () => { const msgs = makeMessages(4); const check = await intelligenceClient.compactor.checkThreshold(msgs); expect(check).toHaveProperty('should_compact'); expect(check).toHaveProperty('current_tokens'); expect(check).toHaveProperty('threshold'); expect(check).toHaveProperty('urgency'); }); it('returns none urgency for small conversations', async () => { const msgs = makeMessages(4); const check = await intelligenceClient.compactor.checkThreshold(msgs); expect(check.urgency).toBe('none'); }); }); describe('compact', () => { it('returns compaction result with expected structure', async () => { const msgs = makeMessages(20); const result = await intelligenceClient.compactor.compact(msgs, 'agent-1'); expect(result).toHaveProperty('compacted_messages'); expect(result).toHaveProperty('summary'); expect(result).toHaveProperty('original_count'); expect(result).toHaveProperty('retained_count'); }); it('generates a summary', async () => { const msgs = makeMessages(20); const result = await intelligenceClient.compactor.compact(msgs, 'agent-1'); expect(result.summary).toBeDefined(); expect(result.summary.length).toBeGreaterThan(0); }); it('handles empty message list', async () => { const result = await intelligenceClient.compactor.compact([], 'agent-1'); expect(result).toHaveProperty('retained_count'); }); }); });