mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 20:17:03 +00:00
121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
import { expect, it } from 'bun:test';
|
|
import express from 'express';
|
|
import type { Server } from 'node:http';
|
|
import type { QuotaResult } from '../../../src/cliproxy/quota/quota-fetcher';
|
|
import type { ClaudeQuotaResult } from '../../../src/cliproxy/quota/quota-types';
|
|
import {
|
|
clearQuotaCache,
|
|
getCachedQuota,
|
|
} from '../../../src/cliproxy/quota/quota-response-cache';
|
|
import {
|
|
createBarPoolAccountQuotaFetcher,
|
|
getCachedBarPoolAccountQuota,
|
|
invalidateCachedBarPoolAccountQuota,
|
|
setCachedBarPoolAccountQuota,
|
|
} from '../../../src/web-server/routes/bar-pool-account-quota-adapter';
|
|
import {
|
|
createBarRouter,
|
|
resetForceFreshDebounce,
|
|
} from '../../../src/web-server/routes/bar-routes';
|
|
|
|
it('stores normalized Claude quota in the existing Bar cache', async () => {
|
|
clearQuotaCache();
|
|
let fetchCount = 0;
|
|
const fetchQuota = createBarPoolAccountQuotaFetcher({
|
|
fetchLegacyAccountQuota: async () => {
|
|
throw new Error('Legacy fetcher should not be called');
|
|
},
|
|
fetchClaudeQuota: async () => {
|
|
fetchCount += 1;
|
|
return {
|
|
success: true,
|
|
windows: [],
|
|
coreUsage: {
|
|
fiveHour: {
|
|
rateLimitType: 'five_hour',
|
|
label: '5h usage limit',
|
|
remainingPercent: 72,
|
|
resetAt: '2026-07-18T16:00:00.000Z',
|
|
status: 'allowed',
|
|
},
|
|
weekly: {
|
|
rateLimitType: 'seven_day',
|
|
label: 'Weekly usage limit',
|
|
remainingPercent: 45,
|
|
resetAt: '2026-07-21T12:00:00.000Z',
|
|
status: 'allowed_warning',
|
|
},
|
|
},
|
|
lastUpdated: 2,
|
|
accountId: 'claude@example.com',
|
|
};
|
|
},
|
|
fetchCodexQuota: async () => {
|
|
throw new Error('Codex fetcher should not be called');
|
|
},
|
|
});
|
|
const app = express();
|
|
app.use(
|
|
'/api/bar',
|
|
createBarRouter({
|
|
getAllAccountsSummary: () =>
|
|
({
|
|
claude: [
|
|
{
|
|
id: 'claude@example.com',
|
|
provider: 'claude',
|
|
nickname: 'Claude pool',
|
|
paused: false,
|
|
isDefault: true,
|
|
tokenFile: 'claude-claude_example_com.json',
|
|
createdAt: '2026-07-18T00:00:00.000Z',
|
|
},
|
|
],
|
|
}) as never,
|
|
getCachedQuota: getCachedBarPoolAccountQuota,
|
|
setCachedQuota: setCachedBarPoolAccountQuota,
|
|
invalidateQuotaCache: invalidateCachedBarPoolAccountQuota,
|
|
fetchAccountQuota: fetchQuota,
|
|
getTodayCostByAccount: () => ({}),
|
|
loadCliproxyDetails: async () => [],
|
|
loadDailyUsage: async () => [],
|
|
loadHourlyUsage: async () => [],
|
|
})
|
|
);
|
|
|
|
resetForceFreshDebounce();
|
|
const server = await new Promise<Server>((resolve, reject) => {
|
|
const instance = app.listen(0, '127.0.0.1');
|
|
instance.once('error', reject);
|
|
instance.once('listening', () => resolve(instance));
|
|
});
|
|
|
|
try {
|
|
const address = server.address();
|
|
if (!address || typeof address === 'string') throw new Error('No server address');
|
|
const url = `http://127.0.0.1:${address.port}/api/bar/summary`;
|
|
const first = (await (await fetch(url)).json()) as Array<Record<string, unknown>>;
|
|
const second = (await (await fetch(url)).json()) as Array<Record<string, unknown>>;
|
|
|
|
expect(fetchCount).toBe(1);
|
|
expect(first[0]).toMatchObject({
|
|
quota_percentage: 45,
|
|
next_reset: '2026-07-18T16:00:00.000Z',
|
|
quotaStatus: 'ok',
|
|
cached: false,
|
|
});
|
|
expect(second[0]).toMatchObject({
|
|
quota_percentage: 45,
|
|
next_reset: '2026-07-18T16:00:00.000Z',
|
|
quotaStatus: 'ok',
|
|
cached: true,
|
|
});
|
|
const sharedCache = getCachedQuota<ClaudeQuotaResult>('claude', 'claude@example.com');
|
|
expect(sharedCache?.windows).toEqual([]);
|
|
expect((sharedCache as ClaudeQuotaResult & Partial<QuotaResult>)?.models).toBeUndefined();
|
|
} finally {
|
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
clearQuotaCache();
|
|
}
|
|
});
|