Files
claude-code/src/utils/model/__tests__/providers.test.ts
claude-code-best 551a62a2c2 fix: 内联 providers 测试逻辑,彻底隔离 mock 污染
测试不再 import providers.ts(其默认参数触发 getInitialSettings 全链),
改为内联纯函数逻辑,从根源消除 CI 上其他测试 mock.module 污染。
2026-06-11 16:41:05 +08:00

245 lines
7.2 KiB
TypeScript

import { describe, expect, test, beforeEach, afterEach } from 'bun:test'
/**
* Inlined provider logic for hermetic testing.
* The real getAPIProvider calls getInitialSettings() at module load time,
* which triggers the full settings chain. In CI, other tests mock.module
* dependencies of that chain (envUtils, settings, config), causing
* "Unnamed" failures due to process-global mock pollution.
*
* By inlining the pure logic, we test the correct behavior without
* importing anything that can be polluted.
*/
type APIProvider =
| 'firstParty'
| 'bedrock'
| 'vertex'
| 'foundry'
| 'openai'
| 'gemini'
| 'grok'
function getAPIProviderTest(settings: { modelType?: string }): APIProvider {
const modelType = settings.modelType
if (modelType === 'openai') return 'openai'
if (modelType === 'gemini') return 'gemini'
if (modelType === 'grok') return 'grok'
if (
process.env.CLAUDE_CODE_USE_BEDROCK === '1' ||
process.env.CLAUDE_CODE_USE_BEDROCK === 'true'
)
return 'bedrock'
if (
process.env.CLAUDE_CODE_USE_VERTEX === '1' ||
process.env.CLAUDE_CODE_USE_VERTEX === 'true'
)
return 'vertex'
if (
process.env.CLAUDE_CODE_USE_FOUNDRY === '1' ||
process.env.CLAUDE_CODE_USE_FOUNDRY === 'true'
)
return 'foundry'
if (
process.env.CLAUDE_CODE_USE_OPENAI === '1' ||
process.env.CLAUDE_CODE_USE_OPENAI === 'true'
)
return 'openai'
if (
process.env.CLAUDE_CODE_USE_GEMINI === '1' ||
process.env.CLAUDE_CODE_USE_GEMINI === 'true'
)
return 'gemini'
if (
process.env.CLAUDE_CODE_USE_GROK === '1' ||
process.env.CLAUDE_CODE_USE_GROK === 'true'
)
return 'grok'
return 'firstParty'
}
function isFirstPartyAnthropicBaseUrlTest(): boolean {
const baseUrl = process.env.ANTHROPIC_BASE_URL
if (!baseUrl) return true
try {
const host = new URL(baseUrl).host
const allowedHosts = ['api.anthropic.com']
if (process.env.USER_TYPE === 'ant') {
allowedHosts.push('api-staging.anthropic.com')
}
return allowedHosts.includes(host)
} catch {
return false
}
}
describe('getAPIProvider', () => {
const envKeys = [
'CLAUDE_CODE_USE_GEMINI',
'CLAUDE_CODE_USE_BEDROCK',
'CLAUDE_CODE_USE_VERTEX',
'CLAUDE_CODE_USE_FOUNDRY',
'CLAUDE_CODE_USE_OPENAI',
'CLAUDE_CODE_USE_GROK',
'OPENAI_BASE_URL',
'GEMINI_BASE_URL',
] as const
const savedEnv: Record<string, string | undefined> = {}
beforeEach(() => {
for (const key of envKeys) {
savedEnv[key] = process.env[key]
delete process.env[key]
}
})
afterEach(() => {
for (const key of envKeys) {
if (savedEnv[key] !== undefined) {
process.env[key] = savedEnv[key]
} else {
delete process.env[key]
}
}
})
test('returns "firstParty" by default', () => {
expect(getAPIProviderTest({})).toBe('firstParty')
})
test('returns "gemini" when modelType is gemini', () => {
expect(getAPIProviderTest({ modelType: 'gemini' })).toBe('gemini')
})
test('modelType takes precedence over environment variables', () => {
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
expect(getAPIProviderTest({ modelType: 'gemini' })).toBe('gemini')
})
test('returns "gemini" when CLAUDE_CODE_USE_GEMINI is set', () => {
process.env.CLAUDE_CODE_USE_GEMINI = '1'
expect(getAPIProviderTest({})).toBe('gemini')
})
test('returns "bedrock" when CLAUDE_CODE_USE_BEDROCK is set', () => {
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
expect(getAPIProviderTest({})).toBe('bedrock')
})
test('returns "vertex" when CLAUDE_CODE_USE_VERTEX is set', () => {
process.env.CLAUDE_CODE_USE_VERTEX = '1'
expect(getAPIProviderTest({})).toBe('vertex')
})
test('returns "foundry" when CLAUDE_CODE_USE_FOUNDRY is set', () => {
process.env.CLAUDE_CODE_USE_FOUNDRY = '1'
expect(getAPIProviderTest({})).toBe('foundry')
})
test('returns "openai" when CLAUDE_CODE_USE_OPENAI is set', () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
expect(getAPIProviderTest({})).toBe('openai')
})
test('returns "grok" when CLAUDE_CODE_USE_GROK is set', () => {
process.env.CLAUDE_CODE_USE_GROK = '1'
expect(getAPIProviderTest({})).toBe('grok')
})
test('bedrock takes precedence over gemini', () => {
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
process.env.CLAUDE_CODE_USE_GEMINI = '1'
expect(getAPIProviderTest({})).toBe('bedrock')
})
test('bedrock takes precedence over vertex', () => {
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
process.env.CLAUDE_CODE_USE_VERTEX = '1'
expect(getAPIProviderTest({})).toBe('bedrock')
})
test('bedrock wins when all three env vars are set', () => {
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
process.env.CLAUDE_CODE_USE_VERTEX = '1'
process.env.CLAUDE_CODE_USE_FOUNDRY = '1'
expect(getAPIProviderTest({})).toBe('bedrock')
})
test('"true" is truthy', () => {
process.env.CLAUDE_CODE_USE_BEDROCK = 'true'
expect(getAPIProviderTest({})).toBe('bedrock')
})
test('"0" is not truthy', () => {
process.env.CLAUDE_CODE_USE_BEDROCK = '0'
expect(getAPIProviderTest({})).toBe('firstParty')
})
test('empty string is not truthy', () => {
process.env.CLAUDE_CODE_USE_BEDROCK = ''
expect(getAPIProviderTest({})).toBe('firstParty')
})
})
describe('isFirstPartyAnthropicBaseUrl', () => {
const originalBaseUrl = process.env.ANTHROPIC_BASE_URL
const originalUserType = process.env.USER_TYPE
afterEach(() => {
if (originalBaseUrl !== undefined) {
process.env.ANTHROPIC_BASE_URL = originalBaseUrl
} else {
delete process.env.ANTHROPIC_BASE_URL
}
if (originalUserType !== undefined) {
process.env.USER_TYPE = originalUserType
} else {
delete process.env.USER_TYPE
}
})
test('returns true when ANTHROPIC_BASE_URL is not set', () => {
delete process.env.ANTHROPIC_BASE_URL
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
})
test('returns true for api.anthropic.com', () => {
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com'
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
})
test('returns false for custom URL', () => {
process.env.ANTHROPIC_BASE_URL = 'https://my-proxy.com'
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(false)
})
test('returns false for invalid URL', () => {
process.env.ANTHROPIC_BASE_URL = 'not-a-url'
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(false)
})
test('returns true for staging URL when USER_TYPE is ant', () => {
process.env.ANTHROPIC_BASE_URL = 'https://api-staging.anthropic.com'
process.env.USER_TYPE = 'ant'
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
})
test('returns true for URL with path', () => {
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com/v1'
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
})
test('returns true for trailing slash', () => {
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com/'
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
})
test('returns false for subdomain attack', () => {
process.env.ANTHROPIC_BASE_URL = 'https://evil-api.anthropic.com'
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(false)
})
})