/** * Session Manager 测试 * * 测试会话管理器的核心功能 * * 注意:SessionManager 使用动态 import 加载 @ai-assistant/core。 * 测试中会创建新实例并记录初始会话数量,以确保测试独立性。 */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { SessionManager, getSessionManager } from '../../../src/session/manager.js'; describe('SessionManager', () => { let manager: SessionManager; let initialCount: number; // 追踪测试中创建的 session ID,用于清理 let createdSessionIds: string[] = []; beforeEach(async () => { vi.clearAllMocks(); createdSessionIds = []; // Create a fresh instance for each test manager = new SessionManager(); await manager.init(); // Record initial session count (may include sessions from storage) initialCount = manager.count(); }); afterEach(async () => { // 清理测试中创建的所有 session for (const sessionId of createdSessionIds) { try { await manager.delete(sessionId); } catch { // 忽略删除失败(可能已经被测试删除) } } }); // 辅助函数:创建 session 并追踪 ID async function createTrackedSession(data: Parameters[0]) { const session = await manager.create(data); createdSessionIds.push(session.id); return session; } describe('init - 初始化', () => { it('初始化成功', async () => { // Manager is already initialized in beforeEach // Verify it works by creating a session const session = await createTrackedSession({ name: 'Test' }); expect(session).toBeDefined(); }); it('重复初始化只执行一次', async () => { await manager.init(); await manager.init(); // 不会抛错,可以正常工作 const session = await createTrackedSession({ name: 'Test' }); expect(session).toBeDefined(); }); }); describe('create - 创建会话', () => { it('创建新会话', async () => { const session = await createTrackedSession({ name: 'New Session' }); expect(session).toBeDefined(); expect(session.name).toBe('New Session'); expect(session.status).toBe('idle'); expect(session.messageCount).toBe(0); }); it('生成唯一 ID', async () => { const session1 = await createTrackedSession({ name: 'Session 1' }); const session2 = await createTrackedSession({ name: 'Session 2' }); expect(session1.id).not.toBe(session2.id); }); it('使用自定义工作目录', async () => { const session = await createTrackedSession({ name: 'Custom Workdir', workdir: '/custom/path', }); expect(session.workdir).toBe('/custom/path'); }); it('无名称时创建匿名会话', async () => { const session = await createTrackedSession({}); expect(session).toBeDefined(); expect(session.name).toBeUndefined(); }); it('设置正确的时间戳', async () => { const before = new Date().toISOString(); const session = await createTrackedSession({ name: 'Test' }); const after = new Date().toISOString(); expect(session.createdAt).toBeDefined(); expect(session.createdAt >= before).toBe(true); expect(session.createdAt <= after).toBe(true); expect(session.createdAt).toBe(session.updatedAt); }); it('创建后 count 增加', async () => { const before = manager.count(); await createTrackedSession({ name: 'Test' }); expect(manager.count()).toBe(before + 1); }); }); describe('list - 列出会话', () => { it('返回所有会话(包含新创建的)', async () => { const countBefore = manager.list().length; await createTrackedSession({ name: 'Session 1' }); await createTrackedSession({ name: 'Session 2' }); const sessions = manager.list(); expect(sessions.length).toBe(countBefore + 2); }); it('按更新时间排序(最新在前)', async () => { const session1 = await createTrackedSession({ name: 'Older Session' }); const session2 = await createTrackedSession({ name: 'Newer Session' }); // Wait a bit and update session1 await new Promise((resolve) => setTimeout(resolve, 10)); manager.updateStatus(session1.id, 'active'); const sessions = manager.list(); // session1 should be first as it was updated last expect(sessions[0].id).toBe(session1.id); }); it('返回数组类型', () => { const sessions = manager.list(); expect(Array.isArray(sessions)).toBe(true); }); }); describe('get - 获取会话', () => { it('返回存在的会话', async () => { const created = await createTrackedSession({ name: 'Test' }); const session = manager.get(created.id); expect(session).toBeDefined(); expect(session?.id).toBe(created.id); }); it('不存在返回 undefined', () => { const session = manager.get('non-existent-id-12345'); expect(session).toBeUndefined(); }); }); describe('delete - 删除会话', () => { it('删除存在的会话', async () => { // 注意:这个测试会自己删除 session,不需要追踪 const session = await manager.create({ name: 'To Delete' }); const result = await manager.delete(session.id); expect(result).toBe(true); expect(manager.exists(session.id)).toBe(false); }); it('删除不存在的会话返回 false', async () => { const result = await manager.delete('non-existent-id-12345'); expect(result).toBe(false); }); it('删除会话时同时删除消息', async () => { // 注意:这个测试会自己删除 session,不需要追踪 const session = await manager.create({ name: 'With Messages' }); await manager.addMessage(session.id, { role: 'user', content: 'Hello' }); await manager.delete(session.id); expect(manager.getMessages(session.id)).toEqual([]); }); it('删除后 count 减少', async () => { // 注意:这个测试会自己删除 session,不需要追踪 const session = await manager.create({ name: 'Test' }); const countAfterCreate = manager.count(); await manager.delete(session.id); expect(manager.count()).toBe(countAfterCreate - 1); }); }); describe('updateStatus - 更新状态', () => { it('更新会话状态', async () => { const session = await createTrackedSession({ name: 'Test' }); const updated = manager.updateStatus(session.id, 'active'); expect(updated?.status).toBe('active'); }); it('更新为 busy 状态', async () => { const session = await createTrackedSession({ name: 'Test' }); const updated = manager.updateStatus(session.id, 'busy'); expect(updated?.status).toBe('busy'); }); it('更新为 idle 状态', async () => { const session = await createTrackedSession({ name: 'Test' }); manager.updateStatus(session.id, 'active'); const updated = manager.updateStatus(session.id, 'idle'); expect(updated?.status).toBe('idle'); }); it('更新时更新 updatedAt', async () => { const session = await createTrackedSession({ name: 'Test' }); const originalUpdatedAt = session.updatedAt; // Wait a bit to ensure time difference await new Promise((resolve) => setTimeout(resolve, 10)); const updated = manager.updateStatus(session.id, 'busy'); expect(updated?.updatedAt).not.toBe(originalUpdatedAt); }); it('不存在的会话返回 undefined', () => { const result = manager.updateStatus('non-existent-id-12345', 'active'); expect(result).toBeUndefined(); }); }); describe('getMessages - 获取消息', () => { it('返回会话消息', async () => { const session = await createTrackedSession({ name: 'Test' }); await manager.addMessage(session.id, { role: 'user', content: 'Hello' }); await manager.addMessage(session.id, { role: 'assistant', content: 'Hi!' }); const messages = manager.getMessages(session.id); expect(messages.length).toBe(2); expect(messages[0].content).toBe('Hello'); expect(messages[1].content).toBe('Hi!'); }); it('新会话消息为空', async () => { const session = await createTrackedSession({ name: 'Test' }); const messages = manager.getMessages(session.id); expect(messages).toEqual([]); }); it('不存在的会话返回空数组', () => { const messages = manager.getMessages('non-existent-id-12345'); expect(messages).toEqual([]); }); }); describe('addMessage - 添加消息', () => { it('添加用户消息', async () => { const session = await createTrackedSession({ name: 'Test' }); const message = await manager.addMessage(session.id, { role: 'user', content: 'Hello', }); expect(message).toBeDefined(); expect(message?.role).toBe('user'); expect(message?.content).toBe('Hello'); expect(message?.id).toBeDefined(); expect(message?.sessionId).toBe(session.id); }); it('添加助手消息', async () => { const session = await createTrackedSession({ name: 'Test' }); const message = await manager.addMessage(session.id, { role: 'assistant', content: 'Hello!', }); expect(message?.role).toBe('assistant'); }); it('添加系统消息', async () => { const session = await createTrackedSession({ name: 'Test' }); const message = await manager.addMessage(session.id, { role: 'system', content: 'System prompt', }); expect(message?.role).toBe('system'); }); it('消息有唯一 ID', async () => { const session = await createTrackedSession({ name: 'Test' }); const msg1 = await manager.addMessage(session.id, { role: 'user', content: 'Hello' }); const msg2 = await manager.addMessage(session.id, { role: 'assistant', content: 'Hi!' }); expect(msg1?.id).not.toBe(msg2?.id); }); it('消息有正确的时间戳', async () => { const session = await createTrackedSession({ name: 'Test' }); const before = new Date().toISOString(); const message = await manager.addMessage(session.id, { role: 'user', content: 'Hello' }); const after = new Date().toISOString(); expect(message?.createdAt).toBeDefined(); expect(message?.createdAt! >= before).toBe(true); expect(message?.createdAt! <= after).toBe(true); }); it('更新会话的 messageCount', async () => { const session = await createTrackedSession({ name: 'Test' }); expect(session.messageCount).toBe(0); await manager.addMessage(session.id, { role: 'user', content: 'Hello' }); const updated = manager.get(session.id); expect(updated?.messageCount).toBe(1); }); it('更新会话的 updatedAt', async () => { const session = await createTrackedSession({ name: 'Test' }); const originalUpdatedAt = session.updatedAt; await new Promise((resolve) => setTimeout(resolve, 10)); await manager.addMessage(session.id, { role: 'user', content: 'Hello' }); const updated = manager.get(session.id); expect(updated?.updatedAt).not.toBe(originalUpdatedAt); }); it('不存在的会话返回 undefined', async () => { const result = await manager.addMessage('non-existent-id-12345', { role: 'user', content: 'Hello', }); expect(result).toBeUndefined(); }); }); describe('updateSessionName - 更新名称', () => { it('更新会话名称', async () => { const session = await createTrackedSession({ name: 'Old Name' }); const updated = await manager.updateSessionName(session.id, 'New Name'); expect(updated?.name).toBe('New Name'); }); it('更新名称更新 updatedAt', async () => { const session = await createTrackedSession({ name: 'Test' }); const originalUpdatedAt = session.updatedAt; await new Promise((resolve) => setTimeout(resolve, 10)); await manager.updateSessionName(session.id, 'Updated'); expect(manager.get(session.id)?.updatedAt).not.toBe(originalUpdatedAt); }); it('不存在的会话返回 undefined', async () => { const result = await manager.updateSessionName('non-existent-id-12345', 'Name'); expect(result).toBeUndefined(); }); it('更新为空名称', async () => { const session = await createTrackedSession({ name: 'Has Name' }); const updated = await manager.updateSessionName(session.id, ''); expect(updated?.name).toBe(''); }); }); describe('count - 会话数量', () => { it('创建后数量增加', async () => { const before = manager.count(); await createTrackedSession({ name: 'Session 1' }); expect(manager.count()).toBe(before + 1); await createTrackedSession({ name: 'Session 2' }); expect(manager.count()).toBe(before + 2); await createTrackedSession({ name: 'Session 3' }); expect(manager.count()).toBe(before + 3); }); it('返回非负整数', () => { expect(manager.count()).toBeGreaterThanOrEqual(0); expect(Number.isInteger(manager.count())).toBe(true); }); }); describe('exists - 检查存在', () => { it('存在的会话返回 true', async () => { const session = await createTrackedSession({ name: 'Test' }); expect(manager.exists(session.id)).toBe(true); }); it('不存在的会话返回 false', () => { expect(manager.exists('non-existent-id-12345')).toBe(false); }); it('删除后返回 false', async () => { // 注意:这个测试会自己删除 session,不需要追踪 const session = await manager.create({ name: 'Test' }); await manager.delete(session.id); expect(manager.exists(session.id)).toBe(false); }); }); describe('边界情况', () => { it('处理特殊字符的会话名称', async () => { const session = await createTrackedSession({ name: '测试会话 <>&"\'`' }); expect(session.name).toBe('测试会话 <>&"\'`'); }); it('处理长消息内容', async () => { const session = await createTrackedSession({ name: 'Test' }); const longContent = 'x'.repeat(10000); const message = await manager.addMessage(session.id, { role: 'user', content: longContent, }); expect(message?.content).toBe(longContent); }); it('处理空字符串消息', async () => { const session = await createTrackedSession({ name: 'Test' }); const message = await manager.addMessage(session.id, { role: 'user', content: '', }); expect(message?.content).toBe(''); }); it('多个会话独立的消息', async () => { const session1 = await createTrackedSession({ name: 'Session 1' }); const session2 = await createTrackedSession({ name: 'Session 2' }); await manager.addMessage(session1.id, { role: 'user', content: 'Message for session 1' }); await manager.addMessage(session2.id, { role: 'user', content: 'Message for session 2' }); const messages1 = manager.getMessages(session1.id); const messages2 = manager.getMessages(session2.id); expect(messages1.length).toBe(1); expect(messages2.length).toBe(1); expect(messages1[0].content).toBe('Message for session 1'); expect(messages2[0].content).toBe('Message for session 2'); }); }); }); describe('getSessionManager - 单例', () => { it('返回同一实例', () => { const manager1 = getSessionManager(); const manager2 = getSessionManager(); expect(manager1).toBe(manager2); }); it('返回 SessionManager 实例', () => { const manager = getSessionManager(); expect(manager).toBeInstanceOf(SessionManager); }); });