159 lines
4.7 KiB
JavaScript
159 lines
4.7 KiB
JavaScript
jest.mock('../src/services/pricingService', () => ({
|
|
calculateCost: jest.fn(),
|
|
getModelPricing: jest.fn()
|
|
}))
|
|
|
|
jest.mock('../src/utils/logger', () => ({
|
|
warn: jest.fn(),
|
|
error: jest.fn(),
|
|
info: jest.fn(),
|
|
debug: jest.fn(),
|
|
success: jest.fn(),
|
|
database: jest.fn(),
|
|
api: jest.fn(),
|
|
security: jest.fn()
|
|
}))
|
|
|
|
describe('CostCalculator', () => {
|
|
let CostCalculator
|
|
let pricingService
|
|
let logger
|
|
|
|
beforeEach(() => {
|
|
jest.resetModules()
|
|
|
|
pricingService = require('../src/services/pricingService')
|
|
logger = require('../src/utils/logger')
|
|
CostCalculator = require('../src/utils/costCalculator')
|
|
|
|
jest.clearAllMocks()
|
|
pricingService.calculateCost.mockReset()
|
|
pricingService.getModelPricing.mockReset()
|
|
})
|
|
|
|
it('uses detailed pricing when pricingService returns a complete result', () => {
|
|
pricingService.calculateCost.mockReturnValue({
|
|
hasPricing: true,
|
|
isLongContextRequest: false,
|
|
inputCost: 0.003,
|
|
outputCost: 0.0075,
|
|
cacheCreateCost: 0.00075,
|
|
cacheReadCost: 0.00003,
|
|
totalCost: 0.01128,
|
|
pricing: {
|
|
input: 0.000003,
|
|
output: 0.000015,
|
|
cacheCreate: 0.00000375,
|
|
cacheRead: 0.0000003
|
|
}
|
|
})
|
|
|
|
const result = CostCalculator.calculateCost(
|
|
{
|
|
input_tokens: 1000,
|
|
output_tokens: 500,
|
|
cache_creation_input_tokens: 200,
|
|
cache_read_input_tokens: 100,
|
|
cache_creation: {
|
|
ephemeral_5m_input_tokens: 200,
|
|
ephemeral_1h_input_tokens: 0
|
|
}
|
|
},
|
|
'claude-sonnet-4-20250514'
|
|
)
|
|
|
|
expect(result.usingDynamicPricing).toBe(true)
|
|
expect(result.pricing.input).toBe(3)
|
|
expect(result.pricing.cacheWrite).toBe(3.75)
|
|
expect(result.costs.total).toBeCloseTo(0.01128, 10)
|
|
expect(result.debug.usedFallbackPricing).toBe(false)
|
|
expect(result.debug.pricingSource).toBe('dynamic')
|
|
expect(logger.warn).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('falls back to unknown pricing for detailed-cache requests with missing model pricing', () => {
|
|
pricingService.calculateCost.mockReturnValue({
|
|
hasPricing: false,
|
|
totalCost: 0,
|
|
isLongContextRequest: false
|
|
})
|
|
pricingService.getModelPricing.mockReturnValue(null)
|
|
|
|
const usage = {
|
|
input_tokens: 1000,
|
|
output_tokens: 500,
|
|
cache_creation_input_tokens: 200,
|
|
cache_read_input_tokens: 100,
|
|
cache_creation: {
|
|
ephemeral_5m_input_tokens: 100,
|
|
ephemeral_1h_input_tokens: 100
|
|
}
|
|
}
|
|
|
|
const first = CostCalculator.calculateCost(usage, 'kimi-k2.5')
|
|
const second = CostCalculator.calculateCost(usage, 'kimi-k2.5')
|
|
|
|
expect(first.usingDynamicPricing).toBe(false)
|
|
expect(first.pricing.input).toBe(3)
|
|
expect(first.pricing.cacheWrite).toBe(3.75)
|
|
expect(first.costs.total).toBeCloseTo(0.01128, 10)
|
|
expect(first.debug.usedFallbackPricing).toBe(true)
|
|
expect(first.debug.pricingSource).toBe('unknown-fallback')
|
|
expect(second.costs.total).toBeCloseTo(first.costs.total, 10)
|
|
expect(logger.warn).toHaveBeenCalledTimes(1)
|
|
expect(logger.warn.mock.calls[0][0]).toContain('kimi-k2.5')
|
|
})
|
|
|
|
it('falls back instead of throwing for unknown long-context models', () => {
|
|
pricingService.calculateCost.mockReturnValue({
|
|
hasPricing: false,
|
|
totalCost: 0,
|
|
isLongContextRequest: false
|
|
})
|
|
pricingService.getModelPricing.mockReturnValue(null)
|
|
|
|
const result = CostCalculator.calculateCost(
|
|
{
|
|
input_tokens: 250000,
|
|
output_tokens: 1000,
|
|
cache_creation_input_tokens: 0,
|
|
cache_read_input_tokens: 0
|
|
},
|
|
'mystery-model[1m]'
|
|
)
|
|
|
|
expect(result.usingDynamicPricing).toBe(false)
|
|
expect(result.costs.total).toBeCloseTo(0.765, 10)
|
|
expect(result.debug.usedFallbackPricing).toBe(true)
|
|
expect(result.debug.isLongContextModel).toBe(true)
|
|
expect(result.debug.pricingSource).toBe('unknown-fallback')
|
|
})
|
|
|
|
it('keeps the legacy dynamic-pricing path for regular requests', () => {
|
|
pricingService.getModelPricing.mockReturnValue({
|
|
input_cost_per_token: 0.000002,
|
|
output_cost_per_token: 0.000008,
|
|
cache_creation_input_token_cost: 0.0000025,
|
|
cache_read_input_token_cost: 0.0000002
|
|
})
|
|
|
|
const result = CostCalculator.calculateCost(
|
|
{
|
|
input_tokens: 2000,
|
|
output_tokens: 1000,
|
|
cache_creation_input_tokens: 500,
|
|
cache_read_input_tokens: 250
|
|
},
|
|
'glm-5'
|
|
)
|
|
|
|
expect(pricingService.calculateCost).not.toHaveBeenCalled()
|
|
expect(result.usingDynamicPricing).toBe(true)
|
|
expect(result.pricing.input).toBe(2)
|
|
expect(result.pricing.output).toBe(8)
|
|
expect(result.costs.total).toBeCloseTo(0.0133, 10)
|
|
expect(result.debug.usedFallbackPricing).toBe(false)
|
|
expect(result.debug.pricingSource).toBe('dynamic')
|
|
})
|
|
})
|