/** * Agent Adapter 测试 * * 测试 initCore、getOrCreateAgent、配置错误处理等关键功能 */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { createMockCoreModule, createMockProviderRegistry, createMockAgentRegistry, createConfigurationErrorMock, } from '../../mocks/core.mock.js'; // 由于 adapter.ts 使用动态 import,需要特殊处理 // 我们需要在每个测试中重新导入模块以确保 mock 生效 describe('Agent Adapter', () => { // 存储原始模块状态 let originalCwd: typeof process.cwd; beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); originalCwd = process.cwd; }); afterEach(() => { process.cwd = originalCwd; vi.restoreAllMocks(); }); describe('initCore - Core 模块初始化', () => { it('成功加载 Core 模块时返回 true', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); const { initCore } = await import('../../../src/agent/adapter.js'); const result = await initCore(); expect(result).toBe(true); }); it('Core 模块缺少必要导出时返回 false', async () => { const incompleteCore = { Agent: undefined, toolRegistry: undefined, loadConfig: undefined, }; vi.doMock('@ai-assistant/core', () => incompleteCore); const { initCore } = await import('../../../src/agent/adapter.js'); const result = await initCore(); expect(result).toBe(false); }); it('Core 模块加载失败时返回 false', async () => { vi.doMock('@ai-assistant/core', () => { throw new Error('Module not found'); }); const { initCore } = await import('../../../src/agent/adapter.js'); const result = await initCore(); expect(result).toBe(false); }); it('调用 ProviderRegistry.init()', async () => { const mockProviderRegistry = createMockProviderRegistry({ isInitialized: vi.fn().mockReturnValue(false), }); const mockCore = createMockCoreModule({ providerRegistry: mockProviderRegistry, }); vi.doMock('@ai-assistant/core', () => mockCore); const { initCore } = await import('../../../src/agent/adapter.js'); await initCore(); expect(mockCore.getProviderRegistry().init).toHaveBeenCalled(); }); it('ProviderRegistry 已初始化时不重复调用 init()', async () => { const mockProviderRegistry = createMockProviderRegistry({ isInitialized: vi.fn().mockReturnValue(true), }); const mockCore = createMockCoreModule({ providerRegistry: mockProviderRegistry, }); vi.doMock('@ai-assistant/core', () => mockCore); const { initCore } = await import('../../../src/agent/adapter.js'); await initCore(); expect(mockCore.getProviderRegistry().init).not.toHaveBeenCalled(); }); it('调用 AgentRegistry.init() 并传入 process.cwd()', async () => { const testCwd = '/test/workdir'; process.cwd = vi.fn().mockReturnValue(testCwd); const mockAgentRegistry = createMockAgentRegistry({ isInitialized: vi.fn().mockReturnValue(false), }); const mockCore = createMockCoreModule({ agentRegistry: mockAgentRegistry, }); vi.doMock('@ai-assistant/core', () => mockCore); const { initCore } = await import('../../../src/agent/adapter.js'); await initCore(); expect(mockCore.agentRegistry.init).toHaveBeenCalledWith(testCwd); }); it('AgentRegistry 已初始化时不重复调用 init()', async () => { const mockAgentRegistry = createMockAgentRegistry({ isInitialized: vi.fn().mockReturnValue(true), }); const mockCore = createMockCoreModule({ agentRegistry: mockAgentRegistry, }); vi.doMock('@ai-assistant/core', () => mockCore); const { initCore } = await import('../../../src/agent/adapter.js'); await initCore(); expect(mockCore.agentRegistry.init).not.toHaveBeenCalled(); }); }); describe('isCoreAvailable - Core 模块可用性检查', () => { it('Core 未初始化时返回 false', async () => { vi.resetModules(); const { isCoreAvailable } = await import('../../../src/agent/adapter.js'); expect(isCoreAvailable()).toBe(false); }); it('Core 初始化后返回 true', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); const { initCore, isCoreAvailable } = await import('../../../src/agent/adapter.js'); await initCore(); expect(isCoreAvailable()).toBe(true); }); }); describe('getOrCreateAgent - Agent 创建与缓存', () => { it('Core 未初始化时返回 null', async () => { vi.resetModules(); const { getOrCreateAgent } = await import('../../../src/agent/adapter.js'); const agent = getOrCreateAgent('session-1'); expect(agent).toBeNull(); }); it('成功创建 Agent 实例', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); // Mock session manager vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue({ exists: vi.fn().mockReturnValue(true), get: vi.fn().mockReturnValue({ id: 'session-1' }), }), })); // Mock permission handler vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const { initCore, getOrCreateAgent } = await import('../../../src/agent/adapter.js'); await initCore(); const agent = getOrCreateAgent('session-1'); expect(agent).not.toBeNull(); expect(mockCore.Agent).toHaveBeenCalled(); }); it('重复获取同一 session 返回缓存的 Agent', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue({ exists: vi.fn().mockReturnValue(true), }), })); vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const { initCore, getOrCreateAgent } = await import('../../../src/agent/adapter.js'); await initCore(); const agent1 = getOrCreateAgent('session-1'); const agent2 = getOrCreateAgent('session-1'); expect(agent1).toBe(agent2); // Agent 构造函数只应被调用一次 expect(mockCore.Agent).toHaveBeenCalledTimes(1); }); it('ConfigurationError 时返回 null', async () => { const mockCore = createMockCoreModule({ loadConfig: createConfigurationErrorMock('未配置 API Key', 'deepseek'), }); vi.doMock('@ai-assistant/core', () => mockCore); vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue({ exists: vi.fn().mockReturnValue(true), }), })); vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const { initCore, getOrCreateAgent } = await import('../../../src/agent/adapter.js'); await initCore(); const agent = getOrCreateAgent('session-1'); expect(agent).toBeNull(); }); it('非 ConfigurationError 时重新抛出', async () => { const mockCore = createMockCoreModule({ loadConfig: vi.fn().mockImplementation(() => { throw new Error('Other error'); }), }); vi.doMock('@ai-assistant/core', () => mockCore); vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue({ exists: vi.fn().mockReturnValue(true), }), })); vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const { initCore, getOrCreateAgent } = await import('../../../src/agent/adapter.js'); await initCore(); expect(() => getOrCreateAgent('session-1')).toThrow('Other error'); }); }); describe('destroyAgent - Agent 销毁', () => { it('从缓存中移除 Agent', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue({ exists: vi.fn().mockReturnValue(true), }), })); vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const { initCore, getOrCreateAgent, destroyAgent } = await import('../../../src/agent/adapter.js'); await initCore(); // 创建 Agent getOrCreateAgent('session-1'); expect(mockCore.Agent).toHaveBeenCalledTimes(1); // 销毁 Agent destroyAgent('session-1'); // 再次获取应创建新 Agent getOrCreateAgent('session-1'); expect(mockCore.Agent).toHaveBeenCalledTimes(2); }); }); describe('getAgentStats - Agent 统计信息', () => { it('Core 未初始化时返回 available: false', async () => { vi.resetModules(); const { getAgentStats } = await import('../../../src/agent/adapter.js'); const stats = getAgentStats('session-1'); expect(stats).toEqual({ available: false }); }); it('Agent 不存在时返回 available: true 但无其他信息', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); const { initCore, getAgentStats } = await import('../../../src/agent/adapter.js'); await initCore(); const stats = getAgentStats('non-existent-session'); expect(stats).toEqual({ available: true }); }); it('Agent 存在时返回完整统计信息', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue({ exists: vi.fn().mockReturnValue(true), }), })); vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const { initCore, getOrCreateAgent, getAgentStats } = await import('../../../src/agent/adapter.js'); await initCore(); // 创建 Agent getOrCreateAgent('session-1'); const stats = getAgentStats('session-1'); expect(stats.available).toBe(true); expect(stats.toolCount).toEqual({ core: 5, discovered: 0, total: 5 }); expect(stats.contextUsage).toBe('10k / 200k'); }); }); describe('cancelProcessing - 取消处理', () => { it('更新会话状态为 idle', async () => { const mockSessionManager = { exists: vi.fn().mockReturnValue(true), updateStatus: vi.fn(), }; vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue(mockSessionManager), })); vi.doMock('../../../src/sse.js', () => ({ emitStatusEvent: vi.fn(), emitLogEvent: vi.fn(), })); const { cancelProcessing } = await import('../../../src/agent/adapter.js'); cancelProcessing('session-1'); expect(mockSessionManager.updateStatus).toHaveBeenCalledWith('session-1', 'idle'); }); }); describe('getContextUsage - 获取上下文使用情况', () => { it('Core 未初始化时返回 null', async () => { vi.resetModules(); const { getContextUsage } = await import('../../../src/agent/adapter.js'); const usage = getContextUsage('session-1'); expect(usage).toBeNull(); }); it('Agent 不存在时返回 null', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); const { initCore, getContextUsage } = await import('../../../src/agent/adapter.js'); await initCore(); const usage = getContextUsage('non-existent-session'); expect(usage).toBeNull(); }); it('Agent 存在时返回使用情况', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue({ exists: vi.fn().mockReturnValue(true), }), })); vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const { initCore, getOrCreateAgent, getContextUsage } = await import('../../../src/agent/adapter.js'); await initCore(); getOrCreateAgent('session-1'); const usage = getContextUsage('session-1'); expect(usage).not.toBeNull(); expect(usage?.formatted).toBe('10k / 200k'); expect(usage?.input).toBe(10000); }); }); describe('compressContext - 上下文压缩', () => { it('Core 未初始化时返回 null', async () => { vi.resetModules(); const { compressContext } = await import('../../../src/agent/adapter.js'); const result = await compressContext('session-1'); expect(result).toBeNull(); }); it('Agent 不存在时返回 null', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); const { initCore, compressContext } = await import('../../../src/agent/adapter.js'); await initCore(); const result = await compressContext('non-existent-session'); expect(result).toBeNull(); }); it('压缩成功时返回结果', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue({ exists: vi.fn().mockReturnValue(true), }), })); vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const { initCore, getOrCreateAgent, compressContext } = await import('../../../src/agent/adapter.js'); await initCore(); getOrCreateAgent('session-1'); const result = await compressContext('session-1'); expect(result).not.toBeNull(); expect(result?.type).toBe('prune'); expect(result?.freedTokens).toBe(1000); }); it('压缩失败时返回错误信息', async () => { const mockCore = createMockCoreModule({ agent: { compactHistory: vi.fn().mockRejectedValue(new Error('Compression failed')), }, }); vi.doMock('@ai-assistant/core', () => mockCore); vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue({ exists: vi.fn().mockReturnValue(true), }), })); vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const { initCore, getOrCreateAgent, compressContext } = await import('../../../src/agent/adapter.js'); await initCore(); getOrCreateAgent('session-1'); const result = await compressContext('session-1'); expect(result).not.toBeNull(); expect(result?.status).toBe('failed_error'); expect(result?.error).toBe('Compression failed'); }); }); describe('processMessage - 消息处理', () => { it('Agent 不可用时发送占位消息', async () => { const mockSessionManager = { exists: vi.fn().mockReturnValue(true), updateStatus: vi.fn(), addMessage: vi.fn().mockResolvedValue({ id: 'msg-1', role: 'assistant', content: 'placeholder' }), }; vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue(mockSessionManager), })); const mockBroadcast = vi.fn(); vi.doMock('../../../src/ws.js', () => ({ broadcastToSession: mockBroadcast, })); vi.doMock('../../../src/sse.js', () => ({ emitStatusEvent: vi.fn(), emitLogEvent: vi.fn(), })); // 不初始化 core,使 agent 为 null vi.resetModules(); const { processMessage } = await import('../../../src/agent/adapter.js'); await processMessage('session-1', 'Hello'); // 应该发送占位消息 expect(mockBroadcast).toHaveBeenCalledWith('session-1', expect.objectContaining({ type: 'chunk', })); }); it('成功处理消息并返回响应', async () => { const mockCore = createMockCoreModule(); vi.doMock('@ai-assistant/core', () => mockCore); const mockSessionManager = { exists: vi.fn().mockReturnValue(true), get: vi.fn().mockReturnValue({ id: 'session-1', name: 'Test' }), updateStatus: vi.fn(), addMessage: vi.fn().mockResolvedValue({ id: 'msg-1', role: 'assistant', content: 'response' }), getMessages: vi.fn().mockReturnValue([]), }; vi.doMock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn().mockReturnValue(mockSessionManager), })); vi.doMock('../../../src/permission/handler.js', () => ({ createServerPermissionCallback: vi.fn().mockReturnValue(async () => ({ allow: true })), })); const mockBroadcast = vi.fn(); vi.doMock('../../../src/ws.js', () => ({ broadcastToSession: mockBroadcast, })); vi.doMock('../../../src/sse.js', () => ({ emitStatusEvent: vi.fn(), emitLogEvent: vi.fn(), })); const { initCore, processMessage } = await import('../../../src/agent/adapter.js'); await initCore(); await processMessage('session-1', 'Hello'); // 应该发送 done 消息 expect(mockBroadcast).toHaveBeenCalledWith('session-1', expect.objectContaining({ type: 'done', })); }); }); });