Files
wei-shaw--claude-relay-service/tests/apiKeyServiceOpenAIResponsesConfig.test.js
T
2026-07-13 12:48:39 +08:00

241 lines
7.3 KiB
JavaScript

jest.mock(
'../config/config',
() => ({
security: {
apiKeyPrefix: 'cr_'
}
}),
{ virtual: true }
)
jest.mock('../src/models/redis', () => ({
setApiKey: jest.fn(),
getApiKey: jest.fn(),
incrementTokenUsage: jest.fn(),
incrementDailyCost: jest.fn(),
incrementAccountUsage: jest.fn(),
addUsageRecord: jest.fn()
}))
jest.mock('../src/services/costRankService', () => ({
addKeyToIndexes: jest.fn()
}))
jest.mock('../src/services/apiKeyIndexService', () => ({
addToIndex: jest.fn(),
updateIndex: jest.fn()
}))
jest.mock('../src/utils/logger', () => ({
success: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
database: jest.fn(),
debug: jest.fn()
}))
jest.mock('../src/services/serviceRatesService', () => ({
getService: jest.fn(),
getServiceRate: jest.fn()
}))
jest.mock('../src/services/requestDetailService', () => ({
captureRequestDetail: jest.fn()
}))
jest.mock('../src/services/billingEventPublisher', () => ({
publishBillingEvent: jest.fn()
}))
jest.mock('../src/utils/costCalculator', () => ({
calculateCost: jest.fn()
}))
jest.mock('../src/utils/modelHelper', () => ({
isClaudeFamilyModel: jest.fn(() => false)
}))
jest.mock('../src/utils/requestDetailHelper', () => ({
finalizeRequestDetailMeta: jest.fn((value) => value)
}))
const redis = require('../src/models/redis')
const serviceRatesService = require('../src/services/serviceRatesService')
const requestDetailService = require('../src/services/requestDetailService')
const billingEventPublisher = require('../src/services/billingEventPublisher')
const CostCalculator = require('../src/utils/costCalculator')
const apiKeyService = require('../src/services/apiKeyService')
describe('apiKeyService openai responses config', () => {
beforeEach(() => {
jest.clearAllMocks()
redis.getApiKey.mockResolvedValue({
id: 'key-1',
name: 'Key',
serviceRates: '{}'
})
redis.setApiKey.mockResolvedValue()
redis.incrementTokenUsage.mockResolvedValue()
redis.incrementDailyCost.mockResolvedValue()
redis.incrementAccountUsage.mockResolvedValue()
redis.addUsageRecord.mockResolvedValue()
serviceRatesService.getService.mockReturnValue('claude')
serviceRatesService.getServiceRate.mockResolvedValue(1)
requestDetailService.captureRequestDetail.mockResolvedValue({ captured: true })
billingEventPublisher.publishBillingEvent.mockResolvedValue()
})
test('generateApiKey stores default toggle values', async () => {
redis.setApiKey.mockResolvedValue()
const result = await apiKeyService.generateApiKey({ name: 'Test Key' })
const [, storedKeyData] = redis.setApiKey.mock.calls[0]
expect(storedKeyData.enableOpenAIResponsesCodexAdaptation).toBe('true')
expect(storedKeyData.enableOpenAIResponsesPayloadRules).toBe('false')
expect(storedKeyData.openaiResponsesPayloadRules).toBe('[]')
expect(result.enableOpenAIResponsesCodexAdaptation).toBe(true)
expect(result.enableOpenAIResponsesPayloadRules).toBe(false)
expect(result.openaiResponsesPayloadRules).toEqual([])
})
test('updateApiKey serializes toggle and payload rule fields', async () => {
redis.getApiKey.mockResolvedValue({
id: 'key-1',
apiKey: 'hashed-key',
name: 'Old Key',
isActive: 'true',
tags: '[]'
})
redis.setApiKey.mockResolvedValue()
await apiKeyService.updateApiKey('key-1', {
enableOpenAIResponsesCodexAdaptation: false,
enableOpenAIResponsesPayloadRules: true,
openaiResponsesPayloadRules: [{ path: 'model', valueType: 'string', value: 'gpt-5' }]
})
const [, storedKeyData] = redis.setApiKey.mock.calls[0]
expect(storedKeyData.enableOpenAIResponsesCodexAdaptation).toBe('false')
expect(storedKeyData.enableOpenAIResponsesPayloadRules).toBe('true')
expect(storedKeyData.openaiResponsesPayloadRules).toBe(
JSON.stringify([{ path: 'model', valueType: 'string', value: 'gpt-5' }])
)
})
test('getApiKeyById returns parsed toggle and rule values', async () => {
redis.getApiKey.mockResolvedValue({
id: 'key-1',
name: 'Key',
apiKey: 'hashed-key',
tokenLimit: '0',
isActive: 'true',
createdAt: '2025-01-01T00:00:00.000Z',
lastUsedAt: '',
expiresAt: '',
userId: '',
userUsername: '',
createdBy: 'admin',
permissions: '[]',
dailyCostLimit: '0',
totalCostLimit: '0',
claudeAccountId: '',
claudeConsoleAccountId: '',
geminiAccountId: '',
openaiAccountId: '',
bedrockAccountId: '',
droidAccountId: '',
azureOpenaiAccountId: '',
ccrAccountId: '',
enableOpenAIResponsesCodexAdaptation: 'false',
enableOpenAIResponsesPayloadRules: 'true',
openaiResponsesPayloadRules: JSON.stringify([
{ path: 'model', valueType: 'string', value: 'gpt-5' }
])
})
const result = await apiKeyService.getApiKeyById('key-1')
expect(result.enableOpenAIResponsesCodexAdaptation).toBe(false)
expect(result.enableOpenAIResponsesPayloadRules).toBe(true)
expect(result.openaiResponsesPayloadRules).toEqual([
{ path: 'model', valueType: 'string', value: 'gpt-5' }
])
})
test('recordUsageWithDetails uses CostCalculator unknown fallback for missing model pricing', async () => {
CostCalculator.calculateCost.mockReturnValue({
costs: {
input: 0.051618,
output: 0.000765,
cacheCreate: 0,
cacheWrite: 0,
cacheRead: 0.0006144,
total: 0.0529974
},
debug: {
usedFallbackPricing: true,
pricingSource: 'unknown-fallback',
isLongContextRequest: false
},
usingDynamicPricing: false
})
const result = await apiKeyService.recordUsageWithDetails(
'key-1',
{
input_tokens: 17206,
output_tokens: 51,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 2048
},
'mimo-v2.5-pro',
'acct-1',
'claude-console',
{
requestId: 'req-1',
endpoint: '/api/v1/messages',
method: 'POST',
statusCode: 200
}
)
expect(CostCalculator.calculateCost).toHaveBeenCalledWith(
{
input_tokens: 17206,
output_tokens: 51,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 2048
},
'mimo-v2.5-pro'
)
expect(result.realCost).toBeCloseTo(0.0529974, 10)
expect(result.ratedCost).toBeCloseTo(0.0529974, 10)
expect(redis.incrementDailyCost.mock.calls[0][0]).toBe('key-1')
expect(redis.incrementDailyCost.mock.calls[0][1]).toBeCloseTo(0.0529974, 10)
expect(redis.incrementDailyCost.mock.calls[0][2]).toBeCloseTo(0.0529974, 10)
expect(redis.addUsageRecord).toHaveBeenCalledWith(
'key-1',
expect.objectContaining({
model: 'mimo-v2.5-pro',
cost: 0.052997,
realCost: 0.052997,
usedFallbackPricing: true,
pricingSource: 'unknown-fallback',
costBreakdown: expect.objectContaining({
input: 0.051618,
output: 0.000765,
cacheRead: 0.0006144,
total: 0.0529974
})
})
)
expect(requestDetailService.captureRequestDetail).toHaveBeenCalledWith(
expect.objectContaining({
requestId: 'req-1',
model: 'mimo-v2.5-pro',
cost: 0.052997,
realCost: 0.052997,
usedFallbackPricing: true,
pricingSource: 'unknown-fallback'
})
)
})
})