Files
OmniRoute/tests/unit/providerlimits-openrouter-credits-parser.test.ts
killer30001000 d71d0f76e5 fix(usage): render OpenRouter PAYG credit pool with real denominator (#12468)
* fix(usage) handle OpenRouter PAYG credit percentage

OpenRouter PAYG accounts without a per-key limit previously rendered
the credits row as 'total: 0, remainingPercentage: 100, unlimited: true',
treating /credits balance as unlimited even when a real credit pool was
present. Route the credit pool through the credits renderer with the
real denominator: total = totalCredits when positive, used = total -
creditBalance, remaining = creditBalance, remainingPercentage =
round(balance / total * 100), isCredits: true, unlimited: false. Per-key
limit still wins. A non-positive pool surfaces the row but never invents
a 100% bar.

Tests cover: explicit key limit, PAYG account credits without key limit,
key limit taking priority over account credits, and a balance without a
positive denominator.

* fix(usage) render OpenRouter PAYG quota as a metered percentage bar

The frontend parser was routing every OpenRouter 'credits' quota through
buildCreditsQuota(), which sets isCredits: true. QuotaCardExpanded
short-circuits on that flag and shows only the USD balance as a bare
number, so a real PAYG payload (used: 7.33, total: 10, remaining: 2.67,
remainingPercentage: 27) was rendered as '$2.67' instead of the '27% left
/ 7.33 / 10' bar the backend already computed.

Drop isCredits: true for any payload whose total is a positive finite
number - the row then goes through the normal normalizeQuotaEntry() path
with currency preserved as an extra. The balance-only fallback (total 0
or non-finite denominator, used by legacy /credits responses) still uses
buildCreditsQuota() so the row stays renderable, and never invents a
100% percentage.

The frontend test now asserts:
- PAYG positive denominator -> total: 10, remainingPercentage: 27,
  currency: 'USD', isCredits !== true.
- Balance-only payload -> isCredits === true, creditCount === 2.67,
  total: 0, no fabricated 100%.
- NaN denominator -> balance-only fallback.
- Non-credits keys -> unchanged normalizeQuotaEntry() path.
- Mixed payload -> normal quota row + PAYG row, both kept.

* docs(changelog): add OpenRouter PAYG fix fragment

* docs(changelog): remove self credit
2026-09-18 12:21:17 -03:00

131 lines
3.8 KiB
TypeScript

/**
* Frontend parser regression #12468 follow-up.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts";
const parseOpenrouter = (data: unknown) => parseQuotaData("openrouter", data) as QuotaRow[];
type QuotaRow = {
name: string;
used: number;
total: number;
remaining: number;
remainingPercentage?: number;
unlimited?: boolean;
isCredits?: boolean;
creditCount?: number;
currency?: string;
};
function findCredits(rows: QuotaRow[]): QuotaRow {
const row = rows.find((r: QuotaRow) => r.name === "credits");
assert.ok(row, "expected 'credits' row");
return row as QuotaRow;
}
test("parseOpenrouter renders PAYG account quota with normal percentage bar (no isCredits short-circuit)", () => {
const rows = parseOpenrouter({
quotas: {
credits: {
used: 7.332_573_982,
total: 10,
remaining: 2.667_426_018,
remainingPercentage: 27,
unlimited: false,
currency: "USD",
},
},
});
const row = findCredits(rows);
assert.equal(row.total, 10, "total must come from PAYG denominator");
assert.ok(Math.abs(row.used - 7.332_573_982) < 1e-6);
assert.ok(Math.abs(row.remaining - 2.667_426_018) < 1e-6);
assert.equal(row.remainingPercentage, 27);
assert.equal(row.currency, "USD");
// The credits renderer in QuotaCardExpanded short-circuits when isCredits
// is true and only shows the USD balance - a positive-denominator PAYG row
// must NOT take that branch.
assert.notEqual(row.isCredits, true, "PAYG row must not flip isCredits");
});
test("parseOpenrouter keeps credit-balance row when no positive denominator", () => {
const rows = parseOpenrouter({
quotas: {
credits: {
used: 0,
total: 0,
remaining: 2.67,
unlimited: false,
currency: "USD",
},
},
});
const row = findCredits(rows);
assert.equal(row.total, 0);
assert.equal(row.used, 0);
assert.equal(row.remaining, 2.67);
assert.equal(row.isCredits, true, "balance row keeps isCredits renderer");
assert.equal(row.creditCount, 2.67);
assert.notEqual(row.remainingPercentage, 100, "no fabricated 100% percentage");
});
test("parseOpenrouter falls back to balance row for non-finite denominator", () => {
const rows = parseOpenrouter({
quotas: {
credits: { used: 0, total: NaN, remaining: 1.5 },
},
});
const row = findCredits(rows);
assert.equal(row.isCredits, true);
assert.equal(row.remaining, 1.5);
});
test("parseOpenrouter keeps per-model rows alongside PAYG credit row", () => {
const rows = parseOpenrouter({
quotas: {
credits: {
used: 7.332_573_982,
total: 10,
remaining: 2.667_426_018,
remainingPercentage: 27,
unlimited: false,
currency: "USD",
},
"anthropic/claude-3.5-sonnet": {
used: 12,
total: 100,
remaining: 88,
resetAt: null,
},
},
});
assert.equal(rows.length, 2);
const credits = findCredits(rows);
assert.equal(credits.total, 10);
assert.ok(Math.abs(credits.used - 7.332_573_982) < 1e-6);
assert.equal(credits.remainingPercentage, 27);
assert.notEqual(credits.isCredits, true);
});
test("parseOpenrouter passes non-credits quota keys through normalizeQuotaEntry", () => {
const rows = parseOpenrouter({
quotas: {
"anthropic/claude-3.5-sonnet": {
used: 12,
total: 100,
remaining: 88,
resetAt: null,
},
},
});
assert.equal(rows.length, 1);
const row = rows[0];
assert.equal(row.name, "anthropic/claude-3.5-sonnet");
assert.equal(row.total, 100);
assert.equal(row.remaining, 88);
assert.notEqual(row.isCredits, true);
});