| import { describe, it, expect, vi, beforeEach } from 'vitest'; |
| import { |
| getMCPServersFromSettings, |
| getProviderById, |
| getProviderByModelId, |
| resolveProviderContext, |
| getAllProviderModels, |
| } from '@/lib/settings-helpers.js'; |
| import type { SettingsService } from '@/services/settings-service.js'; |
|
|
| |
| vi.mock('@automaker/utils', async () => { |
| const actual = await vi.importActual('@automaker/utils'); |
| const mockLogger = { |
| info: vi.fn(), |
| error: vi.fn(), |
| warn: vi.fn(), |
| debug: vi.fn(), |
| }; |
| return { |
| ...actual, |
| createLogger: () => mockLogger, |
| }; |
| }); |
|
|
| describe('settings-helpers.ts', () => { |
| describe('getMCPServersFromSettings', () => { |
| beforeEach(() => { |
| vi.clearAllMocks(); |
| }); |
|
|
| it('should return empty object when settingsService is null', async () => { |
| const result = await getMCPServersFromSettings(null); |
| expect(result).toEqual({}); |
| }); |
|
|
| it('should return empty object when settingsService is undefined', async () => { |
| const result = await getMCPServersFromSettings(undefined); |
| expect(result).toEqual({}); |
| }); |
|
|
| it('should return empty object when no MCP servers configured', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ mcpServers: [] }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result).toEqual({}); |
| }); |
|
|
| it('should return empty object when mcpServers is undefined', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result).toEqual({}); |
| }); |
|
|
| it('should convert enabled stdio server to SDK format', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { |
| id: '1', |
| name: 'test-server', |
| type: 'stdio', |
| command: 'node', |
| args: ['server.js'], |
| env: { NODE_ENV: 'test' }, |
| enabled: true, |
| }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result).toEqual({ |
| 'test-server': { |
| type: 'stdio', |
| command: 'node', |
| args: ['server.js'], |
| env: { NODE_ENV: 'test' }, |
| }, |
| }); |
| }); |
|
|
| it('should convert enabled SSE server to SDK format', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { |
| id: '1', |
| name: 'sse-server', |
| type: 'sse', |
| url: 'http://localhost:3000/sse', |
| headers: { Authorization: 'Bearer token' }, |
| enabled: true, |
| }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result).toEqual({ |
| 'sse-server': { |
| type: 'sse', |
| url: 'http://localhost:3000/sse', |
| headers: { Authorization: 'Bearer token' }, |
| }, |
| }); |
| }); |
|
|
| it('should convert enabled HTTP server to SDK format', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { |
| id: '1', |
| name: 'http-server', |
| type: 'http', |
| url: 'http://localhost:3000/api', |
| headers: { 'X-API-Key': 'secret' }, |
| enabled: true, |
| }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result).toEqual({ |
| 'http-server': { |
| type: 'http', |
| url: 'http://localhost:3000/api', |
| headers: { 'X-API-Key': 'secret' }, |
| }, |
| }); |
| }); |
|
|
| it('should filter out disabled servers', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { |
| id: '1', |
| name: 'enabled-server', |
| type: 'stdio', |
| command: 'node', |
| enabled: true, |
| }, |
| { |
| id: '2', |
| name: 'disabled-server', |
| type: 'stdio', |
| command: 'python', |
| enabled: false, |
| }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(Object.keys(result)).toHaveLength(1); |
| expect(result['enabled-server']).toBeDefined(); |
| expect(result['disabled-server']).toBeUndefined(); |
| }); |
|
|
| it('should treat servers without enabled field as enabled', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { |
| id: '1', |
| name: 'implicit-enabled', |
| type: 'stdio', |
| command: 'node', |
| |
| }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result['implicit-enabled']).toBeDefined(); |
| }); |
|
|
| it('should handle multiple enabled servers', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { id: '1', name: 'server1', type: 'stdio', command: 'node', enabled: true }, |
| { id: '2', name: 'server2', type: 'stdio', command: 'python', enabled: true }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(Object.keys(result)).toHaveLength(2); |
| expect(result['server1']).toBeDefined(); |
| expect(result['server2']).toBeDefined(); |
| }); |
|
|
| it('should return empty object and log error on exception', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockRejectedValue(new Error('Settings error')), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService, '[Test]'); |
| expect(result).toEqual({}); |
| |
| }); |
|
|
| it('should throw error for SSE server without URL', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { |
| id: '1', |
| name: 'bad-sse', |
| type: 'sse', |
| enabled: true, |
| |
| }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| |
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result).toEqual({}); |
| }); |
|
|
| it('should throw error for HTTP server without URL', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { |
| id: '1', |
| name: 'bad-http', |
| type: 'http', |
| enabled: true, |
| |
| }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result).toEqual({}); |
| }); |
|
|
| it('should throw error for stdio server without command', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { |
| id: '1', |
| name: 'bad-stdio', |
| type: 'stdio', |
| enabled: true, |
| |
| }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result).toEqual({}); |
| }); |
|
|
| it('should default to stdio type when type is not specified', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| mcpServers: [ |
| { |
| id: '1', |
| name: 'no-type', |
| command: 'node', |
| enabled: true, |
| |
| }, |
| ], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getMCPServersFromSettings(mockSettingsService); |
| expect(result['no-type']).toEqual({ |
| type: 'stdio', |
| command: 'node', |
| args: undefined, |
| env: undefined, |
| }); |
| }); |
| }); |
|
|
| describe('getProviderById', () => { |
| beforeEach(() => { |
| vi.clearAllMocks(); |
| }); |
|
|
| it('should return provider when found by ID', async () => { |
| const mockProvider = { id: 'zai-1', name: 'Zai', enabled: true }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await getProviderById('zai-1', mockSettingsService); |
| expect(result.provider).toEqual(mockProvider); |
| }); |
|
|
| it('should return undefined when provider not found', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await getProviderById('unknown', mockSettingsService); |
| expect(result.provider).toBeUndefined(); |
| }); |
|
|
| it('should return provider even if disabled (caller handles enabled state)', async () => { |
| const mockProvider = { id: 'disabled-1', name: 'Disabled', enabled: false }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await getProviderById('disabled-1', mockSettingsService); |
| expect(result.provider).toEqual(mockProvider); |
| }); |
| }); |
|
|
| describe('getProviderByModelId', () => { |
| beforeEach(() => { |
| vi.clearAllMocks(); |
| }); |
|
|
| it('should return provider and modelConfig when found by model ID', async () => { |
| const mockModel = { id: 'custom-model-1', name: 'Custom Model' }; |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| models: [mockModel], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await getProviderByModelId('custom-model-1', mockSettingsService); |
| expect(result.provider).toEqual(mockProvider); |
| expect(result.modelConfig).toEqual(mockModel); |
| }); |
|
|
| it('should resolve mapped Claude model when mapsToClaudeModel is present', async () => { |
| const mockModel = { |
| id: 'custom-model-1', |
| name: 'Custom Model', |
| mapsToClaudeModel: 'sonnet-3-5', |
| }; |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| models: [mockModel], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await getProviderByModelId('custom-model-1', mockSettingsService); |
| expect(result.resolvedModel).toBeDefined(); |
| |
| }); |
|
|
| it('should ignore disabled providers', async () => { |
| const mockModel = { id: 'custom-model-1', name: 'Custom Model' }; |
| const mockProvider = { |
| id: 'disabled-1', |
| name: 'Disabled Provider', |
| enabled: false, |
| models: [mockModel], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await getProviderByModelId('custom-model-1', mockSettingsService); |
| expect(result.provider).toBeUndefined(); |
| }); |
| }); |
|
|
| describe('resolveProviderContext', () => { |
| beforeEach(() => { |
| vi.clearAllMocks(); |
| }); |
|
|
| it('should resolve provider by explicit providerId', async () => { |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| models: [{ id: 'custom-model-1', name: 'Custom Model' }], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({ anthropicApiKey: 'test-key' }), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext( |
| mockSettingsService, |
| 'custom-model-1', |
| 'provider-1' |
| ); |
|
|
| expect(result.provider).toEqual(mockProvider); |
| expect(result.credentials).toEqual({ anthropicApiKey: 'test-key' }); |
| }); |
|
|
| it('should return undefined provider when explicit providerId not found', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext( |
| mockSettingsService, |
| 'some-model', |
| 'unknown-provider' |
| ); |
|
|
| expect(result.provider).toBeUndefined(); |
| }); |
|
|
| it('should fallback to model-based lookup when providerId not provided', async () => { |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| models: [{ id: 'custom-model-1', name: 'Custom Model' }], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext(mockSettingsService, 'custom-model-1'); |
|
|
| expect(result.provider).toEqual(mockProvider); |
| expect(result.modelConfig?.id).toBe('custom-model-1'); |
| }); |
|
|
| it('should resolve mapsToClaudeModel to actual Claude model', async () => { |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| models: [ |
| { |
| id: 'custom-model-1', |
| name: 'Custom Model', |
| mapsToClaudeModel: 'sonnet', |
| }, |
| ], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext(mockSettingsService, 'custom-model-1'); |
|
|
| |
| expect(result.resolvedModel).toBeDefined(); |
| expect(result.resolvedModel).toContain('claude'); |
| }); |
|
|
| it('should handle empty providers list', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext(mockSettingsService, 'some-model'); |
|
|
| expect(result.provider).toBeUndefined(); |
| expect(result.resolvedModel).toBeUndefined(); |
| expect(result.modelConfig).toBeUndefined(); |
| }); |
|
|
| it('should handle missing claudeCompatibleProviders field', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({}), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext(mockSettingsService, 'some-model'); |
|
|
| expect(result.provider).toBeUndefined(); |
| }); |
|
|
| it('should skip disabled providers during fallback lookup', async () => { |
| const disabledProvider = { |
| id: 'disabled-1', |
| name: 'Disabled Provider', |
| enabled: false, |
| models: [{ id: 'model-in-disabled', name: 'Model' }], |
| }; |
| const enabledProvider = { |
| id: 'enabled-1', |
| name: 'Enabled Provider', |
| enabled: true, |
| models: [{ id: 'model-in-enabled', name: 'Model' }], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [disabledProvider, enabledProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| |
| const result = await resolveProviderContext(mockSettingsService, 'model-in-enabled'); |
| expect(result.provider?.id).toBe('enabled-1'); |
|
|
| |
| const result2 = await resolveProviderContext(mockSettingsService, 'model-in-disabled'); |
| expect(result2.provider).toBeUndefined(); |
| }); |
|
|
| it('should perform case-insensitive model ID matching', async () => { |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| models: [{ id: 'Custom-Model-1', name: 'Custom Model' }], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext(mockSettingsService, 'custom-model-1'); |
|
|
| expect(result.provider).toEqual(mockProvider); |
| expect(result.modelConfig?.id).toBe('Custom-Model-1'); |
| }); |
|
|
| it('should return error result on exception', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockRejectedValue(new Error('Settings error')), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext(mockSettingsService, 'some-model'); |
|
|
| expect(result.provider).toBeUndefined(); |
| expect(result.credentials).toBeUndefined(); |
| expect(result.resolvedModel).toBeUndefined(); |
| expect(result.modelConfig).toBeUndefined(); |
| }); |
|
|
| it('should persist and load provider config from server settings', async () => { |
| |
| const savedProvider = { |
| id: 'saved-provider-1', |
| name: 'Saved Provider', |
| enabled: true, |
| apiKeySource: 'credentials' as const, |
| models: [ |
| { |
| id: 'saved-model-1', |
| name: 'Saved Model', |
| mapsToClaudeModel: 'sonnet', |
| }, |
| ], |
| }; |
|
|
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [savedProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({ |
| anthropicApiKey: 'saved-api-key', |
| }), |
| } as unknown as SettingsService; |
|
|
| |
| const result = await resolveProviderContext( |
| mockSettingsService, |
| 'saved-model-1', |
| 'saved-provider-1' |
| ); |
|
|
| |
| expect(result.provider).toEqual(savedProvider); |
| expect(result.provider?.id).toBe('saved-provider-1'); |
| expect(result.provider?.models).toHaveLength(1); |
| expect(result.credentials?.anthropicApiKey).toBe('saved-api-key'); |
| |
| expect(result.resolvedModel).toContain('claude'); |
| }); |
|
|
| it('should accept custom logPrefix parameter', async () => { |
| |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| models: [{ id: 'model-1', name: 'Model' }], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| |
| const result = await resolveProviderContext( |
| mockSettingsService, |
| 'model-1', |
| undefined, |
| '[CustomPrefix]' |
| ); |
|
|
| |
| expect(result.provider).toEqual(mockProvider); |
| }); |
|
|
| |
| describe('session restore scenarios (enabled: undefined)', () => { |
| it('should treat provider with enabled: undefined as enabled', async () => { |
| |
| |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: undefined, |
| models: [{ id: 'model-1', name: 'Model' }], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext(mockSettingsService, 'model-1'); |
|
|
| |
| expect(result.provider).toEqual(mockProvider); |
| expect(result.modelConfig?.id).toBe('model-1'); |
| }); |
|
|
| it('should use provider by ID when enabled is undefined', async () => { |
| |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: undefined, |
| models: [{ id: 'custom-model', name: 'Custom Model', mapsToClaudeModel: 'sonnet' }], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({ anthropicApiKey: 'test-key' }), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext( |
| mockSettingsService, |
| 'custom-model', |
| 'provider-1' |
| ); |
|
|
| |
| expect(result.provider).toEqual(mockProvider); |
| expect(result.credentials?.anthropicApiKey).toBe('test-key'); |
| expect(result.resolvedModel).toContain('claude'); |
| }); |
|
|
| it('should find model via fallback in provider with enabled: undefined', async () => { |
| |
| const providerWithUndefinedEnabled = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| |
| models: [{ id: 'model-1', name: 'Model' }], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [providerWithUndefinedEnabled], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext(mockSettingsService, 'model-1'); |
|
|
| expect(result.provider).toEqual(providerWithUndefinedEnabled); |
| expect(result.modelConfig?.id).toBe('model-1'); |
| }); |
|
|
| it('should still use provider for connection when model not found in its models array', async () => { |
| |
| |
| |
| const mockProvider = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| baseUrl: 'https://custom-api.example.com', |
| models: [{ id: 'other-model', name: 'Other Model' }], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [mockProvider], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({ anthropicApiKey: 'test-key' }), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext( |
| mockSettingsService, |
| 'unknown-model', |
| 'provider-1' |
| ); |
|
|
| |
| expect(result.provider).toEqual(mockProvider); |
| |
| expect(result.modelConfig).toBeUndefined(); |
| |
| expect(result.resolvedModel).toBeUndefined(); |
| }); |
|
|
| it('should fallback to find modelConfig in other providers when not in explicit providerId provider', async () => { |
| |
| |
| const provider1 = { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| baseUrl: 'https://provider1.example.com', |
| models: [{ id: 'provider1-model', name: 'Provider 1 Model' }], |
| }; |
| const provider2 = { |
| id: 'provider-2', |
| name: 'Provider 2', |
| enabled: true, |
| baseUrl: 'https://provider2.example.com', |
| models: [ |
| { |
| id: 'shared-model', |
| name: 'Shared Model', |
| mapsToClaudeModel: 'sonnet', |
| }, |
| ], |
| }; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [provider1, provider2], |
| }), |
| getCredentials: vi.fn().mockResolvedValue({ anthropicApiKey: 'test-key' }), |
| } as unknown as SettingsService; |
|
|
| const result = await resolveProviderContext( |
| mockSettingsService, |
| 'shared-model', |
| 'provider-1' |
| ); |
|
|
| |
| expect(result.provider).toEqual(provider1); |
| |
| expect(result.modelConfig?.id).toBe('shared-model'); |
| |
| expect(result.resolvedModel).toContain('claude'); |
| }); |
|
|
| it('should handle multiple providers with mixed enabled states', async () => { |
| |
| const providers = [ |
| { |
| id: 'provider-1', |
| name: 'First Provider', |
| enabled: undefined, |
| models: [{ id: 'model-a', name: 'Model A' }], |
| }, |
| { |
| id: 'provider-2', |
| name: 'Second Provider', |
| |
| models: [{ id: 'model-b', name: 'Model B', mapsToClaudeModel: 'opus' }], |
| }, |
| { |
| id: 'provider-3', |
| name: 'Disabled Provider', |
| enabled: false, |
| models: [{ id: 'model-c', name: 'Model C' }], |
| }, |
| ]; |
|
|
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: providers, |
| }), |
| getCredentials: vi.fn().mockResolvedValue({ anthropicApiKey: 'test-key' }), |
| } as unknown as SettingsService; |
|
|
| |
| const result1 = await resolveProviderContext(mockSettingsService, 'model-a', 'provider-1'); |
| expect(result1.provider?.id).toBe('provider-1'); |
| expect(result1.modelConfig?.id).toBe('model-a'); |
|
|
| |
| const result2 = await resolveProviderContext(mockSettingsService, 'model-b', 'provider-2'); |
| expect(result2.provider?.id).toBe('provider-2'); |
| expect(result2.modelConfig?.id).toBe('model-b'); |
| expect(result2.resolvedModel).toContain('claude'); |
|
|
| |
| |
| const result3 = await resolveProviderContext(mockSettingsService, 'model-c', 'provider-3'); |
| |
| |
| expect(result3.provider).toEqual(providers[2]); |
| expect(result3.modelConfig).toBeUndefined(); |
| }); |
| }); |
| }); |
|
|
| describe('getAllProviderModels', () => { |
| beforeEach(() => { |
| vi.clearAllMocks(); |
| }); |
|
|
| it('should return all models from enabled providers', async () => { |
| const mockProviders = [ |
| { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| models: [ |
| { id: 'model-1', name: 'Model 1' }, |
| { id: 'model-2', name: 'Model 2' }, |
| ], |
| }, |
| { |
| id: 'provider-2', |
| name: 'Provider 2', |
| enabled: true, |
| models: [{ id: 'model-3', name: 'Model 3' }], |
| }, |
| ]; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: mockProviders, |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getAllProviderModels(mockSettingsService); |
|
|
| expect(result).toHaveLength(3); |
| expect(result[0].providerId).toBe('provider-1'); |
| expect(result[0].model.id).toBe('model-1'); |
| expect(result[2].providerId).toBe('provider-2'); |
| }); |
|
|
| it('should filter out disabled providers', async () => { |
| const mockProviders = [ |
| { |
| id: 'enabled-1', |
| name: 'Enabled Provider', |
| enabled: true, |
| models: [{ id: 'model-1', name: 'Model 1' }], |
| }, |
| { |
| id: 'disabled-1', |
| name: 'Disabled Provider', |
| enabled: false, |
| models: [{ id: 'model-2', name: 'Model 2' }], |
| }, |
| ]; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: mockProviders, |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getAllProviderModels(mockSettingsService); |
|
|
| expect(result).toHaveLength(1); |
| expect(result[0].providerId).toBe('enabled-1'); |
| }); |
|
|
| it('should return empty array when no providers configured', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: [], |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getAllProviderModels(mockSettingsService); |
|
|
| expect(result).toEqual([]); |
| }); |
|
|
| it('should handle missing claudeCompatibleProviders field', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({}), |
| } as unknown as SettingsService; |
|
|
| const result = await getAllProviderModels(mockSettingsService); |
|
|
| expect(result).toEqual([]); |
| }); |
|
|
| it('should handle provider with no models', async () => { |
| const mockProviders = [ |
| { |
| id: 'provider-1', |
| name: 'Provider 1', |
| enabled: true, |
| models: [], |
| }, |
| { |
| id: 'provider-2', |
| name: 'Provider 2', |
| enabled: true, |
| |
| }, |
| ]; |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockResolvedValue({ |
| claudeCompatibleProviders: mockProviders, |
| }), |
| } as unknown as SettingsService; |
|
|
| const result = await getAllProviderModels(mockSettingsService); |
|
|
| expect(result).toEqual([]); |
| }); |
|
|
| it('should return empty array on exception', async () => { |
| const mockSettingsService = { |
| getGlobalSettings: vi.fn().mockRejectedValue(new Error('Settings error')), |
| } as unknown as SettingsService; |
|
|
| const result = await getAllProviderModels(mockSettingsService); |
|
|
| expect(result).toEqual([]); |
| }); |
| }); |
| }); |
|
|