5e32375f0e
架构变更: - 采用 pnpm workspaces 实现 Monorepo 结构 - 将现有代码迁移到 packages/core - 新增 packages/server HTTP 服务层 Server 功能: - REST API: 会话管理、工具管理、配置管理 - WebSocket: 实时双向通信支持 - SSE: 服务端事件推送 - Hono + Bun 作为运行时 API 端点: - GET/POST /api/sessions - 会话 CRUD - GET/POST /api/sessions/:id/messages - 消息管理 - GET /api/sessions/:id/events - SSE 事件流 - WS /api/ws/:sessionId - WebSocket 连接 - GET/POST /api/tools - 工具管理 - GET/PUT /api/config - 配置管理
334 lines
9.3 KiB
TypeScript
334 lines
9.3 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import type { Todo, TodoStatus } from '../../../src/session/types.js';
|
|
import type { SessionManager } from '../../../src/session/index.js';
|
|
|
|
// 需要单独导入 todoManager 因为它是单例
|
|
// 每个测试创建新的实例来避免状态污染
|
|
|
|
// 简单的 TodoManager 测试类(复制逻辑以测试)
|
|
class TestTodoManager {
|
|
private sessionManager: SessionManager | null = null;
|
|
|
|
setSessionManager(manager: SessionManager): void {
|
|
this.sessionManager = manager;
|
|
}
|
|
|
|
getTodos(): Todo[] {
|
|
if (!this.sessionManager) {
|
|
return [];
|
|
}
|
|
return this.sessionManager.getTodos();
|
|
}
|
|
|
|
async setTodos(todos: Todo[]): Promise<void> {
|
|
if (!this.sessionManager) {
|
|
return;
|
|
}
|
|
await this.sessionManager.setTodos(todos);
|
|
}
|
|
|
|
async addTodo(content: string, status: TodoStatus = 'pending'): Promise<Todo> {
|
|
const todos = this.getTodos();
|
|
const now = new Date().toISOString();
|
|
const newTodo: Todo = {
|
|
id: this.generateId(),
|
|
content,
|
|
status,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
todos.push(newTodo);
|
|
await this.setTodos(todos);
|
|
return newTodo;
|
|
}
|
|
|
|
async updateTodoStatus(id: string, status: TodoStatus): Promise<boolean> {
|
|
const todos = this.getTodos();
|
|
const todo = todos.find((t) => t.id === id);
|
|
if (!todo) return false;
|
|
|
|
todo.status = status;
|
|
todo.updatedAt = new Date().toISOString();
|
|
await this.setTodos(todos);
|
|
return true;
|
|
}
|
|
|
|
async deleteTodo(id: string): Promise<boolean> {
|
|
const todos = this.getTodos();
|
|
const index = todos.findIndex((t) => t.id === id);
|
|
if (index === -1) return false;
|
|
|
|
todos.splice(index, 1);
|
|
await this.setTodos(todos);
|
|
return true;
|
|
}
|
|
|
|
async clearTodos(): Promise<void> {
|
|
await this.setTodos([]);
|
|
}
|
|
|
|
private generateId(): string {
|
|
return Math.random().toString(36).substring(2, 10);
|
|
}
|
|
|
|
isInitialized(): boolean {
|
|
return this.sessionManager !== null;
|
|
}
|
|
}
|
|
|
|
// Mock SessionManager
|
|
function createMockSessionManager(): SessionManager {
|
|
let todos: Todo[] = [];
|
|
|
|
return {
|
|
getTodos: vi.fn(() => [...todos]),
|
|
setTodos: vi.fn(async (newTodos: Todo[]) => {
|
|
todos = [...newTodos];
|
|
}),
|
|
} as unknown as SessionManager;
|
|
}
|
|
|
|
describe('TodoManager - Todo 管理器', () => {
|
|
let todoManager: TestTodoManager;
|
|
let mockSessionManager: SessionManager;
|
|
|
|
beforeEach(() => {
|
|
todoManager = new TestTodoManager();
|
|
mockSessionManager = createMockSessionManager();
|
|
});
|
|
|
|
describe('初始化状态', () => {
|
|
it('未设置 sessionManager 时 isInitialized 返回 false', () => {
|
|
expect(todoManager.isInitialized()).toBe(false);
|
|
});
|
|
|
|
it('设置 sessionManager 后 isInitialized 返回 true', () => {
|
|
todoManager.setSessionManager(mockSessionManager);
|
|
|
|
expect(todoManager.isInitialized()).toBe(true);
|
|
});
|
|
|
|
it('未初始化时 getTodos 返回空数组', () => {
|
|
expect(todoManager.getTodos()).toEqual([]);
|
|
});
|
|
|
|
it('未初始化时 setTodos 不报错', async () => {
|
|
await expect(todoManager.setTodos([{
|
|
id: '1',
|
|
content: 'test',
|
|
status: 'pending',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}])).resolves.not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('getTodos', () => {
|
|
beforeEach(() => {
|
|
todoManager.setSessionManager(mockSessionManager);
|
|
});
|
|
|
|
it('返回空数组当没有 todos', () => {
|
|
const todos = todoManager.getTodos();
|
|
|
|
expect(todos).toEqual([]);
|
|
});
|
|
|
|
it('返回 sessionManager 中的 todos', async () => {
|
|
await todoManager.addTodo('Task 1');
|
|
await todoManager.addTodo('Task 2');
|
|
|
|
const todos = todoManager.getTodos();
|
|
|
|
expect(todos).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
describe('setTodos', () => {
|
|
beforeEach(() => {
|
|
todoManager.setSessionManager(mockSessionManager);
|
|
});
|
|
|
|
it('设置 todos 列表', async () => {
|
|
const todos: Todo[] = [
|
|
{
|
|
id: '1',
|
|
content: 'Task 1',
|
|
status: 'pending',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
},
|
|
];
|
|
|
|
await todoManager.setTodos(todos);
|
|
|
|
expect(todoManager.getTodos()).toHaveLength(1);
|
|
expect(todoManager.getTodos()[0].content).toBe('Task 1');
|
|
});
|
|
|
|
it('清空 todos', async () => {
|
|
await todoManager.addTodo('Task 1');
|
|
await todoManager.setTodos([]);
|
|
|
|
expect(todoManager.getTodos()).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('addTodo', () => {
|
|
beforeEach(() => {
|
|
todoManager.setSessionManager(mockSessionManager);
|
|
});
|
|
|
|
it('添加新 todo 默认状态为 pending', async () => {
|
|
const todo = await todoManager.addTodo('New task');
|
|
|
|
expect(todo.content).toBe('New task');
|
|
expect(todo.status).toBe('pending');
|
|
expect(todo.id).toBeDefined();
|
|
expect(todo.createdAt).toBeDefined();
|
|
expect(todo.updatedAt).toBeDefined();
|
|
});
|
|
|
|
it('添加新 todo 指定状态', async () => {
|
|
const todo = await todoManager.addTodo('In progress task', 'in_progress');
|
|
|
|
expect(todo.status).toBe('in_progress');
|
|
});
|
|
|
|
it('添加的 todo 出现在列表中', async () => {
|
|
await todoManager.addTodo('Task 1');
|
|
await todoManager.addTodo('Task 2');
|
|
|
|
const todos = todoManager.getTodos();
|
|
|
|
expect(todos).toHaveLength(2);
|
|
expect(todos.some(t => t.content === 'Task 1')).toBe(true);
|
|
expect(todos.some(t => t.content === 'Task 2')).toBe(true);
|
|
});
|
|
|
|
it('每个 todo 有唯一 ID', async () => {
|
|
const todo1 = await todoManager.addTodo('Task 1');
|
|
const todo2 = await todoManager.addTodo('Task 2');
|
|
|
|
expect(todo1.id).not.toBe(todo2.id);
|
|
});
|
|
});
|
|
|
|
describe('updateTodoStatus', () => {
|
|
beforeEach(() => {
|
|
todoManager.setSessionManager(mockSessionManager);
|
|
});
|
|
|
|
it('更新存在的 todo 状态', async () => {
|
|
const todo = await todoManager.addTodo('Task');
|
|
const result = await todoManager.updateTodoStatus(todo.id, 'completed');
|
|
|
|
expect(result).toBe(true);
|
|
const updated = todoManager.getTodos().find(t => t.id === todo.id);
|
|
expect(updated?.status).toBe('completed');
|
|
});
|
|
|
|
it('更新不存在的 todo 返回 false', async () => {
|
|
const result = await todoManager.updateTodoStatus('non-existent-id', 'completed');
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('更新状态时更新 updatedAt', async () => {
|
|
const todo = await todoManager.addTodo('Task');
|
|
const originalUpdatedAt = todo.updatedAt;
|
|
|
|
// 等待一小段时间确保时间戳不同
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
|
|
await todoManager.updateTodoStatus(todo.id, 'in_progress');
|
|
const updated = todoManager.getTodos().find(t => t.id === todo.id);
|
|
|
|
expect(updated?.updatedAt).not.toBe(originalUpdatedAt);
|
|
});
|
|
});
|
|
|
|
describe('deleteTodo', () => {
|
|
beforeEach(() => {
|
|
todoManager.setSessionManager(mockSessionManager);
|
|
});
|
|
|
|
it('删除存在的 todo', async () => {
|
|
const todo = await todoManager.addTodo('Task to delete');
|
|
const result = await todoManager.deleteTodo(todo.id);
|
|
|
|
expect(result).toBe(true);
|
|
expect(todoManager.getTodos().find(t => t.id === todo.id)).toBeUndefined();
|
|
});
|
|
|
|
it('删除不存在的 todo 返回 false', async () => {
|
|
const result = await todoManager.deleteTodo('non-existent-id');
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('删除后列表长度减少', async () => {
|
|
await todoManager.addTodo('Task 1');
|
|
const todo2 = await todoManager.addTodo('Task 2');
|
|
await todoManager.addTodo('Task 3');
|
|
|
|
await todoManager.deleteTodo(todo2.id);
|
|
|
|
expect(todoManager.getTodos()).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
describe('clearTodos', () => {
|
|
beforeEach(() => {
|
|
todoManager.setSessionManager(mockSessionManager);
|
|
});
|
|
|
|
it('清空所有 todos', async () => {
|
|
await todoManager.addTodo('Task 1');
|
|
await todoManager.addTodo('Task 2');
|
|
await todoManager.addTodo('Task 3');
|
|
|
|
await todoManager.clearTodos();
|
|
|
|
expect(todoManager.getTodos()).toHaveLength(0);
|
|
});
|
|
|
|
it('清空空列表不报错', async () => {
|
|
await expect(todoManager.clearTodos()).resolves.not.toThrow();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Todo 状态流转', () => {
|
|
let todoManager: TestTodoManager;
|
|
let mockSessionManager: SessionManager;
|
|
|
|
beforeEach(() => {
|
|
todoManager = new TestTodoManager();
|
|
mockSessionManager = createMockSessionManager();
|
|
todoManager.setSessionManager(mockSessionManager);
|
|
});
|
|
|
|
it('pending -> in_progress -> completed', async () => {
|
|
const todo = await todoManager.addTodo('Task');
|
|
|
|
expect(todo.status).toBe('pending');
|
|
|
|
await todoManager.updateTodoStatus(todo.id, 'in_progress');
|
|
let updated = todoManager.getTodos().find(t => t.id === todo.id);
|
|
expect(updated?.status).toBe('in_progress');
|
|
|
|
await todoManager.updateTodoStatus(todo.id, 'completed');
|
|
updated = todoManager.getTodos().find(t => t.id === todo.id);
|
|
expect(updated?.status).toBe('completed');
|
|
});
|
|
|
|
it('直接从 pending 到 completed', async () => {
|
|
const todo = await todoManager.addTodo('Quick task');
|
|
|
|
await todoManager.updateTodoStatus(todo.id, 'completed');
|
|
const updated = todoManager.getTodos().find(t => t.id === todo.id);
|
|
expect(updated?.status).toBe('completed');
|
|
});
|
|
});
|