109 lines
3.2 KiB
JavaScript
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');
|
|
});
|
|
});
|