import { describe, it, expect, beforeEach, vi } from 'vitest'; import { SkillRegistry, getSkillRegistry, resetSkillRegistry, } from '../../../src/skills/registry.js'; import type { Skill } from '../../../src/skills/types.js'; // Mock loader to prevent file system access vi.mock('../../../src/skills/loader.js', () => ({ skillLoader: { loadFromDirectory: vi.fn().mockResolvedValue([]), getUserSkillsDir: vi.fn().mockReturnValue('/mock/user/skills'), getProjectSkillsDir: vi.fn().mockReturnValue('/mock/project/skills'), }, })); // Mock builtin skills vi.mock('../../../src/skills/builtin/index.js', () => ({ builtinSkills: [ { name: 'test-builtin', description: '内置测试 Skill', promptTemplate: '测试提示: {{param1}}', parameters: { param1: { type: 'string', required: true, description: '参数1' }, }, source: 'builtin', enabled: true, }, ], })); describe('SkillRegistry - Skill 注册表', () => { let registry: SkillRegistry; beforeEach(() => { resetSkillRegistry(); registry = new SkillRegistry({ autoLoad: false }); }); describe('register - 注册', () => { it('成功注册 Skill', () => { const skill: Skill = { name: 'test-skill', description: '测试 Skill', promptTemplate: '测试提示', source: 'user', }; registry.register(skill); expect(registry.has('test-skill')).toBe(true); }); it('高优先级 Skill 可以覆盖低优先级', () => { const builtinSkill: Skill = { name: 'same-name', description: '内置版本', promptTemplate: '内置提示', source: 'builtin', }; const projectSkill: Skill = { name: 'same-name', description: '项目版本', promptTemplate: '项目提示', source: 'project', }; registry.register(builtinSkill); registry.register(projectSkill); const result = registry.get('same-name'); expect(result?.source).toBe('project'); }); it('低优先级 Skill 不能覆盖高优先级', () => { const projectSkill: Skill = { name: 'same-name', description: '项目版本', promptTemplate: '项目提示', source: 'project', }; const builtinSkill: Skill = { name: 'same-name', description: '内置版本', promptTemplate: '内置提示', source: 'builtin', }; registry.register(projectSkill); registry.register(builtinSkill); const result = registry.get('same-name'); expect(result?.source).toBe('project'); }); }); describe('get - 获取', () => { it('获取存在的 Skill', () => { const skill: Skill = { name: 'test-skill', description: '测试', promptTemplate: '提示', source: 'user', }; registry.register(skill); const result = registry.get('test-skill'); expect(result).toBeDefined(); expect(result?.name).toBe('test-skill'); }); it('获取不存在的 Skill 返回 undefined', () => { const result = registry.get('non-existent'); expect(result).toBeUndefined(); }); }); describe('search - 搜索', () => { beforeEach(() => { const skills: Skill[] = [ { name: 'code-review', description: '代码审查', promptTemplate: '审查代码', keywords: ['review', 'quality'], category: 'development', source: 'builtin', enabled: true, }, { name: 'generate-tests', description: '生成测试', promptTemplate: '生成测试代码', keywords: ['test', 'unit'], category: 'testing', source: 'builtin', enabled: true, }, { name: 'explain-code', description: '解释代码功能', promptTemplate: '解释代码', category: 'development', source: 'builtin', enabled: true, }, ]; for (const skill of skills) { registry.register(skill); } }); it('按名称精确匹配', () => { const results = registry.search('code-review'); expect(results.length).toBeGreaterThan(0); expect(results[0].skill.name).toBe('code-review'); expect(results[0].score).toBe(100); }); it('按名称前缀匹配', () => { const results = registry.search('code'); expect(results.length).toBe(2); // code-review, explain-code }); it('按描述匹配', () => { const results = registry.search('审查'); expect(results.length).toBeGreaterThan(0); expect(results[0].skill.name).toBe('code-review'); }); it('按关键词匹配', () => { const results = registry.search('quality'); expect(results.length).toBeGreaterThan(0); expect(results[0].skill.name).toBe('code-review'); }); it('按分类匹配', () => { const results = registry.search('testing'); expect(results.length).toBeGreaterThan(0); expect(results[0].skill.name).toBe('generate-tests'); }); it('限制结果数量', () => { const results = registry.search('code', 1); expect(results.length).toBe(1); }); }); describe('renderPrompt - 渲染模板', () => { it('成功渲染模板', () => { const skill: Skill = { name: 'test', description: '测试', promptTemplate: '你好 {{name}},请{{action}}', source: 'user', }; const result = registry.renderPrompt(skill, { name: '世界', action: '测试', }); expect(result.success).toBe(true); expect(result.prompt).toBe('你好 世界,请测试'); }); it('缺少必需参数时返回错误', () => { const skill: Skill = { name: 'test', description: '测试', promptTemplate: '{{param}}', parameters: { param: { type: 'string', required: true, description: '必需参数' }, }, source: 'user', }; const result = registry.renderPrompt(skill, {}); expect(result.success).toBe(false); expect(result.error).toContain('缺少必需参数'); }); it('使用默认值', () => { const skill: Skill = { name: 'test', description: '测试', promptTemplate: '值: {{param}}', parameters: { param: { type: 'string', required: false, default: '默认值', description: '可选参数', }, }, source: 'user', }; const result = registry.renderPrompt(skill, {}); expect(result.success).toBe(true); expect(result.prompt).toBe('值: 默认值'); }); it('保留未匹配的变量', () => { const skill: Skill = { name: 'test', description: '测试', promptTemplate: '{{known}} 和 {{unknown}}', source: 'user', }; const result = registry.renderPrompt(skill, { known: '已知' }); expect(result.success).toBe(true); expect(result.prompt).toBe('已知 和 {{unknown}}'); }); }); describe('execute - 执行', () => { it('成功执行 Skill', () => { const skill: Skill = { name: 'test', description: '测试', promptTemplate: '提示: {{param}}', source: 'user', enabled: true, }; registry.register(skill); const result = registry.execute('test', { param: '值' }); expect(result.success).toBe(true); expect(result.prompt).toBe('提示: 值'); }); it('Skill 不存在时返回错误', () => { const result = registry.execute('non-existent', {}); expect(result.success).toBe(false); expect(result.error).toContain('不存在'); }); it('Skill 禁用时返回错误', () => { const skill: Skill = { name: 'disabled', description: '禁用的 Skill', promptTemplate: '提示', source: 'user', enabled: false, }; registry.register(skill); const result = registry.execute('disabled', {}); expect(result.success).toBe(false); expect(result.error).toContain('禁用'); }); }); describe('getByCategory - 按分类获取', () => { it('返回指定分类的 Skills', () => { const skills: Skill[] = [ { name: 'skill1', description: '1', promptTemplate: '1', category: 'dev', source: 'builtin', enabled: true, }, { name: 'skill2', description: '2', promptTemplate: '2', category: 'dev', source: 'builtin', enabled: true, }, { name: 'skill3', description: '3', promptTemplate: '3', category: 'test', source: 'builtin', enabled: true, }, ]; for (const skill of skills) { registry.register(skill); } const devSkills = registry.getByCategory('dev'); expect(devSkills.length).toBe(2); }); }); describe('getStats - 统计信息', () => { it('返回正确的统计信息', () => { const skills: Skill[] = [ { name: 'builtin1', description: '1', promptTemplate: '1', category: 'dev', source: 'builtin', enabled: true, }, { name: 'user1', description: '2', promptTemplate: '2', category: 'dev', source: 'user', enabled: true, }, { name: 'disabled1', description: '3', promptTemplate: '3', source: 'builtin', enabled: false, }, ]; for (const skill of skills) { registry.register(skill); } const stats = registry.getStats(); expect(stats.total).toBe(3); expect(stats.enabled).toBe(2); expect(stats.bySource.builtin).toBe(2); expect(stats.bySource.user).toBe(1); expect(stats.byCategory.dev).toBe(2); }); }); }); describe('getSkillRegistry - 全局注册表', () => { beforeEach(() => { resetSkillRegistry(); }); it('返回单例实例', () => { const registry1 = getSkillRegistry(); const registry2 = getSkillRegistry(); expect(registry1).toBe(registry2); }); it('resetSkillRegistry 重置实例', () => { const registry1 = getSkillRegistry(); resetSkillRegistry(); const registry2 = getSkillRegistry(); expect(registry1).not.toBe(registry2); }); });