Files

60 lines
2.2 KiB
TypeScript
Raw Permalink Normal View History

2026-09-02 17:30:18 +07:00
import { expect, test } from 'bun:test';
import { costOf, formatUsd, rateFor, usageLine } from '../src/pricing';
test('a known model resolves to a rate', () => {
expect(rateFor('gpt-5')).toEqual({ inputPerMTok: 1.25, outputPerMTok: 10 });
expect(rateFor('claude-sonnet-4-5')).toEqual({ inputPerMTok: 3, outputPerMTok: 15 });
});
test('a dated model id resolves through its prefix', () => {
expect(rateFor('claude-sonnet-4-5-20250929')).toEqual(rateFor('claude-sonnet-4'));
});
test('the longest matching prefix wins so mini does not resolve to the base model', () => {
expect(rateFor('gpt-5-mini')?.inputPerMTok).toBe(0.25);
expect(rateFor('gpt-5')?.inputPerMTok).toBe(1.25);
expect(rateFor('gpt-5-nano')?.inputPerMTok).toBe(0.05);
});
test('an OpenRouter-style prefixed id is stripped before matching', () => {
expect(rateFor('anthropic/claude-sonnet-4-5')).toEqual(rateFor('claude-sonnet-4-5'));
expect(rateFor('openai/gpt-5-mini')).toEqual(rateFor('gpt-5-mini'));
});
test('matching is case-insensitive', () => {
expect(rateFor('GPT-5')).toEqual(rateFor('gpt-5'));
});
test('an unknown model has no rate and no cost', () => {
expect(rateFor('some-local-llama')).toBeUndefined();
expect(costOf('some-local-llama', 1000, 500)).toBeUndefined();
});
test('cost is computed per million tokens', () => {
// 1M in at $1.25 plus 1M out at $10.
expect(costOf('gpt-5', 1_000_000, 1_000_000)).toBeCloseTo(11.25, 6);
expect(costOf('gpt-5', 500_000, 0)).toBeCloseTo(0.625, 6);
expect(costOf('gpt-5', 0, 0)).toBe(0);
});
test('formatUsd keeps sub-cent amounts visible', () => {
expect(formatUsd(0)).toBe('$0.00');
expect(formatUsd(0.0001234)).toBe('$0.0001');
expect(formatUsd(0.005)).toBe('$0.0050');
expect(formatUsd(1.239)).toBe('$1.24');
expect(formatUsd(12)).toBe('$12.00');
});
test('usageLine reports tokens plus dollars for a priced model', () => {
const line = usageLine('gpt-5', 10_000, 2_000);
expect(line).toContain('10000 in / 2000 out tokens');
expect(line).toContain('$0.03');
});
test('usageLine says so plainly when the model is unpriced', () => {
const line = usageLine('qwen-local', 10, 5);
expect(line).toContain('10 in / 5 out tokens');
expect(line).toContain('unpriced');
expect(line).not.toContain('$');
});