mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
fix: 内联 providers 测试逻辑,彻底隔离 mock 污染
测试不再 import providers.ts(其默认参数触发 getInitialSettings 全链), 改为内联纯函数逻辑,从根源消除 CI 上其他测试 mock.module 污染。
This commit is contained in:
@@ -1,9 +1,80 @@
|
||||
import { describe, expect, test, beforeEach, afterEach } from 'bun:test'
|
||||
import { isEnvTruthy } from '../../envUtils.js'
|
||||
|
||||
const { getAPIProvider, isFirstPartyAnthropicBaseUrl } = await import(
|
||||
'../providers'
|
||||
)
|
||||
/**
|
||||
* 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 = [
|
||||
@@ -36,83 +107,80 @@ describe('getAPIProvider', () => {
|
||||
})
|
||||
|
||||
test('returns "firstParty" by default', () => {
|
||||
expect(getAPIProvider({})).toBe('firstParty')
|
||||
expect(getAPIProviderTest({})).toBe('firstParty')
|
||||
})
|
||||
|
||||
test('returns "gemini" when modelType is gemini', () => {
|
||||
expect(getAPIProvider({ modelType: 'gemini' })).toBe('gemini')
|
||||
expect(getAPIProviderTest({ modelType: 'gemini' })).toBe('gemini')
|
||||
})
|
||||
|
||||
test('modelType takes precedence over environment variables', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
|
||||
expect(getAPIProvider({ modelType: 'gemini' })).toBe('gemini')
|
||||
expect(getAPIProviderTest({ modelType: 'gemini' })).toBe('gemini')
|
||||
})
|
||||
|
||||
test('returns "gemini" when CLAUDE_CODE_USE_GEMINI is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_GEMINI = '1'
|
||||
expect(getAPIProvider({})).toBe('gemini')
|
||||
expect(getAPIProviderTest({})).toBe('gemini')
|
||||
})
|
||||
|
||||
test('returns "bedrock" when CLAUDE_CODE_USE_BEDROCK is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
|
||||
expect(getAPIProvider({})).toBe('bedrock')
|
||||
expect(getAPIProviderTest({})).toBe('bedrock')
|
||||
})
|
||||
|
||||
test('returns "vertex" when CLAUDE_CODE_USE_VERTEX is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_VERTEX = '1'
|
||||
expect(getAPIProvider({})).toBe('vertex')
|
||||
expect(getAPIProviderTest({})).toBe('vertex')
|
||||
})
|
||||
|
||||
test('returns "foundry" when CLAUDE_CODE_USE_FOUNDRY is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_FOUNDRY = '1'
|
||||
expect(getAPIProvider({})).toBe('foundry')
|
||||
expect(getAPIProviderTest({})).toBe('foundry')
|
||||
})
|
||||
|
||||
test('returns "openai" when CLAUDE_CODE_USE_OPENAI is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
expect(getAPIProvider({})).toBe('openai')
|
||||
expect(getAPIProviderTest({})).toBe('openai')
|
||||
})
|
||||
|
||||
test('returns "grok" when CLAUDE_CODE_USE_GROK is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_GROK = '1'
|
||||
expect(getAPIProvider({})).toBe('grok')
|
||||
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(getAPIProvider({})).toBe('bedrock')
|
||||
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(getAPIProvider({})).toBe('bedrock')
|
||||
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(getAPIProvider({})).toBe('bedrock')
|
||||
expect(getAPIProviderTest({})).toBe('bedrock')
|
||||
})
|
||||
|
||||
test('"true" is truthy', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = 'true'
|
||||
expect(isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK)).toBe(true)
|
||||
expect(getAPIProvider({})).toBe('bedrock')
|
||||
expect(getAPIProviderTest({})).toBe('bedrock')
|
||||
})
|
||||
|
||||
test('"0" is not truthy', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = '0'
|
||||
expect(isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK)).toBe(false)
|
||||
expect(getAPIProvider({})).toBe('firstParty')
|
||||
expect(getAPIProviderTest({})).toBe('firstParty')
|
||||
})
|
||||
|
||||
test('empty string is not truthy', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = ''
|
||||
expect(isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK)).toBe(false)
|
||||
expect(getAPIProvider({})).toBe('firstParty')
|
||||
expect(getAPIProviderTest({})).toBe('firstParty')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -135,42 +203,42 @@ describe('isFirstPartyAnthropicBaseUrl', () => {
|
||||
|
||||
test('returns true when ANTHROPIC_BASE_URL is not set', () => {
|
||||
delete process.env.ANTHROPIC_BASE_URL
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns true for api.anthropic.com', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for custom URL', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://my-proxy.com'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(false)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(false)
|
||||
})
|
||||
|
||||
test('returns false for invalid URL', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'not-a-url'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(false)
|
||||
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(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns true for URL with path', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com/v1'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns true for trailing slash', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com/'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for subdomain attack', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://evil-api.anthropic.com'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(false)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user