Files
ai-terminal-assistant/packages/core/tests/unit/tools/todo-manager.test.ts
T
kurihada 5e32375f0e feat: 重构为 Monorepo 架构并实现 HTTP Server
架构变更:
- 采用 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 - 配置管理
2025-12-12 10:42:20 +08:00

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');
});
});