codeburn/tests/models-hoist.test.ts
2026-05-11 19:02:28 +03:00

132 lines
4.3 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { calculateCost, getModelCosts, getShortModelName } from '../src/models.js'
// Lock down the post-hoist refactor: every model name a real user has
// emitted in the last year should resolve to the same display name and
// the same costs as before. If this list grows or shrinks, the refactor
// is fine — it's the per-name resolution that must stay stable.
const KNOWN_NAMES = [
'claude-opus-4-7',
'claude-opus-4-6',
'claude-opus-4-5',
'claude-sonnet-4-6',
'claude-sonnet-4-5',
'claude-haiku-4-5',
'claude-3-5-sonnet',
'claude-3-5-haiku',
'claude-opus-4-7-20250101',
'claude-sonnet-4-6-20250929',
'anthropic/claude-opus-4-7',
'anthropic--claude-4.6-opus',
'anthropic--claude-4.6-sonnet',
'claude-4.6-sonnet',
'gpt-5',
'gpt-5-mini',
'gpt-5-nano',
'gpt-5-pro',
'gpt-5.1',
'gpt-5.1-codex',
'gpt-5.1-codex-mini',
'gpt-5.2',
'gpt-5.2-low',
'gpt-5.3-codex',
'gpt-5.4',
'gpt-5.4-mini',
'gpt-4o',
'gpt-4o-mini',
'gpt-4.1',
'gpt-4.1-mini',
'gpt-4.1-nano',
'gemini-2.5-pro',
'gemini-2.5-flash',
'gemini-3.1-pro-preview',
'gemini-3-flash-preview',
'gemini-3.1-pro',
'gemini-3-flash',
'cursor-auto',
'cursor-agent-auto',
'copilot-auto',
'copilot-openai-auto',
'kiro-auto',
'cline-auto',
'qwen-auto',
'kimi-auto',
'kimi-for-coding',
'kimi-k2-thinking-turbo',
'kimi-k2.6',
'o3',
'o4-mini',
'deepseek-coder',
'deepseek-coder-max',
'deepseek-r1',
'MiniMax-M2.7',
'MiniMax-M2.7-highspeed',
]
describe('post-hoist resolution stability', () => {
it('every known model resolves to a non-empty short name', () => {
for (const name of KNOWN_NAMES) {
const short = getShortModelName(name)
expect(short, `short name for ${name}`).toBeTruthy()
expect(typeof short, `short name for ${name}`).toBe('string')
}
})
it('gpt-5-mini does NOT collide with gpt-5 (longest-prefix wins)', () => {
expect(getShortModelName('gpt-5-mini')).toBe('GPT-5 Mini')
expect(getShortModelName('gpt-5')).toBe('GPT-5')
expect(getShortModelName('gpt-5-nano')).toBe('GPT-5 Nano')
expect(getShortModelName('gpt-5-pro')).toBe('GPT-5 Pro')
})
it('gpt-5.1-codex-mini does NOT collapse to gpt-5.1-codex or gpt-5', () => {
expect(getShortModelName('gpt-5.1-codex-mini')).toBe('GPT-5.1 Codex Mini')
expect(getShortModelName('gpt-5.1-codex')).toBe('GPT-5.1 Codex')
expect(getShortModelName('gpt-5.1')).toBe('GPT-5.1')
})
it('claude-haiku-4-5 does NOT collapse to claude-haiku-4 or claude-3-5-haiku', () => {
expect(getShortModelName('claude-haiku-4-5')).toBe('Haiku 4.5')
expect(getShortModelName('claude-3-5-haiku')).toBe('Haiku 3.5')
})
it('kimi managed aliases resolve to priced Kimi models', () => {
expect(getShortModelName('kimi-auto')).toBe('Kimi (auto)')
expect(getShortModelName('kimi-for-coding')).toBe('Kimi K2 Thinking')
expect(getShortModelName('kimi-k2-thinking-turbo')).toBe('Kimi K2 Thinking Turbo')
expect(getShortModelName('kimi-k2.6')).toBe('Kimi K2.6')
expect(getModelCosts('kimi-auto')?.inputCostPerToken).toBeGreaterThan(0)
})
it('getModelCosts returns positive token costs for every known name', () => {
for (const name of KNOWN_NAMES) {
const c = getModelCosts(name)
expect(c, `costs for ${name}`).not.toBeNull()
expect(c!.inputCostPerToken).toBeGreaterThan(0)
expect(c!.outputCostPerToken).toBeGreaterThan(0)
}
})
it('calculateCost is stable for a typical Sonnet 4.6 turn', () => {
// 1k input, 2k output, 50k cache read — common Claude Code shape.
const cost = calculateCost('claude-sonnet-4-6', 1000, 2000, 0, 50_000, 0)
expect(cost).toBeGreaterThan(0)
expect(Number.isFinite(cost)).toBe(true)
})
it('calculateCost clamps NaN/negative inputs to 0', () => {
const c1 = calculateCost('claude-sonnet-4-6', NaN, 1000, 0, 0, 0)
const c2 = calculateCost('claude-sonnet-4-6', 0, 1000, 0, 0, 0)
expect(c1).toBe(c2)
const c3 = calculateCost('claude-sonnet-4-6', -1000, 1000, 0, 0, 0)
expect(c3).toBe(c2)
})
it('repeated calls return the same cost (memoized sort cache is consistent)', () => {
const a = getModelCosts('gpt-5-mini')
const b = getModelCosts('gpt-5-mini')
const c = getModelCosts('gpt-5-mini')
expect(a).toEqual(b)
expect(b).toEqual(c)
})
})