Files
ai-terminal-assistant/packages/server/tests/unit/routes/tools.test.ts
T
kurihada e53035ffc0 refactor(core,server): 统一模块职责并消除类型重复
Core 模块职责:
- 添加 inferPermissionType() 权限类型推断函数
- 添加 partToApiFormat()/partsToApiFormat() API 格式转换函数
- 添加 ApiPart/ApiTextPart/ApiToolPart/ApiReasoningPart 类型
- 统一导出 toolRegistry 供 Server 使用

Server 模块职责:
- 重命名 PermissionRequestContext 为 PermissionDisplayContext
- 移除本地 toolRegistry,直接使用 Core 的注册表
- 使用 Core 的 inferPermissionType 替代本地实现
- 使用 Core 的 partsToApiFormat 替代手动转换

文档更新:
- 在 gui-server-client.md 添加第11章「模块职责边界」
- 明确 Core 和 Server 的职责划分
2025-12-16 21:28:19 +08:00

144 lines
4.4 KiB
TypeScript

/**
* Tools Route 测试
*
* 测试工具管理 REST API 端点
* 注意:现在使用 Core 的 toolRegistry,工具已预先注册
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Hono } from 'hono';
import { toolsRouter } from '../../../src/routes/tools.js';
import { toolRegistry } from '@ai-assistant/core';
// Create test app
const app = new Hono();
app.route('/tools', toolsRouter);
describe('Tools Route', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('GET /tools - 列出所有工具', () => {
it('返回工具列表', async () => {
const res = await app.request('/tools');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(Array.isArray(json.data)).toBe(true);
// Core toolRegistry 已注册工具
expect(json.data.length).toBeGreaterThan(0);
});
it('工具包含必要字段', async () => {
const res = await app.request('/tools');
const json = await res.json();
expect(res.status).toBe(200);
// 检查第一个工具的字段
if (json.data.length > 0) {
const tool = json.data[0];
expect(tool).toHaveProperty('name');
expect(tool).toHaveProperty('description');
expect(tool).toHaveProperty('parameters');
// 不应该包含 execute 函数
expect(tool).not.toHaveProperty('execute');
}
});
it('返回的工具数量与 Core Registry 一致', async () => {
const res = await app.request('/tools');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.data.length).toBe(toolRegistry.getAllTools().length);
});
});
describe('GET /tools/:name - 获取单个工具', () => {
it('返回存在的工具 (bash)', async () => {
const res = await app.request('/tools/bash');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(json.data.name).toBe('bash');
expect(json.data.description).toBeDefined();
});
it('返回存在的工具 (read_file)', async () => {
const res = await app.request('/tools/read_file');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(json.data.name).toBe('read_file');
});
it('工具不存在返回 404', async () => {
const res = await app.request('/tools/non-existent-tool-12345');
const json = await res.json();
expect(res.status).toBe(404);
expect(json.success).toBe(false);
expect(json.error).toBe('Tool not found');
});
});
describe('POST /tools/:name/execute - 执行工具', () => {
it('存在的工具返回占位响应', async () => {
const res = await app.request('/tools/bash/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ params: { command: 'echo test' } }),
});
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(json.data.tool).toBe('bash');
expect(json.data.params).toEqual({ command: 'echo test' });
// 当前是占位实现
expect(json.data.message).toContain('not yet implemented');
});
it('工具不存在返回 404', async () => {
const res = await app.request('/tools/non-existent-tool-67890/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ params: {} }),
});
const json = await res.json();
expect(res.status).toBe(404);
expect(json.success).toBe(false);
expect(json.error).toBe('Tool not found');
});
it('无 params 时使用空对象', async () => {
const res = await app.request('/tools/bash/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
});
const json = await res.json();
expect(res.status).toBe(200);
expect(json.data.params).toEqual({});
});
it('无效 JSON 返回 500', async () => {
const res = await app.request('/tools/bash/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: 'invalid json',
});
const json = await res.json();
expect(res.status).toBe(500);
expect(json.success).toBe(false);
});
});
});