import { describe, it, expect } from 'vitest'; import { searchTools } from '../../../src/tools/search.js'; import type { ToolMetadata, ToolCategory } from '../../../src/tools/types.js'; // 创建测试用的工具元数据 function createToolMetadata( name: string, options: Partial<{ category: ToolCategory; description: string; keywords: string[]; deferLoading: boolean; }> = {} ): ToolMetadata { return { name, category: options.category ?? 'core', description: options.description ?? `Description for ${name}`, keywords: options.keywords ?? [name], deferLoading: options.deferLoading ?? true, }; } describe('searchTools - 工具搜索算法', () => { const testTools: ToolMetadata[] = [ createToolMetadata('read_file', { category: 'filesystem', description: '读取文件内容', keywords: ['read', 'file', 'open', 'cat', '文件', '读取'], }), createToolMetadata('write_file', { category: 'filesystem', description: '写入文件内容', keywords: ['write', 'file', 'save', 'create', '文件', '写入'], }), createToolMetadata('bash', { category: 'shell', description: '执行 bash 命令', keywords: ['bash', 'shell', 'command', 'execute', 'run', '命令', '执行'], }), createToolMetadata('glob', { category: 'filesystem', description: '搜索匹配模式的文件', keywords: ['glob', 'pattern', 'find', 'search', 'file', '搜索', '模式'], }), createToolMetadata('grep', { category: 'filesystem', description: '在文件内容中搜索', keywords: ['grep', 'search', 'content', 'find', '搜索', '内容'], }), createToolMetadata('git_status', { category: 'git', description: '查看 Git 仓库状态', keywords: ['git', 'status', 'repository', '状态', '仓库'], }), createToolMetadata('git_commit', { category: 'git', description: '提交代码更改', keywords: ['git', 'commit', 'save', '提交', '保存'], }), ]; describe('基础搜索功能', () => { it('按名称精确匹配得最高分', () => { const results = searchTools('bash', testTools); expect(results.length).toBeGreaterThan(0); expect(results[0].name).toBe('bash'); expect(results[0].score).toBeGreaterThanOrEqual(10); // 名称精确匹配 +10 }); it('按名称包含匹配', () => { const results = searchTools('file', testTools); expect(results.length).toBeGreaterThan(0); // read_file 和 write_file 都应该匹配 const fileTools = results.filter((r) => r.name.includes('file')); expect(fileTools.length).toBe(2); }); it('按关键词精确匹配', () => { const results = searchTools('shell', testTools); expect(results.length).toBeGreaterThan(0); expect(results[0].name).toBe('bash'); }); it('按描述内容匹配', () => { const results = searchTools('仓库', testTools); expect(results.length).toBeGreaterThan(0); expect(results.some((r) => r.name === 'git_status')).toBe(true); }); it('中文关键词搜索', () => { const results = searchTools('文件', testTools); expect(results.length).toBeGreaterThan(0); // read_file 和 write_file 都有 '文件' 关键词 expect(results.some((r) => r.name === 'read_file')).toBe(true); expect(results.some((r) => r.name === 'write_file')).toBe(true); }); }); describe('分词功能', () => { it('空格分隔的多词查询', () => { const results = searchTools('read file', testTools); expect(results.length).toBeGreaterThan(0); // read_file 应该得到最高分(匹配 read 和 file) expect(results[0].name).toBe('read_file'); }); it('逗号分隔的多词查询', () => { const results = searchTools('git,status', testTools); expect(results.length).toBeGreaterThan(0); expect(results[0].name).toBe('git_status'); }); it('中文逗号分隔', () => { const results = searchTools('读取,文件', testTools); expect(results.length).toBeGreaterThan(0); expect(results[0].name).toBe('read_file'); }); it('下划线分隔', () => { const results = searchTools('git_commit', testTools); expect(results.length).toBeGreaterThan(0); expect(results[0].name).toBe('git_commit'); }); it('连字符分隔', () => { const results = searchTools('read-write', testTools); // 应该匹配到包含 read 或 write 关键词的工具 expect(results.length).toBeGreaterThan(0); }); it('顿号分隔(中文)', () => { const results = searchTools('搜索、文件', testTools); expect(results.length).toBeGreaterThan(0); }); }); describe('评分规则', () => { it('名称精确匹配优先于包含匹配', () => { const tools: ToolMetadata[] = [ createToolMetadata('bash', { keywords: ['shell'] }), createToolMetadata('bash_advanced', { keywords: ['advanced'] }), // 移除 bash 关键词,避免额外得分 ]; const results = searchTools('bash', tools); expect(results[0].name).toBe('bash'); // 精确匹配得分更高 }); it('关键词精确匹配优先于包含匹配', () => { const tools: ToolMetadata[] = [ createToolMetadata('tool_a', { keywords: ['git'] }), createToolMetadata('tool_b', { keywords: ['github', 'gitlab'] }), ]; const results = searchTools('git', tools); expect(results[0].name).toBe('tool_a'); // 关键词精确匹配得分更高 }); it('多词查询累加分数', () => { const results = searchTools('git commit save', testTools); // git_commit 应该匹配 git, commit, save (在关键词中) expect(results[0].name).toBe('git_commit'); }); }); describe('结果限制', () => { it('默认返回最多 5 个结果', () => { const results = searchTools('file', testTools); expect(results.length).toBeLessThanOrEqual(5); }); it('自定义限制结果数量', () => { const results = searchTools('file', testTools, 2); expect(results.length).toBeLessThanOrEqual(2); }); it('limit 为 0 时返回空数组', () => { const results = searchTools('file', testTools, 0); expect(results).toHaveLength(0); }); }); describe('只搜索延迟加载的工具', () => { it('跳过 deferLoading=false 的工具', () => { const tools: ToolMetadata[] = [ createToolMetadata('core_tool', { keywords: ['test'], deferLoading: false, }), createToolMetadata('deferred_tool', { keywords: ['test'], deferLoading: true, }), ]; const results = searchTools('test', tools); expect(results).toHaveLength(1); expect(results[0].name).toBe('deferred_tool'); }); it('全部为 deferLoading=false 时返回空数组', () => { const tools: ToolMetadata[] = [ createToolMetadata('tool_a', { deferLoading: false }), createToolMetadata('tool_b', { deferLoading: false }), ]; const results = searchTools('tool', tools); expect(results).toHaveLength(0); }); }); describe('边界情况', () => { it('空查询返回空数组', () => { const results = searchTools('', testTools); expect(results).toHaveLength(0); }); it('只有空格的查询返回空数组', () => { const results = searchTools(' ', testTools); expect(results).toHaveLength(0); }); it('空工具列表返回空数组', () => { const results = searchTools('test', []); expect(results).toHaveLength(0); }); it('无匹配时返回空数组', () => { const results = searchTools('xyznonexistent', testTools); expect(results).toHaveLength(0); }); it('大小写不敏感', () => { const results = searchTools('BASH', testTools); expect(results.length).toBeGreaterThan(0); expect(results[0].name).toBe('bash'); }); }); describe('搜索结果结构', () => { it('返回正确的结果结构', () => { const results = searchTools('bash', testTools); expect(results[0]).toHaveProperty('name'); expect(results[0]).toHaveProperty('description'); expect(results[0]).toHaveProperty('category'); expect(results[0]).toHaveProperty('score'); }); it('结果按分数降序排列', () => { const results = searchTools('file', testTools); for (let i = 1; i < results.length; i++) { expect(results[i - 1].score).toBeGreaterThanOrEqual(results[i].score); } }); }); });