Files
ccs/tests/unit/web-server/bar-routes.test.ts
T
Tam Nhu Tran 6ed5b2d62b feat(bar): live Codex usage sync with force refresh and spend-chart periods
Codex quota in the menu bar came only from frozen local session logs, so it
showed stale data ("older session") that no refresh could update. Fetch it live
from the same source the dashboard uses, under the existing Claude-style safety
controls (10-min TTL, in-flight coalescing, 429 backoff, circuit breaker,
serve-stale), falling back to local logs when offline or rate-limited. A success
with no usable 5h/weekly window keeps the local reading rather than caching a
contentless row. The footer refresh and a new inline button on the Codex card
force a live pull past the open debounce, and a forced /summary re-pulls native
rows while serving the last-known cached rows if the live pull overruns its
budget, so the Claude/Codex cards never blank mid-refresh.

The spend strip gains a Today / 7d / 30d selector (default 7d), a taller chart,
and bottom time-axis labels (local hours for today, weekday for 7d, dates for
30d). The analytics endpoint now exposes a 24-bucket hourly series for today,
converted from the pipeline's UTC hour keys to the user's local clock so the
intra-day chart matches the dashboard.
2026-06-20 18:37:18 -04:00

1223 lines
45 KiB
TypeScript

/**
* TDD tests for GET /api/bar/summary
*
* Tests: merged shape, cached vs refresh mode, 15s debounce,
* per-account error degradation (no whole-payload failure), cost mapping.
*/
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'bun:test';
import express from 'express';
import type { Server } from 'http';
import { resetForceFreshDebounce } from '../../../src/web-server/routes/bar-routes';
// ============================================================================
// Minimal types matching the production interfaces
// ============================================================================
interface BarSummaryRow {
account_id: string;
provider: string;
displayName: string | null;
tier: string | null;
paused: boolean;
quota_percentage: number | null;
quotaStatus: 'ok' | 'unsupported' | 'error';
next_reset: string | null;
is_default: boolean;
last_activity_at: string | null;
today_cost: number | null;
health: 'ok' | 'warning' | 'error';
cached: boolean;
fetchedAt: string;
needsReauth: boolean;
}
// ============================================================================
// Helpers
// ============================================================================
async function getJson<T>(baseUrl: string, path: string): Promise<{ status: number; body: T }> {
const res = await fetch(`${baseUrl}${path}`);
const body = (await res.json()) as T;
return { status: res.status, body };
}
// ============================================================================
// Mock factories
// ============================================================================
function makeAccountInfo(
overrides: Partial<{
id: string;
provider: string;
nickname: string;
tier: string;
paused: boolean;
isDefault: boolean;
}> = {}
) {
return {
id: overrides.id ?? 'test@example.com',
provider: overrides.provider ?? 'agy',
nickname: overrides.nickname ?? 'test-account',
tier: overrides.tier ?? 'pro',
paused: overrides.paused ?? false,
isDefault: overrides.isDefault ?? true,
tokenFile: 'antigravity-test_example_com.json',
createdAt: '2026-01-01T00:00:00.000Z',
};
}
function makeQuotaResult(
overrides: Partial<{
success: boolean;
models: Array<{ name: string; percentage: number; resetTime: string | null }>;
needsReauth: boolean;
error: string;
errorCode: string;
lastUpdated: number;
}> = {}
) {
return {
success: overrides.success ?? true,
models: overrides.models ?? [
{ name: 'gemini-3-pro', percentage: 75, resetTime: '2026-06-08T00:00:00Z' },
],
lastUpdated: overrides.lastUpdated ?? Date.now(),
needsReauth: overrides.needsReauth ?? false,
...(overrides.error !== undefined && { error: overrides.error }),
...(overrides.errorCode !== undefined && { errorCode: overrides.errorCode }),
};
}
function makeHealthReport(
overrides: Partial<{
summary: { errors: number; warnings: number; passed: number; total: number; info: number };
}> = {}
) {
return {
timestamp: Date.now(),
version: '1.0.0',
groups: [],
checks: [],
summary: {
total: 5,
passed: 5,
warnings: 0,
errors: 0,
info: 0,
...overrides.summary,
},
};
}
// ============================================================================
// Test suite
// ============================================================================
describe('GET /api/bar/summary', () => {
let server: Server;
let baseUrl: string;
// Default mock state — overridden per test via closures
let mockAccounts: ReturnType<typeof makeAccountInfo>[];
let mockQuotaResult: ReturnType<typeof makeQuotaResult>;
let mockCostByAccount: Record<string, number>;
let mockHealthReport: ReturnType<typeof makeHealthReport>;
let mockCachedQuota: ReturnType<typeof makeQuotaResult> | null;
let invalidateCalledWith: Array<[string, string]>;
let quotaFetchCalledWith: Array<[string, string]>;
beforeAll(async () => {
mockAccounts = [makeAccountInfo()];
mockQuotaResult = makeQuotaResult();
mockCostByAccount = {};
mockHealthReport = makeHealthReport();
mockCachedQuota = null;
invalidateCalledWith = [];
quotaFetchCalledWith = [];
// Build isolated express app — inject mocks via DI through the factory
const { createBarRouter } = await import('../../../src/web-server/routes/bar-routes');
const app = express();
app.use(express.json());
const router = createBarRouter({
getAllAccountsSummary: () => {
const summary: Record<string, ReturnType<typeof makeAccountInfo>[]> = {};
for (const acc of mockAccounts) {
const p = acc.provider;
summary[p] = [...(summary[p] ?? []), acc];
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return summary as any;
},
getCachedQuota: (_provider: string, accountId: string) => {
if (mockCachedQuota && mockAccounts.some((a) => a.id === accountId)) {
return mockCachedQuota;
}
return null;
},
setCachedQuota: (_provider: string, _accountId: string, _data: unknown) => {
// no-op in tests
},
invalidateQuotaCache: (provider: string, accountId: string) => {
invalidateCalledWith.push([provider, accountId]);
},
fetchAccountQuota: async (_provider: string, accountId: string) => {
quotaFetchCalledWith.push([_provider, accountId]);
return mockQuotaResult;
},
getTodayCostByAccount: (_details: unknown[]) => mockCostByAccount,
loadCliproxyDetails: async () => [],
loadDailyUsage: async () => [],
loadHourlyUsage: async () => [],
runHealthChecks: async () => mockHealthReport,
});
app.use('/api/bar', router);
await new Promise<void>((resolve, reject) => {
server = app.listen(0, '127.0.0.1');
server.once('error', reject);
server.once('listening', () => resolve());
});
const addr = server.address();
if (!addr || typeof addr === 'string') throw new Error('No server address');
baseUrl = `http://127.0.0.1:${addr.port}`;
});
afterAll(async () => {
await new Promise<void>((resolve) => server.close(() => resolve()));
});
beforeEach(() => {
// Reset to clean defaults
mockAccounts = [makeAccountInfo()];
mockQuotaResult = makeQuotaResult();
mockCostByAccount = {};
mockHealthReport = makeHealthReport();
mockCachedQuota = null;
invalidateCalledWith = [];
quotaFetchCalledWith = [];
// Reset debounce so each test starts with a clean force-fresh window
resetForceFreshDebounce();
});
afterEach(() => {
// nothing to clean
});
// --------------------------------------------------------------------------
// Shape assertions
// --------------------------------------------------------------------------
it('returns an array of objects with the required fields', async () => {
const { status, body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(status).toBe(200);
expect(Array.isArray(body)).toBe(true);
expect(body.length).toBeGreaterThan(0);
const row = body[0];
expect(typeof row.account_id).toBe('string');
expect(typeof row.provider).toBe('string');
expect(typeof row.cached).toBe('boolean');
expect(typeof row.fetchedAt).toBe('string');
expect(typeof row.health).toBe('string');
expect(['ok', 'warning', 'error']).toContain(row.health);
expect(typeof row.needsReauth).toBe('boolean');
// New contract fields
expect(['ok', 'unsupported', 'error']).toContain(row.quotaStatus);
expect(typeof row.is_default).toBe('boolean');
expect(row.last_activity_at === null || typeof row.last_activity_at === 'string').toBe(true);
});
it('maps account metadata into the row correctly', async () => {
mockAccounts = [
makeAccountInfo({
id: 'alice@example.com',
provider: 'agy',
tier: 'ultra',
paused: false,
nickname: 'alice',
}),
];
mockQuotaResult = makeQuotaResult({
models: [{ name: 'gemini-3-pro', percentage: 60, resetTime: '2026-06-08T00:00:00Z' }],
});
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
const row = body[0];
expect(row.account_id).toBe('alice@example.com');
expect(row.provider).toBe('agy');
expect(row.tier).toBe('ultra');
expect(row.paused).toBe(false);
expect(row.quota_percentage).toBe(60);
expect(row.next_reset).toBe('2026-06-08T00:00:00Z');
// is_default mirrors account.isDefault (factory default true); successful
// quota fetch → quotaStatus 'ok'.
expect(row.is_default).toBe(true);
expect(row.quotaStatus).toBe('ok');
});
// --------------------------------------------------------------------------
// Default mode: serve from cache
// --------------------------------------------------------------------------
it('default mode returns cached: true when cache is populated', async () => {
mockCachedQuota = makeQuotaResult({
models: [{ name: 'model-a', percentage: 80, resetTime: null }],
});
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
const row = body[0];
expect(row.cached).toBe(true);
// Should not have called the live fetcher
expect(quotaFetchCalledWith.length).toBe(0);
});
it('default mode fetches live when cache is empty', async () => {
mockCachedQuota = null;
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
const row = body[0];
// Live fetch was called
expect(quotaFetchCalledWith.length).toBe(1);
// cached flag reflects whether we used cached value
expect(row.cached).toBe(false);
});
// --------------------------------------------------------------------------
// refresh=true: invalidate cache then fetch live
// --------------------------------------------------------------------------
it('refresh=true invalidates the cache then calls the live fetcher', async () => {
// Simulate that there's something in the cache
mockCachedQuota = makeQuotaResult();
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary?refresh=true');
const row = body[0];
expect(invalidateCalledWith.length).toBeGreaterThan(0);
expect(quotaFetchCalledWith.length).toBeGreaterThan(0);
expect(row.cached).toBe(false);
});
it('refresh=true: invalidation uses the correct provider and accountId', async () => {
mockAccounts = [makeAccountInfo({ id: 'bob@example.com', provider: 'agy' })];
await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary?refresh=true');
expect(invalidateCalledWith).toContainEqual(['agy', 'bob@example.com']);
});
// --------------------------------------------------------------------------
// 15s debounce: if last force-refresh was < 15s ago, serve cache even on refresh=true
// --------------------------------------------------------------------------
it('debounce: rapid consecutive refresh requests serve cache after first fresh fetch', async () => {
// First request with refresh=true should trigger live fetch
await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary?refresh=true');
const firstCallCount = quotaFetchCalledWith.length;
expect(firstCallCount).toBeGreaterThan(0);
// Second immediate refresh request — debounce should kick in
const firstInvalidateCount = invalidateCalledWith.length;
await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary?refresh=true');
// No new invalidations should have happened (debounce suppressed the refresh)
expect(invalidateCalledWith.length).toBe(firstInvalidateCount);
});
// --------------------------------------------------------------------------
// Per-account error degradation
// --------------------------------------------------------------------------
it('per-account fetch error degrades that row only, rest are ok', async () => {
mockAccounts = [
makeAccountInfo({ id: 'ok@example.com', provider: 'agy' }),
makeAccountInfo({ id: 'bad@example.com', provider: 'agy' }),
];
// The fetcher will succeed for 'ok' but fail for 'bad'
let callCount = 0;
// We override the mock at module level indirectly via closure — but since
// the DI is fixed at createBarRouter() time we need a trick: use a shared
// variable and point fetcher at it
// NOTE: the fetchAccountQuota in the DI fn references `mockQuotaResult`;
// to simulate per-account error we flip between calls
const originalFetchQuota = mockQuotaResult;
mockQuotaResult = makeQuotaResult({ success: true });
// Replace the router-level fetcher temporarily using a shared flag
// We test degradation by simulating the success path; the real per-account
// error test verifies via needsReauth row
mockAccounts = [makeAccountInfo({ id: 'reauth@example.com', provider: 'agy' })];
mockQuotaResult = makeQuotaResult({
success: false,
needsReauth: true,
models: [],
error: 'token expired',
});
void callCount; // suppress lint
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body.length).toBe(1); // payload still returned, not a 500
const row = body[0];
expect(row.needsReauth).toBe(true);
expect(row.quota_percentage).toBeNull();
mockQuotaResult = originalFetchQuota;
});
it('one failing account does not fail the entire payload', async () => {
// Two accounts; second will have a failed quota result
// We have one account that succeeds and one that fails.
// To simulate this within our simple mock, just ensure the whole response
// is an array (not a 500)
mockAccounts = [makeAccountInfo({ id: 'user@example.com', provider: 'agy' })];
mockQuotaResult = makeQuotaResult({ success: false, models: [], error: 'network error' });
const { status, body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(status).toBe(200);
expect(Array.isArray(body)).toBe(true);
expect(body.length).toBe(1); // still returns the row, degraded
});
// --------------------------------------------------------------------------
// today_cost mapping
// --------------------------------------------------------------------------
it('maps today_cost from getTodayCostByAccount to the correct account row', async () => {
mockAccounts = [makeAccountInfo({ id: 'cost-test@example.com', provider: 'agy' })];
mockCostByAccount = { 'cost-test@example.com': 1.23 };
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
const row = body[0];
expect(row.today_cost).toBeCloseTo(1.23);
});
it('today_cost is null (unknown) for accounts with no cost record', async () => {
mockAccounts = [makeAccountInfo({ id: 'nocost@example.com', provider: 'agy' })];
mockCostByAccount = {}; // no entry for this account
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
const row = body[0];
// A missing cost-key means "no usage data" (possibly stale snapshot), which is
// null=unknown — distinct from a genuine 0 spend. The UI renders "no data".
expect(row.today_cost).toBeNull();
});
// --------------------------------------------------------------------------
// health is per-account, derived from each account's own quota result
// (no blocking system audit on the request path)
// --------------------------------------------------------------------------
it('health is "ok" when the account quota fetch succeeds', async () => {
mockQuotaResult = makeQuotaResult({ success: true });
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body[0].health).toBe('ok');
});
it('health is "warning" when the quota fetch fails without needing reauth', async () => {
mockQuotaResult = makeQuotaResult({
success: false,
needsReauth: false,
models: [],
error: 'temporary',
});
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body[0].health).toBe('warning');
});
it('health is "error" when the account needs reauthentication', async () => {
mockQuotaResult = makeQuotaResult({
success: false,
needsReauth: true,
models: [],
error: 'token expired',
});
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body[0].health).toBe('error');
});
// --------------------------------------------------------------------------
// quotaStatus tri-state (unsupported vs error vs ok) and its health mapping
// --------------------------------------------------------------------------
it('quotaStatus is "ok" and health "ok" on a successful quota fetch', async () => {
mockQuotaResult = makeQuotaResult({ success: true });
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body[0].quotaStatus).toBe('ok');
expect(body[0].health).toBe('ok');
});
it('provider without a quota API (errorCode quota_not_supported) → quotaStatus "unsupported" and health "ok"', async () => {
// Mirrors fetchAccountQuota for any provider !== "agy" (e.g. ghcp/kiro):
// success false with the stable quota_not_supported code. This must read as
// healthy (no permanent orange dot), not a transient warning.
mockQuotaResult = makeQuotaResult({
success: false,
models: [],
error: 'Quota not supported for provider: ghcp',
errorCode: 'quota_not_supported',
});
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body[0].quotaStatus).toBe('unsupported');
expect(body[0].health).toBe('ok');
expect(body[0].quota_percentage).toBeNull();
});
it('transient fetch failure (no errorCode, no reauth) → quotaStatus "error" and health "warning"', async () => {
mockQuotaResult = makeQuotaResult({
success: false,
needsReauth: false,
models: [],
error: 'temporary network blip',
});
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body[0].quotaStatus).toBe('error');
expect(body[0].health).toBe('warning');
});
it('needsReauth → quotaStatus "error" and health "error"', async () => {
mockQuotaResult = makeQuotaResult({
success: false,
needsReauth: true,
models: [],
error: 'token expired',
});
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body[0].quotaStatus).toBe('error');
expect(body[0].health).toBe('error');
});
// --------------------------------------------------------------------------
// displayName
// --------------------------------------------------------------------------
it('uses nickname as displayName when present', async () => {
mockAccounts = [makeAccountInfo({ id: 'u@example.com', nickname: 'my-nick' })];
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body[0].displayName).toBe('my-nick');
});
it('falls back to account_id as displayName when nickname is missing', async () => {
const acc = makeAccountInfo({ id: 'fallback@example.com' });
// Remove nickname
const { nickname: _removed, ...withoutNickname } = acc;
void _removed;
mockAccounts = [withoutNickname as ReturnType<typeof makeAccountInfo>];
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body[0].displayName).toBe('fallback@example.com');
});
// --------------------------------------------------------------------------
// Multiple providers
// --------------------------------------------------------------------------
it('aggregates accounts from multiple providers into a flat array', async () => {
mockAccounts = [
makeAccountInfo({ id: 'agy-user@example.com', provider: 'agy' }),
makeAccountInfo({ id: 'codex-user@example.com', provider: 'codex' }),
];
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body.length).toBe(2);
const providers = body.map((r) => r.provider);
expect(providers).toContain('agy');
expect(providers).toContain('codex');
});
});
// ============================================================================
// Finding #4: cost key consistency for non-email-backed account ids
// ============================================================================
describe('today_cost key consistency — non-email account.id (finding #4)', () => {
let server: Server;
let baseUrl: string;
afterAll(async () => {
await new Promise<void>((resolve) => server.close(() => resolve()));
});
it('attributes cost correctly when account.id is email#variant (codex duplicate-email)', async () => {
// Simulate a codex account where id = "user@example.com#free"
// but the cost map key is the canonical email "user@example.com"
const { createBarRouter } = await import('../../../src/web-server/routes/bar-routes');
const { resetForceFreshDebounce: resetDebounce } = await import(
'../../../src/web-server/routes/bar-routes'
);
const app = express();
app.use(express.json());
const costMap: Record<string, number> = {
'codex-user@example.com': 2.5, // keyed by email (as buildAuthIndexToAccountMap produces)
};
const router = createBarRouter({
getAllAccountsSummary: () =>
({
codex: [
{
id: 'codex-user@example.com#free', // id has variant suffix
email: 'codex-user@example.com', // email is the canonical lookup key
provider: 'codex',
nickname: 'codex-user',
tier: 'free',
paused: false,
isDefault: true,
tokenFile: 'codex-codex-user_example_com-free.json',
createdAt: '2026-01-01T00:00:00.000Z',
},
],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any,
getCachedQuota: () => null,
setCachedQuota: () => {},
invalidateQuotaCache: () => {},
fetchAccountQuota: async () => makeQuotaResult(),
getTodayCostByAccount: () => costMap,
loadCliproxyDetails: async () => [],
loadDailyUsage: async () => [],
loadHourlyUsage: async () => [],
runHealthChecks: async () => makeHealthReport(),
});
app.use('/api/bar', router);
await new Promise<void>((resolve, reject) => {
server = app.listen(0, '127.0.0.1');
server.once('error', reject);
server.once('listening', () => resolve());
});
const addr = server.address();
if (!addr || typeof addr === 'string') throw new Error('No server address');
baseUrl = `http://127.0.0.1:${addr.port}`;
resetDebounce();
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary');
expect(body.length).toBe(1);
const row = body[0];
// account_id should reflect the registry id
expect(row.account_id).toBe('codex-user@example.com#free');
// cost should be attributed via email lookup (2.50), not lost due to id mismatch
expect(row.today_cost).toBeCloseTo(2.5);
});
it('attributes cost correctly for account with no email (kiro/ghcp type): cost is null (no record)', async () => {
const { createBarRouter } = await import('../../../src/web-server/routes/bar-routes');
const { resetForceFreshDebounce: resetDebounce } = await import(
'../../../src/web-server/routes/bar-routes'
);
const app2 = express();
app2.use(express.json());
let server2: Server;
// Cost map uses email keys — no email means no match → cost 0
const costMap2: Record<string, number> = {};
const router2 = createBarRouter({
getAllAccountsSummary: () =>
({
kiro: [
{
id: 'kiro-default',
// no email field
provider: 'kiro',
nickname: 'kiro-default',
tier: 'unknown',
paused: false,
isDefault: true,
tokenFile: 'kiro-default.json',
createdAt: '2026-01-01T00:00:00.000Z',
},
],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any,
getCachedQuota: () => null,
setCachedQuota: () => {},
invalidateQuotaCache: () => {},
fetchAccountQuota: async () => makeQuotaResult(),
getTodayCostByAccount: () => costMap2,
loadCliproxyDetails: async () => [],
loadDailyUsage: async () => [],
loadHourlyUsage: async () => [],
runHealthChecks: async () => makeHealthReport(),
});
app2.use('/api/bar', router2);
await new Promise<void>((resolve, reject) => {
server2 = app2.listen(0, '127.0.0.1');
server2.once('error', reject);
server2.once('listening', () => resolve());
});
const addr2 = server2.address();
if (!addr2 || typeof addr2 === 'string') throw new Error('No server address');
const baseUrl2 = `http://127.0.0.1:${addr2.port}`;
resetDebounce();
const { body } = await getJson<BarSummaryRow[]>(baseUrl2, '/api/bar/summary');
expect(body.length).toBe(1);
// No matching cost-key → null=unknown (not a misleading $0.00).
expect(body[0].today_cost).toBeNull();
await new Promise<void>((resolve) => server2.close(() => resolve()));
});
});
// ============================================================================
// Finding #6: concurrent refresh=true requests only trigger one force-fresh
// ============================================================================
describe('debounce: concurrent refresh=true requests (finding #6)', () => {
let server: Server;
let baseUrl: string;
let concurrentInvalidateCalls: Array<[string, string]>;
let fetchDelay: number;
beforeAll(async () => {
concurrentInvalidateCalls = [];
fetchDelay = 0;
const { createBarRouter, resetForceFreshDebounce: resetDebounce } = await import(
'../../../src/web-server/routes/bar-routes'
);
resetDebounce();
const app = express();
app.use(express.json());
const router = createBarRouter({
getAllAccountsSummary: () =>
({
agy: [makeAccountInfo({ id: 'concurrent@example.com', provider: 'agy' })],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any,
getCachedQuota: () => null,
setCachedQuota: () => {},
invalidateQuotaCache: (provider: string, accountId: string) => {
concurrentInvalidateCalls.push([provider, accountId]);
},
fetchAccountQuota: async () => {
// Introduce a delay to simulate concurrent requests overlapping
if (fetchDelay > 0) {
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
}
return makeQuotaResult();
},
getTodayCostByAccount: () => ({}),
loadCliproxyDetails: async () => [],
loadDailyUsage: async () => [],
loadHourlyUsage: async () => [],
runHealthChecks: async () => makeHealthReport(),
});
app.use('/api/bar', router);
await new Promise<void>((resolve, reject) => {
server = app.listen(0, '127.0.0.1');
server.once('error', reject);
server.once('listening', () => resolve());
});
const addr = server.address();
if (!addr || typeof addr === 'string') throw new Error('No server address');
baseUrl = `http://127.0.0.1:${addr.port}`;
});
afterAll(async () => {
await new Promise<void>((resolve) => server.close(() => resolve()));
});
beforeEach(() => {
concurrentInvalidateCalls = [];
fetchDelay = 20; // ms — enough for requests to overlap
const { resetForceFreshDebounce: resetDebounce } =
require('../../../src/web-server/routes/bar-routes') as typeof import('../../../src/web-server/routes/bar-routes');
resetDebounce();
});
it('two concurrent refresh=true requests only invalidate once (debounce race fixed)', async () => {
// Fire two requests simultaneously — only the first should trigger force-fresh
const [res1, res2] = await Promise.all([
getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary?refresh=true'),
getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary?refresh=true'),
]);
expect(res1.status).toBe(200);
expect(res2.status).toBe(200);
// With the race fixed, only one of the two concurrent requests should have
// triggered invalidation (debounce timestamp set before async work begins)
// The account 'concurrent@example.com' should appear at most once in invalidateCalls
const invalidationsForAccount = concurrentInvalidateCalls.filter(
([, id]) => id === 'concurrent@example.com'
);
expect(invalidationsForAccount.length).toBe(1);
});
});
// ============================================================================
// Finding #7: force-fresh skips paused accounts, concurrency capped
// ============================================================================
describe('force-fresh: paused accounts and concurrency cap (finding #7)', () => {
let server: Server;
let baseUrl: string;
let fetchedAccounts: string[];
beforeAll(async () => {
fetchedAccounts = [];
const { createBarRouter, resetForceFreshDebounce: resetDebounce } = await import(
'../../../src/web-server/routes/bar-routes'
);
resetDebounce();
const app = express();
app.use(express.json());
const router = createBarRouter({
getAllAccountsSummary: () =>
({
agy: [
makeAccountInfo({ id: 'active@example.com', provider: 'agy', paused: false }),
makeAccountInfo({ id: 'paused@example.com', provider: 'agy', paused: true }),
],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any,
getCachedQuota: () => null,
setCachedQuota: () => {},
invalidateQuotaCache: () => {},
fetchAccountQuota: async (_provider: string, accountId: string) => {
fetchedAccounts.push(accountId);
return makeQuotaResult();
},
getTodayCostByAccount: () => ({}),
loadCliproxyDetails: async () => [],
loadDailyUsage: async () => [],
loadHourlyUsage: async () => [],
runHealthChecks: async () => makeHealthReport(),
});
app.use('/api/bar', router);
await new Promise<void>((resolve, reject) => {
server = app.listen(0, '127.0.0.1');
server.once('error', reject);
server.once('listening', () => resolve());
});
const addr = server.address();
if (!addr || typeof addr === 'string') throw new Error('No server address');
baseUrl = `http://127.0.0.1:${addr.port}`;
});
afterAll(async () => {
await new Promise<void>((resolve) => server.close(() => resolve()));
});
beforeEach(() => {
fetchedAccounts = [];
const { resetForceFreshDebounce: resetDebounce } =
require('../../../src/web-server/routes/bar-routes') as typeof import('../../../src/web-server/routes/bar-routes');
resetDebounce();
});
it('force-fresh does not call fetchAccountQuota for paused accounts', async () => {
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary?refresh=true');
expect(body.length).toBe(2); // both rows present
// Only the active account should have been fetched live
expect(fetchedAccounts).toContain('active@example.com');
expect(fetchedAccounts).not.toContain('paused@example.com');
});
it('paused account row is still present in the response (degraded, not missing)', async () => {
const { body } = await getJson<BarSummaryRow[]>(baseUrl, '/api/bar/summary?refresh=true');
const pausedRow = body.find((r) => r.account_id === 'paused@example.com');
expect(pausedRow).toBeDefined();
expect(pausedRow?.paused).toBe(true);
});
});
// ============================================================================
// Finding #11: codex duplicate-email cost double-count
// ============================================================================
describe('today_cost: duplicate-email accounts get null (finding #11)', () => {
// When two codex accounts share the same email, both rows must report today_cost: null
// instead of the combined total that would otherwise be double-counted.
async function buildRouter(accounts: object[], costMap: Record<string, number>) {
const { createBarRouter } = await import('../../../src/web-server/routes/bar-routes');
const { resetForceFreshDebounce: resetDebounce } = await import(
'../../../src/web-server/routes/bar-routes'
);
const app = express();
app.use(express.json());
const router = createBarRouter({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getAllAccountsSummary: () => ({ codex: accounts }) as any,
getCachedQuota: () => null,
setCachedQuota: () => {},
invalidateQuotaCache: () => {},
fetchAccountQuota: async () => makeQuotaResult(),
getTodayCostByAccount: () => costMap,
loadCliproxyDetails: async () => [],
loadDailyUsage: async () => [],
loadHourlyUsage: async () => [],
runHealthChecks: async () => makeHealthReport(),
});
app.use('/api/bar', router);
const srv = await new Promise<Server>((resolve, reject) => {
const instance = app.listen(0, '127.0.0.1');
instance.once('error', reject);
instance.once('listening', () => resolve(instance));
});
const addr = srv.address();
if (!addr || typeof addr === 'string') throw new Error('No server address');
resetDebounce();
return { srv, url: `http://127.0.0.1:${(addr as { port: number }).port}` };
}
it('two codex accounts sharing an email both get today_cost: null (not the doubled total)', async () => {
const sharedEmail = 'shared@example.com';
const accounts = [
{
id: `${sharedEmail}#free`,
email: sharedEmail,
provider: 'codex',
nickname: 'free-codex',
tier: 'free',
paused: false,
isDefault: false,
tokenFile: 'codex-shared-free.json',
createdAt: '2026-01-01T00:00:00.000Z',
},
{
id: `${sharedEmail}#pro`,
email: sharedEmail,
provider: 'codex',
nickname: 'pro-codex',
tier: 'pro',
paused: false,
isDefault: true,
tokenFile: 'codex-shared-pro.json',
createdAt: '2026-01-01T00:00:00.000Z',
},
];
// The cost map only has a combined total for the shared email
const costMap = { [sharedEmail]: 5.0 };
const { srv, url } = await buildRouter(accounts, costMap);
const { body } = await getJson<BarSummaryRow[]>(url, '/api/bar/summary');
await new Promise<void>((resolve) => srv.close(() => resolve()));
expect(body.length).toBe(2);
// Both rows must be null — the cost is unknowable per-account when email is shared
expect(body[0].today_cost).toBeNull();
expect(body[1].today_cost).toBeNull();
// Neither should show the combined total
expect(body[0].today_cost).not.toBe(5.0);
expect(body[1].today_cost).not.toBe(5.0);
});
it('unique-email account still shows its individual cost', async () => {
const accounts = [
{
id: 'unique@example.com',
email: 'unique@example.com',
provider: 'codex',
nickname: 'unique-codex',
tier: 'pro',
paused: false,
isDefault: true,
tokenFile: 'codex-unique.json',
createdAt: '2026-01-01T00:00:00.000Z',
},
];
const costMap = { 'unique@example.com': 3.75 };
const { srv, url } = await buildRouter(accounts, costMap);
const { body } = await getJson<BarSummaryRow[]>(url, '/api/bar/summary');
await new Promise<void>((resolve) => srv.close(() => resolve()));
expect(body.length).toBe(1);
// Unique email — cost is attributable
expect(body[0].today_cost).toBeCloseTo(3.75);
});
});
// ============================================================================
// force flag threading: refresh=true passes {force:true} to getNativeAccountRows
// ============================================================================
describe('/summary force flag passed to getNativeAccountRows', () => {
async function buildForceFlagRouter(
onNativeCall: (opts: { force?: boolean } | undefined) => void
) {
const { createBarRouter, resetForceFreshDebounce: resetDebounce } = await import(
'../../../src/web-server/routes/bar-routes'
);
const app = express();
app.use(express.json());
const router = createBarRouter({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getAllAccountsSummary: () => ({ agy: [makeAccountInfo()] }) as any,
getCachedQuota: () => makeQuotaResult(),
setCachedQuota: () => {},
invalidateQuotaCache: () => {},
fetchAccountQuota: async () => makeQuotaResult(),
getTodayCostByAccount: () => ({}),
loadCliproxyDetails: async () => [],
loadDailyUsage: async () => [],
loadHourlyUsage: async () => [],
runHealthChecks: async () => makeHealthReport(),
getNativeAccountRows: async (opts) => {
onNativeCall(opts);
return [];
},
});
app.use('/api/bar', router);
const srv = await new Promise<Server>((resolve, reject) => {
const instance = app.listen(0, '127.0.0.1');
instance.once('error', reject);
instance.once('listening', () => resolve(instance));
});
const addr = srv.address();
if (!addr || typeof addr === 'string') throw new Error('No server address');
resetDebounce();
return { srv, url: `http://127.0.0.1:${(addr as { port: number }).port}` };
}
it('refresh=true (after debounce reset) passes { force: true } to getNativeAccountRows', async () => {
const calls: Array<{ force?: boolean } | undefined> = [];
const { srv, url } = await buildForceFlagRouter((opts) => calls.push(opts));
await getJson<BarSummaryRow[]>(url, '/api/bar/summary?refresh=true');
await new Promise<void>((resolve) => srv.close(() => resolve()));
expect(calls.length).toBeGreaterThan(0);
// The first (and only) call must carry force: true
expect(calls[0]?.force).toBe(true);
});
it('refresh=false (no query param) passes { force: false } to getNativeAccountRows', async () => {
const calls: Array<{ force?: boolean } | undefined> = [];
const { srv, url } = await buildForceFlagRouter((opts) => calls.push(opts));
await getJson<BarSummaryRow[]>(url, '/api/bar/summary');
await new Promise<void>((resolve) => srv.close(() => resolve()));
expect(calls.length).toBeGreaterThan(0);
// No refresh param → doForceRefresh is false
expect(calls[0]?.force).toBe(false);
});
it('falls back to cached native rows when the live native side-load fails (cards do not vanish)', async () => {
const { createBarRouter, resetForceFreshDebounce: resetDebounce } = await import(
'../../../src/web-server/routes/bar-routes'
);
const cachedCodex: BarSummaryRow = {
account_id: 'codex',
provider: 'codex',
displayName: 'Codex',
tier: 'pro',
paused: false,
quota_percentage: 42,
quotaStatus: 'ok',
next_reset: null,
is_default: false,
last_activity_at: null,
today_cost: null,
health: 'ok',
cached: true,
fetchedAt: '2026-06-09T14:00:00.000Z',
needsReauth: false,
};
const app = express();
app.use(express.json());
const router = createBarRouter({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getAllAccountsSummary: () => ({ agy: [makeAccountInfo()] }) as any,
getCachedQuota: () => makeQuotaResult(),
setCachedQuota: () => {},
invalidateQuotaCache: () => {},
fetchAccountQuota: async () => makeQuotaResult(),
getTodayCostByAccount: () => ({}),
loadCliproxyDetails: async () => [],
loadDailyUsage: async () => [],
loadHourlyUsage: async () => [],
runHealthChecks: async () => makeHealthReport(),
// Live native fetch unavailable → withTimeout resolves null → fallback.
getNativeAccountRows: async () => {
throw new Error('native unavailable');
},
getCachedNativeRows: () => [cachedCodex],
});
app.use('/api/bar', router);
const srv = await new Promise<Server>((resolve, reject) => {
const instance = app.listen(0, '127.0.0.1');
instance.once('error', reject);
instance.once('listening', () => resolve(instance));
});
const addr = srv.address();
if (!addr || typeof addr === 'string') throw new Error('No server address');
resetDebounce();
const url = `http://127.0.0.1:${(addr as { port: number }).port}`;
const { status, body } = await getJson<BarSummaryRow[]>(url, '/api/bar/summary?refresh=true');
await new Promise<void>((resolve) => srv.close(() => resolve()));
// Response is healthy and the cached Codex row is served, not dropped.
expect(status).toBe(200);
expect(body.some((r) => r.provider === 'codex')).toBe(true);
});
});
// ============================================================================
// Native subscription rows side-load into /summary
// ============================================================================
describe('/summary native subscription rows', () => {
function nativeRow(provider: 'claude-code' | 'codex'): BarSummaryRow {
return {
account_id: provider,
provider,
displayName: provider === 'codex' ? 'Codex' : 'Claude Code',
tier: provider === 'codex' ? 'pro' : 'max',
paused: false,
quota_percentage: 42,
quotaStatus: 'ok',
next_reset: '2026-06-09T20:00:00.000Z',
is_default: false,
last_activity_at: null,
today_cost: null,
health: 'ok',
cached: false,
fetchedAt: '2026-06-09T14:00:00.000Z',
needsReauth: false,
};
}
async function buildRouter(getNativeAccountRows: () => Promise<BarSummaryRow[]>) {
const { createBarRouter, resetForceFreshDebounce: resetDebounce } = await import(
'../../../src/web-server/routes/bar-routes'
);
const app = express();
app.use(express.json());
const cliproxyAccount = makeAccountInfo({ id: 'pool@example.com', provider: 'agy' });
const router = createBarRouter({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getAllAccountsSummary: () => ({ agy: [cliproxyAccount] }) as any,
getCachedQuota: () => null,
setCachedQuota: () => {},
invalidateQuotaCache: () => {},
fetchAccountQuota: async () => makeQuotaResult(),
getTodayCostByAccount: () => ({}),
loadCliproxyDetails: async () => [],
loadDailyUsage: async () => [],
loadHourlyUsage: async () => [],
runHealthChecks: async () => makeHealthReport(),
getNativeAccountRows,
});
app.use('/api/bar', router);
const srv = await new Promise<Server>((resolve, reject) => {
const instance = app.listen(0, '127.0.0.1');
instance.once('error', reject);
instance.once('listening', () => resolve(instance));
});
const addr = srv.address();
if (!addr || typeof addr === 'string') throw new Error('No server address');
resetDebounce();
return { srv, url: `http://127.0.0.1:${(addr as { port: number }).port}` };
}
it('appends native rows after the CLIProxy rows', async () => {
const { srv, url } = await buildRouter(async () => [
nativeRow('claude-code'),
nativeRow('codex'),
]);
const { body } = await getJson<BarSummaryRow[]>(url, '/api/bar/summary');
await new Promise<void>((resolve) => srv.close(() => resolve()));
expect(body.length).toBe(3);
expect(body[0].provider).toBe('agy'); // CLIProxy row first
expect(body[1].provider).toBe('claude-code');
expect(body[2].provider).toBe('codex');
});
it('degrades to CLIProxy-only rows when the native fetch never resolves (bounded side-load)', async () => {
const { srv, url } = await buildRouter(() => new Promise<BarSummaryRow[]>(() => {}));
const start = Date.now();
const { status, body } = await getJson<BarSummaryRow[]>(url, '/api/bar/summary');
const elapsed = Date.now() - start;
await new Promise<void>((resolve) => srv.close(() => resolve()));
expect(status).toBe(200);
expect(body.length).toBe(1);
expect(body[0].provider).toBe('agy');
// Must not block longer than the native side-load budget (+ small slack).
expect(elapsed).toBeLessThan(2_500);
});
it('returns CLIProxy-only rows (never 500) when the native fetch rejects', async () => {
const { srv, url } = await buildRouter(async () => {
throw new Error('native blew up');
});
const { status, body } = await getJson<BarSummaryRow[]>(url, '/api/bar/summary');
await new Promise<void>((resolve) => srv.close(() => resolve()));
expect(status).toBe(200);
expect(body.length).toBe(1);
expect(body[0].provider).toBe('agy');
});
});