a657af9bb7
- 添加 afterEach 钩子清理测试中创建的 session - 引入 createTrackedSession 辅助函数追踪需要清理的 session - 防止测试产生的 session 持久化到磁盘影响用户数据
471 lines
16 KiB
TypeScript
471 lines
16 KiB
TypeScript
/**
|
|
* 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<SessionManager['create']>[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);
|
|
});
|
|
});
|