import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; // Mock fs vi.mock('fs', () => ({ existsSync: vi.fn(), readFileSync: vi.fn(), writeFileSync: vi.fn(), mkdirSync: vi.fn(), })); import * as fs from 'fs'; import { getConfig, loadConfig, saveConfig } from '../../../src/utils/config.js'; describe('Config - 配置管理', () => { const originalEnv = { ...process.env }; beforeEach(() => { vi.clearAllMocks(); // 清理环境变量 delete process.env.AI_PROVIDER; delete process.env.ANTHROPIC_API_KEY; delete process.env.DEEPSEEK_API_KEY; delete process.env.AI_MODEL; delete process.env.AI_MAX_TOKENS; }); afterEach(() => { // 恢复环境变量 process.env = { ...originalEnv }; }); describe('getConfig - 获取原始配置', () => { it('配置文件存在时返回内容', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ provider: 'anthropic', apiKey: 'test-key', model: 'claude-3-opus', })); const config = getConfig(); expect(config.provider).toBe('anthropic'); expect(config.apiKey).toBe('test-key'); expect(config.model).toBe('claude-3-opus'); }); it('配置文件不存在时返回空对象', () => { vi.mocked(fs.existsSync).mockReturnValue(false); const config = getConfig(); expect(config).toEqual({}); }); it('配置文件解析错误时返回空对象', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue('invalid json'); const config = getConfig(); expect(config).toEqual({}); }); }); describe('loadConfig - 加载完整配置', () => { it('从环境变量获取 Anthropic 配置', () => { process.env.ANTHROPIC_API_KEY = 'env-anthropic-key'; vi.mocked(fs.existsSync).mockReturnValue(false); const config = loadConfig(); expect(config.provider).toBe('anthropic'); expect(config.apiKey).toBe('env-anthropic-key'); expect(config.model).toBe('claude-sonnet-4-20250514'); }); it('从环境变量获取 DeepSeek 配置', () => { process.env.AI_PROVIDER = 'deepseek'; process.env.DEEPSEEK_API_KEY = 'env-deepseek-key'; vi.mocked(fs.existsSync).mockReturnValue(false); const config = loadConfig(); expect(config.provider).toBe('deepseek'); expect(config.apiKey).toBe('env-deepseek-key'); expect(config.model).toBe('deepseek-chat'); }); it('配置文件优先级高于默认值', () => { process.env.ANTHROPIC_API_KEY = 'env-key'; vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ model: 'custom-model', maxTokens: 8192, })); const config = loadConfig(); expect(config.model).toBe('custom-model'); expect(config.maxTokens).toBe(8192); }); it('配置文件中的 provider 优先', () => { process.env.ANTHROPIC_API_KEY = 'anthropic-key'; process.env.DEEPSEEK_API_KEY = 'deepseek-key'; vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ provider: 'deepseek', deepseekApiKey: 'stored-deepseek-key', })); const config = loadConfig(); expect(config.provider).toBe('deepseek'); // 使用环境变量中的 API Key(优先级更高) expect(config.apiKey).toBe('deepseek-key'); }); it('包含系统提示词', () => { process.env.ANTHROPIC_API_KEY = 'test-key'; vi.mocked(fs.existsSync).mockReturnValue(false); const config = loadConfig(); expect(config.systemPrompt).toBeDefined(); expect(config.systemPrompt).toContain('终端'); }); it('默认 maxTokens 为 4096', () => { process.env.ANTHROPIC_API_KEY = 'test-key'; vi.mocked(fs.existsSync).mockReturnValue(false); const config = loadConfig(); expect(config.maxTokens).toBe(4096); }); it('环境变量设置的 maxTokens', () => { process.env.ANTHROPIC_API_KEY = 'test-key'; process.env.AI_MAX_TOKENS = '16384'; vi.mocked(fs.existsSync).mockReturnValue(false); const config = loadConfig(); expect(config.maxTokens).toBe(16384); }); }); describe('saveConfig - 保存配置', () => { it('创建目录并保存配置', () => { vi.mocked(fs.existsSync).mockReturnValue(false); saveConfig({ provider: 'anthropic', apiKey: 'new-key' }); expect(fs.mkdirSync).toHaveBeenCalledWith( expect.any(String), { recursive: true } ); expect(fs.writeFileSync).toHaveBeenCalledWith( expect.any(String), expect.stringContaining('anthropic') ); }); it('合并现有配置', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ provider: 'anthropic', apiKey: 'old-key', model: 'old-model', })); saveConfig({ apiKey: 'new-key' }); const writeCall = vi.mocked(fs.writeFileSync).mock.calls[0]; const savedConfig = JSON.parse(writeCall[1] as string); expect(savedConfig.provider).toBe('anthropic'); // 保留 expect(savedConfig.apiKey).toBe('new-key'); // 更新 expect(savedConfig.model).toBe('old-model'); // 保留 }); it('目录已存在时不重新创建', () => { vi.mocked(fs.existsSync) .mockReturnValueOnce(true) // 目录存在 .mockReturnValueOnce(true); // 配置文件存在 vi.mocked(fs.readFileSync).mockReturnValue('{}'); saveConfig({ apiKey: 'test' }); expect(fs.mkdirSync).not.toHaveBeenCalled(); }); }); });