Files
ccs/tests/unit/web-server/bar-analytics.test.ts
T
Tam Nhu Tran 78f9fc7c0b feat(bar): harden summary against provider hangs and add analytics endpoint
Summary aggregator could block indefinitely: it ran the full system health
audit (a synchronous execSync) on every request and had no per-account or
request-level timeout. Now:
- per-account fetch is bounded; whole response is raced against a deadline
- health is derived per-account from each quota result (no blocking audit)
- stale-while-revalidate keeps the last value when a refresh is slow/fails

Adds GET /api/bar/analytics plus a pure, tested aggregator rolling up
today/7d/30d/all-time spend, a 7-day sparkline, and top models.
2026-06-08 09:31:18 -04:00

99 lines
3.4 KiB
TypeScript

import { describe, it, expect } from 'bun:test';
import { computeBarAnalytics } from '../../../src/web-server/usage/bar-analytics';
import type { CliproxyUsageHistoryDetail } from '../../../src/web-server/usage/cliproxy-usage-transformer';
const NOW = new Date('2026-06-08T12:00:00-04:00');
function detail(over: Partial<CliproxyUsageHistoryDetail>): CliproxyUsageHistoryDetail {
return {
model: 'gpt-5.5',
timestamp: NOW.toISOString(),
inputTokens: 100,
outputTokens: 50,
cacheReadTokens: 0,
requestCount: 1,
cost: 1,
failed: false,
...over,
};
}
/** Build an ISO timestamp `n` whole days before NOW (local). */
function daysAgo(n: number): string {
const d = new Date(NOW.getFullYear(), NOW.getMonth(), NOW.getDate() - n, 10, 0, 0);
return d.toISOString();
}
describe('computeBarAnalytics', () => {
it('returns an empty/zeroed payload for no details', () => {
const a = computeBarAnalytics([], NOW);
expect(a.today.cost).toBe(0);
expect(a.allTime.cost).toBe(0);
expect(a.byDay).toHaveLength(7);
expect(a.topModels).toHaveLength(0);
expect(a.topModelsWindow).toBe('all');
});
it('rolls today / 7d / 30d / allTime into the right windows', () => {
const a = computeBarAnalytics(
[
detail({ timestamp: daysAgo(0), cost: 2, requestCount: 1 }), // today
detail({ timestamp: daysAgo(3), cost: 3, requestCount: 2 }), // 7d + 30d
detail({ timestamp: daysAgo(20), cost: 5, requestCount: 1 }), // 30d only
detail({ timestamp: daysAgo(90), cost: 10, requestCount: 4 }), // allTime only
],
NOW
);
expect(a.today.cost).toBe(2);
expect(a.last7d.cost).toBe(5); // 2 + 3
expect(a.last30d.cost).toBe(10); // 2 + 3 + 5
expect(a.allTime.cost).toBe(20); // + 10
expect(a.allTime.requests).toBe(8);
});
it('excludes failed requests from spend', () => {
const a = computeBarAnalytics(
[detail({ cost: 9, failed: true }), detail({ cost: 1, failed: false })],
NOW
);
expect(a.today.cost).toBe(1);
expect(a.allTime.cost).toBe(1);
});
it('zero-fills the 7-day sparkline in chronological order', () => {
const a = computeBarAnalytics([detail({ timestamp: daysAgo(2), cost: 4 })], NOW);
expect(a.byDay).toHaveLength(7);
// oldest first, newest last
expect(a.byDay[0].date < a.byDay[6].date).toBe(true);
const hit = a.byDay.find((d) => d.cost > 0);
expect(hit?.cost).toBe(4);
});
it('ranks top models by spend and labels the window 30d when recent data exists', () => {
const a = computeBarAnalytics(
[
detail({ model: 'gpt-5.4', timestamp: daysAgo(1), cost: 5 }),
detail({ model: 'gpt-5.5', timestamp: daysAgo(1), cost: 8 }),
detail({ model: 'gpt-5.4', timestamp: daysAgo(2), cost: 2 }),
],
NOW
);
expect(a.topModelsWindow).toBe('30d');
expect(a.topModels[0].model).toBe('gpt-5.5'); // 8
expect(a.topModels[1].model).toBe('gpt-5.4'); // 7
});
it('falls back to all-time top models when the last 30 days are idle', () => {
const a = computeBarAnalytics(
[
detail({ model: 'gpt-5.4', timestamp: daysAgo(60), cost: 100 }),
detail({ model: 'gpt-5.5', timestamp: daysAgo(45), cost: 40 }),
],
NOW
);
expect(a.last30d.cost).toBe(0);
expect(a.topModelsWindow).toBe('all');
expect(a.topModels[0].model).toBe('gpt-5.4');
});
});