Files
Bohdan Triapitsyn a8f0f99c3d fix(quota): normalize OpenCode Go reset timestamps
Go returns ISO reset dates, but the shared usage contract requires numeric timestamps. Convert dates to milliseconds before building usage windows.

Verified all three windows against the live Go API. Added numeric timestamp and partial-window regressions; 14 focused tests, web type-check, lint, and syntax checks pass.
2026-09-09 23:23:22 +03:00

92 lines
4.0 KiB
JavaScript

import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
const previousDataDirectory = process.env.OPENCHAMBER_DATA_DIR;
const temporaryDataDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-opencode-go-'));
process.env.OPENCHAMBER_DATA_DIR = temporaryDataDirectory;
vi.mock('../../opencode/auth.js', () => ({
readAuthFile: () => ({ 'opencode-go': { key: 'test-key' } }),
}));
import { fetchOpenCodeGoUsage, fetchQuota, parseOpenCodeGoUsage } from './opencode-go.js';
afterEach(() => {
vi.unstubAllGlobals();
});
afterAll(() => {
if (previousDataDirectory === undefined) delete process.env.OPENCHAMBER_DATA_DIR;
else process.env.OPENCHAMBER_DATA_DIR = previousDataDirectory;
fs.rmSync(temporaryDataDirectory, { recursive: true, force: true });
});
describe('OpenCode Go quota provider', () => {
it('parses partial API usage windows', () => {
const windows = parseOpenCodeGoUsage({ usage: { rolling: { percent: 25, resetsAt: '2026-08-12T12:00:00.000Z' }, weekly: { percent: 40, resetsAt: '2026-08-19T12:00:00.000Z' } } });
expect(windows['5h'].usedPercent).toBe(25);
expect(windows['5h'].resetAt).toBe(Date.parse('2026-08-12T12:00:00.000Z'));
expect(windows.weekly.usedPercent).toBe(40);
expect(windows.monthly).toBeUndefined();
});
it('returns numeric reset timestamps for all usage windows', async () => {
const resetsAt = '2026-10-01T00:00:00.000Z';
const windows = await fetchOpenCodeGoUsage('test-key', async () => new Response(JSON.stringify({
usage: {
rolling: { percent: 25, resetsAt },
weekly: { percent: 40, resetsAt },
monthly: { percent: 60, resetsAt },
},
})));
expect(Object.keys(windows)).toEqual(['5h', 'weekly', 'monthly']);
for (const window of Object.values(windows)) {
expect(window.resetAt).toBe(Date.parse(resetsAt));
}
});
it('preserves valid windows when another reset date is invalid', () => {
const windows = parseOpenCodeGoUsage({ usage: {
rolling: { percent: 25, resetsAt: 'invalid' },
weekly: { percent: 40, resetsAt: '2026-08-19T12:00:00.000Z' },
monthly: { percent: 60, resetsAt: null },
} });
expect(Object.keys(windows)).toEqual(['weekly']);
expect(windows.weekly.resetAt).toBe(Date.parse('2026-08-19T12:00:00.000Z'));
});
it('does not expose credentials in authentication errors', async () => {
await expect(fetchOpenCodeGoUsage('secret', async () => new Response('', { status: 403 }))).rejects.toThrow('authentication failed');
});
it('uses the Go usage API with bearer authentication', async () => {
let request;
const usage = await fetchOpenCodeGoUsage('secret', async (url, options) => {
request = { url, options };
return new Response(JSON.stringify({ usage: { rolling: { percent: 25, resetsAt: '2026-08-12T12:00:00.000Z' } } }));
});
expect(request.url).toBe('https://opencode.ai/zen/go/v1/usage');
expect(request.options.headers).toMatchObject({
Accept: 'application/json',
Authorization: 'Bearer secret',
'x-opencode-session': 'openchamber-usage',
});
expect(request.options.headers.Cookie).toBeUndefined();
expect(usage['5h'].usedPercent).toBe(25);
});
it('reads the API key from the OpenCode auth file', async () => {
const legacyPath = path.join(temporaryDataDirectory, 'quota', 'opencode-go.json');
fs.mkdirSync(path.dirname(legacyPath), { recursive: true });
fs.writeFileSync(legacyPath, '{not valid json', { mode: 0o600 });
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ usage: { rolling: { percent: 25, resetsAt: '2026-08-12T12:00:00.000Z' } } })));
vi.stubGlobal('fetch', fetchMock);
const result = await fetchQuota();
expect(result).toMatchObject({ providerId: 'opencode-go', ok: true, configured: true });
expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer test-key');
expect(fs.existsSync(legacyPath)).toBe(false);
});
});