import { describe, expect, test } from 'bun:test'; import { friendlyModelName, limitsFromPayload } from './claude-usage'; /** * The live payload, 2026-08-18, trimmed to the fields this reads. * * Kept verbatim rather than minimised because the two things that are easy to get wrong are both visible * only in the whole shape: every model-scoped BUCKET is null while `limits[]` carries a real scoped entry, * and the flat buckets and the array report the same windows twice. */ const LIVE = { five_hour: { utilization: 15.0, resets_at: '2026-08-18T08:00:00.434323+00:00' }, seven_day: { utilization: 22.0, resets_at: '2026-08-19T01:00:00.434348+00:00' }, seven_day_oauth_apps: null, seven_day_opus: null, seven_day_sonnet: null, limits: [ { kind: 'session', group: 'session', percent: 15, resets_at: '2026-08-18T08:00:00.434323+00:00', scope: null }, { kind: 'weekly_all', group: 'weekly', percent: 22, resets_at: '2026-08-19T01:00:00.434348+00:00', scope: null }, { kind: 'weekly_scoped', group: 'weekly', percent: 6, resets_at: '2026-08-19T01:00:00.434561+00:00', scope: { model: { id: null, display_name: 'Fable' }, surface: null }, }, ], }; describe('limitsFromPayload', () => { test('reads the live payload the way Claude Code displays it', () => { expect(limitsFromPayload(LIVE)).toEqual([ { label: 'Session', percent: 15, resetsAt: '2026-08-18T08:00:00.434323+00:00' }, { label: 'Weekly', percent: 22, resetsAt: '2026-08-19T01:00:00.434348+00:00' }, { label: 'Fable Weekly', percent: 6, resetsAt: '2026-08-19T01:00:00.434561+00:00' }, ]); }); test('a scoped window survives its flat bucket being null', () => { // `seven_day_opus` and friends stayed behind at null when the scoped entries moved into `limits[]`, so // a reader that trusts the buckets alone drops an allowance the account is actually spending against. const scoped = limitsFromPayload(LIVE).find((l) => l.label === 'Fable Weekly'); expect(scoped?.percent).toBe(6); expect(LIVE.seven_day_opus).toBeNull(); }); test('the unscoped entries in limits[] are not repeated as their own rows', () => { // `session` and `weekly_all` restate the flat buckets. Reading both would print Session and Weekly twice. expect(limitsFromPayload(LIVE).map((l) => l.label)).toHaveLength(3); }); test('the oauth-apps bucket shadows the plain weekly one when it is populated', () => { const limits = limitsFromPayload({ ...LIVE, seven_day_oauth_apps: { utilization: 40, resets_at: null }, }); expect(limits.find((l) => l.label === 'Weekly')?.percent).toBe(40); }); test('a fractional payload is scaled up, and a percent payload is left alone', () => { /* The two conventions are indistinguishable one value at a time — `1.0` is either 1% or 100% — so the scale is decided by the WHOLE payload: any value at or above 1 means it is already percentages. */ const fractions = { five_hour: { utilization: 0.37, resets_at: null }, limits: [] }; expect(limitsFromPayload(fractions)[0]?.percent).toBeCloseTo(37); const percents = { five_hour: { utilization: 1.0, resets_at: null }, seven_day: { utilization: 22 }, limits: [] }; expect(limitsFromPayload(percents)[0]?.percent).toBe(1); }); test('a scoped window is named from its kind, never from the model name', () => { // "Opus 5 (1M context)" contains "1M", and reading a window out of the title turns that into a minute. const limits = limitsFromPayload({ limits: [ { kind: 'weekly_scoped', percent: 12, resets_at: null, scope: { model: { display_name: 'Opus 5 (1M context)' } }, }, ], }); expect(limits).toEqual([{ label: 'Opus 5 (1M context) Weekly', percent: 12, resetsAt: null }]); }); test('an empty or malformed payload produces no limits rather than throwing', () => { expect(limitsFromPayload({})).toEqual([]); expect(limitsFromPayload({ limits: 'not an array', five_hour: 7 })).toEqual([]); }); }); describe('friendlyModelName', () => { test('rejoins the version and drops the date suffix', () => { expect(friendlyModelName('claude-opus-5')).toBe('Opus 5'); expect(friendlyModelName('claude-opus-4-8')).toBe('Opus 4.8'); expect(friendlyModelName('claude-haiku-4-5-20251001')).toBe('Haiku 4.5'); expect(friendlyModelName('claude-fable-5')).toBe('Fable 5'); }); test('handles ids that are not Anthropic ids', () => { expect(friendlyModelName('kimi-for-coding')).toBe('Kimi For Coding'); expect(friendlyModelName('')).toBe('Unknown'); }); });