import { describe, it, expect, vi } from 'vitest'; import { createProviderFromConfig } from '../src/llm-config.js'; import type { SecretStore } from '@mcpctl/shared'; function mockSecretStore(secrets: Record = {}): SecretStore { return { get: vi.fn(async (key: string) => secrets[key] ?? null), set: vi.fn(async () => {}), delete: vi.fn(async () => true), backend: () => 'mock', }; } describe('createProviderFromConfig', () => { it('returns empty registry for undefined config', async () => { const store = mockSecretStore(); const registry = await createProviderFromConfig(undefined, store); expect(registry.getActive()).toBeNull(); expect(registry.list()).toEqual([]); }); it('returns empty registry for provider=none', async () => { const store = mockSecretStore(); const registry = await createProviderFromConfig({ provider: 'none' }, store); expect(registry.getActive()).toBeNull(); }); it('creates gemini-cli provider using ACP', async () => { const store = mockSecretStore(); const registry = await createProviderFromConfig( { provider: 'gemini-cli', model: 'gemini-2.5-flash', binaryPath: '/usr/bin/gemini' }, store, ); expect(registry.getActive()).not.toBeNull(); expect(registry.getActive()!.name).toBe('gemini-cli'); // ACP provider has dispose method expect(typeof registry.getActive()!.dispose).toBe('function'); }); it('creates ollama provider', async () => { const store = mockSecretStore(); const registry = await createProviderFromConfig( { provider: 'ollama', model: 'llama3.2', url: 'http://localhost:11434' }, store, ); expect(registry.getActive()!.name).toBe('ollama'); }); it('creates anthropic provider with API key from secret store', async () => { const store = mockSecretStore({ 'anthropic-api-key': 'sk-ant-test' }); const registry = await createProviderFromConfig( { provider: 'anthropic', model: 'claude-haiku-3-5-20241022' }, store, ); expect(registry.getActive()!.name).toBe('anthropic'); expect(store.get).toHaveBeenCalledWith('anthropic-api-key'); }); it('returns empty registry when anthropic API key is missing', async () => { const store = mockSecretStore(); const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); const registry = await createProviderFromConfig( { provider: 'anthropic', model: 'claude-haiku-3-5-20241022' }, store, ); expect(registry.getActive()).toBeNull(); expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('Anthropic API key not found')); stderrSpy.mockRestore(); }); it('creates openai provider with API key from secret store', async () => { const store = mockSecretStore({ 'openai-api-key': 'sk-test' }); const registry = await createProviderFromConfig( { provider: 'openai', model: 'gpt-4o', url: 'https://api.openai.com' }, store, ); expect(registry.getActive()!.name).toBe('openai'); expect(store.get).toHaveBeenCalledWith('openai-api-key'); }); it('returns empty registry when openai API key is missing', async () => { const store = mockSecretStore(); const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); const registry = await createProviderFromConfig( { provider: 'openai' }, store, ); expect(registry.getActive()).toBeNull(); stderrSpy.mockRestore(); }); it('creates deepseek provider with API key from secret store', async () => { const store = mockSecretStore({ 'deepseek-api-key': 'sk-ds-test' }); const registry = await createProviderFromConfig( { provider: 'deepseek', model: 'deepseek-chat' }, store, ); expect(registry.getActive()!.name).toBe('deepseek'); expect(store.get).toHaveBeenCalledWith('deepseek-api-key'); }); it('returns empty registry when deepseek API key is missing', async () => { const store = mockSecretStore(); const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); const registry = await createProviderFromConfig( { provider: 'deepseek' }, store, ); expect(registry.getActive()).toBeNull(); stderrSpy.mockRestore(); }); it('creates vllm provider using OpenAI provider', async () => { const store = mockSecretStore(); const registry = await createProviderFromConfig( { provider: 'vllm', model: 'my-model', url: 'http://gpu-server:8000' }, store, ); // vLLM reuses OpenAI provider under the hood, wrapped with NamedProvider expect(registry.getActive()).not.toBeNull(); expect(registry.getActive()!.name).toBe('vllm'); }); it('returns empty registry when vllm URL is missing', async () => { const store = mockSecretStore(); const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); const registry = await createProviderFromConfig( { provider: 'vllm' }, store, ); expect(registry.getActive()).toBeNull(); expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('vLLM URL not configured')); stderrSpy.mockRestore(); }); });