/** * Sessions Route 测试 * * 测试会话管理 REST API 端点 * * 注意:消息存储已移至 Core 层,GET /sessions/:id/messages 从 Core Storage 读取 */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { Hono } from 'hono'; // Use vi.hoisted to create mocks before vi.mock is hoisted const { mockList, mockCreate, mockGet, mockExists, mockDelete, mockMessageListBySession, mockPartGetByIds, } = vi.hoisted(() => ({ mockList: vi.fn(), mockCreate: vi.fn(), mockGet: vi.fn(), mockExists: vi.fn(), mockDelete: vi.fn(), mockMessageListBySession: vi.fn(), mockPartGetByIds: vi.fn(), })); vi.mock('../../../src/session/manager.js', () => ({ getSessionManager: vi.fn(() => ({ list: mockList, create: mockCreate, get: mockGet, exists: mockExists, delete: mockDelete, })), })); import { sessionsRouter } from '../../../src/routes/sessions.js'; // Create test app const app = new Hono(); app.route('/sessions', sessionsRouter); describe('Sessions Route', () => { beforeEach(() => { vi.clearAllMocks(); // Mock dynamic import of @ai-assistant/core vi.doMock('@ai-assistant/core', () => ({ MessageStorage: { listBySession: mockMessageListBySession, }, PartStorage: { getByIds: mockPartGetByIds, }, })); }); describe('GET /sessions - 列出会话', () => { it('返回会话列表', async () => { const sessions = [ { id: 'session-1', name: 'Test 1', status: 'idle', createdAt: 1000, updatedAt: 1000 }, { id: 'session-2', name: 'Test 2', status: 'active', createdAt: 2000, updatedAt: 2000 }, ]; mockList.mockReturnValue(sessions); const res = await app.request('/sessions'); const json = await res.json(); expect(res.status).toBe(200); expect(json.success).toBe(true); expect(json.data).toEqual(sessions); }); it('空列表返回空数组', async () => { mockList.mockReturnValue([]); const res = await app.request('/sessions'); const json = await res.json(); expect(res.status).toBe(200); expect(json.success).toBe(true); expect(json.data).toEqual([]); }); }); describe('POST /sessions - 创建会话', () => { it('创建新会话', async () => { const newSession = { id: 'session-1', name: 'New Session', status: 'idle', createdAt: Date.now(), updatedAt: Date.now(), }; mockCreate.mockResolvedValue(newSession); const res = await app.request('/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'New Session' }), }); const json = await res.json(); expect(res.status).toBe(201); expect(json.success).toBe(true); expect(json.data).toEqual(newSession); }); it('创建会话(无需必填字段)', async () => { // CreateSessionInputSchema 的所有字段都是可选的,任何对象都是有效的 const newSession = { id: 'session-2', name: undefined, status: 'idle', createdAt: Date.now(), updatedAt: Date.now(), }; mockCreate.mockResolvedValue(newSession); const res = await app.request('/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}), }); const json = await res.json(); expect(res.status).toBe(201); expect(json.success).toBe(true); }); }); describe('GET /sessions/:id - 获取单个会话', () => { it('返回会话详情', async () => { const session = { id: 'session-1', name: 'Test', status: 'idle' }; mockGet.mockReturnValue(session); const res = await app.request('/sessions/session-1'); const json = await res.json(); expect(res.status).toBe(200); expect(json.success).toBe(true); expect(json.data).toEqual(session); }); it('不存在的会话返回 404', async () => { mockGet.mockReturnValue(null); const res = await app.request('/sessions/non-existent'); const json = await res.json(); expect(res.status).toBe(404); expect(json.success).toBe(false); expect(json.error).toBe('Session not found'); }); }); describe('DELETE /sessions/:id - 删除会话', () => { it('删除存在的会话', async () => { mockExists.mockReturnValue(true); mockDelete.mockResolvedValue(true); const res = await app.request('/sessions/session-1', { method: 'DELETE', }); const json = await res.json(); expect(res.status).toBe(200); expect(json.success).toBe(true); expect(mockDelete).toHaveBeenCalledWith('session-1'); }); it('不存在的会话返回 404', async () => { mockExists.mockReturnValue(false); const res = await app.request('/sessions/non-existent', { method: 'DELETE', }); const json = await res.json(); expect(res.status).toBe(404); expect(json.success).toBe(false); expect(json.error).toBe('Session not found'); }); }); describe('GET /sessions/:id/messages - 获取消息', () => { it('不存在的会话返回 404', async () => { mockExists.mockReturnValue(false); const res = await app.request('/sessions/non-existent/messages'); const json = await res.json(); expect(res.status).toBe(404); expect(json.success).toBe(false); expect(json.error).toBe('Session not found'); }); it('会话存在时返回消息列表', async () => { mockExists.mockReturnValue(true); // Mock empty messages (Core Storage will fail to import in tests, returning empty array) const res = await app.request('/sessions/session-1/messages'); const json = await res.json(); expect(res.status).toBe(200); expect(json.success).toBe(true); // In test environment, dynamic import fails and returns empty array expect(json.data).toEqual([]); }); it('Core Storage 错误时返回空数组', async () => { mockExists.mockReturnValue(true); // The route handles errors gracefully const res = await app.request('/sessions/session-1/messages'); const json = await res.json(); expect(res.status).toBe(200); expect(json.success).toBe(true); expect(json.data).toEqual([]); }); }); });