import { describe, expect, test } from 'bun:test' describe('deriveUsageBaseUrl', () => { test('derives the usage host from the api host', async () => { const { deriveUsageBaseUrl } = await import('./usage.ts') expect(deriveUsageBaseUrl('https://api.telastaging.com')).toBe('https://usage-api.telastaging.com') expect(deriveUsageBaseUrl('https://api.tela.com')).toBe('https://usage-api.tela.com') }) test('an explicit override wins and loses its trailing slash', async () => { const { deriveUsageBaseUrl } = await import('./usage.ts') expect(deriveUsageBaseUrl('https://api.tela.com', 'https://elsewhere.test/')) .toBe('https://elsewhere.test') }) test('refuses to guess for a localhost environment', async () => { const { deriveUsageBaseUrl } = await import('./usage.ts') expect(() => deriveUsageBaseUrl('http://localhost:3000')).toThrow('TELA_USAGE_API_URL') }) test('an override makes localhost workable', async () => { const { deriveUsageBaseUrl } = await import('./usage.ts') expect(deriveUsageBaseUrl('http://localhost:3000', 'http://localhost:9000')) .toBe('http://localhost:9000') }) }) describe('groupUsageByModel', () => { const events = [ { model: 'gpt-5', cost: 1, effectiveCost: 10, inputAmount: 100, outputAmount: 50 }, { model: 'gpt-5', cost: 2, effectiveCost: 20, inputAmount: 200, outputAmount: 100 }, { model: 'gemini-2.5-flash', cost: 0.5, effectiveCost: 5, inputAmount: 90, outputAmount: 10 }, { cost: 0.1, effectiveCost: 1, inputAmount: 1, outputAmount: 1 }, ] test('groups by model and sorts by cost', async () => { const { groupUsageByModel } = await import('./usage.ts') expect(groupUsageByModel(events).map(r => r.model)).toEqual(['gpt-5', 'gemini-2.5-flash', 'unknown']) }) test('keeps provider cost and billed cost apart', async () => { const { groupUsageByModel } = await import('./usage.ts') const [top] = groupUsageByModel(events) expect(top!.cost).toBe(3) expect(top!.effectiveCost).toBe(30) expect(top!.events).toBe(2) }) test('sums input and output tokens', async () => { const { groupUsageByModel } = await import('./usage.ts') expect(groupUsageByModel(events)[0]!.tokens).toBe(450) }) test('buckets events with no model under unknown', async () => { const { groupUsageByModel } = await import('./usage.ts') expect(groupUsageByModel(events).find(r => r.model === 'unknown')?.events).toBe(1) }) test('falls back to provider cost when the billed figure is absent', async () => { const { groupUsageByModel } = await import('./usage.ts') expect(groupUsageByModel([{ model: 'x', cost: 2, inputAmount: 0, outputAmount: 0 }])[0]!.effectiveCost).toBe(2) }) test('returns nothing for no events', async () => { const { groupUsageByModel } = await import('./usage.ts') expect(groupUsageByModel([])).toEqual([]) }) }) describe('formatModelUsage', () => { test('renders a row per model with both costs', async () => { const { formatModelUsage } = await import('./usage.ts') const output = formatModelUsage([ { model: 'gpt-5', cost: 1.0454, effectiveCost: 19.62, events: 31, tokens: 171532 }, ]) expect(output).toContain('gpt-5') expect(output).toContain('$1.0454') expect(output).toContain('$19.6200') }) test('says so when nothing matched rather than printing an empty table', async () => { const { formatModelUsage } = await import('./usage.ts') expect(formatModelUsage([])).toBe('No usage events matched.') }) }) describe('waitForUsageEvents', () => { test('returns as soon as the expected events have replicated', async () => { const { waitForUsageEvents } = await import('./usage.ts') let call = 0 const result = await waitForUsageEvents({}, { minEvents: 2, intervalMs: 1, poll: async () => ({ data: (call++ === 0 ? [] : [{}, {}]) as never }), }) expect(result.complete).toBe(true) expect(result.events).toHaveLength(2) }) test('reports incomplete rather than pretending an empty result is a zero cost', async () => { const { waitForUsageEvents } = await import('./usage.ts') const result = await waitForUsageEvents({}, { minEvents: 1, timeoutMs: 5, intervalMs: 1, poll: async () => ({ data: [] }), }) expect(result.complete).toBe(false) expect(result.events).toEqual([]) }) test('polls at least once even with a zero timeout', async () => { const { waitForUsageEvents } = await import('./usage.ts') let calls = 0 await waitForUsageEvents({}, { timeoutMs: 0, intervalMs: 1, poll: async () => { calls++; return { data: [] } }, }) expect(calls).toBe(1) }) test('a partial result is still incomplete', async () => { const { waitForUsageEvents } = await import('./usage.ts') const result = await waitForUsageEvents({}, { minEvents: 31, timeoutMs: 5, intervalMs: 1, poll: async () => ({ data: [{}, {}] as never }), }) expect(result.complete).toBe(false) expect(result.events).toHaveLength(2) }) test('the documented lag is a minute', async () => { const { USAGE_REPLICATION_LAG_MS } = await import('./usage.ts') expect(USAGE_REPLICATION_LAG_MS).toBe(60_000) }) })