feat: 添加完整的单元测试套件
- 新增 vitest 测试框架配置 - 添加 54 个测试文件,共 951 个测试用例 - 覆盖核心模块: - Agent: executor, registry, config-loader, permission-merger - Context: manager, compaction, prune, token-counter - Permission: manager, bash/file/git/web checkers, wildcard - Session: manager, storage - Tools: filesystem (12个), git (10个), web, shell, todo, task - LSP: client, server, language - Utils: config, diff - UI: terminal
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import {
|
||||
loadAgentConfig,
|
||||
saveAgentConfig,
|
||||
getConfigTemplate,
|
||||
} from '../../../src/agent/config-loader.js';
|
||||
import type { AgentConfigFile } from '../../../src/agent/types.js';
|
||||
|
||||
// Mock fs
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: vi.fn(),
|
||||
promises: {
|
||||
readFile: vi.fn(),
|
||||
writeFile: vi.fn(),
|
||||
mkdir: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock js-yaml
|
||||
vi.mock('js-yaml', () => ({
|
||||
load: vi.fn((content: string) => JSON.parse(content)),
|
||||
dump: vi.fn((obj: unknown) => JSON.stringify(obj, null, 2)),
|
||||
}));
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
||||
describe('loadAgentConfig - 加载 Agent 配置', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('无配置文件时返回 null', async () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const config = await loadAgentConfig('/test/project');
|
||||
|
||||
expect(config).toBeNull();
|
||||
});
|
||||
|
||||
it('加载 JSON 配置文件', async () => {
|
||||
const mockConfig: AgentConfigFile = {
|
||||
defaults: {
|
||||
maxSteps: 20,
|
||||
},
|
||||
agents: {
|
||||
'custom-agent': {
|
||||
description: '自定义 Agent',
|
||||
mode: 'subagent',
|
||||
prompt: '你是助手',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((path: unknown) =>
|
||||
String(path).endsWith('.json')
|
||||
);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(mockConfig));
|
||||
|
||||
const config = await loadAgentConfig('/test/project');
|
||||
|
||||
expect(config).not.toBeNull();
|
||||
expect(config?.defaults?.maxSteps).toBe(20);
|
||||
expect(config?.agents?.['custom-agent']).toBeDefined();
|
||||
});
|
||||
|
||||
it('加载 YAML 配置文件', async () => {
|
||||
const mockConfig: AgentConfigFile = {
|
||||
defaults: {
|
||||
maxSteps: 15,
|
||||
},
|
||||
agents: {},
|
||||
};
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((path: unknown) =>
|
||||
String(path).endsWith('.yaml')
|
||||
);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(mockConfig));
|
||||
|
||||
const config = await loadAgentConfig('/test/project');
|
||||
|
||||
expect(config).not.toBeNull();
|
||||
});
|
||||
|
||||
it('无效配置格式返回 null', async () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue('invalid json');
|
||||
|
||||
// 会在解析时失败
|
||||
const config = await loadAgentConfig('/test/project');
|
||||
|
||||
// 取决于实现,可能是 null 或抛出错误后 null
|
||||
expect(config).toBeNull();
|
||||
});
|
||||
|
||||
it('配置搜索顺序', async () => {
|
||||
// 测试搜索多个路径
|
||||
const calls: string[] = [];
|
||||
vi.mocked(fs.existsSync).mockImplementation((path: unknown) => {
|
||||
calls.push(String(path));
|
||||
return false;
|
||||
});
|
||||
|
||||
await loadAgentConfig('/test/project');
|
||||
|
||||
// 应该搜索多个配置路径
|
||||
expect(calls.length).toBeGreaterThan(0);
|
||||
expect(calls.some(p => p.includes('.ai-assist'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveAgentConfig - 保存 Agent 配置', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
});
|
||||
|
||||
it('保存 JSON 格式配置', async () => {
|
||||
const config: AgentConfigFile = {
|
||||
defaults: {
|
||||
maxSteps: 10,
|
||||
},
|
||||
agents: {},
|
||||
};
|
||||
|
||||
await saveAgentConfig('/test/project', config, 'json');
|
||||
|
||||
expect(fs.promises.mkdir).toHaveBeenCalled();
|
||||
expect(fs.promises.writeFile).toHaveBeenCalledWith(
|
||||
expect.stringContaining('agents.json'),
|
||||
expect.any(String),
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
|
||||
it('保存 YAML 格式配置', async () => {
|
||||
const config: AgentConfigFile = {
|
||||
defaults: {
|
||||
maxSteps: 10,
|
||||
},
|
||||
agents: {},
|
||||
};
|
||||
|
||||
await saveAgentConfig('/test/project', config, 'yaml');
|
||||
|
||||
expect(fs.promises.writeFile).toHaveBeenCalledWith(
|
||||
expect.stringContaining('agents.yaml'),
|
||||
expect.any(String),
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
|
||||
it('创建配置目录如果不存在', async () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
await saveAgentConfig('/test/project', { agents: {} }, 'json');
|
||||
|
||||
expect(fs.promises.mkdir).toHaveBeenCalledWith(
|
||||
expect.stringContaining('.ai-assist'),
|
||||
{ recursive: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('目录已存在时不重复创建', async () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
|
||||
await saveAgentConfig('/test/project', { agents: {} }, 'json');
|
||||
|
||||
expect(fs.promises.mkdir).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConfigTemplate - 获取配置模板', () => {
|
||||
it('返回有效的配置模板', () => {
|
||||
const template = getConfigTemplate();
|
||||
|
||||
expect(template).toBeDefined();
|
||||
expect(template.defaults).toBeDefined();
|
||||
expect(template.agents).toBeDefined();
|
||||
});
|
||||
|
||||
it('模板包含默认配置', () => {
|
||||
const template = getConfigTemplate();
|
||||
|
||||
expect(template.defaults?.maxSteps).toBeDefined();
|
||||
expect(template.defaults?.model).toBeDefined();
|
||||
});
|
||||
|
||||
it('模板包含示例 Agent', () => {
|
||||
const template = getConfigTemplate();
|
||||
|
||||
expect(template.agents).toBeDefined();
|
||||
expect(Object.keys(template.agents || {}).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('示例 Agent 包含必要字段', () => {
|
||||
const template = getConfigTemplate();
|
||||
const agents = template.agents || {};
|
||||
const firstAgent = Object.values(agents)[0];
|
||||
|
||||
expect(firstAgent).toBeDefined();
|
||||
expect(firstAgent.description).toBeDefined();
|
||||
expect(firstAgent.mode).toBeDefined();
|
||||
});
|
||||
|
||||
it('模板包含权限配置示例', () => {
|
||||
const template = getConfigTemplate();
|
||||
|
||||
expect(template.defaults?.permission).toBeDefined();
|
||||
expect(template.defaults?.permission?.bash).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('配置验证', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('空对象是有效配置', async () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify({}));
|
||||
|
||||
const config = await loadAgentConfig('/test/project');
|
||||
|
||||
expect(config).not.toBeNull();
|
||||
});
|
||||
|
||||
it('只有 defaults 的配置有效', async () => {
|
||||
const mockConfig = {
|
||||
defaults: {
|
||||
maxSteps: 10,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(mockConfig));
|
||||
|
||||
const config = await loadAgentConfig('/test/project');
|
||||
|
||||
expect(config).not.toBeNull();
|
||||
expect(config?.defaults?.maxSteps).toBe(10);
|
||||
});
|
||||
|
||||
it('只有 agents 的配置有效', async () => {
|
||||
const mockConfig = {
|
||||
agents: {
|
||||
'test-agent': {
|
||||
description: 'Test',
|
||||
mode: 'subagent',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(mockConfig));
|
||||
|
||||
const config = await loadAgentConfig('/test/project');
|
||||
|
||||
expect(config).not.toBeNull();
|
||||
expect(config?.agents?.['test-agent']).toBeDefined();
|
||||
});
|
||||
|
||||
it('defaults 为非对象时配置无效', async () => {
|
||||
const mockConfig = {
|
||||
defaults: 'invalid',
|
||||
};
|
||||
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(mockConfig));
|
||||
|
||||
const config = await loadAgentConfig('/test/project');
|
||||
|
||||
// 应该返回 null(无效配置)
|
||||
expect(config).toBeNull();
|
||||
});
|
||||
|
||||
it('agents 为非对象时配置无效', async () => {
|
||||
const mockConfig = {
|
||||
agents: 'invalid',
|
||||
};
|
||||
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(mockConfig));
|
||||
|
||||
const config = await loadAgentConfig('/test/project');
|
||||
|
||||
expect(config).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,363 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
|
||||
// Mock state
|
||||
const mockState = {
|
||||
generateTextResult: {
|
||||
text: '任务完成',
|
||||
steps: [{ toolCalls: [] }],
|
||||
},
|
||||
streamTextResult: {
|
||||
textStream: (async function* () {
|
||||
yield '流式';
|
||||
yield '输出';
|
||||
})(),
|
||||
response: Promise.resolve({}),
|
||||
},
|
||||
};
|
||||
|
||||
// Mock @ai-sdk/anthropic
|
||||
vi.mock('@ai-sdk/anthropic', () => ({
|
||||
createAnthropic: vi.fn(() => vi.fn(() => ({ modelId: 'claude-3' }))),
|
||||
}));
|
||||
|
||||
// Mock @ai-sdk/deepseek
|
||||
vi.mock('@ai-sdk/deepseek', () => ({
|
||||
createDeepSeek: vi.fn(() => vi.fn(() => ({ modelId: 'deepseek' }))),
|
||||
}));
|
||||
|
||||
// Mock ai package
|
||||
vi.mock('ai', () => ({
|
||||
generateText: vi.fn(async () => mockState.generateTextResult),
|
||||
streamText: vi.fn(() => mockState.streamTextResult),
|
||||
stepCountIs: vi.fn(() => () => false),
|
||||
}));
|
||||
|
||||
// Mock permission-merger
|
||||
vi.mock('../../../src/agent/permission-merger.js', () => ({
|
||||
checkBashPermission: vi.fn(() => 'allow'),
|
||||
}));
|
||||
|
||||
// Mock types
|
||||
vi.mock('../../../src/types/index.js', () => ({
|
||||
buildZodSchema: vi.fn(() => ({})),
|
||||
}));
|
||||
|
||||
import { AgentExecutor } from '../../../src/agent/executor.js';
|
||||
import { generateText, streamText } from 'ai';
|
||||
import { checkBashPermission } from '../../../src/agent/permission-merger.js';
|
||||
|
||||
describe('AgentExecutor - Agent 执行器', () => {
|
||||
let executor: AgentExecutor;
|
||||
let mockToolRegistry: any;
|
||||
let mockAgentInfo: any;
|
||||
let mockBaseConfig: any;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockToolRegistry = {
|
||||
getAllTools: vi.fn(() => [
|
||||
{
|
||||
name: 'bash',
|
||||
description: '执行命令',
|
||||
parameters: { command: { type: 'string', required: true } },
|
||||
execute: vi.fn().mockResolvedValue({ success: true, output: 'ok' }),
|
||||
},
|
||||
{
|
||||
name: 'read_file',
|
||||
description: '读取文件',
|
||||
parameters: { path: { type: 'string', required: true } },
|
||||
execute: vi.fn().mockResolvedValue({ success: true, output: 'content' }),
|
||||
},
|
||||
{
|
||||
name: 'task',
|
||||
description: '子任务',
|
||||
parameters: { prompt: { type: 'string', required: true } },
|
||||
execute: vi.fn().mockResolvedValue({ success: true, output: 'done' }),
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
||||
mockAgentInfo = {
|
||||
name: 'test-agent',
|
||||
description: '测试 Agent',
|
||||
mode: 'subagent',
|
||||
prompt: '你是测试助手',
|
||||
};
|
||||
|
||||
mockBaseConfig = {
|
||||
provider: 'anthropic',
|
||||
model: 'claude-3-sonnet',
|
||||
apiKey: 'test-api-key',
|
||||
maxTokens: 4096,
|
||||
systemPrompt: '默认系统提示词',
|
||||
};
|
||||
|
||||
// 重置 mock 结果
|
||||
mockState.generateTextResult = {
|
||||
text: '任务完成',
|
||||
steps: [{ toolCalls: [] }],
|
||||
};
|
||||
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
});
|
||||
|
||||
describe('构造函数', () => {
|
||||
it('成功创建 Anthropic provider', () => {
|
||||
const exec = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
expect(exec).toBeDefined();
|
||||
});
|
||||
|
||||
it('成功创建 DeepSeek provider', () => {
|
||||
const config = { ...mockBaseConfig, provider: 'deepseek' as const };
|
||||
const exec = new AgentExecutor(mockAgentInfo, config, mockToolRegistry);
|
||||
expect(exec).toBeDefined();
|
||||
});
|
||||
|
||||
it('不支持的 provider 抛出错误', () => {
|
||||
const config = { ...mockBaseConfig, provider: 'unknown' as any };
|
||||
expect(() => new AgentExecutor(mockAgentInfo, config, mockToolRegistry)).toThrow('不支持的 provider');
|
||||
});
|
||||
|
||||
it('使用 Agent 指定的 provider', () => {
|
||||
const agentInfo = {
|
||||
...mockAgentInfo,
|
||||
model: { provider: 'deepseek' as const, model: 'deepseek-chat' },
|
||||
};
|
||||
const exec = new AgentExecutor(agentInfo, mockBaseConfig, mockToolRegistry);
|
||||
expect(exec).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('execute - 执行', () => {
|
||||
it('非流式模式成功执行', async () => {
|
||||
const result = await executor.execute('测试任务', {});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.text).toBe('任务完成');
|
||||
expect(generateText).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('流式模式成功执行', async () => {
|
||||
const onStream = vi.fn();
|
||||
|
||||
// 重置流式结果
|
||||
mockState.streamTextResult = {
|
||||
textStream: (async function* () {
|
||||
yield '流式';
|
||||
yield '输出';
|
||||
})(),
|
||||
response: Promise.resolve({}),
|
||||
};
|
||||
|
||||
const result = await executor.execute('测试任务', { onStream });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.text).toBe('流式输出');
|
||||
expect(streamText).toHaveBeenCalled();
|
||||
expect(onStream).toHaveBeenCalledWith('流式');
|
||||
expect(onStream).toHaveBeenCalledWith('输出');
|
||||
});
|
||||
|
||||
it('执行失败返回错误', async () => {
|
||||
vi.mocked(generateText).mockRejectedValueOnce(new Error('API 错误'));
|
||||
|
||||
const result = await executor.execute('测试任务', {});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('API 错误');
|
||||
});
|
||||
|
||||
it('传递父会话 ID', async () => {
|
||||
const result = await executor.execute('测试任务', {
|
||||
parentSessionId: 'parent-123',
|
||||
});
|
||||
|
||||
expect(result.sessionId).toBe('parent-123');
|
||||
});
|
||||
|
||||
it('无父会话 ID 使用 standalone', async () => {
|
||||
const result = await executor.execute('测试任务', {});
|
||||
|
||||
expect(result.sessionId).toBe('standalone');
|
||||
});
|
||||
});
|
||||
|
||||
describe('工具过滤', () => {
|
||||
it('无配置返回所有工具', async () => {
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
expect(Object.keys(call.tools || {})).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('enabled 配置只保留指定工具', async () => {
|
||||
mockAgentInfo.tools = { enabled: ['bash'] };
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
expect(Object.keys(call.tools || {})).toContain('bash');
|
||||
expect(Object.keys(call.tools || {})).not.toContain('read_file');
|
||||
});
|
||||
|
||||
it('disabled 配置移除指定工具', async () => {
|
||||
mockAgentInfo.tools = { disabled: ['task'] };
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
expect(Object.keys(call.tools || {})).not.toContain('task');
|
||||
});
|
||||
|
||||
it('noTask 配置移除 task 工具', async () => {
|
||||
mockAgentInfo.tools = { noTask: true };
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
expect(Object.keys(call.tools || {})).not.toContain('task');
|
||||
});
|
||||
});
|
||||
|
||||
describe('权限检查', () => {
|
||||
it('bash 命令被拒绝', async () => {
|
||||
vi.mocked(checkBashPermission).mockReturnValue('deny');
|
||||
mockAgentInfo.permission = { bash: { deny: ['rm *'] } };
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
|
||||
// 获取工具并执行
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
const bashTool = call.tools?.bash;
|
||||
|
||||
if (bashTool && 'execute' in bashTool) {
|
||||
const result = await bashTool.execute({ command: 'rm -rf /' });
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('权限拒绝');
|
||||
}
|
||||
});
|
||||
|
||||
it('文件写入被拒绝', async () => {
|
||||
mockAgentInfo.permission = { file: { write: 'deny' } };
|
||||
|
||||
// 添加 write_file 工具
|
||||
mockToolRegistry.getAllTools.mockReturnValue([
|
||||
{
|
||||
name: 'write_file',
|
||||
description: '写文件',
|
||||
parameters: { path: { type: 'string', required: true } },
|
||||
execute: vi.fn().mockResolvedValue({ success: true, output: 'ok' }),
|
||||
},
|
||||
]);
|
||||
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
const writeTool = call.tools?.write_file;
|
||||
|
||||
if (writeTool && 'execute' in writeTool) {
|
||||
const result = await writeTool.execute({ path: '/test.txt', content: 'test' });
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('权限拒绝');
|
||||
}
|
||||
});
|
||||
|
||||
it('Git 写操作被拒绝', async () => {
|
||||
mockAgentInfo.permission = { git: { write: 'deny' } };
|
||||
|
||||
mockToolRegistry.getAllTools.mockReturnValue([
|
||||
{
|
||||
name: 'git_push',
|
||||
description: 'Git push',
|
||||
parameters: {},
|
||||
execute: vi.fn().mockResolvedValue({ success: true, output: 'ok' }),
|
||||
},
|
||||
]);
|
||||
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
const gitTool = call.tools?.git_push;
|
||||
|
||||
if (gitTool && 'execute' in gitTool) {
|
||||
const result = await gitTool.execute({});
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Git 写操作被禁止');
|
||||
}
|
||||
});
|
||||
|
||||
it('无权限配置允许所有操作', async () => {
|
||||
// 无 permission 配置
|
||||
delete mockAgentInfo.permission;
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
const bashTool = call.tools?.bash;
|
||||
|
||||
if (bashTool && 'execute' in bashTool) {
|
||||
const result = await bashTool.execute({ command: 'ls' });
|
||||
expect(result.success).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('系统提示词', () => {
|
||||
it('使用 Agent 自定义提示词', async () => {
|
||||
mockAgentInfo.prompt = '自定义提示词';
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
expect(call.system).toBe('自定义提示词');
|
||||
});
|
||||
|
||||
it('无自定义提示词使用基础配置', async () => {
|
||||
delete mockAgentInfo.prompt;
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
expect(call.system).toBe('默认系统提示词');
|
||||
});
|
||||
});
|
||||
|
||||
describe('模型配置', () => {
|
||||
it('使用 Agent 指定的模型', async () => {
|
||||
mockAgentInfo.model = { model: 'claude-3-opus' };
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
|
||||
await executor.execute('测试', {});
|
||||
|
||||
expect(generateText).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('使用 Agent 指定的 maxSteps', async () => {
|
||||
mockAgentInfo.maxSteps = 5;
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
|
||||
await executor.execute('测试', {});
|
||||
|
||||
expect(generateText).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('使用 Agent 指定的 maxTokens', async () => {
|
||||
mockAgentInfo.model = { maxTokens: 8192 };
|
||||
executor = new AgentExecutor(mockAgentInfo, mockBaseConfig, mockToolRegistry);
|
||||
|
||||
await executor.execute('测试', {});
|
||||
|
||||
const call = vi.mocked(generateText).mock.calls[0][0];
|
||||
expect(call.maxOutputTokens).toBe(8192);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,234 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
mergePermissions,
|
||||
matchRule,
|
||||
checkBashPermission,
|
||||
checkFilePathPermission,
|
||||
SYSTEM_DEFAULT_PERMISSION,
|
||||
} from '../../../src/agent/permission-merger.js';
|
||||
import type { AgentPermission, AgentBashPermission } from '../../../src/agent/types.js';
|
||||
|
||||
describe('matchRule - 命令规则匹配', () => {
|
||||
describe('通配符 * 匹配', () => {
|
||||
it('git diff* 匹配 git diff --staged', () => {
|
||||
expect(matchRule('git diff --staged', 'git diff*')).toBe(true);
|
||||
});
|
||||
|
||||
it('git diff* 不匹配 git status', () => {
|
||||
expect(matchRule('git status', 'git diff*')).toBe(false);
|
||||
});
|
||||
|
||||
it('rm -rf* 匹配危险命令', () => {
|
||||
expect(matchRule('rm -rf /', 'rm -rf*')).toBe(true);
|
||||
expect(matchRule('rm -rf /home/user', 'rm -rf*')).toBe(true);
|
||||
});
|
||||
|
||||
it('rm -rf* 不匹配普通 rm', () => {
|
||||
expect(matchRule('rm file.txt', 'rm -rf*')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('精确匹配', () => {
|
||||
it('pwd 精确匹配', () => {
|
||||
expect(matchRule('pwd', 'pwd')).toBe(true);
|
||||
});
|
||||
|
||||
it('pwd 不匹配带参数', () => {
|
||||
expect(matchRule('pwd /home', 'pwd')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('大小写不敏感', () => {
|
||||
it('忽略大小写', () => {
|
||||
expect(matchRule('GIT DIFF', 'git diff*')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkBashPermission - Bash 权限检查', () => {
|
||||
it('禁用时返回 deny', () => {
|
||||
const permission: AgentBashPermission = { enabled: false };
|
||||
expect(checkBashPermission('ls', permission)).toBe('deny');
|
||||
});
|
||||
|
||||
it('匹配 allow 规则', () => {
|
||||
const permission: AgentBashPermission = {
|
||||
enabled: true,
|
||||
rules: [{ pattern: 'ls *', action: 'allow' }],
|
||||
default: 'deny',
|
||||
};
|
||||
expect(checkBashPermission('ls -la', permission)).toBe('allow');
|
||||
});
|
||||
|
||||
it('匹配 deny 规则', () => {
|
||||
const permission: AgentBashPermission = {
|
||||
enabled: true,
|
||||
rules: [{ pattern: 'rm -rf*', action: 'deny' }],
|
||||
default: 'allow',
|
||||
};
|
||||
expect(checkBashPermission('rm -rf /', permission)).toBe('deny');
|
||||
});
|
||||
|
||||
it('无匹配时返回默认值', () => {
|
||||
const permission: AgentBashPermission = {
|
||||
enabled: true,
|
||||
rules: [],
|
||||
default: 'ask',
|
||||
};
|
||||
expect(checkBashPermission('npm install', permission)).toBe('ask');
|
||||
});
|
||||
|
||||
it('规则优先级:先匹配的规则优先', () => {
|
||||
const permission: AgentBashPermission = {
|
||||
enabled: true,
|
||||
rules: [
|
||||
{ pattern: 'git push --force*', action: 'deny' },
|
||||
{ pattern: 'git push*', action: 'ask' },
|
||||
],
|
||||
default: 'allow',
|
||||
};
|
||||
expect(checkBashPermission('git push --force origin', permission)).toBe('deny');
|
||||
expect(checkBashPermission('git push origin', permission)).toBe('ask');
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkFilePathPermission - 文件路径权限检查', () => {
|
||||
it('无敏感路径规则返回 null', () => {
|
||||
expect(checkFilePathPermission('/home/user/file.txt', undefined)).toBeNull();
|
||||
expect(checkFilePathPermission('/home/user/file.txt', [])).toBeNull();
|
||||
});
|
||||
|
||||
it('匹配敏感路径规则', () => {
|
||||
const rules = [
|
||||
{ pattern: '*.env', action: 'deny' as const },
|
||||
{ pattern: '/etc/*', action: 'ask' as const },
|
||||
];
|
||||
expect(checkFilePathPermission('.env', rules)).toBe('deny');
|
||||
expect(checkFilePathPermission('/etc/passwd', rules)).toBe('ask');
|
||||
});
|
||||
|
||||
it('不匹配时返回 null', () => {
|
||||
const rules = [{ pattern: '*.env', action: 'deny' as const }];
|
||||
expect(checkFilePathPermission('config.json', rules)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergePermissions - 权限合并', () => {
|
||||
describe('优先级:Agent > Global > System', () => {
|
||||
it('Agent 配置覆盖 Global 和 System', () => {
|
||||
const system: AgentPermission = { file: { read: 'allow', write: 'ask' } };
|
||||
const global: AgentPermission = { file: { write: 'allow' } };
|
||||
const agent: AgentPermission = { file: { write: 'deny' } };
|
||||
|
||||
const merged = mergePermissions(system, global, agent);
|
||||
expect(merged.file?.write).toBe('deny');
|
||||
});
|
||||
|
||||
it('Global 配置覆盖 System', () => {
|
||||
const system: AgentPermission = { file: { write: 'ask' } };
|
||||
const global: AgentPermission = { file: { write: 'allow' } };
|
||||
|
||||
const merged = mergePermissions(system, global, undefined);
|
||||
expect(merged.file?.write).toBe('allow');
|
||||
});
|
||||
|
||||
it('无覆盖时使用 System 默认值', () => {
|
||||
const merged = mergePermissions(SYSTEM_DEFAULT_PERMISSION, undefined, undefined);
|
||||
expect(merged.file?.read).toBe('allow');
|
||||
expect(merged.file?.write).toBe('ask');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bash 权限合并', () => {
|
||||
it('Agent 禁用 bash 覆盖全局', () => {
|
||||
const system: AgentPermission = { bash: { enabled: true } };
|
||||
const global: AgentPermission = { bash: { enabled: true } };
|
||||
const agent: AgentPermission = { bash: { enabled: false } };
|
||||
|
||||
const merged = mergePermissions(system, global, agent);
|
||||
expect(merged.bash?.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('Global 禁用 bash 且 Agent 未覆盖', () => {
|
||||
const system: AgentPermission = { bash: { enabled: true } };
|
||||
const global: AgentPermission = { bash: { enabled: false } };
|
||||
|
||||
const merged = mergePermissions(system, global, undefined);
|
||||
expect(merged.bash?.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('规则按优先级合并:Agent > Global > System', () => {
|
||||
const system: AgentPermission = {
|
||||
bash: { rules: [{ pattern: 'ls *', action: 'allow' }] },
|
||||
};
|
||||
const global: AgentPermission = {
|
||||
bash: { rules: [{ pattern: 'cat *', action: 'allow' }] },
|
||||
};
|
||||
const agent: AgentPermission = {
|
||||
bash: { rules: [{ pattern: 'rm *', action: 'deny' }] },
|
||||
};
|
||||
|
||||
const merged = mergePermissions(system, global, agent);
|
||||
// Agent 规则在前
|
||||
expect(merged.bash?.rules?.[0].pattern).toBe('rm *');
|
||||
expect(merged.bash?.rules?.[1].pattern).toBe('cat *');
|
||||
expect(merged.bash?.rules?.[2].pattern).toBe('ls *');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Git 权限合并', () => {
|
||||
it('合并所有级别的 Git 权限', () => {
|
||||
const system: AgentPermission = { git: { read: 'allow', write: 'ask', dangerous: 'deny' } };
|
||||
const agent: AgentPermission = { git: { write: 'deny' } };
|
||||
|
||||
const merged = mergePermissions(system, undefined, agent);
|
||||
expect(merged.git?.read).toBe('allow'); // 来自 system
|
||||
expect(merged.git?.write).toBe('deny'); // 来自 agent
|
||||
expect(merged.git?.dangerous).toBe('deny'); // 来自 system
|
||||
});
|
||||
});
|
||||
|
||||
describe('Web 权限合并', () => {
|
||||
it('合并 Web 权限', () => {
|
||||
const system: AgentPermission = { web: 'ask' };
|
||||
const agent: AgentPermission = { web: 'deny' };
|
||||
|
||||
const merged = mergePermissions(system, undefined, agent);
|
||||
expect(merged.web).toBe('deny');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SYSTEM_DEFAULT_PERMISSION - 系统默认权限', () => {
|
||||
it('文件读取默认允许', () => {
|
||||
expect(SYSTEM_DEFAULT_PERMISSION.file?.read).toBe('allow');
|
||||
});
|
||||
|
||||
it('文件写入默认询问', () => {
|
||||
expect(SYSTEM_DEFAULT_PERMISSION.file?.write).toBe('ask');
|
||||
});
|
||||
|
||||
it('Bash 默认启用', () => {
|
||||
expect(SYSTEM_DEFAULT_PERMISSION.bash?.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('包含安全命令白名单', () => {
|
||||
const rules = SYSTEM_DEFAULT_PERMISSION.bash?.rules ?? [];
|
||||
const lsRule = rules.find((r) => r.pattern === 'ls *');
|
||||
expect(lsRule?.action).toBe('allow');
|
||||
});
|
||||
|
||||
it('包含危险命令黑名单', () => {
|
||||
const rules = SYSTEM_DEFAULT_PERMISSION.bash?.rules ?? [];
|
||||
const rmRule = rules.find((r) => r.pattern === 'rm -rf *');
|
||||
expect(rmRule?.action).toBe('deny');
|
||||
});
|
||||
|
||||
it('Git 读取默认允许', () => {
|
||||
expect(SYSTEM_DEFAULT_PERMISSION.git?.read).toBe('allow');
|
||||
});
|
||||
|
||||
it('Git 危险操作默认拒绝', () => {
|
||||
expect(SYSTEM_DEFAULT_PERMISSION.git?.dangerous).toBe('deny');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,350 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { AgentRegistry } from '../../../src/agent/registry.js';
|
||||
import type { AgentInfo, AgentConfigFile } from '../../../src/agent/types.js';
|
||||
|
||||
// Mock config-loader
|
||||
vi.mock('../../../src/agent/config-loader.js', () => ({
|
||||
loadAgentConfig: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock presets
|
||||
vi.mock('../../../src/agent/presets/index.js', () => ({
|
||||
presetAgents: {
|
||||
explore: {
|
||||
description: '代码探索 Agent',
|
||||
mode: 'subagent' as const,
|
||||
prompt: '你是代码探索助手',
|
||||
maxSteps: 5,
|
||||
},
|
||||
'code-reviewer': {
|
||||
description: '代码审查 Agent',
|
||||
mode: 'subagent' as const,
|
||||
prompt: '你是代码审查助手',
|
||||
},
|
||||
build: {
|
||||
description: '构建 Agent',
|
||||
mode: 'all' as const,
|
||||
prompt: '你是构建助手',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
import { loadAgentConfig } from '../../../src/agent/config-loader.js';
|
||||
|
||||
describe('AgentRegistry - Agent 注册表', () => {
|
||||
let registry: AgentRegistry;
|
||||
|
||||
beforeEach(() => {
|
||||
registry = new AgentRegistry();
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(loadAgentConfig).mockResolvedValue(null);
|
||||
});
|
||||
|
||||
describe('init - 初始化', () => {
|
||||
it('初始化后注册预设 Agent', async () => {
|
||||
await registry.init('/test/project');
|
||||
|
||||
expect(registry.has('explore')).toBe(true);
|
||||
expect(registry.has('code-reviewer')).toBe(true);
|
||||
expect(registry.has('build')).toBe(true);
|
||||
});
|
||||
|
||||
it('初始化加载用户配置', async () => {
|
||||
const userConfig: AgentConfigFile = {
|
||||
defaults: {
|
||||
maxSteps: 20,
|
||||
},
|
||||
agents: {
|
||||
'custom-agent': {
|
||||
description: '自定义 Agent',
|
||||
mode: 'subagent',
|
||||
prompt: '你是自定义助手',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(loadAgentConfig).mockResolvedValue(userConfig);
|
||||
|
||||
await registry.init('/test/project');
|
||||
|
||||
expect(registry.has('custom-agent')).toBe(true);
|
||||
});
|
||||
|
||||
it('重复初始化只执行一次', async () => {
|
||||
await registry.init('/test/project');
|
||||
await registry.init('/test/project');
|
||||
|
||||
expect(loadAgentConfig).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get - 获取 Agent', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('获取存在的 Agent', () => {
|
||||
const agent = registry.get('explore');
|
||||
|
||||
expect(agent).toBeDefined();
|
||||
expect(agent?.name).toBe('explore');
|
||||
expect(agent?.description).toBe('代码探索 Agent');
|
||||
});
|
||||
|
||||
it('获取不存在的 Agent 返回 undefined', () => {
|
||||
const agent = registry.get('non-existent');
|
||||
|
||||
expect(agent).toBeUndefined();
|
||||
});
|
||||
|
||||
it('获取的 Agent 应用全局配置', async () => {
|
||||
vi.mocked(loadAgentConfig).mockResolvedValue({
|
||||
defaults: {
|
||||
maxSteps: 25,
|
||||
},
|
||||
});
|
||||
|
||||
const newRegistry = new AgentRegistry();
|
||||
await newRegistry.init('/test/project');
|
||||
|
||||
const agent = newRegistry.get('code-reviewer');
|
||||
|
||||
// code-reviewer 没有设置 maxSteps,应该使用全局默认值
|
||||
expect(agent?.maxSteps).toBe(25);
|
||||
});
|
||||
|
||||
it('Agent 自己的配置优先于全局配置', async () => {
|
||||
vi.mocked(loadAgentConfig).mockResolvedValue({
|
||||
defaults: {
|
||||
maxSteps: 25,
|
||||
},
|
||||
});
|
||||
|
||||
const newRegistry = new AgentRegistry();
|
||||
await newRegistry.init('/test/project');
|
||||
|
||||
const agent = newRegistry.get('explore');
|
||||
|
||||
// explore 设置了 maxSteps: 5
|
||||
expect(agent?.maxSteps).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('list - 列出 Agent', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('列出所有 Agent', () => {
|
||||
const agents = registry.list();
|
||||
|
||||
expect(agents.length).toBeGreaterThan(0);
|
||||
expect(agents.some(a => a.name === 'explore')).toBe(true);
|
||||
expect(agents.some(a => a.name === 'code-reviewer')).toBe(true);
|
||||
});
|
||||
|
||||
it('按 mode 过滤 Agent', () => {
|
||||
const subagents = registry.list('subagent');
|
||||
|
||||
expect(subagents.every(a => a.mode === 'subagent' || a.mode === 'all')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listSubagents - 列出子 Agent', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('排除 primary-only 的 Agent', () => {
|
||||
const subagents = registry.listSubagents();
|
||||
|
||||
expect(subagents.every(a => a.mode !== 'primary')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listPrimaryAgents - 列出主 Agent', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('排除 subagent-only 的 Agent', () => {
|
||||
const primaryAgents = registry.listPrimaryAgents();
|
||||
|
||||
expect(primaryAgents.every(a => a.mode !== 'subagent')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('register - 动态注册', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('注册新 Agent', () => {
|
||||
const newAgent: AgentInfo = {
|
||||
name: 'dynamic-agent',
|
||||
description: '动态注册的 Agent',
|
||||
mode: 'subagent',
|
||||
prompt: '你是动态 Agent',
|
||||
};
|
||||
|
||||
registry.register(newAgent);
|
||||
|
||||
expect(registry.has('dynamic-agent')).toBe(true);
|
||||
expect(registry.get('dynamic-agent')?.description).toBe('动态注册的 Agent');
|
||||
});
|
||||
|
||||
it('覆盖已有 Agent', () => {
|
||||
const updatedAgent: AgentInfo = {
|
||||
name: 'explore',
|
||||
description: '更新后的探索 Agent',
|
||||
mode: 'all',
|
||||
prompt: '更新后的提示',
|
||||
};
|
||||
|
||||
registry.register(updatedAgent);
|
||||
|
||||
const agent = registry.get('explore');
|
||||
expect(agent?.description).toBe('更新后的探索 Agent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove - 移除 Agent', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('移除存在的 Agent', () => {
|
||||
const result = registry.remove('explore');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(registry.has('explore')).toBe(false);
|
||||
});
|
||||
|
||||
it('移除不存在的 Agent 返回 false', () => {
|
||||
const result = registry.remove('non-existent');
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('has - 检查 Agent 是否存在', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('存在的 Agent 返回 true', () => {
|
||||
expect(registry.has('explore')).toBe(true);
|
||||
});
|
||||
|
||||
it('不存在的 Agent 返回 false', () => {
|
||||
expect(registry.has('non-existent')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('size - 获取 Agent 数量', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('返回正确的数量', () => {
|
||||
expect(registry.size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('添加后数量增加', () => {
|
||||
const initialSize = registry.size;
|
||||
|
||||
registry.register({
|
||||
name: 'new-agent',
|
||||
description: 'New',
|
||||
mode: 'subagent',
|
||||
prompt: 'New agent',
|
||||
});
|
||||
|
||||
expect(registry.size).toBe(initialSize + 1);
|
||||
});
|
||||
|
||||
it('移除后数量减少', () => {
|
||||
const initialSize = registry.size;
|
||||
|
||||
registry.remove('explore');
|
||||
|
||||
expect(registry.size).toBe(initialSize - 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNames - 获取所有 Agent 名称', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('返回所有 Agent 名称', () => {
|
||||
const names = registry.getNames();
|
||||
|
||||
expect(names).toContain('explore');
|
||||
expect(names).toContain('code-reviewer');
|
||||
expect(names).toContain('build');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGlobalConfig - 获取全局配置', () => {
|
||||
it('无用户配置时返回 null', async () => {
|
||||
vi.mocked(loadAgentConfig).mockResolvedValue(null);
|
||||
|
||||
await registry.init('/test/project');
|
||||
|
||||
expect(registry.getGlobalConfig()).toBeNull();
|
||||
});
|
||||
|
||||
it('有用户配置时返回 defaults', async () => {
|
||||
vi.mocked(loadAgentConfig).mockResolvedValue({
|
||||
defaults: {
|
||||
maxSteps: 30,
|
||||
model: {
|
||||
temperature: 0.5,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const newRegistry = new AgentRegistry();
|
||||
await newRegistry.init('/test/project');
|
||||
|
||||
const globalConfig = newRegistry.getGlobalConfig();
|
||||
|
||||
expect(globalConfig?.maxSteps).toBe(30);
|
||||
expect(globalConfig?.model?.temperature).toBe(0.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateSubagentDescription - 生成子 Agent 描述', () => {
|
||||
beforeEach(async () => {
|
||||
await registry.init('/test/project');
|
||||
});
|
||||
|
||||
it('生成包含所有子 Agent 的描述', () => {
|
||||
const description = registry.generateSubagentDescription();
|
||||
|
||||
expect(description).toContain('explore');
|
||||
expect(description).toContain('代码探索');
|
||||
});
|
||||
|
||||
it('无子 Agent 时返回提示信息', async () => {
|
||||
// 移除所有 Agent
|
||||
for (const name of registry.getNames()) {
|
||||
registry.remove(name);
|
||||
}
|
||||
|
||||
const description = registry.generateSubagentDescription();
|
||||
|
||||
expect(description).toContain('没有可用');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('agentRegistry 单例', () => {
|
||||
it('导出单例实例', async () => {
|
||||
const { agentRegistry } = await import('../../../src/agent/registry.js');
|
||||
|
||||
expect(agentRegistry).toBeDefined();
|
||||
expect(agentRegistry).toBeInstanceOf(AgentRegistry);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user