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 - 配置管理
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user