| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { |
| 5 | foldTasks, |
| 6 | gigabytes, |
| 7 | shownMeters, |
| 8 | alertText, |
| 9 | alertTone, |
| 10 | cardCheckResult, |
| 11 | dollars, |
| 12 | needsAttention, |
| 13 | usageGlance, |
| 14 | parseCaps, |
| 15 | parseLimitRequest, |
| 16 | parsePrepay, |
| 17 | parseSpendLimit, |
| 18 | planStatus, |
| 19 | readDollars, |
| 20 | requestStatus, |
| 21 | share, |
| 22 | spendPath, |
| 23 | spendRange, |
| 24 | wholeDollars, |
| 25 | } from "./billing.ts"; |
| 26 | |
| 27 | test("money reads as dollars", () => { |
| 28 | assert.equal(dollars(9_500_000), "$9.50"); |
| 29 | assert.equal(dollars(-1_250_000), "−$1.25"); |
| 30 | assert.equal(wholeDollars(1_000_000_000), "$1,000"); |
| 31 | assert.equal(wholeDollars(100_000), "$0.10"); |
| 32 | assert.equal(readDollars("$1,000"), 1_000_000_000); |
| 33 | assert.equal(readDollars(""), null); |
| 34 | assert.equal(readDollars("lots"), null); |
| 35 | }); |
| 36 | |
| 37 | test("a prepayment is $25 or more, by card up to $10,000, by bank transfer from $1,000", () => { |
| 38 | assert.deepEqual(parsePrepay({ amount: "100" }), { ok: true, value: { amountCents: 10_000, method: "card" } }); |
| 39 | assert.deepEqual(parsePrepay({ amount: "100", custom: "250" }), { |
| 40 | ok: true, |
| 41 | value: { amountCents: 25_000, method: "card" }, |
| 42 | }); |
| 43 | assert.equal(parsePrepay({ custom: "10" }).ok, false); |
| 44 | assert.equal(parsePrepay({ custom: "20000" }).ok, false); |
| 45 | assert.equal(parsePrepay({ custom: "500", method: "bank_transfer" }).ok, false); |
| 46 | assert.deepEqual(parsePrepay({ custom: "20000", method: "bank_transfer" }), { |
| 47 | ok: true, |
| 48 | value: { amountCents: 2_000_000, method: "bank_transfer" }, |
| 49 | }); |
| 50 | assert.equal(parsePrepay({}).ok, false); |
| 51 | }); |
| 52 | |
| 53 | test("caps stay in range, and empty goes back to the default", () => { |
| 54 | assert.deepEqual(parseCaps({ run: "", issue: "" }), { ok: true, value: { runCapMicros: null, issueCapMicros: null } }); |
| 55 | assert.deepEqual(parseCaps({ run: "0.10", issue: "1000" }), { |
| 56 | ok: true, |
| 57 | value: { runCapMicros: 100_000, issueCapMicros: 1_000_000_000 }, |
| 58 | }); |
| 59 | assert.equal(parseCaps({ run: "0.05" }).ok, false); |
| 60 | assert.equal(parseCaps({ issue: "2000" }).ok, false); |
| 61 | assert.equal(parseCaps({ run: "two" }).ok, false); |
| 62 | }); |
| 63 | |
| 64 | test("a limit request needs an amount, a reason and a monthly spend; an overage only a reason", () => { |
| 65 | assert.deepEqual(parseLimitRequest({ kind: "limit", amount: "5000", reason: " A launch ", expected: "3000" }), { |
| 66 | ok: true, |
| 67 | value: { kind: "limit", amountMicros: 5_000_000_000, reason: "A launch", expectedMonthlyMicros: 3_000_000_000 }, |
| 68 | }); |
| 69 | assert.equal(parseLimitRequest({ kind: "limit", amount: "5000", reason: "" , expected: "1" }).ok, false); |
| 70 | assert.equal(parseLimitRequest({ kind: "limit", reason: "x", expected: "1" }).ok, false); |
| 71 | assert.deepEqual(parseLimitRequest({ kind: "overage", reason: "A loop ran all night" }), { |
| 72 | ok: true, |
| 73 | value: { kind: "overage", amountMicros: 0, reason: "A loop ran all night", expectedMonthlyMicros: 0 }, |
| 74 | }); |
| 75 | }); |
| 76 | |
| 77 | test("a spend limit is automatic, fixed, the full amount available, or the one-time raise", () => { |
| 78 | assert.deepEqual(parseSpendLimit({ mode: "automatic" }), { ok: true, value: { micros: null, useFull: false, raiseOnce: false } }); |
| 79 | assert.deepEqual(parseSpendLimit({ mode: "fixed", limit: "500" }), { |
| 80 | ok: true, |
| 81 | value: { micros: 500_000_000, useFull: false, raiseOnce: false }, |
| 82 | }); |
| 83 | assert.equal(parseSpendLimit({ mode: "fixed", limit: "0.5" }).ok, false); |
| 84 | assert.deepEqual(parseSpendLimit({ mode: "full" }), { ok: true, value: { micros: null, useFull: true, raiseOnce: false } }); |
| 85 | assert.deepEqual(parseSpendLimit({ mode: "raise" }), { ok: true, value: { micros: null, useFull: false, raiseOnce: true } }); |
| 86 | assert.deepEqual(parseSpendLimit({ mode: "raise", limit: "150" }), { |
| 87 | ok: true, |
| 88 | value: { micros: 150_000_000, useFull: false, raiseOnce: true }, |
| 89 | }); |
| 90 | }); |
| 91 | |
| 92 | test("the spend limit's range: up to the highest ceiling plus prepaid, then once to twice it, then ask", () => { |
| 93 | // A first month at $100, nothing prepaid, the raise unused: $100 alone, $200 once. |
| 94 | const range = spendRange({ availableMicros: 100_000_000, ceilingMicros: 100_000_000, raiseOnceMicros: 200_000_000, raisedAt: null }); |
| 95 | assert.deepEqual(range, { selfServeMicros: 100_000_000, raiseOnceMicros: 200_000_000, raisedAt: null }); |
| 96 | assert.equal(spendPath(80_000_000, range), "self"); |
| 97 | assert.equal(spendPath(100_000_000, range), "self"); |
| 98 | assert.equal(spendPath(150_000_000, range), "raise"); |
| 99 | assert.equal(spendPath(200_000_000, range), "raise"); |
| 100 | assert.equal(spendPath(200_000_001, range), "ask"); |
| 101 | // The raise used: only asking goes further. |
| 102 | const used = spendRange({ availableMicros: 200_000_000, ceilingMicros: 200_000_000, raiseOnceMicros: null, raisedAt: "2026-10-01T00:00:00Z" }); |
| 103 | assert.equal(used.raiseOnceMicros, null); |
| 104 | assert.equal(spendPath(300_000_000, used), "ask"); |
| 105 | // No ceiling: anything goes. |
| 106 | assert.equal(spendPath(1e12, spendRange({ availableMicros: null, ceilingMicros: null, raiseOnceMicros: null })), "self"); |
| 107 | // A raise no higher than what is available is no raise. |
| 108 | assert.equal(spendRange({ availableMicros: 300_000_000, ceilingMicros: 100_000_000, raiseOnceMicros: 300_000_000 }).raiseOnceMicros, null); |
| 109 | }); |
| 110 | |
| 111 | test("the plan card says where the workspace stands", () => { |
| 112 | const free = { plan: "free" as const, trialMicrosLeft: 0, trialVerified: false, firstMonth: false }; |
| 113 | assert.deepEqual(planStatus(null, free), { kind: "free", label: "Free" }); |
| 114 | assert.equal(planStatus(null, { ...free, trialVerified: true, trialMicrosLeft: 5_000_000 }).kind, "trial"); |
| 115 | assert.equal(planStatus(null, { ...free, trialVerified: true }).kind, "free"); |
| 116 | const subscription = { feature: "plan" as const, status: "active" as const, periodEnd: null, startedBy: "a", startedAt: "" }; |
| 117 | assert.deepEqual(planStatus({ on: true, subscription }, { ...free, plan: "paid", firstMonth: true }), { |
| 118 | kind: "paid", |
| 119 | label: "On the g1t plan, first month", |
| 120 | }); |
| 121 | assert.equal(planStatus({ on: true, subscription: { ...subscription, status: "canceling" } }, { ...free, plan: "paid" }).kind, "canceling"); |
| 122 | assert.equal(planStatus({ on: false, subscription: { ...subscription, status: "past_due" } }, free).kind, "past_due"); |
| 123 | assert.equal(planStatus({ on: true, included: true, subscription: null }, free).kind, "comped"); |
| 124 | assert.equal(planStatus(null, { ...free, plan: "internal" }).kind, "comped"); |
| 125 | assert.equal(planStatus(null, { ...free, plan: "enterprise" }).kind, "enterprise"); |
| 126 | }); |
| 127 | |
| 128 | test("the overview's Usage card reads the month by what pays", () => { |
| 129 | const usage = { |
| 130 | free: false, |
| 131 | spentMicros: 14_000_000, |
| 132 | usedMicros: 11_000_000, |
| 133 | byTask: [ |
| 134 | { key: "review", micros: 2_000_000, runs: 4 }, |
| 135 | { key: "implement", micros: 12_000_000, runs: 9 }, |
| 136 | { key: "plan", micros: 0, runs: 1 }, |
| 137 | ], |
| 138 | }; |
| 139 | const entitlements = { includedMicros: 10_000_000, includedUsedMicros: 10_000_000, trialMicrosLeft: 0 }; |
| 140 | const plan = usageGlance({ usage, status: { kind: "paid", label: "" }, entitlements, limit: { spentMicros: 4_000_000, spendLimitMicros: 200_000_000 }, trialMicros: 5_000_000 }); |
| 141 | assert.equal(plan.kind, "plan"); |
| 142 | assert.deepEqual(plan.credit, { label: "Included usage", usedMicros: 10_000_000, ofMicros: 10_000_000 }); |
| 143 | assert.deepEqual(plan.onDemand, { micros: 4_000_000, limitMicros: 200_000_000 }); |
| 144 | // Most first, and nothing that cost nothing. |
| 145 | assert.deepEqual(plan.lines.map((line) => line.label), ["Making changes", "Reviews"]); |
| 146 | const trial = usageGlance({ usage, status: { kind: "trial", label: "" }, entitlements: { ...entitlements, trialMicrosLeft: 1_500_000 }, limit: null, trialMicros: 5_000_000 }); |
| 147 | assert.deepEqual(trial.credit, { label: "Trial credit", usedMicros: 3_500_000, ofMicros: 5_000_000 }); |
| 148 | assert.equal(trial.onDemand, null); |
| 149 | assert.equal(usageGlance({ usage, status: { kind: "comped", label: "" }, entitlements, limit: null, trialMicros: 0 }).kind, "comped"); |
| 150 | assert.equal(usageGlance({ usage, status: { kind: "free", label: "" }, entitlements, limit: null, trialMicros: 0 }).kind, "forge"); |
| 151 | // While g1t charges nothing, usage is shown at cost. |
| 152 | const beta = usageGlance({ usage: { ...usage, free: true }, status: { kind: "paid", label: "" }, entitlements, limit: null, trialMicros: 0 }); |
| 153 | assert.equal(beta.kind, "beta"); |
| 154 | assert.equal(beta.spentMicros, 11_000_000); |
| 155 | }); |
| 156 | |
| 157 | test("a meter's share stays between 0 and 1", () => { |
| 158 | assert.equal(share(5, 10), 0.5); |
| 159 | assert.equal(share(15, 10), 1); |
| 160 | assert.equal(share(5, 0), 0); |
| 161 | assert.equal(share(5, null), 0); |
| 162 | }); |
| 163 | |
| 164 | test("after a card check the owner reads whether the trial started", () => { |
| 165 | assert.match(cardCheckResult({ trialVerified: true, trialMicrosLeft: 5_000_000 }, 5_000_000), /[$]5 trial is ready/); |
| 166 | assert.match(cardCheckResult({ trialVerified: true, trialMicrosLeft: 0 }, 5_000_000), /Prepaid cards can still pay for the plan/); |
| 167 | assert.match(cardCheckResult({ trialVerified: false, trialMicrosLeft: 0 }, 5_000_000), /did not finish/); |
| 168 | }); |
| 169 | |
| 170 | test("alerts read as sentences", () => { |
| 171 | const alert = { meter: "included", level: 90, usedMicros: 9_000_000, limitMicros: 10_000_000, message: "" }; |
| 172 | assert.equal(alertText(alert), "90% of the plan's included usage: $9.00 of $10.00."); |
| 173 | assert.equal(alertText({ ...alert, meter: "spend_limit", level: 100 }), "All of your spend limit: $9.00 of $10.00."); |
| 174 | assert.equal(alertTone(50), "ok"); |
| 175 | assert.equal(alertTone(75), "warning"); |
| 176 | assert.equal(alertTone(100), "stopped"); |
| 177 | }); |
| 178 | |
| 179 | test("a workspace needs attention when paused, on a spike, or at 90%", () => { |
| 180 | assert.equal(needsAttention(null), false); |
| 181 | assert.equal(needsAttention({ paused: null, spike: null, alerts: [] }), false); |
| 182 | assert.equal(needsAttention({ paused: "Spend spike", spike: null, alerts: [] }), true); |
| 183 | const spike = { id: "s", status: "continued", hourMicros: 1, averageMicros: 1, detectedAt: "" }; |
| 184 | assert.equal(needsAttention({ paused: null, spike, alerts: [] }), false); |
| 185 | assert.equal(needsAttention({ paused: null, spike: { ...spike, status: "open" }, alerts: [] }), true); |
| 186 | const at = (level: number) => ({ meter: "spend_limit", level, usedMicros: 0, limitMicros: 0, message: "" }); |
| 187 | assert.equal(needsAttention({ paused: null, spike: null, alerts: [at(75)] }), false); |
| 188 | assert.equal(needsAttention({ paused: null, spike: null, alerts: [at(90)] }), true); |
| 189 | }); |
| 190 | |
| 191 | test("a request says where it stands", () => { |
| 192 | const request = { |
| 193 | id: "r", |
| 194 | workspace: "acme", |
| 195 | kind: "limit", |
| 196 | amountMicros: 1, |
| 197 | reason: "", |
| 198 | expectedMonthlyMicros: 0, |
| 199 | status: "open", |
| 200 | createdBy: "a", |
| 201 | createdAt: "", |
| 202 | }; |
| 203 | assert.equal(requestStatus(request), "Waiting for an answer"); |
| 204 | assert.equal(requestStatus({ ...request, status: "approved", decidedMicros: 2_000_000_000 }), "Approved at $2,000"); |
| 205 | assert.equal(requestStatus({ ...request, status: "declined" }), "Declined"); |
| 206 | }); |
| 207 | |
| 208 | const METERS = [ |
| 209 | { key: "agents", label: "Agents & sandboxes", micros: 1_200_000, quantity: "4 runs" }, |
| 210 | { key: "builds", label: "Builds", micros: 0, quantity: null }, |
| 211 | { key: "requests", label: "Requests & CPU", micros: 0, quantity: null }, |
| 212 | { key: "domains", label: "Custom domains", micros: 120_000, quantity: "1 custom domain" }, |
| 213 | { key: "git_storage", label: "Git operations & storage", micros: 0, quantity: "1,200 git operations" }, |
| 214 | { key: "search_scans", label: "Search & security scans", micros: 3_000, quantity: null }, |
| 215 | ]; |
| 216 | |
| 217 | test("the plan shows every meter, with no quota beside any of them", () => { |
| 218 | const shown = shownMeters(METERS, true); |
| 219 | assert.deepEqual( |
| 220 | shown.map((m) => m.key), |
| 221 | ["agents", "builds", "requests", "domains", "git_storage", "search_scans"], |
| 222 | ); |
| 223 | for (const meter of shown) assert.ok(!/ of |apps up/i.test(`${meter.label} ${meter.quantity ?? ""}`)); |
| 224 | }); |
| 225 | |
| 226 | test("a free workspace sees only what it can use, and anything it used anyway", () => { |
| 227 | assert.deepEqual( |
| 228 | shownMeters(METERS, false).map((m) => m.key), |
| 229 | ["agents", "domains", "git_storage", "search_scans"], |
| 230 | ); |
| 231 | }); |
| 232 | |
| 233 | test("storage reads in powers of ten", () => { |
| 234 | assert.equal(gigabytes(1_000_000_000), "1 GB"); |
| 235 | assert.equal(gigabytes(1_500_000_000), "1.5 GB"); |
| 236 | assert.equal(gigabytes(500_000_000), "500 MB"); |
| 237 | }); |
| 238 | |
| 239 | test("kinds of work without words of their own are one Other, listed once and last", () => { |
| 240 | const slices = [ |
| 241 | { key: "check", micros: 5, runs: 50 }, |
| 242 | { key: "review", micros: 3, runs: 5 }, |
| 243 | { key: "other", micros: 1, runs: 7 }, |
| 244 | { key: "implement", micros: 9, runs: 4 }, |
| 245 | { key: "answer", micros: 2, runs: 11 }, |
| 246 | ]; |
| 247 | assert.deepEqual(foldTasks(slices), [ |
| 248 | { key: "review", micros: 3, runs: 5 }, |
| 249 | { key: "implement", micros: 9, runs: 4 }, |
| 250 | { key: "other", micros: 8, runs: 68 }, |
| 251 | ]); |
| 252 | assert.deepEqual(foldTasks([{ key: "plan", micros: 1, runs: 1 }]), [{ key: "plan", micros: 1, runs: 1 }]); |
| 253 | }); |