the panel rendered "Not signed in" over an account that was signed in fine. cause: `snapshotClaudeUsage` had the read and the write in ONE try block, so on a machine that had not run `db:push` the upsert threw on a missing `ai_provider_usage`, the catch returned null, and a perfectly good reading went in the bin. the route then merged null into an empty stored list and answered with no accounts at all. which makes the claim in docs/ai-usage-panel.md — "degrades to no tabs rather than a 500" — false as written. the stored half is an ENHANCEMENT: it buys tabs for accounts that cannot be measured, and an enhancement that fails must cost only itself. so the write gets its own try and the reading is returned either way. observed on this machine rather than reasoned about: to_regclass came back null, and the regression test fails against the old shape (verified by reverting it, not by assuming). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
49 lines
1.8 KiB
TypeScript
49 lines
1.8 KiB
TypeScript
import { describe, expect, test, mock } from 'bun:test';
|
|
|
|
/**
|
|
* A store failure must not lose the reading.
|
|
*
|
|
* These were one `try` block until 2026-08-18, so an install that had not run `db:push` had no
|
|
* `ai_provider_usage` table, the upsert threw, and the panel rendered "Not signed in" over an account that
|
|
* was signed in perfectly well. Observed on this machine, not reasoned about.
|
|
*/
|
|
const READING = {
|
|
account: { email: 'owner@example.com', plan: 'Max 20x' },
|
|
limits: [{ label: 'Session', percent: 21, resetsAt: '2026-08-18T08:00:00Z' }],
|
|
days: [{ date: '2026-08-18', tokens: 100 }],
|
|
models: [{ name: 'Opus 5', total: 100, input: 1, output: 1, cacheRead: 1, cacheWrite: 1 }],
|
|
fetchedAt: '2026-08-18T05:00:00.000Z',
|
|
};
|
|
|
|
mock.module('./claude-usage', () => ({
|
|
readClaudeUsage: async () => READING,
|
|
readAccountEmail: async () => READING.account.email,
|
|
}));
|
|
|
|
mock.module('officerdb', () => ({
|
|
recordProviderUsage: async () => {
|
|
throw new Error('relation "ai_provider_usage" does not exist');
|
|
},
|
|
listProviderUsage: async () => {
|
|
throw new Error('relation "ai_provider_usage" does not exist');
|
|
},
|
|
}));
|
|
|
|
const { snapshotClaudeUsage, listStoredUsage } = await import('./usage-store');
|
|
|
|
describe('a missing ai_provider_usage table', () => {
|
|
test('does not stop the signed-in account from being reported', () => {
|
|
return snapshotClaudeUsage(1).then((account) => {
|
|
expect(account).not.toBeNull();
|
|
expect(account!.email).toBe('owner@example.com');
|
|
expect(account!.plan).toBe('Max 20x');
|
|
expect(account!.limits).toHaveLength(1);
|
|
expect(account!.current).toBe(true);
|
|
});
|
|
});
|
|
|
|
test('degrades the stored list to empty rather than throwing', () => {
|
|
return listStoredUsage(1, 'owner@example.com').then((rows) => expect(rows).toEqual([]));
|
|
});
|
|
});
|