mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +00:00
187 lines
5.9 KiB
TypeScript
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);
|
|
});
|
|
});
|