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