import { describe, it, expect, beforeEach, vi } from 'vitest'; // Mock provider registry vi.mock('../../../src/provider/index.js', () => ({ providerRegistry: { getInfo: vi.fn(), getConfig: vi.fn(), getModelInfo: vi.fn(), }, resolveApiKey: vi.fn(), })); // Mock agent registry vi.mock('../../../src/agent/registry.js', () => ({ agentRegistry: { getGlobalConfig: vi.fn(), }, })); import { providerRegistry, resolveApiKey } from '../../../src/provider/index.js'; import { agentRegistry } from '../../../src/agent/registry.js'; import { loadConfig, loadVisionConfig, ConfigurationError } from '../../../src/utils/config.js'; describe('Config - 配置管理', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('loadConfig - 加载完整配置', () => { it('从 AgentRegistry defaults.model 获取配置', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({ model: { provider: 'openai', model: 'gpt-4o', maxTokens: 8192, }, }); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'openai', baseUrl: 'https://api.openai.com/v1', }); vi.mocked(resolveApiKey).mockReturnValue('openai-api-key'); const config = loadConfig(); expect(config.provider).toBe('openai'); expect(config.model).toBe('gpt-4o'); expect(config.apiKey).toBe('openai-api-key'); expect(config.maxTokens).toBe(8192); expect(config.baseUrl).toBe('https://api.openai.com/v1'); }); it('未配置 defaults.model 时使用第一个有 API Key 的 provider', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue(null); // anthropic 没有 API Key vi.mocked(providerRegistry.getConfig).mockImplementation((id) => { if (id === 'openai') { return { id: 'openai' }; } return undefined; }); vi.mocked(resolveApiKey).mockImplementation((config) => { if (config?.id === 'openai') { return 'openai-fallback-key'; } return undefined; }); const config = loadConfig(); expect(config.provider).toBe('openai'); expect(config.apiKey).toBe('openai-fallback-key'); expect(config.model).toBe('gpt-4o'); // 默认模型 }); it('没有任何 provider 配置 API Key 时抛出错误', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue(null); vi.mocked(providerRegistry.getConfig).mockReturnValue(undefined); vi.mocked(resolveApiKey).mockReturnValue(undefined); expect(() => loadConfig()).toThrow(ConfigurationError); expect(() => loadConfig()).toThrow('请先在 Providers 面板配置一个模型提供商的 API Key'); }); it('配置了 provider 但没有 API Key 时抛出错误', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({ model: { provider: 'deepseek', }, }); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'deepseek' }); vi.mocked(resolveApiKey).mockReturnValue(undefined); expect(() => loadConfig()).toThrow(ConfigurationError); expect(() => loadConfig()).toThrow('请在 Providers 面板配置 deepseek 的 API Key'); }); it('包含系统提示词', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue(null); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'anthropic' }); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const config = loadConfig(); expect(config.systemPrompt).toBeDefined(); expect(config.systemPrompt).toContain('终端'); }); it('默认 maxTokens 为 4096', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({ model: { provider: 'anthropic', }, }); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'anthropic' }); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const config = loadConfig(); expect(config.maxTokens).toBe(4096); }); it('从 ProviderConfig 获取 baseUrl', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({ model: { provider: 'openai', }, }); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'openai', 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('获取模型的 contextWindow', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({ model: { provider: 'anthropic', model: 'claude-sonnet-4-20250514', }, }); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'anthropic' }); vi.mocked(providerRegistry.getModelInfo).mockReturnValue({ id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', contextWindow: 200000, }); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const config = loadConfig(); expect(config.contextWindow).toBe(200000); }); }); describe('loadVisionConfig - Vision 配置', () => { it('返回 null 当没有配置 defaults.vision', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue(null); const visionConfig = loadVisionConfig(); expect(visionConfig).toBeNull(); }); it('返回 null 当 vision provider 没有 API Key', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({ vision: { provider: 'openai', model: 'gpt-4o', }, }); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'openai' }); vi.mocked(resolveApiKey).mockReturnValue(undefined); const visionConfig = loadVisionConfig(); expect(visionConfig).toBeNull(); }); it('从 defaults.vision 获取 Vision 配置', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({ vision: { provider: 'openai', model: 'gpt-4o', }, }); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'openai', baseUrl: 'https://api.openai.com/v1', }); 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-4o'); expect(visionConfig?.baseUrl).toBe('https://api.openai.com/v1'); }); it('使用默认模型当 vision.model 未指定', () => { vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({ vision: { provider: 'anthropic', }, }); vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'anthropic' }); vi.mocked(resolveApiKey).mockReturnValue('test-key'); const visionConfig = loadVisionConfig(); expect(visionConfig?.model).toBe('claude-sonnet-4-20250514'); }); }); describe('ConfigurationError', () => { it('包含 provider 和 missingKey 信息', () => { const error = new ConfigurationError('Test error', 'openai', 'apiKey'); expect(error.name).toBe('ConfigurationError'); expect(error.message).toBe('Test error'); expect(error.provider).toBe('openai'); expect(error.missingKey).toBe('apiKey'); }); }); });