codeburn/tests/codex-credits.test.ts
iamtoruk 7876c8e9d7 fix(pricing): version the pricing cache and drop codex-credits' dead reasoning param
The pricing cache written to disk had no schema version, so a cache written
by a pre-#1078 binary lacked cacheWriteCostIsExplicit on every entry. Reading
it back resolved the missing key to undefined (falsy), silently reintroducing
the surcharge-fabrication bug #1078 killed for up to CACHE_TTL_MS after an
upgrade. loadCachedPricing now rejects any cache whose version doesn't match
the current schema instead of reading it verbatim.

codexCredits() still accepted an optional reasoningTokens param that added it
to output - the exact double-count #1078 removed from every real caller. The
only caller never passed it; deleted it so it can't be reintroduced by
accident.

parser.ts's activeGeneratedTokens fallback went through billableOutputTokens
in #1078, but codex is the only caller of activeDurationMs/activeGeneratedTokens
and always sets both together, so the fallback branch is unreachable for it.
Reverted to reduce diff noise.
2026-08-21 12:48:55 -07:00

48 lines
2.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { codexCredits, codexCreditRate } from '../src/codex-credits.js'
describe('codexCreditRate', () => {
it('resolves the documented per-model rates', () => {
expect(codexCreditRate('gpt-5.5')).toEqual({ input: 125, cachedInput: 12.5, output: 750 })
expect(codexCreditRate('gpt-5.4')).toEqual({ input: 62.5, cachedInput: 6.25, output: 375 })
expect(codexCreditRate('gpt-5.4-mini')).toEqual({ input: 18.75, cachedInput: 1.875, output: 113 })
})
it('tolerates codex suffix variants and casing', () => {
expect(codexCreditRate('GPT-5.5-codex')?.input).toBe(125)
expect(codexCreditRate('gpt-5.4-codex-mini')?.input).toBe(18.75)
})
it('returns null for models with no known credit rate', () => {
expect(codexCreditRate('gpt-4o')).toBeNull()
expect(codexCreditRate('claude-opus-4-8')).toBeNull()
})
})
describe('codexCredits', () => {
it('charges 1M input tokens at the input rate', () => {
expect(codexCredits('gpt-5.5', { inputTokens: 1_000_000, cachedReadTokens: 0, outputTokens: 0 })).toBe(125)
})
it('charges 1M output tokens at the output rate', () => {
expect(codexCredits('gpt-5.5', { inputTokens: 0, cachedReadTokens: 0, outputTokens: 1_000_000 })).toBe(750)
})
it('charges cache-read tokens at the cheaper cached rate', () => {
expect(codexCredits('gpt-5.5', { inputTokens: 0, cachedReadTokens: 1_000_000, outputTokens: 0 })).toBe(12.5)
})
it('sums a mixed record (gpt-5.4)', () => {
// 2M input (125) + 1M cached (6.25) + 0.5M output (187.5) = 318.75
const credits = codexCredits('gpt-5.4', { inputTokens: 2_000_000, cachedReadTokens: 1_000_000, outputTokens: 500_000 })
expect(credits).toBeCloseTo(125 + 6.25 + 187.5, 6)
})
it('clamps negative / non-finite token counts to 0', () => {
expect(codexCredits('gpt-5.5', { inputTokens: -100, cachedReadTokens: NaN, outputTokens: 1_000_000 })).toBe(750)
})
it('returns null for an unknown model', () => {
expect(codexCredits('gpt-4o', { inputTokens: 1_000_000, cachedReadTokens: 0, outputTokens: 0 })).toBeNull()
})
})