Files
ccs/tests/unit/web-server/cliproxy-usage-transformer.test.ts
T

187 lines
5.9 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import type { CliproxyUsageApiResponse } from '../../../src/cliproxy/stats-fetcher';
import {
buildCliproxyUsageHistoryAggregates,
extractCliproxyUsageHistoryDetails,
mergeCliproxyUsageHistoryDetails,
transformCliproxyToDailyUsage,
transformCliproxyToHourlyUsage,
transformCliproxyToMonthlyUsage,
} from '../../../src/web-server/usage/cliproxy-usage-transformer';
const sampleResponse: CliproxyUsageApiResponse = {
usage: {
apis: {
gemini: {
models: {
'gemini-2.5-pro': {
details: [
{
timestamp: '2026-03-01T10:15:00.000Z',
source: 'account-a',
auth_index: 0,
tokens: {
input_tokens: 100,
output_tokens: 50,
reasoning_tokens: 0,
cached_tokens: 20,
total_tokens: 170,
},
failed: false,
},
{
timestamp: '2026-03-01T11:30:00.000Z',
source: 'account-a',
auth_index: 0,
tokens: {
input_tokens: 40,
output_tokens: 10,
reasoning_tokens: 0,
cached_tokens: 5,
total_tokens: 55,
},
failed: true,
},
{
timestamp: '2026-03-01T12:15:00.000Z',
source: 'account-a',
auth_index: 0,
tokens: {
input_tokens: 0,
output_tokens: 0,
reasoning_tokens: 0,
cached_tokens: 0,
total_tokens: 0,
},
failed: true,
},
{
timestamp: '2026-03-01T10:45:00.000Z',
source: 'account-a',
auth_index: 0,
tokens: {
input_tokens: 30,
output_tokens: 20,
reasoning_tokens: 0,
cached_tokens: 10,
total_tokens: 60,
},
failed: false,
},
],
},
},
},
codex: {
models: {
'gpt-4.1': {
details: [
{
timestamp: '2026-03-02T01:00:00.000Z',
source: 'account-b',
auth_index: 1,
tokens: {
input_tokens: 70,
output_tokens: 30,
reasoning_tokens: 0,
cached_tokens: 0,
total_tokens: 100,
},
failed: false,
},
],
},
},
},
},
},
};
describe('cliproxy usage transformer', () => {
it('retains failed requests when they carry usage and skips zero-usage failures', () => {
const flat = extractCliproxyUsageHistoryDetails(sampleResponse);
expect(flat).toHaveLength(4);
expect(
flat.some(
(entry) =>
entry.failed === true &&
entry.inputTokens === 40 &&
entry.outputTokens === 10
)
).toBe(true);
expect(
flat.some(
(entry) =>
entry.failed === true &&
entry.inputTokens === 0 &&
entry.outputTokens === 0
)
).toBe(false);
});
it('deduplicates repeated snapshot details when merging history', () => {
const details = extractCliproxyUsageHistoryDetails(sampleResponse);
const merged = mergeCliproxyUsageHistoryDetails(details, details);
expect(merged).toHaveLength(details.length);
});
it('preserves legitimate duplicate requests when the incoming batch has more occurrences', () => {
const details = extractCliproxyUsageHistoryDetails(sampleResponse);
const repeated = [details[0], { ...details[0] }];
const merged = mergeCliproxyUsageHistoryDetails([details[0]], repeated);
expect(merged).toHaveLength(2);
});
it('rebuilds daily history aggregates from merged detail history', () => {
const details = extractCliproxyUsageHistoryDetails(sampleResponse);
const { daily } = buildCliproxyUsageHistoryAggregates(details);
expect(daily).toHaveLength(2);
expect(daily[0].date).toBe('2026-03-02');
expect(daily[1].date).toBe('2026-03-01');
});
it('transforms daily usage with aggregated model totals', () => {
const daily = transformCliproxyToDailyUsage(sampleResponse);
expect(daily).toHaveLength(2);
expect(daily[0].date).toBe('2026-03-02');
expect(daily[0].source).toBe('cliproxy');
expect(daily[1].date).toBe('2026-03-01');
const marchFirst = daily.find((d) => d.date === '2026-03-01');
expect(marchFirst?.inputTokens).toBe(170);
expect(marchFirst?.outputTokens).toBe(80);
expect(marchFirst?.cacheReadTokens).toBe(35);
expect(marchFirst?.modelsUsed).toContain('gemini-2.5-pro');
});
it('transforms hourly usage with hour buckets', () => {
const hourly = transformCliproxyToHourlyUsage(sampleResponse);
expect(hourly).toHaveLength(3);
expect(hourly[0].hour).toBe('2026-03-02 01:00');
const tenAm = hourly.find((h) => h.hour === '2026-03-01 10:00');
expect(tenAm?.inputTokens).toBe(130);
expect(tenAm?.outputTokens).toBe(70);
const elevenAm = hourly.find((h) => h.hour === '2026-03-01 11:00');
expect(elevenAm?.inputTokens).toBe(40);
expect(elevenAm?.outputTokens).toBe(10);
});
it('transforms monthly usage with cliproxy source', () => {
const monthly = transformCliproxyToMonthlyUsage(sampleResponse);
expect(monthly).toHaveLength(1);
expect(monthly[0].month).toBe('2026-03');
expect(monthly[0].source).toBe('cliproxy');
expect(monthly[0].inputTokens).toBe(240);
expect(monthly[0].outputTokens).toBe(110);
expect(monthly[0].cacheReadTokens).toBe(35);
});
});