import { describe, it, expect, beforeEach, vi } from 'vitest'; // Mock fs vi.mock('fs', () => ({ existsSync: vi.fn(), readFileSync: vi.fn(), writeFileSync: vi.fn(), mkdirSync: vi.fn(), })); // Mock provider registry vi.mock('../../../src/provider/index.js', () => ({ providerRegistry: { getInfo: vi.fn(), getConfig: vi.fn(), }, resolveApiKey: vi.fn(), })); import * as fs from 'fs'; import { providerRegistry, resolveApiKey } from '../../../src/provider/index.js'; import { getConfig, loadConfig, saveConfig, loadVisionConfig } from '../../../src/utils/config.js'; describe('Config - 配置管理', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('getConfig - 获取原始配置', () => { it('配置文件存在时返回内容', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ provider: 'anthropic', model: 'claude-3-opus', })); const config = getConfig(); expect(config.provider).toBe('anthropic'); 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('通过 ProviderRegistry 获取 Anthropic 配置', () => { vi.mocked(fs.existsSync).mockReturnValue(false); vi.mocked(providerRegistry.getConfig).mockReturnValue(undefined); vi.mocked(resolveApiKey).mockReturnValue('resolved-anthropic-key'); const config = loadConfig(); expect(config.provider).toBe('anthropic'); expect(config.apiKey).toBe('resolved-anthropic-key'); expect(config.model).toBe('claude-sonnet-4-20250514'); }); it('从配置文件获取 DeepSeek provider', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ provider: 'deepseek', })); vi.mocked(providerRegistry.getConfig).mockReturnValue(undefined); vi.mocked(resolveApiKey).mockReturnValue('resolved-deepseek-key'); const config = loadConfig(); expect(config.provider).toBe('deepseek'); expect(config.apiKey).toBe('resolved-deepseek-key'); expect(config.model).toBe('deepseek-chat'); }); it('配置文件中的 model 和 maxTokens', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ model: 'custom-model', maxTokens: 8192, })); vi.mocked(providerRegistry.getConfig).mockReturnValue(undefined); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const config = loadConfig(); expect(config.model).toBe('custom-model'); expect(config.maxTokens).toBe(8192); }); it('包含系统提示词', () => { vi.mocked(fs.existsSync).mockReturnValue(false); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const config = loadConfig(); expect(config.systemPrompt).toBeDefined(); expect(config.systemPrompt).toContain('终端'); }); it('默认 maxTokens 为 4096', () => { vi.mocked(fs.existsSync).mockReturnValue(false); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const config = loadConfig(); expect(config.maxTokens).toBe(4096); }); it('从配置文件获取 baseUrl', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ baseUrl: 'https://custom.api.com/v1', })); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const config = loadConfig(); expect(config.baseUrl).toBe('https://custom.api.com/v1'); }); it('从 ProviderConfig 获取 baseUrl', () => { vi.mocked(fs.existsSync).mockReturnValue(false); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'anthropic', baseUrl: 'https://provider-config.api.com/v1', }); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const config = loadConfig(); expect(config.baseUrl).toBe('https://provider-config.api.com/v1'); }); }); describe('saveConfig - 保存配置', () => { it('创建目录并保存配置', () => { vi.mocked(fs.existsSync).mockReturnValue(false); saveConfig({ provider: 'anthropic' }); 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', model: 'old-model', })); saveConfig({ model: 'new-model' }); const writeCall = vi.mocked(fs.writeFileSync).mock.calls[0]; const savedConfig = JSON.parse(writeCall[1] as string); expect(savedConfig.provider).toBe('anthropic'); expect(savedConfig.model).toBe('new-model'); }); it('目录已存在时不重新创建', () => { vi.mocked(fs.existsSync) .mockReturnValueOnce(true) // 目录存在 .mockReturnValueOnce(true); // 配置文件存在 vi.mocked(fs.readFileSync).mockReturnValue('{}'); saveConfig({ provider: 'test' }); expect(fs.mkdirSync).not.toHaveBeenCalled(); }); }); describe('loadVisionConfig - Vision 配置', () => { it('返回 null 当没有配置 Vision API Key', () => { vi.mocked(fs.existsSync).mockReturnValue(false); vi.mocked(resolveApiKey).mockReturnValue(undefined); const visionConfig = loadVisionConfig(); expect(visionConfig).toBeNull(); }); it('通过 ProviderRegistry 获取 Vision 配置', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ visionProvider: 'openai', visionModel: 'gpt-4-vision', })); vi.mocked(resolveApiKey).mockReturnValue('vision-api-key'); const visionConfig = loadVisionConfig(); expect(visionConfig).not.toBeNull(); expect(visionConfig?.provider).toBe('openai'); expect(visionConfig?.apiKey).toBe('vision-api-key'); expect(visionConfig?.model).toBe('gpt-4-vision'); }); it('默认使用 anthropic provider', () => { vi.mocked(fs.existsSync).mockReturnValue(false); vi.mocked(resolveApiKey).mockReturnValue('anthropic-key'); const visionConfig = loadVisionConfig(); expect(visionConfig).not.toBeNull(); expect(visionConfig?.provider).toBe('anthropic'); }); it('Vision baseUrl 配置', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ visionBaseUrl: 'https://vision.api.com/v1', })); vi.mocked(resolveApiKey).mockReturnValue('vision-key'); const visionConfig = loadVisionConfig(); expect(visionConfig?.baseUrl).toBe('https://vision.api.com/v1'); }); it('使用默认 Vision 模型', () => { vi.mocked(fs.existsSync).mockReturnValue(false); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const visionConfig = loadVisionConfig(); expect(visionConfig?.model).toBe('claude-sonnet-4-20250514'); }); }); });