From ba7f2145d0dadc43ca23658097534d928adb5ef5 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Tue, 30 Jun 2026 20:33:35 -0300 Subject: [PATCH] refactor(usage): extract pure quota-normalize helpers from providerLimits.ts (#5730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit providerLimits.ts (954 lines, frozen-baselined) is the heavily DB/network-coupled provider quota sync module. Extract a small, fully SELF-CONTAINED leaf of pure quota-key/quota-value normalization helpers (+ the isRecord type guard they share), leaving all sync/DB/network code in the host: - providerLimits/quotaNormalize.ts (72) isRecord, isUsageQuotaKeyAllowed, normalizeUsageQuotaKey, normalizeUsageQuotasForProvider, sanitizeUsageQuotasForProvider Host providerLimits.ts: 954 -> 890. The leaf imports only the external antigravity/agy model-alias helpers the moved bodies reference (moved from the host's import block) — it does NOT import the host, so check:cycles stays clean (no cycle). isRecord (used ~9x in the host) is co-extracted and imported back. These five were all module-internal, so the public API is unchanged (13 exported functions). Bodies moved byte-identical. Behavior-preserving: 18 existing provider-limits consumer tests stay green (sanitize-scope 3, db-provider-limits 3, proxy-fail-closed 3, rotating-expired-guard 7, codex-quota-sync 2); new tests/unit/providerlimits-quotanormalize-split.test.ts (19 assertions) pins isRecord + isUsageQuotaKeyAllowed + guards the 13-function public API. Refs #3501. --- src/lib/usage/providerLimits.ts | 76 +------------ .../usage/providerLimits/quotaNormalize.ts | 72 ++++++++++++ ...roviderlimits-quotanormalize-split.test.ts | 104 ++++++++++++++++++ 3 files changed, 182 insertions(+), 70 deletions(-) create mode 100644 src/lib/usage/providerLimits/quotaNormalize.ts create mode 100644 tests/unit/providerlimits-quotanormalize-split.test.ts diff --git a/src/lib/usage/providerLimits.ts b/src/lib/usage/providerLimits.ts index 3a7aeeebe2..d089f23483 100644 --- a/src/lib/usage/providerLimits.ts +++ b/src/lib/usage/providerLimits.ts @@ -27,12 +27,13 @@ import { extractCodeAssistSubscriptionTier, } from "@omniroute/open-sse/services/codeAssistSubscription.ts"; import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts"; -import { - isUserCallableAntigravityModelId, - toClientAntigravityModelId, -} from "@omniroute/open-sse/config/antigravityModelAliases.ts"; -import { isUserCallableAgyModelId } from "@omniroute/open-sse/config/agyModels.ts"; import { onUsageRecorded } from "./usageEvents"; +import { + isRecord, + isUsageQuotaKeyAllowed, + normalizeUsageQuotasForProvider, + sanitizeUsageQuotasForProvider, +} from "./providerLimits/quotaNormalize"; type JsonRecord = Record; type SyncSource = "manual" | "scheduled"; @@ -79,10 +80,6 @@ const PROVIDER_LIMITS_AUTO_SYNC_SETTING_KEY = "provider_limits_auto_sync_last_ru const DEFAULT_PROVIDER_LIMITS_POST_USAGE_REFRESH_DELAY_MS = 5_000; const pendingPostUsageRefreshes = new Set(); -function isRecord(value: unknown): value is JsonRecord { - return value !== null && typeof value === "object" && !Array.isArray(value); -} - function toProviderLimitsCacheEntry( usage: JsonRecord, source: SyncSource, @@ -135,67 +132,6 @@ export function notifyProviderUsageRecorded( // the provider-limits route and the background auto-sync scheduler at server boot. onUsageRecorded(notifyProviderUsageRecorded); -function isUsageQuotaKeyAllowed(provider: string, quotaKey: string): boolean { - if (quotaKey === "credits" || quotaKey === "models") return true; - if (provider === "antigravity") return isUserCallableAntigravityModelId(quotaKey); - if (provider === "agy") return isUserCallableAgyModelId(quotaKey); - return true; -} - -function normalizeUsageQuotaKey(provider: string, quotaKey: string): string | null { - if (quotaKey === "credits" || quotaKey === "models") return quotaKey; - if (provider === "antigravity" || provider === "agy") { - const clientKey = toClientAntigravityModelId(quotaKey); - return isUsageQuotaKeyAllowed(provider, clientKey) ? clientKey : null; - } - return isUsageQuotaKeyAllowed(provider, quotaKey) ? quotaKey : null; -} - -function normalizeUsageQuotasForProvider( - provider: string, - quotas: JsonRecord | null | undefined -): JsonRecord | null { - if (!isRecord(quotas)) return quotas ?? null; - - const normalized: JsonRecord = {}; - let changed = false; - - for (const [quotaKey, quota] of Object.entries(quotas)) { - const normalizedKey = normalizeUsageQuotaKey(provider, quotaKey); - if (!normalizedKey) { - changed = true; - continue; - } - - const existing = normalized[normalizedKey]; - if (existing && isRecord(existing) && isRecord(quota)) { - const existingSource = String(existing.quotaSource ?? ""); - const nextSource = String(quota.quotaSource ?? ""); - const sourceRank: Record = { - fetchAvailableModels: 0, - localUsageHistory: 1, - retrieveUserQuota: 2, - }; - if ((sourceRank[existingSource] ?? 0) > (sourceRank[nextSource] ?? 0)) { - continue; - } - } - - normalized[normalizedKey] = quota as JsonRecord; - if (normalizedKey !== quotaKey) changed = true; - } - - return changed ? normalized : quotas; -} - -function sanitizeUsageQuotasForProvider(provider: string, usage: JsonRecord): JsonRecord { - if (provider !== "antigravity" && provider !== "agy") return usage; - if (!isRecord(usage.quotas)) return usage; - - const sanitizedQuotas = normalizeUsageQuotasForProvider(provider, usage.quotas); - return sanitizedQuotas === usage.quotas ? usage : { ...usage, quotas: sanitizedQuotas }; -} - function hasRetrieveUserQuotaSource( provider: string, cache: ProviderLimitsCacheEntry | undefined diff --git a/src/lib/usage/providerLimits/quotaNormalize.ts b/src/lib/usage/providerLimits/quotaNormalize.ts new file mode 100644 index 0000000000..de1dffefa8 --- /dev/null +++ b/src/lib/usage/providerLimits/quotaNormalize.ts @@ -0,0 +1,72 @@ +import { + isUserCallableAntigravityModelId, + toClientAntigravityModelId, +} from "@omniroute/open-sse/config/antigravityModelAliases.ts"; +import { isUserCallableAgyModelId } from "@omniroute/open-sse/config/agyModels.ts"; + +type JsonRecord = Record; + +export function isRecord(value: unknown): value is JsonRecord { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +export function isUsageQuotaKeyAllowed(provider: string, quotaKey: string): boolean { + if (quotaKey === "credits" || quotaKey === "models") return true; + if (provider === "antigravity") return isUserCallableAntigravityModelId(quotaKey); + if (provider === "agy") return isUserCallableAgyModelId(quotaKey); + return true; +} + +export function normalizeUsageQuotaKey(provider: string, quotaKey: string): string | null { + if (quotaKey === "credits" || quotaKey === "models") return quotaKey; + if (provider === "antigravity" || provider === "agy") { + const clientKey = toClientAntigravityModelId(quotaKey); + return isUsageQuotaKeyAllowed(provider, clientKey) ? clientKey : null; + } + return isUsageQuotaKeyAllowed(provider, quotaKey) ? quotaKey : null; +} + +export function normalizeUsageQuotasForProvider( + provider: string, + quotas: JsonRecord | null | undefined +): JsonRecord | null { + if (!isRecord(quotas)) return quotas ?? null; + + const normalized: JsonRecord = {}; + let changed = false; + + for (const [quotaKey, quota] of Object.entries(quotas)) { + const normalizedKey = normalizeUsageQuotaKey(provider, quotaKey); + if (!normalizedKey) { + changed = true; + continue; + } + + const existing = normalized[normalizedKey]; + if (existing && isRecord(existing) && isRecord(quota)) { + const existingSource = String(existing.quotaSource ?? ""); + const nextSource = String(quota.quotaSource ?? ""); + const sourceRank: Record = { + fetchAvailableModels: 0, + localUsageHistory: 1, + retrieveUserQuota: 2, + }; + if ((sourceRank[existingSource] ?? 0) > (sourceRank[nextSource] ?? 0)) { + continue; + } + } + + normalized[normalizedKey] = quota as JsonRecord; + if (normalizedKey !== quotaKey) changed = true; + } + + return changed ? normalized : quotas; +} + +export function sanitizeUsageQuotasForProvider(provider: string, usage: JsonRecord): JsonRecord { + if (provider !== "antigravity" && provider !== "agy") return usage; + if (!isRecord(usage.quotas)) return usage; + + const sanitizedQuotas = normalizeUsageQuotasForProvider(provider, usage.quotas); + return sanitizedQuotas === usage.quotas ? usage : { ...usage, quotas: sanitizedQuotas }; +} diff --git a/tests/unit/providerlimits-quotanormalize-split.test.ts b/tests/unit/providerlimits-quotanormalize-split.test.ts new file mode 100644 index 0000000000..c95d5c0ec8 --- /dev/null +++ b/tests/unit/providerlimits-quotanormalize-split.test.ts @@ -0,0 +1,104 @@ +/** + * Characterization + API-surface test: providerLimits.ts god-file decomposition. + * + * The pure quota-key/quota-value normalization helpers (+ the isRecord type + * guard they share) were extracted verbatim from src/lib/usage/providerLimits.ts + * into the self-contained leaf src/lib/usage/providerLimits/quotaNormalize.ts + * (no DB, no network — the leaf does NOT import the host, so no cycle). The sync + * orchestration / DB / network code stays in the host. + * + * Verifies that: + * 1. isRecord + isUsageQuotaKeyAllowed behave correctly (DB-free). + * 2. The host providerLimits.ts still exposes the FULL public API (13 funcs). + * 3. The quotaNormalize leaf exports its helpers directly. + * + * Deeper quota-sanitization behaviour is covered by the existing + * provider-limits-sanitize-scope-3821 / db-provider-limits suites; this test + * pins the extraction boundary + the simplest pure branches. + */ + +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; + +import { + isRecord, + isUsageQuotaKeyAllowed, + normalizeUsageQuotasForProvider, + sanitizeUsageQuotasForProvider, +} from "../../src/lib/usage/providerLimits/quotaNormalize.ts"; + +describe("providerLimits/quotaNormalize — isRecord", () => { + it("is true only for plain non-null, non-array objects", () => { + assert.equal(isRecord({ a: 1 }), true); + assert.equal(isRecord({}), true); + assert.equal(isRecord(null), false); + assert.equal(isRecord([1, 2]), false); + assert.equal(isRecord("x"), false); + assert.equal(isRecord(5), false); + }); +}); + +describe("providerLimits/quotaNormalize — isUsageQuotaKeyAllowed", () => { + it("always allows the generic 'credits' and 'models' keys", () => { + assert.equal(isUsageQuotaKeyAllowed("openai", "credits"), true); + assert.equal(isUsageQuotaKeyAllowed("anthropic", "models"), true); + assert.equal(isUsageQuotaKeyAllowed("antigravity", "credits"), true); + }); +}); + +describe("providerLimits/quotaNormalize — sanitize/normalize are callable & pure-shaped", () => { + it("sanitizeUsageQuotasForProvider returns a record (no throw on a plain usage object)", () => { + const usage = { quotas: { credits: { used: 1, limit: 10 } } }; + const out = sanitizeUsageQuotasForProvider("openai", usage); + assert.equal(isRecord(out), true); + }); + it("normalizeUsageQuotasForProvider is exported and callable", () => { + assert.equal(typeof normalizeUsageQuotasForProvider, "function"); + }); +}); + +// ── host public API surface ────────────────────────────────────────────────── + +const host = await import("../../src/lib/usage/providerLimits.ts"); + +describe("providerLimits.ts public API surface (13 functions)", () => { + const expected = [ + "fetchAndPersistProviderLimits", + "fetchLiveProviderLimits", + "getCachedProviderLimitsMap", + "getLastProviderLimitsAutoSyncTime", + "getProviderLimitsSyncIntervalMinutes", + "getProviderLimitsSyncIntervalMs", + "getProviderLimitsSyncSpacingMs", + "getSanitizedCachedProviderLimitsMap", + "notifyProviderUsageRecorded", + "quotaPathShouldMarkExpired", + "refreshAndUpdateCredentials", + "shouldAttemptRotatingRefresh", + "syncAllProviderLimits", + ]; + for (const name of expected) { + it(`exposes ${name} as a function`, () => { + assert.equal(typeof host[name], "function", `${name} must be a function on the host`); + }); + } + it("loses no public function in the split", () => { + const missing = expected.filter((n) => typeof host[n] !== "function"); + assert.deepEqual(missing, [], `missing: ${missing.join(", ")}`); + }); +}); + +describe("quotaNormalize.ts exports its helpers directly", () => { + it("the moved helpers are functions on the leaf", async () => { + const qn = await import("../../src/lib/usage/providerLimits/quotaNormalize.ts"); + for (const fn of [ + "isRecord", + "isUsageQuotaKeyAllowed", + "normalizeUsageQuotaKey", + "normalizeUsageQuotasForProvider", + "sanitizeUsageQuotasForProvider", + ]) { + assert.equal(typeof qn[fn], "function", fn); + } + }); +});