fix(quota): validate Hyper credentials and clean up balance labels

Reject invalid credentials while preserving valid token fallback, parse balances with existing boundary helpers, and keep credit values free of untranslated unit text. Inject auth and HTTP dependencies for focused tests in both runtimes.

Validated web quota and registry tests (35 passed), VS Code quota tests (70 passed), both package type checks and lint, extension build, and changed-line anti-slop checks. Reviewed dead-code output. Live Hyper validation was not run because no API key is available.
This commit is contained in:
Bohdan Triapitsyn
2026-09-05 23:26:35 +03:00
parent 5e7c147785
commit b0282b2720
5 changed files with 259 additions and 203 deletions
+121 -71
View File
@@ -26,7 +26,7 @@ const AUTH = JSON.stringify({
((fs as unknown) as { existsSync: () => boolean }).existsSync = () => true;
((fs as unknown) as { readFileSync: () => string }).readFileSync = () => AUTH;
import { fetchQuotaForProvider } from './quotaProviders';
import { fetchHyperQuota, fetchQuotaForProvider } from './quotaProviders';
type MockResponseInit = { ok?: boolean; status?: number };
@@ -85,6 +85,13 @@ const stubFetchFailing = (json: () => Promise<unknown>, init: MockResponseInit):
globalThis.fetch = (async () => ({ json, ...init }) as unknown as Response) as typeof fetch;
};
test('dispatches Charm Hyper through the generic quota API', async () => {
stubFetchReturning(async () => Response.json({ balance: 100 }));
const result = await fetchQuotaForProvider('hyper');
assert.equal(result.ok, true);
assert.equal(result.usage?.windows.credits?.valueLabel, '100');
});
describe('OpenCode Go quota provider (VS Code parity)', () => {
test('uses the opencode-go key from auth.json', async () => {
let request: RequestInit | undefined;
@@ -717,85 +724,128 @@ describe('DeepSeek quota provider (VS Code parity)', () => {
});
describe('Charm Hyper quota provider (VS Code parity)', () => {
beforeEach(() => {
const fsMock = fs as unknown as { existsSync: () => boolean; readFileSync: () => string };
fsMock.existsSync = () => true;
fsMock.readFileSync = () => AUTH;
});
const readAuth = () => ({ hyper: { key: 'test-token' } });
test('builds credits and credits_balance windows from documented payload (numeric balance)', async () => {
stubFetchReturning(() => Promise.resolve(mockResponse({ balance: 100 })));
for (const { balance, credits, dollars } of [
{ balance: 100, credits: '100', dollars: '$5.00' },
{ balance: '50', credits: '50', dollars: '$2.50' },
{ balance: 25.5, credits: '25.50', dollars: '$1.28' },
{ balance: 0, credits: '0', dollars: '$0.00' },
{ balance: '0', credits: '0', dollars: '$0.00' },
]) {
test(`formats balance ${JSON.stringify(balance)} without an untranslated unit`, async () => {
const result = await fetchHyperQuota({ readAuth, fetchImpl: async () => Response.json({ balance }) });
assert.equal(result.ok, true);
assert.equal(result.providerId, 'hyper');
assert.equal(result.configured, true);
assert.ok(result.usage);
assert.equal(result.usage.windows.credits?.valueLabel, credits);
assert.equal(result.usage.windows.credits_balance?.valueLabel, dollars);
for (const window of Object.values(result.usage.windows)) {
assert.equal(window.usedPercent, null);
assert.equal(window.remainingPercent, null);
assert.equal(window.windowSeconds, null);
assert.equal(window.resetAt, null);
assert.equal(window.resetAfterSeconds, null);
}
});
}
const result = await fetchQuotaForProvider('hyper');
for (const payload of [
{}, null, [], { balance: '' }, { balance: ' \t ' }, { balance: 'NaN' },
{ balance: 'Infinity' }, { balance: null }, { balance: true }, { balance: [] },
{ balance: {} },
]) {
test(`rejects invalid payload ${JSON.stringify(payload)} instead of showing zero`, async () => {
const result = await fetchHyperQuota({ readAuth, fetchImpl: async () => Response.json(payload) });
assert.equal(result.ok, false);
assert.equal(result.configured, true);
assert.equal(result.error, 'No quota data in response');
assert.equal(result.usage, null);
});
}
assert.equal(result.ok, true);
assert.equal(result.providerId, 'hyper');
for (const [index, auth] of [
{ hyper: { key: 'test-token' } },
{ hyper: { token: 'test-token' } },
{ hyper: 'test-token' },
{ hyper: { key: ' ', token: 'test-token' } },
{ hyper: { key: 42, token: 'test-token' } },
].entries()) {
test(`uses validated credential variant ${index} for the documented request`, async () => {
let requests = 0;
const result = await fetchHyperQuota({
readAuth: () => auth,
fetchImpl: async (url, options) => {
requests += 1;
assert.equal(url, 'https://hyper.charm.land/v1/credits');
assert.equal(options.method, 'GET');
assert.equal(new Headers(options.headers).get('Authorization'), 'Bearer test-token');
assert.ok(options.signal instanceof AbortSignal);
return Response.json({ balance: 100 });
},
});
assert.equal(requests, 1);
assert.equal(result.ok, true);
assert.equal(JSON.stringify(result).includes('test-token'), false);
});
}
const balanceWindow = result.usage!.windows.credits_balance!;
assert.equal(balanceWindow.valueLabel, '$5.00');
assert.equal(balanceWindow.usedPercent, null);
assert.equal(balanceWindow.windowSeconds, null);
assert.equal(balanceWindow.resetAt, null);
for (const [index, readInvalidAuth] of [
() => ({}),
() => ({ hyper: { key: '' } }),
() => ({ hyper: { key: ' ' } }),
() => ({ hyper: { key: 42 } }),
].entries()) {
test(`does not request usage with missing or invalid credential variant ${index}`, async () => {
let requests = 0;
const result = await fetchHyperQuota({
readAuth: readInvalidAuth,
fetchImpl: async () => {
requests += 1;
return Response.json({ balance: 100 });
},
});
assert.equal(requests, 0);
assert.equal(result.ok, false);
assert.equal(result.configured, false);
assert.equal(result.error, 'Not configured');
});
}
const creditsWindow = result.usage!.windows.credits!;
assert.equal(creditsWindow.valueLabel, '100 credits');
assert.equal(creditsWindow.usedPercent, null);
assert.equal(creditsWindow.windowSeconds, null);
assert.equal(creditsWindow.resetAt, null);
});
test('tolerates a string balance', async () => {
stubFetchReturning(() => Promise.resolve(mockResponse({ balance: '50' })));
const result = await fetchQuotaForProvider('hyper');
assert.equal(result.ok, true);
assert.equal(result.usage!.windows.credits!.valueLabel, '50 credits');
assert.equal(result.usage!.windows.credits_balance!.valueLabel, '$2.50');
});
test('maps 401 to session-expired', async () => {
stubFetchFailing(async () => ({}), { ok: false, status: 401 });
const result = await fetchQuotaForProvider('hyper');
assert.equal(result.ok, false);
assert.equal(result.error, 'Session expired — please re-authenticate with Charm Hyper');
});
test('reports a normalized timeout error', async () => {
stubFetchReturning(() => Promise.reject(new DOMException('The operation timed out.', 'TimeoutError')));
const result = await fetchQuotaForProvider('hyper');
assert.equal(result.ok, false);
assert.equal(result.error, 'Request timed out');
});
test('returns no-quota-data on a 200 payload with no balance', async () => {
stubFetchReturning(() => Promise.resolve(mockResponse({})));
const result = await fetchQuotaForProvider('hyper');
for (const { status, error } of [
{ status: 401, error: 'Session expired — please re-authenticate with Charm Hyper' },
{ status: 403, error: 'Session expired — please re-authenticate with Charm Hyper' },
{ status: 429, error: 'API error: 429' },
{ status: 500, error: 'API error: 500' },
]) {
test(`reports HTTP ${status} as a failure`, async () => {
const result = await fetchHyperQuota({ readAuth, fetchImpl: async () => new Response(null, { status }) });
assert.equal(result.ok, false);
assert.equal(result.configured, true);
assert.equal(result.error, error);
assert.equal(result.usage, null);
});
}
test('reports invalid JSON as a parse failure', async () => {
const result = await fetchHyperQuota({ readAuth, fetchImpl: async () => new Response('{') });
assert.equal(result.error, 'Invalid response from provider');
assert.equal(result.ok, false);
assert.equal(result.configured, true);
assert.equal(result.error, 'No quota data in response');
assert.equal(result.usage, null);
});
test('keeps a literal zero balance as a valid valueLabel', async () => {
stubFetchReturning(() => Promise.resolve(mockResponse({ balance: 0 })));
const result = await fetchQuotaForProvider('hyper');
assert.equal(result.ok, true);
assert.equal(result.usage!.windows.credits!.valueLabel, '0 credits');
assert.equal(result.usage!.windows.credits_balance!.valueLabel, '$0.00');
});
test('teardown: restore fs', () => {
const fsMock = fs as unknown as { existsSync: unknown; readFileSync: unknown };
fsMock.existsSync = ORIGINAL_FS.existsSync;
fsMock.readFileSync = ORIGINAL_FS.readFileSync;
});
for (const { failure, message } of [
{ failure: new DOMException('Timed out', 'TimeoutError'), message: 'Request timed out' },
{ failure: new Error('Network unavailable'), message: 'Network unavailable' },
]) {
test(`reports ${message}`, async () => {
const result = await fetchHyperQuota({ readAuth, fetchImpl: async () => { throw failure; } });
assert.equal(result.error, message);
assert.equal(result.ok, false);
assert.equal(result.configured, true);
assert.equal(result.usage, null);
});
}
});
+19 -17
View File
@@ -155,10 +155,6 @@ type DeepseekPayload = {
}>;
};
type HyperPayload = {
balance?: number | string;
};
type NeuralwattPayload = {
balance?: {
credits_remaining_usd?: number | string;
@@ -857,8 +853,7 @@ export const listConfiguredQuotaProviders = () => {
configured.add('deepseek');
}
const hyperAuth = normalizeAuthEntry(getAuthEntry(auth, ['hyper']));
if (hyperAuth && ((hyperAuth as Record<string, unknown>).key || (hyperAuth as Record<string, unknown>).token)) {
if (getHyperApiKey(auth)) {
configured.add('hyper');
}
@@ -2798,10 +2793,18 @@ const fetchDeepseekQuota = async (): Promise<ProviderResult> => {
const HYPER_QUOTA_URL = 'https://hyper.charm.land/v1/credits';
const HYPER_CREDIT_TO_USD = 0.05;
const fetchHyperQuota = async (): Promise<ProviderResult> => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['hyper'])) as Record<string, unknown> | null;
const apiKey = (entry?.key as string | undefined) ?? (entry?.token as string | undefined);
const getHyperApiKey = (auth: AuthFile) => {
const entry = normalizeAuthEntry(getAuthEntry(auth, ['hyper']));
return asNonEmptyString(entry?.key) ?? asNonEmptyString(entry?.token);
};
type HyperQuotaDependencies = {
readAuth?: () => AuthFile;
fetchImpl?: (url: string, options: RequestInit) => Promise<Response>;
};
export const fetchHyperQuota = async ({ readAuth = readAuthFile, fetchImpl = fetch }: HyperQuotaDependencies = {}): Promise<ProviderResult> => {
const apiKey = getHyperApiKey(readAuth());
if (!apiKey) {
return buildResult({
@@ -2816,7 +2819,7 @@ const fetchHyperQuota = async (): Promise<ProviderResult> => {
const timeoutSignal = AbortSignal.timeout(15_000);
try {
const response = await fetch(HYPER_QUOTA_URL, {
const response = await fetchImpl(HYPER_QUOTA_URL, {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
@@ -2837,11 +2840,10 @@ const fetchHyperQuota = async (): Promise<ProviderResult> => {
});
}
const payload = await response.json() as HyperPayload;
const payload = asObject(await response.json());
const rawBalance = payload?.balance;
const balance = (typeof rawBalance === 'number' || (typeof rawBalance === 'string' && rawBalance.trim() !== ''))
? toNumber(rawBalance)
: null;
const balance = toNumber(asNonEmptyString(rawBalance)
?? (Number.isFinite(rawBalance) ? rawBalance : null));
if (balance === null) {
return buildResult({
@@ -2854,7 +2856,7 @@ const fetchHyperQuota = async (): Promise<ProviderResult> => {
}
const creditsLabel = Number.isInteger(balance) ? String(balance) : formatMoney(balance);
const windows: Record<string, UsageWindow> = {
const windows = {
credits_balance: toUsageWindow({
usedPercent: null,
windowSeconds: null,
@@ -2865,7 +2867,7 @@ const fetchHyperQuota = async (): Promise<ProviderResult> => {
usedPercent: null,
windowSeconds: null,
resetAt: null,
valueLabel: `${creditsLabel} credits`,
valueLabel: creditsLabel,
}),
};