eb80b2c9e6
- todo-manager.test: 修复空日期字符串导致的 Invalid time value 错误 - config-loader.test: 更新测试以匹配简化后的配置加载逻辑 - mcp/config.test: 修复配置路径匹配问题 - task.test/task-extended.test: 添加缺失的 agentEventEmitter mock - presets/index.test: 更新预设 Agent 数量和 maxSteps 测试 - agent.test: 添加缺失的 mock 函数并修正模式切换测试 - 删除过时的 session/manager.test 和 storage.test (使用已废弃的 API)
281 lines
7.5 KiB
TypeScript
281 lines
7.5 KiB
TypeScript
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 配置文件(现在只支持 JSON,此测试确认行为)', async () => {
|
||
// 实现已改为只支持 JSON,这里测试当找不到 JSON 文件时返回 null
|
||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||
|
||
const config = await loadAgentConfig('/test/project');
|
||
|
||
expect(config).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');
|
||
|
||
// 只搜索一个路径(全局配置目录的 agents.json)
|
||
expect(calls.length).toBe(1);
|
||
expect(calls[0]).toContain('agents.json');
|
||
});
|
||
});
|
||
|
||
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 格式配置(现在只支持 JSON)', async () => {
|
||
const config: AgentConfigFile = {
|
||
defaults: {
|
||
maxSteps: 10,
|
||
},
|
||
agents: {},
|
||
};
|
||
|
||
// 即使传入 yaml 格式,也会保存为 JSON
|
||
await saveAgentConfig('/test/project', config, 'yaml');
|
||
|
||
expect(fs.promises.writeFile).toHaveBeenCalledWith(
|
||
expect.stringContaining('agents.json'),
|
||
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.any(String),
|
||
{ 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();
|
||
});
|
||
});
|