// Core billing primitives: commitCredit (debit), refundCredit (inverse), // and applyTierPromotion (the Core→paid upgrade bookkeeping). Default // quotas (no config file → getTierQuotas falls back): Core lifetime=10, // geminiCapLifetime=5; Pro monthly=50, geminiCapMonthly=25. import { test, describe, before } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { initCredits, getOrCreateRow, getUserCreditRow, setUserTier, commitCredit, refundCredit, applyTierPromotion, } from "../credits.js"; before(async () => { await initCredits({ dataDir: mkdtempSync(path.join(tmpdir(), "relay-money-")) }); }); describe("commitCredit", () => { test("Core debits the lifetime bucket; Gemini tracked separately", async () => { const creditKey = "inst:core-commit"; await commitCredit({ creditKey, tier: "core", backend: "gemini" }); let row = await getOrCreateRow({ creditKey }); assert.equal(row.lifetime_consumed, 1); assert.equal(row.lifetime_gemini_consumed, 1); // A hardware call bumps lifetime but NOT the Gemini sub-counter. await commitCredit({ creditKey, tier: "core", backend: "hardware" }); row = await getOrCreateRow({ creditKey }); assert.equal(row.lifetime_consumed, 2); assert.equal(row.lifetime_gemini_consumed, 1); }); test("paid tier debits the monthly bucket", async () => { await setUserTier({ userId: "p-commit", tier: "pro" }); await commitCredit({ creditKey: "user:p-commit", tier: "pro", backend: "gemini" }); const row = await getUserCreditRow("p-commit"); assert.equal(row.monthly_consumed, 1); assert.equal(row.monthly_gemini_consumed, 1); }); test("spend order: once the tier bucket is exhausted, debit purchased", async () => { const creditKey = "inst:core-exhausted"; const row = await getOrCreateRow({ creditKey }); row.lifetime_consumed = 10; // at the Core lifetime cap row.purchased_balance = 3; await commitCredit({ creditKey, tier: "core", backend: "hardware" }); const after = await getOrCreateRow({ creditKey }); assert.equal(after.lifetime_consumed, 10, "tier counter must not exceed the cap"); assert.equal(after.purchased_balance, 2, "overflow comes out of purchased"); }); }); describe("refundCredit", () => { test("mirrors a Core commit (tier bucket first, Gemini sub-counter too)", async () => { const creditKey = "inst:core-refund"; await commitCredit({ creditKey, tier: "core", backend: "gemini" }); await refundCredit({ creditKey, tier: "core", backend: "gemini" }); const row = await getOrCreateRow({ creditKey }); assert.equal(row.lifetime_consumed, 0); assert.equal(row.lifetime_gemini_consumed, 0); }); test("refund with an empty tier bucket credits the purchased bucket", async () => { const creditKey = "inst:refund-to-purchased"; const row = await getOrCreateRow({ creditKey }); row.lifetime_consumed = 0; row.purchased_balance = 0; await refundCredit({ creditKey, tier: "core", backend: "hardware" }); const after = await getOrCreateRow({ creditKey }); assert.equal(after.lifetime_consumed, 0, "must floor at 0, not go negative"); assert.equal(after.purchased_balance, 1, "refund lands in purchased when tier is already 0"); }); }); describe("applyTierPromotion", () => { test("Core→paid transfers leftover Core credits to purchased and resets the cycle", async () => { const row = await getOrCreateRow({ creditKey: "inst:promo" }); row.lifetime_consumed = 4; // 6 of 10 Core credits unused row.monthly_consumed = 2; // stale paid-counter noise that must be zeroed const promoted = await applyTierPromotion(row, "pro"); assert.equal(promoted, true); assert.equal(row.tier_snapshot, "pro"); assert.equal(row.purchased_balance, 6, "leftover Core credits carry forward as durable top-up"); assert.equal(row.purchased_total_ever, 6); assert.equal(row.monthly_consumed, 0, "promotion starts a fresh monthly cycle"); }); test("idempotent: a second promotion on an already-paid row is a no-op", async () => { const row = await getOrCreateRow({ creditKey: "inst:promo-again" }); row.lifetime_consumed = 4; await applyTierPromotion(row, "pro"); // first: fires const purchasedAfterFirst = row.purchased_balance; const promoted = await applyTierPromotion(row, "max"); // second: must bail assert.equal(promoted, false); assert.equal(row.purchased_balance, purchasedAfterFirst, "no second leftover transfer"); assert.equal(row.tier_snapshot, "pro", "bails before flipping tier again"); }); test("promoting to Core is a no-op", async () => { const row = await getOrCreateRow({ creditKey: "inst:promo-core" }); assert.equal(await applyTierPromotion(row, "core"), false); }); });