Files
ai-terminal-assistant/packages/core/tests/unit/tools/search.test.ts
T
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

279 lines
8.6 KiB
TypeScript

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