Files
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

251 lines
6.6 KiB
TypeScript

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