// @vitest-environment node import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const getCachedShellEnvMock = vi.fn<() => NodeJS.ProcessEnv | null>(); vi.mock('@main/utils/shellEnv', () => ({ getCachedShellEnv: () => getCachedShellEnvMock(), })); describe('ProviderConnectionService', () => { const originalOpenAiApiKey = process.env.OPENAI_API_KEY; const originalCodexApiKey = process.env.CODEX_API_KEY; function createConfig(authMode: 'auto' | 'oauth' | 'api_key' = 'auto') { return { providerConnections: { anthropic: { authMode, }, codex: { apiKeyBetaEnabled: false, authMode: 'oauth' as const, }, }, runtime: { providerBackends: { gemini: 'auto' as const, codex: 'codex-native' as const, }, }, }; } beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); getCachedShellEnvMock.mockReturnValue({}); delete process.env.OPENAI_API_KEY; delete process.env.CODEX_API_KEY; }); afterEach(() => { if (originalOpenAiApiKey === undefined) { delete process.env.OPENAI_API_KEY; } else { process.env.OPENAI_API_KEY = originalOpenAiApiKey; } if (originalCodexApiKey === undefined) { delete process.env.CODEX_API_KEY; } else { process.env.CODEX_API_KEY = originalCodexApiKey; } }); it('removes Anthropic environment credentials when OAuth mode is selected', async () => { const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred: vi.fn().mockResolvedValue(null), } as never, { getConfig: () => createConfig('oauth'), } as never ); const result = await service.applyConfiguredConnectionEnv( { ANTHROPIC_API_KEY: 'direct-key', ANTHROPIC_AUTH_TOKEN: 'proxy-token', }, 'anthropic' ); expect(result.ANTHROPIC_API_KEY).toBeUndefined(); expect(result.ANTHROPIC_AUTH_TOKEN).toBeUndefined(); }); it('injects the stored Anthropic API key when api_key mode is selected', async () => { const lookupPreferred = vi.fn().mockResolvedValue({ envVarName: 'ANTHROPIC_API_KEY', value: 'stored-key', }); const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred, } as never, { getConfig: () => createConfig('api_key'), } as never ); const result = await service.applyConfiguredConnectionEnv( { ANTHROPIC_API_KEY: undefined, ANTHROPIC_AUTH_TOKEN: 'proxy-token', }, 'anthropic' ); expect(lookupPreferred).toHaveBeenCalledWith('ANTHROPIC_API_KEY'); expect(result.ANTHROPIC_API_KEY).toBe('stored-key'); expect(result.ANTHROPIC_AUTH_TOKEN).toBeUndefined(); }); it('reports a missing Anthropic API key when api_key mode is selected', async () => { const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred: vi.fn().mockResolvedValue(null), } as never, { getConfig: () => createConfig('api_key'), } as never ); const issue = await service.getConfiguredConnectionIssue({}, 'anthropic'); expect(issue).toContain('Anthropic API key mode is enabled'); expect(issue).toContain('ANTHROPIC_API_KEY'); }); it('prefers stored API key status over environment detection for Anthropic', async () => { getCachedShellEnvMock.mockReturnValue({ ANTHROPIC_API_KEY: 'shell-key', }); const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred: vi.fn().mockResolvedValue({ envVarName: 'ANTHROPIC_API_KEY', value: 'stored-key', }), } as never, { getConfig: () => createConfig('auto'), } as never ); const info = await service.getConnectionInfo('anthropic'); expect(info).toMatchObject({ supportsOAuth: true, supportsApiKey: true, configuredAuthMode: 'auto', apiKeyConfigured: true, apiKeySource: 'stored', apiKeySourceLabel: 'Stored in app', }); }); it('exposes Codex as native-only API-key runtime', async () => { const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred: vi.fn().mockResolvedValue(null), } as never, { getConfig: () => createConfig('auto'), } as never ); const info = await service.getConnectionInfo('codex'); expect(info).toMatchObject({ supportsOAuth: false, supportsApiKey: true, configurableAuthModes: [], configuredAuthMode: null, apiKeyConfigured: false, apiKeySource: null, apiKeySourceLabel: null, }); expect(info.apiKeyBetaAvailable).toBeUndefined(); expect(info.apiKeyBetaEnabled).toBeUndefined(); }); it('mirrors a stored OpenAI key into CODEX_API_KEY for native Codex launches', async () => { const lookupPreferred = vi.fn().mockResolvedValue({ envVarName: 'OPENAI_API_KEY', value: 'openai-stored-key', }); const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred, } as never, { getConfig: () => createConfig('auto'), } as never ); const result = await service.applyConfiguredConnectionEnv({}, 'codex'); expect(lookupPreferred).toHaveBeenCalledWith('OPENAI_API_KEY'); expect(result.OPENAI_API_KEY).toBe('openai-stored-key'); expect(result.CODEX_API_KEY).toBe('openai-stored-key'); }); it('keeps ambient OpenAI credentials for native Codex launches', async () => { const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred: vi.fn().mockResolvedValue(null), } as never, { getConfig: () => createConfig('auto'), } as never ); const result = await service.applyConfiguredConnectionEnv( { OPENAI_API_KEY: 'shell-openai-key', }, 'codex' ); expect(result.OPENAI_API_KEY).toBe('shell-openai-key'); expect(result.CODEX_API_KEY).toBe('shell-openai-key'); }); it('accepts CODEX_API_KEY as the native external credential source for Codex', async () => { getCachedShellEnvMock.mockReturnValue({ CODEX_API_KEY: 'native-key', }); const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred: vi.fn().mockResolvedValue(null), } as never, { getConfig: () => createConfig('auto'), } as never ); const info = await service.getConnectionInfo('codex'); const issue = await service.getConfiguredConnectionIssue( { CODEX_API_KEY: 'native-key', }, 'codex' ); expect(info.apiKeyConfigured).toBe(true); expect(info.apiKeySource).toBe('environment'); expect(info.apiKeySourceLabel).toBe('Detected from CODEX_API_KEY'); expect(issue).toBeNull(); }); it('reports a missing native Codex credential when neither OPENAI_API_KEY nor CODEX_API_KEY exist', async () => { const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred: vi.fn().mockResolvedValue(null), } as never, { getConfig: () => createConfig('auto'), } as never ); const issue = await service.getConfiguredConnectionIssue({}, 'codex'); expect(issue).toContain('Codex native requires OPENAI_API_KEY or CODEX_API_KEY'); }); it('augments PTY env for native Codex without dropping existing OpenAI credentials', async () => { const { ProviderConnectionService } = await import('@main/services/runtime/ProviderConnectionService'); const service = new ProviderConnectionService( { lookupPreferred: vi.fn().mockResolvedValue(null), } as never, { getConfig: () => createConfig('auto'), } as never ); const result = await service.augmentConfiguredConnectionEnv( { OPENAI_API_KEY: 'shell-key', }, 'codex' ); expect(result.OPENAI_API_KEY).toBe('shell-key'); expect(result.CODEX_API_KEY).toBe('shell-key'); }); });