Files
openchamber/packages/web/server/lib/quota/providers/kimi.test.js
T

109 lines
3.2 KiB
JavaScript

import { afterEach, describe, expect, it, vi } from 'vitest';
vi.mock('../../opencode/auth.js', () => ({
readAuthFile: () => ({ 'kimi-for-coding': { key: 'test-token' } }),
}));
import { fetchQuota } from './kimi.js';
afterEach(() => {
vi.unstubAllGlobals();
});
const mockResponse = (body, init = {}) => ({
ok: true,
status: 200,
json: async () => body,
...init,
});
describe('Kimi for Coding quota provider', () => {
it('computes weekly usedPercent from the used field (live API shape, no remaining field)', async () => {
// Captured from GET https://api.kimi.com/coding/v1/usages — the weekly
// `usage` block only ever includes `used`, never `remaining`.
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(
mockResponse({
usage: { limit: '100', used: '100', resetTime: '2026-08-04T06:21:48.514003Z' },
limits: [{
window: { duration: 300, timeUnit: 'TIME_UNIT_MINUTE' },
detail: { limit: '100', remaining: '100', resetTime: '2026-08-03T07:21:48.514003Z' },
}],
}),
));
const result = await fetchQuota();
expect(result.ok).toBe(true);
expect(result.usage.windows.weekly.usedPercent).toBe(100);
expect(result.usage.windows['Rate Limit (300m)'].usedPercent).toBe(0);
});
it('falls back to computing usedPercent from remaining when used is absent', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(
mockResponse({
usage: { limit: '2048', remaining: '512', resetTime: '2026-08-04T06:21:48.514003Z' },
limits: [],
}),
));
const result = await fetchQuota();
expect(result.usage.windows.weekly.usedPercent).toBe(75);
});
it('prefers used over remaining when both fields are present', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(
mockResponse({
usage: { limit: '100', used: '30', remaining: '999', resetTime: null },
limits: [],
}),
));
const result = await fetchQuota();
expect(result.usage.windows.weekly.usedPercent).toBe(30);
});
it('reports null usedPercent when neither used nor remaining is present', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(
mockResponse({
usage: { limit: '100', resetTime: null },
limits: [],
}),
));
const result = await fetchQuota();
expect(result.usage.windows.weekly.usedPercent).toBeNull();
});
it('reports not configured when no credentials are stored', async () => {
vi.doMock('../../opencode/auth.js', () => ({ readAuthFile: () => ({}) }));
vi.resetModules();
const { fetchQuota: fetchQuotaFresh } = await import('./kimi.js');
const result = await fetchQuotaFresh();
expect(result.ok).toBe(false);
expect(result.configured).toBe(false);
expect(result.error).toBe('Not configured');
vi.doUnmock('../../opencode/auth.js');
vi.resetModules();
});
it('surfaces API errors with status', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: false,
status: 401,
json: async () => ({}),
}));
const result = await fetchQuota();
expect(result.ok).toBe(false);
expect(result.configured).toBe(true);
expect(result.error).toBe('API error: 401');
});
});