128 lines
4.0 KiB
JavaScript
128 lines
4.0 KiB
JavaScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
vi.mock('../../opencode/auth.js', () => ({
|
|
readAuthFile: () => ({ 'cline-pass': { key: 'test-token' } }),
|
|
}));
|
|
|
|
import { fetchQuota } from './cline-pass.js';
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
const mockResponse = (body, init = {}) => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => body,
|
|
...init,
|
|
});
|
|
|
|
// Live-verified response shape of
|
|
// GET https://api.cline.bot/api/v1/users/me/plan/usage-limits
|
|
const documentedPayload = {
|
|
data: {
|
|
limits: [
|
|
{ type: 'five_hour', percentUsed: 43, resetsAt: '2026-09-08T17:00:44.598174595Z' },
|
|
{ type: 'weekly', percentUsed: 17, resetsAt: '2026-09-13T17:00:44.598174595Z' },
|
|
{ type: 'monthly', percentUsed: 8, resetsAt: '2026-10-01T00:00:00Z' },
|
|
],
|
|
},
|
|
success: true,
|
|
};
|
|
|
|
describe('ClinePass quota provider', () => {
|
|
it('maps documented limit kinds to 5h/weekly/monthly windows', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(documentedPayload)));
|
|
|
|
const result = await fetchQuota();
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.providerId).toBe('cline-pass');
|
|
expect(Object.keys(result.usage.windows)).toEqual(['5h', 'weekly', 'monthly']);
|
|
expect(result.usage.windows['5h'].usedPercent).toBe(43);
|
|
expect(result.usage.windows['5h'].remainingPercent).toBe(57);
|
|
expect(result.usage.windows['5h'].windowSeconds).toBe(18_000);
|
|
expect(result.usage.windows['5h'].resetAt).toBe(Date.parse('2026-09-08T17:00:44.598174595Z'));
|
|
expect(result.usage.windows.weekly.usedPercent).toBe(17);
|
|
expect(result.usage.windows.weekly.windowSeconds).toBe(604_800);
|
|
expect(result.usage.windows.monthly.usedPercent).toBe(8);
|
|
expect(result.usage.windows.monthly.windowSeconds).toBeNull();
|
|
});
|
|
|
|
it('ignores unknown limit types', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(
|
|
mockResponse({ data: { limits: [{ type: 'quarterly', percentUsed: 5, resetsAt: '2026-10-01T00:00:00Z' }] } }),
|
|
));
|
|
|
|
const result = await fetchQuota();
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.usage).toBeNull();
|
|
expect(result.error).toBe('No quota data in response');
|
|
});
|
|
|
|
it('parses numeric-string percentUsed', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(
|
|
mockResponse({ data: { limits: [{ type: 'weekly', percentUsed: '51' }] } }),
|
|
));
|
|
|
|
const result = await fetchQuota();
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.usage.windows.weekly.usedPercent).toBe(51);
|
|
});
|
|
|
|
for (const payload of [{ data: { limits: [] } }, { data: {} }, {}]) {
|
|
it(`rejects ${JSON.stringify(payload)} without quota data`, async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(payload)));
|
|
|
|
const result = await fetchQuota();
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.configured).toBe(true);
|
|
expect(result.usage).toBeNull();
|
|
expect(result.error).toBe('No quota data in response');
|
|
});
|
|
}
|
|
|
|
it('maps 401 to session-expired error', 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('Session expired — please re-authenticate with ClinePass');
|
|
});
|
|
|
|
it('surfaces non-401 API errors with status', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 503,
|
|
json: async () => ({}),
|
|
}));
|
|
|
|
const result = await fetchQuota();
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.error).toBe('API error: 503');
|
|
});
|
|
|
|
it('reports invalid-response on JSON parse failure', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => { throw new SyntaxError('Unexpected token'); },
|
|
}));
|
|
|
|
const result = await fetchQuota();
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.error).toBe('Invalid response from provider');
|
|
});
|
|
});
|