mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Desktop provider choice must override a separate Claude OAuth login. The OpenAI Codex fetch path is explicitly forced by the ChatGPT Official runtime marker, and the Anthropic SDK client no longer carries Claude authToken when that OpenAI transport is selected. Constraint: Users can have both Claude OAuth and ChatGPT OAuth saved. Rejected: Rely on isClaudeAISubscriber to choose transport | it makes Claude login override the selected ChatGPT provider. Confidence: high Scope-risk: moderate Tested: bun test src/services/api/client.test.ts Tested: bun test src/services/openaiAuth/fetch.test.ts src/server/__tests__/conversation-service.test.ts --test-name-pattern "ChatGPT Official|buildOpenAICodexFetch" Tested: git diff --check
118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
import { describe, expect, mock, test } from 'bun:test'
|
|
|
|
mock.module('src/utils/http.js', () => ({
|
|
getAuthHeaders: mock(() => ({})),
|
|
getMCPUserAgent: mock(() => 'client-test-agent'),
|
|
getUserAgent: mock(() => 'client-test-agent'),
|
|
getWebFetchUserAgent: mock(() => 'client-test-agent'),
|
|
withOAuth401Retry: mock(async <T>(fn: () => Promise<T>) => fn()),
|
|
}))
|
|
|
|
describe('resolveAnthropicClientApiKey', () => {
|
|
test('does not inherit a local api key when a provider auth token is explicit', async () => {
|
|
const { resolveAnthropicClientApiKey } = await import('./client.js')
|
|
const getFallbackApiKey = mock(() => 'sk-keychain-fallback')
|
|
|
|
const apiKey = resolveAnthropicClientApiKey({
|
|
envAuthToken: 'provider-bearer-token',
|
|
envApiKey: undefined,
|
|
getFallbackApiKey,
|
|
})
|
|
|
|
expect(apiKey).toBeNull()
|
|
expect(getFallbackApiKey).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('preserves an explicit api key when the caller opts into dual auth', async () => {
|
|
const { resolveAnthropicClientApiKey } = await import('./client.js')
|
|
const getFallbackApiKey = mock(() => 'sk-keychain-fallback')
|
|
|
|
const apiKey = resolveAnthropicClientApiKey({
|
|
explicitApiKey: 'sk-explicit-api-key',
|
|
envAuthToken: 'provider-bearer-token',
|
|
getFallbackApiKey,
|
|
})
|
|
|
|
expect(apiKey).toBe('sk-explicit-api-key')
|
|
expect(getFallbackApiKey).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('falls back to the local api key when no provider auth token is present', async () => {
|
|
const { resolveAnthropicClientApiKey } = await import('./client.js')
|
|
const getFallbackApiKey = mock(() => 'sk-keychain-fallback')
|
|
|
|
const apiKey = resolveAnthropicClientApiKey({
|
|
envAuthToken: undefined,
|
|
envApiKey: undefined,
|
|
getFallbackApiKey,
|
|
})
|
|
|
|
expect(apiKey).toBe('sk-keychain-fallback')
|
|
expect(getFallbackApiKey).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('shouldUseOpenAICodexTransport', () => {
|
|
test('lets ChatGPT Official marker override a saved Claude subscriber login', async () => {
|
|
const { shouldUseOpenAICodexTransport } = await import('./client.js')
|
|
|
|
expect(shouldUseOpenAICodexTransport({
|
|
hasOpenAIAuth: true,
|
|
isClaudeSubscriber: true,
|
|
forceOpenAICodex: true,
|
|
isOpenAIModel: true,
|
|
hasAnthropicAuthToken: false,
|
|
hasExplicitApiKey: false,
|
|
hasFallbackApiKey: false,
|
|
})).toBe(true)
|
|
})
|
|
|
|
test('keeps Claude subscriber transport when ChatGPT Official is not selected', async () => {
|
|
const { shouldUseOpenAICodexTransport } = await import('./client.js')
|
|
|
|
expect(shouldUseOpenAICodexTransport({
|
|
hasOpenAIAuth: true,
|
|
isClaudeSubscriber: true,
|
|
forceOpenAICodex: false,
|
|
isOpenAIModel: true,
|
|
hasAnthropicAuthToken: false,
|
|
hasExplicitApiKey: false,
|
|
hasFallbackApiKey: false,
|
|
})).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('getAnthropicClient', () => {
|
|
test('passes bearer-token provider auth without an SDK api key', async () => {
|
|
const { getAnthropicClient } = await import('./client.js')
|
|
const originalAuthToken = process.env.ANTHROPIC_AUTH_TOKEN
|
|
const originalApiKey = process.env.ANTHROPIC_API_KEY
|
|
const originalSimple = process.env.CLAUDE_CODE_SIMPLE
|
|
|
|
process.env.ANTHROPIC_AUTH_TOKEN = 'provider-bearer-token'
|
|
process.env.CLAUDE_CODE_SIMPLE = '1'
|
|
delete process.env.ANTHROPIC_API_KEY
|
|
|
|
try {
|
|
const client = await getAnthropicClient({
|
|
maxRetries: 0,
|
|
model: 'claude-sonnet-4-6',
|
|
})
|
|
|
|
expect(client.apiKey).toBeNull()
|
|
expect(client._options.defaultHeaders).toMatchObject({
|
|
Authorization: 'Bearer provider-bearer-token',
|
|
})
|
|
} finally {
|
|
if (originalAuthToken === undefined) delete process.env.ANTHROPIC_AUTH_TOKEN
|
|
else process.env.ANTHROPIC_AUTH_TOKEN = originalAuthToken
|
|
|
|
if (originalApiKey === undefined) delete process.env.ANTHROPIC_API_KEY
|
|
else process.env.ANTHROPIC_API_KEY = originalApiKey
|
|
|
|
if (originalSimple === undefined) delete process.env.CLAUDE_CODE_SIMPLE
|
|
else process.env.CLAUDE_CODE_SIMPLE = originalSimple
|
|
}
|
|
})
|
|
})
|