92 lines
3.9 KiB
TypeScript
92 lines
3.9 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const genericModule = await import("../../open-sse/services/genericQuotaFetcher.ts");
|
|
const preflightModule = await import("../../open-sse/services/quotaPreflight.ts");
|
|
|
|
const { convertUsageToQuotaInfo, registerGenericQuotaFetchers } = genericModule;
|
|
const { getQuotaFetcher } = preflightModule;
|
|
|
|
test("convertUsageToQuotaInfo returns null on null/undefined input", () => {
|
|
assert.equal(convertUsageToQuotaInfo(null), null);
|
|
assert.equal(convertUsageToQuotaInfo(undefined), null);
|
|
});
|
|
|
|
test("convertUsageToQuotaInfo returns null when only an error message is present", () => {
|
|
// Auth-expired-style response from getUsageForProvider — fail open.
|
|
assert.equal(convertUsageToQuotaInfo({ message: "auth expired" }), null);
|
|
});
|
|
|
|
test("convertUsageToQuotaInfo maps remainingPercentage into per-window percentUsed", () => {
|
|
const result = convertUsageToQuotaInfo({
|
|
quotas: {
|
|
session: { remainingPercentage: 30, resetAt: "2026-05-14T20:00:00Z" },
|
|
weekly: { remainingPercentage: 10, resetAt: "2026-05-21T00:00:00Z" },
|
|
},
|
|
});
|
|
assert.ok(result);
|
|
assert.deepEqual(result!.windows, {
|
|
session: { percentUsed: 0.7, resetAt: "2026-05-14T20:00:00Z" },
|
|
weekly: { percentUsed: 0.9, resetAt: "2026-05-21T00:00:00Z" },
|
|
});
|
|
// Worst-case percentUsed mirrors what the legacy single-signal field needs.
|
|
assert.equal(result!.percentUsed, 0.9);
|
|
// Reset time should track the worst-case window so preflight can surface it.
|
|
assert.equal(result!.resetAt, "2026-05-21T00:00:00Z");
|
|
});
|
|
|
|
test("convertUsageToQuotaInfo falls back to used/total when remainingPercentage is absent", () => {
|
|
const result = convertUsageToQuotaInfo({
|
|
quotas: { session: { used: 45, total: 100, resetAt: null } },
|
|
});
|
|
assert.ok(result);
|
|
assert.equal(result!.windows!.session.percentUsed, 0.45);
|
|
});
|
|
|
|
test("convertUsageToQuotaInfo skips unlimited and unmeasurable windows", () => {
|
|
const result = convertUsageToQuotaInfo({
|
|
quotas: {
|
|
session: { remainingPercentage: 50, resetAt: null },
|
|
// No percentage and no used/total → skipped.
|
|
unknown_shape: { resetAt: null },
|
|
// Unlimited windows are intentionally ignored — preflight can't block on them.
|
|
unlimited_credits: { unlimited: true, remainingPercentage: 99 },
|
|
},
|
|
});
|
|
assert.ok(result);
|
|
assert.deepEqual(Object.keys(result!.windows || {}), ["session"]);
|
|
});
|
|
|
|
test("convertUsageToQuotaInfo returns null when no windows are measurable", () => {
|
|
const result = convertUsageToQuotaInfo({
|
|
quotas: { unlimited_thing: { unlimited: true } },
|
|
});
|
|
assert.equal(result, null);
|
|
});
|
|
|
|
test("convertUsageToQuotaInfo clamps remainingPercentage outside 0-100", () => {
|
|
const result = convertUsageToQuotaInfo({
|
|
quotas: {
|
|
a: { remainingPercentage: 150, resetAt: null }, // clamped to 100 → 0% used
|
|
b: { remainingPercentage: -10, resetAt: null }, // clamped to 0 → 100% used
|
|
},
|
|
});
|
|
assert.ok(result);
|
|
assert.equal(result!.windows!.a.percentUsed, 0);
|
|
assert.equal(result!.windows!.b.percentUsed, 1);
|
|
});
|
|
|
|
test("registerGenericQuotaFetchers registers Claude, GLM, and OpenCode Go via the generic adapter", () => {
|
|
registerGenericQuotaFetchers();
|
|
// Claude has no bespoke fetcher → should be registered.
|
|
assert.ok(getQuotaFetcher("claude"), "claude should be registered");
|
|
assert.ok(getQuotaFetcher("glm"), "glm should be registered");
|
|
assert.ok(getQuotaFetcher("zai"), "zai should be registered");
|
|
assert.ok(getQuotaFetcher("opencode-go"), "opencode-go should be registered");
|
|
// Codex has its own dedicated fetcher (registered by codexQuotaFetcher.ts,
|
|
// not by the generic registrar) — the generic registrar skips it. We can't
|
|
// assert "codex" here without first calling registerCodexQuotaFetcher,
|
|
// which would couple this test to chat.ts startup wiring. The skip list
|
|
// semantics are exercised by the source code review.
|
|
});
|