Files
ai-terminal-assistant/packages/server/tests/unit/routes/agents.test.ts
T
kurihada 1b7d55848d refactor(server): 消除与 Core 的重复类型定义
- 删除 Server 中 60+ 个与 Core 重复的类型定义
- 将动态导入 (await import) 改为静态类型导入 (import type)
- 保留必要的运行时静态导入
- 修复测试文件中的 mock 初始化问题
- 净删除约 960 行重复代码

重构文件:
- routes/checkpoints.ts: 删除 155 行重复类型
- routes/agents.ts: 删除 93 行重复类型
- routes/commands.ts: 删除 83 行重复类型
- routes/mcp.ts: 修复类型窄化
- routes/hooks.ts: 已使用静态导入
- routes/providers.ts: 删除 63 行重复类型
- session/manager.ts: 删除 41 行重复类型
- routes/sessions.ts: 添加类型导入
- permission/handler.ts: 添加类型导入
2025-12-16 20:19:24 +08:00

330 lines
9.7 KiB
TypeScript

/**
* Agents Route 测试
*
* 测试 Agent 预设管理 REST API 端点
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Hono } from 'hono';
// Create mock agent module
const mockAgentRegistry = vi.hoisted(() => ({
init: vi.fn().mockResolvedValue(undefined),
get: vi.fn(),
list: vi.fn(),
}));
const mockPresetAgents = vi.hoisted(() => ({
general: {
description: 'General purpose agent',
mode: 'subagent' as const,
model: { model: 'claude-3-5-sonnet' },
},
explore: {
description: 'Code exploration agent',
mode: 'subagent' as const,
},
}));
const mockAgentModule = vi.hoisted(() => ({
agentRegistry: mockAgentRegistry,
loadAgentConfig: vi.fn(),
saveAgentConfig: vi.fn(),
presetAgents: mockPresetAgents,
isPresetAgent: vi.fn((name: string) => name in mockPresetAgents),
}));
vi.mock('@ai-assistant/core', () => mockAgentModule);
vi.mock('../../../src/routes/config.js', () => ({
getConfig: vi.fn(() => ({ workdir: '/test/workdir' })),
}));
import { agentsRouter } from '../../../src/routes/agents.js';
// Create test app
const app = new Hono();
app.route('/agents', agentsRouter);
describe('Agents Route', () => {
beforeEach(() => {
vi.clearAllMocks();
mockAgentModule.loadAgentConfig.mockResolvedValue(null);
mockAgentModule.saveAgentConfig.mockResolvedValue(undefined);
});
describe('GET /agents - 获取所有 Agent 列表', () => {
it('返回 Agent 列表', async () => {
mockAgentRegistry.list.mockReturnValue([
{ name: 'general', description: 'General purpose agent', mode: 'subagent' },
{ name: 'explore', description: 'Code exploration agent', mode: 'subagent' },
]);
const res = await app.request('/agents');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(json.data).toHaveLength(2);
expect(json.data[0].name).toBe('general');
});
it('返回空列表', async () => {
mockAgentRegistry.list.mockReturnValue([]);
const res = await app.request('/agents');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.data).toEqual([]);
});
it('包含 isPreset 和 isCustomized 标记', async () => {
mockAgentRegistry.list.mockReturnValue([
{ name: 'general', description: 'General purpose agent', mode: 'subagent' },
]);
mockAgentModule.loadAgentConfig.mockResolvedValue({
agents: { general: { model: { model: 'custom-model' } } },
});
const res = await app.request('/agents');
const json = await res.json();
expect(json.data[0].isPreset).toBe(true);
expect(json.data[0].isCustomized).toBe(true);
});
});
describe('GET /agents/presets - 获取预设 Agent 列表', () => {
it('返回预设列表', async () => {
const res = await app.request('/agents/presets');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(json.data).toHaveLength(2);
expect(json.data.some((a: { name: string }) => a.name === 'general')).toBe(true);
expect(json.data.some((a: { name: string }) => a.name === 'explore')).toBe(true);
});
it('预设都标记为 isPreset: true', async () => {
const res = await app.request('/agents/presets');
const json = await res.json();
expect(json.data.every((a: { isPreset: boolean }) => a.isPreset === true)).toBe(true);
});
});
describe('GET /agents/defaults - 获取全局默认配置', () => {
it('返回默认配置', async () => {
mockAgentModule.loadAgentConfig.mockResolvedValue({
defaults: {
maxSteps: 10,
model: { model: 'claude-3-5-sonnet' },
},
});
const res = await app.request('/agents/defaults');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(json.data.maxSteps).toBe(10);
});
it('无配置时返回空对象', async () => {
mockAgentModule.loadAgentConfig.mockResolvedValue(null);
const res = await app.request('/agents/defaults');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.data).toEqual({});
});
});
describe('PUT /agents/defaults - 更新全局默认配置', () => {
it('更新成功', async () => {
const res = await app.request('/agents/defaults', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ maxSteps: 20 }),
});
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(mockAgentModule.saveAgentConfig).toHaveBeenCalled();
});
});
describe('GET /agents/:name - 获取单个 Agent 详情', () => {
it('返回 Agent 详情', async () => {
mockAgentRegistry.get.mockReturnValue({
name: 'general',
description: 'General purpose agent',
mode: 'subagent',
});
const res = await app.request('/agents/general');
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(json.data.name).toBe('general');
expect(json.data.isPreset).toBe(true);
});
it('Agent 不存在返回 404', async () => {
mockAgentRegistry.get.mockReturnValue(undefined);
const res = await app.request('/agents/non-existent');
const json = await res.json();
expect(res.status).toBe(404);
expect(json.success).toBe(false);
expect(json.error).toContain('not found');
});
});
describe('POST /agents - 创建新 Agent', () => {
it('创建成功', async () => {
mockAgentRegistry.get.mockReturnValue({
name: 'my-agent',
description: 'My custom agent',
mode: 'subagent',
});
const res = await app.request('/agents', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'my-agent',
description: 'My custom agent',
mode: 'subagent',
}),
});
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(mockAgentModule.saveAgentConfig).toHaveBeenCalled();
});
it('缺少名称返回 400', async () => {
const res = await app.request('/agents', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ description: 'No name agent' }),
});
const json = await res.json();
expect(res.status).toBe(400);
expect(json.success).toBe(false);
expect(json.error).toContain('name is required');
});
it('名称与预设冲突返回 400', async () => {
const res = await app.request('/agents', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'general',
description: 'Trying to create preset',
mode: 'subagent',
}),
});
const json = await res.json();
expect(res.status).toBe(400);
expect(json.error).toContain('preset name');
});
it('Agent 已存在返回 409', async () => {
mockAgentModule.loadAgentConfig.mockResolvedValue({
agents: { 'my-agent': { description: 'Existing' } },
});
const res = await app.request('/agents', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'my-agent',
description: 'Duplicate',
mode: 'subagent',
}),
});
const json = await res.json();
expect(res.status).toBe(409);
expect(json.error).toContain('already exists');
});
});
describe('PUT /agents/:name - 更新 Agent', () => {
it('更新成功', async () => {
mockAgentRegistry.get.mockReturnValue({
name: 'general',
description: 'Updated description',
mode: 'subagent',
});
const res = await app.request('/agents/general', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
description: 'Updated description',
mode: 'subagent',
}),
});
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(json.data.isCustomized).toBe(true);
});
});
describe('DELETE /agents/:name - 删除 Agent', () => {
it('删除自定义 Agent 成功', async () => {
mockAgentModule.loadAgentConfig.mockResolvedValue({
agents: { 'my-agent': { description: 'Custom agent' } },
});
const res = await app.request('/agents/my-agent', {
method: 'DELETE',
});
const json = await res.json();
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(mockAgentModule.saveAgentConfig).toHaveBeenCalled();
});
it('删除预设 Agent 返回 400', async () => {
mockAgentModule.loadAgentConfig.mockResolvedValue({ agents: {} });
const res = await app.request('/agents/general', {
method: 'DELETE',
});
const json = await res.json();
expect(res.status).toBe(400);
expect(json.error).toContain('Cannot delete preset');
});
it('Agent 不存在返回 404', async () => {
mockAgentModule.loadAgentConfig.mockResolvedValue({ agents: {} });
mockAgentModule.isPresetAgent.mockReturnValue(false);
const res = await app.request('/agents/non-existent', {
method: 'DELETE',
});
const json = await res.json();
expect(res.status).toBe(404);
expect(json.error).toContain('not found');
});
});
});