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,250 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import {
|
||||
CommandRegistry,
|
||||
getCommandRegistry,
|
||||
resetCommandRegistry,
|
||||
} from '../../../src/commands/registry.js';
|
||||
import type { Command } from '../../../src/commands/types.js';
|
||||
|
||||
// Mock loader
|
||||
vi.mock('../../../src/commands/loader.js', () => ({
|
||||
commandLoader: {
|
||||
loadFromDirectory: vi.fn().mockResolvedValue([]),
|
||||
getUserCommandsDir: vi.fn().mockReturnValue('/mock/user/commands'),
|
||||
getProjectCommandsDir: vi.fn().mockReturnValue('/mock/project/commands'),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock builtin commands
|
||||
vi.mock('../../../src/commands/builtin/index.js', () => ({
|
||||
builtinCommands: [
|
||||
{
|
||||
name: 'test-builtin',
|
||||
description: '内置测试命令',
|
||||
template: '测试模板 $ARGUMENTS',
|
||||
source: 'builtin',
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
describe('CommandRegistry - Command 注册表', () => {
|
||||
let registry: CommandRegistry;
|
||||
|
||||
beforeEach(() => {
|
||||
resetCommandRegistry();
|
||||
registry = new CommandRegistry();
|
||||
});
|
||||
|
||||
describe('register - 注册', () => {
|
||||
it('成功注册 Command', () => {
|
||||
const command: Command = {
|
||||
name: 'test',
|
||||
description: '测试',
|
||||
template: '模板',
|
||||
source: 'user',
|
||||
};
|
||||
|
||||
registry.register(command);
|
||||
expect(registry.has('test')).toBe(true);
|
||||
});
|
||||
|
||||
it('高优先级可以覆盖低优先级', () => {
|
||||
const builtin: Command = {
|
||||
name: 'same',
|
||||
description: '内置版本',
|
||||
template: '内置模板',
|
||||
source: 'builtin',
|
||||
};
|
||||
|
||||
const project: Command = {
|
||||
name: 'same',
|
||||
description: '项目版本',
|
||||
template: '项目模板',
|
||||
source: 'project',
|
||||
};
|
||||
|
||||
registry.register(builtin);
|
||||
registry.register(project);
|
||||
|
||||
const result = registry.get('same');
|
||||
expect(result?.source).toBe('project');
|
||||
expect(result?.description).toBe('项目版本');
|
||||
});
|
||||
|
||||
it('低优先级不能覆盖高优先级', () => {
|
||||
const project: Command = {
|
||||
name: 'same',
|
||||
description: '项目版本',
|
||||
template: '项目模板',
|
||||
source: 'project',
|
||||
};
|
||||
|
||||
const builtin: Command = {
|
||||
name: 'same',
|
||||
description: '内置版本',
|
||||
template: '内置模板',
|
||||
source: 'builtin',
|
||||
};
|
||||
|
||||
registry.register(project);
|
||||
registry.register(builtin);
|
||||
|
||||
const result = registry.get('same');
|
||||
expect(result?.source).toBe('project');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get - 获取', () => {
|
||||
it('获取存在的 Command', () => {
|
||||
const command: Command = {
|
||||
name: 'test',
|
||||
description: '测试',
|
||||
template: '模板',
|
||||
source: 'user',
|
||||
};
|
||||
|
||||
registry.register(command);
|
||||
const result = registry.get('test');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.name).toBe('test');
|
||||
});
|
||||
|
||||
it('获取不存在的 Command 返回 undefined', () => {
|
||||
const result = registry.get('non-existent');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('search - 搜索', () => {
|
||||
beforeEach(() => {
|
||||
const commands: Command[] = [
|
||||
{
|
||||
name: 'review',
|
||||
description: '代码审查',
|
||||
template: '审查模板',
|
||||
source: 'builtin',
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
description: '运行测试',
|
||||
template: '测试模板',
|
||||
source: 'builtin',
|
||||
},
|
||||
{
|
||||
name: 'deploy/staging',
|
||||
description: '部署到 staging',
|
||||
template: '部署模板',
|
||||
source: 'project',
|
||||
},
|
||||
];
|
||||
|
||||
for (const cmd of commands) {
|
||||
registry.register(cmd);
|
||||
}
|
||||
});
|
||||
|
||||
it('按名称精确匹配', () => {
|
||||
const results = registry.search('review');
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].command.name).toBe('review');
|
||||
expect(results[0].score).toBe(100);
|
||||
});
|
||||
|
||||
it('按名称前缀匹配', () => {
|
||||
const results = registry.search('deploy');
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].command.name).toBe('deploy/staging');
|
||||
});
|
||||
|
||||
it('按描述匹配', () => {
|
||||
const results = registry.search('代码');
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].command.name).toBe('review');
|
||||
});
|
||||
|
||||
it('限制结果数量', () => {
|
||||
const results = registry.search('', 1);
|
||||
expect(results.length).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('list - 列表', () => {
|
||||
it('返回所有 Commands 的摘要', () => {
|
||||
const command: Command = {
|
||||
name: 'test',
|
||||
description: '测试',
|
||||
template: '模板',
|
||||
source: 'user',
|
||||
};
|
||||
|
||||
registry.register(command);
|
||||
const list = registry.list();
|
||||
|
||||
expect(list.length).toBe(1);
|
||||
expect(list[0].name).toBe('test');
|
||||
expect(list[0].description).toBe('测试');
|
||||
expect(list[0].source).toBe('user');
|
||||
});
|
||||
|
||||
it('按名称排序', () => {
|
||||
const commands: Command[] = [
|
||||
{ name: 'zebra', description: '', template: '', source: 'user' },
|
||||
{ name: 'alpha', description: '', template: '', source: 'user' },
|
||||
{ name: 'beta', description: '', template: '', source: 'user' },
|
||||
];
|
||||
|
||||
for (const cmd of commands) {
|
||||
registry.register(cmd);
|
||||
}
|
||||
|
||||
const list = registry.list();
|
||||
expect(list[0].name).toBe('alpha');
|
||||
expect(list[1].name).toBe('beta');
|
||||
expect(list[2].name).toBe('zebra');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStats - 统计', () => {
|
||||
it('返回正确的统计信息', () => {
|
||||
const commands: Command[] = [
|
||||
{ name: 'b1', description: '', template: '', source: 'builtin' },
|
||||
{ name: 'b2', description: '', template: '', source: 'builtin' },
|
||||
{ name: 'u1', description: '', template: '', source: 'user' },
|
||||
{ name: 'p1', description: '', template: '', source: 'project' },
|
||||
];
|
||||
|
||||
for (const cmd of commands) {
|
||||
registry.register(cmd);
|
||||
}
|
||||
|
||||
const stats = registry.getStats();
|
||||
|
||||
expect(stats.total).toBe(4);
|
||||
expect(stats.bySource.builtin).toBe(2);
|
||||
expect(stats.bySource.user).toBe(1);
|
||||
expect(stats.bySource.project).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCommandRegistry - 全局注册表', () => {
|
||||
beforeEach(() => {
|
||||
resetCommandRegistry();
|
||||
});
|
||||
|
||||
it('返回单例实例', () => {
|
||||
const registry1 = getCommandRegistry();
|
||||
const registry2 = getCommandRegistry();
|
||||
|
||||
expect(registry1).toBe(registry2);
|
||||
});
|
||||
|
||||
it('resetCommandRegistry 重置实例', () => {
|
||||
const registry1 = getCommandRegistry();
|
||||
resetCommandRegistry();
|
||||
const registry2 = getCommandRegistry();
|
||||
|
||||
expect(registry1).not.toBe(registry2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user