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

127 lines
4.7 KiB
JavaScript
Raw Normal View History

import { describe, expect, it } from 'vitest';
import { fetchQuota, isConfigured } from './hyper.js';
const readAuth = () => ({ hyper: { key: 'test-token' } });
// https://hyper.charm.land/docs/api/credits.html documents the balance payload.
// https://hyper.charm.land/faq defines one Hypercredit as $0.05.
describe('Charm Hyper quota provider', () => {
it.each([
[100, '100', '$5.00'],
['50', '50', '$2.50'],
[25.5, '25.50', '$1.28'],
[0, '0', '$0.00'],
['0', '0', '$0.00'],
])('formats balance %s without an untranslated unit', async (balance, credits, dollars) => {
const result = await fetchQuota({
readAuth,
fetchImpl: async () => Response.json({ balance }),
});
expect(result.ok).toBe(true);
expect(result.providerId).toBe('hyper');
expect(result.configured).toBe(true);
expect(result.usage.windows.credits.valueLabel).toBe(credits);
expect(result.usage.windows.credits_balance.valueLabel).toBe(dollars);
for (const window of Object.values(result.usage.windows)) {
expect(window.usedPercent).toBeNull();
expect(window.remainingPercent).toBeNull();
expect(window.windowSeconds).toBeNull();
expect(window.resetAt).toBeNull();
expect(window.resetAfterSeconds).toBeNull();
}
});
it.each([
{}, null, [], { balance: '' }, { balance: ' \t ' }, { balance: 'NaN' },
{ balance: 'Infinity' }, { balance: null }, { balance: true }, { balance: [] },
{ balance: {} },
])('rejects invalid payload %j instead of showing zero', async (payload) => {
const result = await fetchQuota({ readAuth, fetchImpl: async () => Response.json(payload) });
expect(result.ok).toBe(false);
expect(result.configured).toBe(true);
expect(result.error).toBe('No quota data in response');
expect(result.usage).toBeNull();
});
it.each([
{ hyper: { key: 'test-token' } },
{ hyper: { token: 'test-token' } },
{ hyper: 'test-token' },
{ hyper: { key: ' ', token: 'test-token' } },
{ hyper: { key: 42, token: 'test-token' } },
])('uses a validated credential for the documented request', async (auth) => {
expect(isConfigured(auth)).toBe(true);
let requests = 0;
const result = await fetchQuota({
readAuth: () => auth,
fetchImpl: async (url, options) => {
requests += 1;
expect(url).toBe('https://hyper.charm.land/v1/credits');
expect(options.method).toBe('GET');
expect(new Headers(options.headers).get('Authorization')).toBe('Bearer test-token');
expect(options.signal).toBeInstanceOf(AbortSignal);
return Response.json({ balance: 100 });
},
});
expect(requests).toBe(1);
expect(result.ok).toBe(true);
expect(JSON.stringify(result)).not.toContain('test-token');
});
it.each([{}, { hyper: { key: '' } }, { hyper: { key: ' ' } }, { hyper: { key: 42 } }])(
'does not request usage without a valid credential',
async (auth) => {
expect(isConfigured(auth)).toBe(false);
let requests = 0;
const result = await fetchQuota({
readAuth: () => auth,
fetchImpl: async () => {
requests += 1;
return Response.json({ balance: 100 });
},
});
expect(requests).toBe(0);
expect(result.ok).toBe(false);
expect(result.configured).toBe(false);
expect(result.error).toBe('Not configured');
},
);
it.each([
[401, 'Session expired — please re-authenticate with Charm Hyper'],
[403, 'Session expired — please re-authenticate with Charm Hyper'],
[429, 'API error: 429'],
[500, 'API error: 500'],
])('reports HTTP %s as a failure', async (status, error) => {
const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response(null, { status }) });
expect(result.ok).toBe(false);
expect(result.configured).toBe(true);
expect(result.error).toBe(error);
expect(result.usage).toBeNull();
});
it('reports invalid JSON as a parse failure', async () => {
const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response('{') });
expect(result.error).toBe('Invalid response from provider');
expect(result.ok).toBe(false);
expect(result.configured).toBe(true);
expect(result.usage).toBeNull();
});
it.each([
[new DOMException('Timed out', 'TimeoutError'), 'Request timed out'],
[new Error('Network unavailable'), 'Network unavailable'],
])('reports request failure', async (failure, message) => {
const result = await fetchQuota({ readAuth, fetchImpl: async () => { throw failure; } });
expect(result.error).toBe(message);
expect(result.ok).toBe(false);
expect(result.configured).toBe(true);
expect(result.usage).toBeNull();
});
});