diff --git a/open-sse/services/usage.ts b/open-sse/services/usage.ts index 1f3cade0f6..9d85d18cb5 100644 --- a/open-sse/services/usage.ts +++ b/open-sse/services/usage.ts @@ -2917,4 +2917,17 @@ export const __testing = { getMiniMaxPlanLabel, inferMiniMaxPlanLabelFromTotals, getOpencodeUsage, + getClaudePlanLabel, + createQuotaFromUsage, + getMiniMaxQuotaResetAt, + isMiniMaxTextQuotaModel, + getMiniMaxSessionTotal, + getMiniMaxWeeklyTotal, + createMiniMaxQuotaFromCount, + getMiniMaxAuthErrorMessage, + getMiniMaxErrorSummary, + mapCodeAssistSubscriptionToPlanLabel, + mapCodeAssistTierIdToLabel, + mapSubscriptionTierStringToPlanLabel, + toDisplayLabel, }; diff --git a/tests/unit/usage-utils.test.ts b/tests/unit/usage-utils.test.ts new file mode 100644 index 0000000000..8d3a99c018 --- /dev/null +++ b/tests/unit/usage-utils.test.ts @@ -0,0 +1,447 @@ +/** + * Tests for pure utility functions exported via usage.__testing + * + * Covers: parseResetTime, formatGitHubQuotaSnapshot, inferGitHubPlanName, + * getMiniMaxPlanLabel, inferMiniMaxPlanLabelFromTotals, + * extractCodeAssistSubscriptionTier, extractCodeAssistOnboardTierId. + * + * These are pure functions (no fetch, no DB) — run without DATA_DIR override. + */ + +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; + +process.env.NODE_ENV = "test"; + +const usage = await import("../../open-sse/services/usage.ts"); +const { __testing } = usage; + +/* ------------------------------------------------------------------ */ +/* parseResetTime */ +/* ------------------------------------------------------------------ */ +describe("parseResetTime", () => { + it("returns null for null / undefined", () => { + assert.equal(__testing.parseResetTime(null), null); + assert.equal(__testing.parseResetTime(undefined), null); + }); + + it("returns null for epoch-zero", () => { + assert.equal(__testing.parseResetTime(0), null); + assert.equal(__testing.parseResetTime("1970-01-01T00:00:00.000Z"), null); + }); + + it("parses a number as ms timestamp (value > 1e12)", () => { + const ms = 1_700_000_000_000; + const out = __testing.parseResetTime(ms); + assert.equal(out, new Date(ms).toISOString()); + }); + + it("parses a number as seconds (value < 1e12)", () => { + const sec = 1_700_000_000; // < 1e12 + const out = __testing.parseResetTime(sec); + assert.equal(out, new Date(sec * 1000).toISOString()); + }); + + it("parses an ISO date string", () => { + const iso = "2026-06-15T12:00:00.000Z"; + assert.equal(__testing.parseResetTime(iso), new Date(iso).toISOString()); + }); + + it("parses a Date object", () => { + const d = new Date("2026-07-01T00:00:00Z"); + assert.equal(__testing.parseResetTime(d), d.toISOString()); + }); + + it("returns null for non-date values (objects, booleans)", () => { + assert.equal(__testing.parseResetTime({}), null); + assert.equal(__testing.parseResetTime(true), null); + }); + + it("returns null for invalid date strings", () => { + assert.equal(__testing.parseResetTime("not-a-date"), null); + }); +}); + +/* ------------------------------------------------------------------ */ +/* formatGitHubQuotaSnapshot */ +/* ------------------------------------------------------------------ */ +describe("formatGitHubQuotaSnapshot", () => { + it("returns null for empty object", () => { + assert.equal(__testing.formatGitHubQuotaSnapshot({}), null); + }); + + it("builds quota from snapshot with all fields", () => { + const snap = { + entitlement: 1000, + used: 300, + remaining: 700, + percent_remaining: 70, + }; + const q = __testing.formatGitHubQuotaSnapshot(snap, "2026-07-01T00:00:00.000Z"); + assert.equal(q.total, 1000); + assert.equal(q.used, 300); + assert.equal(q.remaining, 700); + assert.equal(q.remainingPercentage, 70); + assert.equal(q.resetAt, "2026-07-01T00:00:00.000Z"); + assert.equal(q.unlimited, false); + }); + + it("uses entitlement when total is missing", () => { + const snap = { entitlement: 500, remaining: 0.5, percent_remaining: 50 }; + const q = __testing.formatGitHubQuotaSnapshot(snap); + assert.equal(q.total, 500); + assert.ok(q.remaining !== undefined); + }); + + it("detects unlimited plan", () => { + const snap = { entitlement: 0, total: 0, unlimited: true }; + const q = __testing.formatGitHubQuotaSnapshot(snap); + assert.equal(q.unlimited, true); + assert.equal(q.total, 0); // total is 0 when percent_remaining is missing + }); + + it("clamps negative values to 0", () => { + const snap = { used: -10, entitlement: 100, remaining: -5, percent_remaining: -1 }; + const q = __testing.formatGitHubQuotaSnapshot(snap); + assert.equal(q.used, 0); + assert.equal(q.remaining, 0); + assert.equal(q.remainingPercentage, 0); + }); + + it("computes missing used from total - remaining", () => { + const snap = { entitlement: 200, remaining: 50 }; + const q = __testing.formatGitHubQuotaSnapshot(snap); + assert.equal(q.used, 150); // 200 - 50 + }); + + it("computes missing remaining from total - used", () => { + const snap = { used: 30, entitlement: 100 }; + const q = __testing.formatGitHubQuotaSnapshot(snap); + assert.equal(q.remaining, 70); + }); +}); + +/* ------------------------------------------------------------------ */ +/* inferGitHubPlanName */ +/* ------------------------------------------------------------------ */ +describe("inferGitHubPlanName", () => { + it("detects Copilot Pro+ from combined string", () => { + const data = { copilot_plan: "PRO_PLUS" }; + assert.equal(__testing.inferGitHubPlanName(data, null), "Copilot Pro+"); + }); + + it("detects Copilot Enterprise", () => { + const data = { copilot_plan: "ENTERPRISE" }; + assert.equal(__testing.inferGitHubPlanName(data, null), "Copilot Enterprise"); + }); + + it("detects Copilot Business", () => { + const data = { copilot_plan: "BUSINESS" }; + assert.equal(__testing.inferGitHubPlanName(data, null), "Copilot Business"); + }); + + it("detects Copilot Student", () => { + const data = { copilot_plan: "STUDENT" }; + assert.equal(__testing.inferGitHubPlanName(data, null), "Copilot Student"); + }); + + it("detects Copilot Free", () => { + const data = { copilot_plan: "FREE" }; + assert.equal(__testing.inferGitHubPlanName(data, null), "Copilot Free"); + }); + + it("detects Copilot Pro", () => { + const data = { copilot_plan: "PRO" }; + assert.equal(__testing.inferGitHubPlanName(data, null), "Copilot Pro"); + }); + + it("infers Pro+ from premiumTotal >= 1400", () => { + const data = { copilot_plan: "INDIVIDUAL" }; + const premium = { used: 0, total: 1500, remaining: 1500, remainingPercentage: 100, unlimited: false }; + assert.equal(__testing.inferGitHubPlanName(data, premium), "Copilot Pro+"); + }); + + it("infers Enterprise from premiumTotal >= 900", () => { + const data = { copilot_plan: "INDIVIDUAL" }; + const premium = { used: 0, total: 900, remaining: 900, remainingPercentage: 100, unlimited: false }; + assert.equal(__testing.inferGitHubPlanName(data, premium), "Copilot Enterprise"); + }); + + it("infers Pro when premiumTotal >= 250 and combined has INDIVIDUAL", () => { + const data = { copilot_plan: "INDIVIDUAL" }; + const premium = { used: 0, total: 300, remaining: 300, remainingPercentage: 100, unlimited: false }; + assert.equal(__testing.inferGitHubPlanName(data, premium), "Copilot Pro"); + }); + + it("returns 'GitHub Copilot' fallback when nothing matches", () => { + const data = {}; + assert.equal(__testing.inferGitHubPlanName(data, null), "GitHub Copilot"); + }); + + it("falls back to sku label when planText is empty", () => { + const data = { access_type_sku: "BUSINESS_SPO" }; + // "BUSINESS_SPO" upper-cased matches "BUSINESS" check first + assert.equal(__testing.inferGitHubPlanName(data, null), "Copilot Business"); + }); +}); + +/* ------------------------------------------------------------------ */ +/* getMiniMaxPlanLabel */ +/* ------------------------------------------------------------------ */ +describe("getMiniMaxPlanLabel", () => { + it("returns cleaned title from payload", () => { + const payload = { current_subscribe_title: "MiniMax Coding Plan Pro" }; + assert.equal(__testing.getMiniMaxPlanLabel(payload), "Pro"); + }); + + it("removes 'minimax ' prefix and 'coding plan' text", () => { + const payload = { plan: "MiniMax Coding Plan Ultra" }; + assert.equal(__testing.getMiniMaxPlanLabel(payload), "Ultra"); + }); + + it("calls inferMiniMaxPlanLabelFromTotals when no title present", () => { + const models = [{ current_interval_total_count: 500, current_weekly_total_count: 200 }]; + const label = __testing.getMiniMaxPlanLabel({}, models); + assert.ok(typeof label === "string"); + assert.ok(label.length > 0); + }); + + it("returns 'Coding Plan' fallback when nothing matches", () => { + assert.equal(__testing.getMiniMaxPlanLabel({}), "Coding Plan"); + }); + + it("picks first non-empty string from multiple candidate fields", () => { + const payload = { combo_title: "", plan_name: "MiniMax Turbo", plan: "" }; + const label = __testing.getMiniMaxPlanLabel(payload); + assert.equal(label, "Turbo"); + }); +}); + +/* ------------------------------------------------------------------ */ +/* inferMiniMaxPlanLabelFromTotals */ +/* ------------------------------------------------------------------ */ +describe("inferMiniMaxPlanLabelFromTotals", () => { + it("returns 'Max' for totals >= 15K", () => { + const models = [{ current_interval_total_count: 20_000 }]; + assert.equal(__testing.inferMiniMaxPlanLabelFromTotals(models), "Max"); + }); + + it("returns 'Plus' for totals >= 4.5K but < 15K", () => { + const models = [{ current_interval_total_count: 5_000 }]; + assert.equal(__testing.inferMiniMaxPlanLabelFromTotals(models), "Plus"); + }); + + it("returns 'Starter' for totals >= 1.5K but < 4.5K", () => { + const models = [{ current_interval_total_count: 2_000 }]; + assert.equal(__testing.inferMiniMaxPlanLabelFromTotals(models), "Starter"); + }); + + it("returns null when models array is empty", () => { + assert.equal(__testing.inferMiniMaxPlanLabelFromTotals([]), null); + }); +}); + +/* ------------------------------------------------------------------ */ +/* getMiniMaxQuotaResetAt */ +/* ------------------------------------------------------------------ */ +describe("getMiniMaxQuotaResetAt", () => { + it("computes resetAt from remainsMs", () => { + const model = { remains_time: 3600_000 }; // 1 hour in ms + const capturedAt = 1_700_000_000_000; + const result = __testing.getMiniMaxQuotaResetAt(model, capturedAt, "remains_time", "remainsTime", "end_time", "endTime"); + assert.equal(result, new Date(capturedAt + 3600_000).toISOString()); + }); + + it("falls back to endTime when remainsMs is 0", () => { + const model = { end_time: 1_800_000_000_000 }; + const capturedAt = 1_700_000_000_000; + const result = __testing.getMiniMaxQuotaResetAt(model, capturedAt, "remains_time", "remainsTime", "end_time", "endTime"); + assert.equal(result, new Date(1_800_000_000_000).toISOString()); + }); +}); + +/* ------------------------------------------------------------------ */ +/* getMiniMaxSessionTotal / getMiniMaxWeeklyTotal */ +/* ------------------------------------------------------------------ */ +describe("getMiniMaxSessionTotal", () => { + it("returns current_interval_total_count", () => { + assert.equal(__testing.getMiniMaxSessionTotal({ current_interval_total_count: 999 }), 999); + }); + + it("clamps to 0 for negative", () => { + assert.equal(__testing.getMiniMaxSessionTotal({ current_interval_total_count: -5 }), 0); + }); +}); + +describe("getMiniMaxWeeklyTotal", () => { + it("returns current_weekly_total_count", () => { + assert.equal(__testing.getMiniMaxWeeklyTotal({ current_weekly_total_count: 500 }), 500); + }); + + it("clamps to 0 for negative", () => { + assert.equal(__testing.getMiniMaxWeeklyTotal({ current_weekly_total_count: -1 }), 0); + }); +}); + +/* ------------------------------------------------------------------ */ +/* createMiniMaxQuotaFromCount */ +/* ------------------------------------------------------------------ */ +describe("createMiniMaxQuotaFromCount", () => { + it("computes used = total - count when countMeansRemaining", () => { + const q = __testing.createMiniMaxQuotaFromCount(100, 30, null, true); + assert.equal(q.used, 70); + assert.equal(q.remaining, 30); + }); + + it("treats count as used when countMeansRemaining is false", () => { + const q = __testing.createMiniMaxQuotaFromCount(100, 30, null, false); + assert.equal(q.used, 30); + assert.equal(q.remaining, 70); + }); +}); + +/* ------------------------------------------------------------------ */ +/* createQuotaFromUsage */ +/* ------------------------------------------------------------------ */ +describe("createQuotaFromUsage", () => { + it("builds UsageQuota with correct math", () => { + const q = __testing.createQuotaFromUsage(200, 1000, "2026-07-01T00:00:00.000Z"); + assert.equal(q.used, 200); + assert.equal(q.total, 1000); + assert.equal(q.remaining, 800); + assert.equal(q.remainingPercentage, 80); + assert.equal(q.resetAt, "2026-07-01T00:00:00.000Z"); + assert.equal(q.unlimited, false); + }); + + it("clamps used to total", () => { + const q = __testing.createQuotaFromUsage(9999, 100, null); + assert.equal(q.used, 100); + assert.equal(q.remaining, 0); + assert.equal(q.remainingPercentage, 0); + }); +}); + +/* ------------------------------------------------------------------ */ +/* isMiniMaxTextQuotaModel */ +/* ------------------------------------------------------------------ */ +describe("isMiniMaxTextQuotaModel", () => { + it("returns true for minimax-m prefixed models", () => { + assert.equal(__testing.isMiniMaxTextQuotaModel("minimax-m1"), true); + assert.equal(__testing.isMiniMaxTextQuotaModel("Minimax-M2.5"), true); + }); + + it("returns true for coding-plan prefixed models", () => { + assert.equal(__testing.isMiniMaxTextQuotaModel("Coding-Plan-1"), true); + }); + + it("returns false for other models", () => { + assert.equal(__testing.isMiniMaxTextQuotaModel("tts-1"), false); + assert.equal(__testing.isMiniMaxTextQuotaModel("") as boolean, false); + }); +}); + +/* ------------------------------------------------------------------ */ +/* getMiniMaxAuthErrorMessage */ +/* ------------------------------------------------------------------ */ +describe("getMiniMaxAuthErrorMessage", () => { + it("returns token plan message when message includes 'token plan'", () => { + const msg = __testing.getMiniMaxAuthErrorMessage("invalid token plan key"); + assert.ok(msg.includes("Token Plan API key")); + }); + + it("returns token plan message when message includes 'coding plan'", () => { + const msg = __testing.getMiniMaxAuthErrorMessage("coding plan inactive"); + assert.ok(msg.includes("Token Plan API key")); + }); + + it("returns token plan message when message includes 'active period'", () => { + const msg = __testing.getMiniMaxAuthErrorMessage("active period ended"); + assert.ok(msg.includes("Token Plan API key")); + }); + + it("returns generic message for unknown errors", () => { + const msg = __testing.getMiniMaxAuthErrorMessage("something else"); + assert.ok(msg.includes("access denied")); + }); +}); + +/* ------------------------------------------------------------------ */ +/* getMiniMaxErrorSummary */ +/* ------------------------------------------------------------------ */ +describe("getMiniMaxErrorSummary", () => { + it("returns status-only when message is empty", () => { + const s = __testing.getMiniMaxErrorSummary(500, ""); + assert.equal(s, "MiniMax usage endpoint error (500)."); + }); + + it("includes message when under 160 chars", () => { + const s = __testing.getMiniMaxErrorSummary(403, "forbidden"); + assert.equal(s, "MiniMax usage endpoint error (403): forbidden"); + }); + + it("truncates message over 160 chars", () => { + const long = "x".repeat(200); + const s = __testing.getMiniMaxErrorSummary(429, long); + assert.ok(s.length < 200); + assert.ok(s.endsWith("...")); + }); +}); + +/* ------------------------------------------------------------------ */ +/* getClaudePlanLabel */ +/* ------------------------------------------------------------------ */ +describe("getClaudePlanLabel", () => { + it("returns first valid candidate", () => { + assert.equal(__testing.getClaudePlanLabel(null, "Pro Plan", "Free"), "Pro Plan"); + }); + + it("skips null/undefined/empty candidates", () => { + assert.equal(__testing.getClaudePlanLabel(null, "", "Pro", undefined), "Pro"); + }); + + it("ignores 'claude code' or 'unknown'", () => { + assert.equal(__testing.getClaudePlanLabel("Claude Code"), null); + assert.equal(__testing.getClaudePlanLabel("Unknown"), null); + }); + + it("returns null when all candidates are filtered out", () => { + assert.equal(__testing.getClaudePlanLabel("claude code", "unknown"), null); + assert.equal(__testing.getClaudePlanLabel(), null); + }); +}); + +/* ------------------------------------------------------------------ */ +/* extractCodeAssist helpers */ +/* ------------------------------------------------------------------ */ +describe("extractCodeAssistOnboardTierId", () => { + it("extracts tier id from paidTier", () => { + const sub = { paidTier: { id: "pro-tier" } }; + assert.equal(__testing.extractCodeAssistOnboardTierId(sub), "pro-tier"); + }); + + it("extracts tier id from currentTier when paidTier is absent", () => { + const sub = { currentTier: { id: "starter-tier" } }; + assert.equal(__testing.extractCodeAssistOnboardTierId(sub), "starter-tier"); + }); + + it("returns 'legacy-tier' when no tier data is present", () => { + assert.equal(__testing.extractCodeAssistOnboardTierId({}), "legacy-tier"); + }); +}); + +describe("extractCodeAssistSubscriptionTier", () => { + it("reads tier name from paidTier.name", () => { + const info = { paidTier: { name: "ULTRA" } }; + assert.equal(__testing.extractCodeAssistSubscriptionTier(info), "ULTRA"); + }); + + it("reads tier from currentTier.name", () => { + const info = { currentTier: { name: "Pro" } }; + assert.equal(__testing.extractCodeAssistSubscriptionTier(info), "Pro"); + }); + + it("returns null when nothing matches", () => { + assert.equal(__testing.extractCodeAssistSubscriptionTier({}), null); + }); +});