Skip to content

g1t/apps/web/app/lib/billing.test.ts

308 lines15,839 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
Usage lists Other once: kinds of work without words of their own are added into it5 foldTasks,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas6 gigabytes,
7 shownMeters,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look8 alertText,
9 alertTone,
10 cardCheckResult,
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging11 creditLine,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look12 dollars,
13 needsAttention,
14 usageGlance,
15 parseCaps,
16 parseLimitRequest,
17 parsePrepay,
18 parseSpendLimit,
19 planStatus,
20 readDollars,
21 requestStatus,
22 share,
23 spendPath,
24 spendRange,
25 wholeDollars,
26} from "./billing.ts";
27
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging28test("a credit from g1t reads in a line, with what is left and when it expires", () => {
29 const now = new Date("2026-10-07T12:00:00Z");
30 const grant = { amountMicros: 25_000_000, leftMicros: 12_400_000, expiresAt: "2027-01-05T23:59:59Z", state: "open" as const };
31 assert.equal(creditLine(grant, now), "$25.00 credit, $12.40 left, expires Jan 5, 2027");
32 assert.equal(creditLine({ ...grant, expiresAt: "2026-12-05T23:59:59Z" }, now), "$25.00 credit, $12.40 left, expires Dec 5");
33 assert.equal(creditLine({ ...grant, expiresAt: null }, now), "$25.00 credit, $12.40 left");
34 assert.equal(creditLine({ ...grant, state: "used", leftMicros: 0 }, now), "$25.00 credit, all used");
35 assert.equal(creditLine({ ...grant, state: "revoked", leftMicros: 0 }, now), "$25.00 credit, withdrawn");
36});
37
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look38test("money reads as dollars", () => {
39 assert.equal(dollars(9_500_000), "$9.50");
40 assert.equal(dollars(-1_250_000), "−$1.25");
41 assert.equal(wholeDollars(1_000_000_000), "$1,000");
42 assert.equal(wholeDollars(100_000), "$0.10");
43 assert.equal(readDollars("$1,000"), 1_000_000_000);
44 assert.equal(readDollars(""), null);
45 assert.equal(readDollars("lots"), null);
46});
47
48test("a prepayment is $25 or more, by card up to $10,000, by bank transfer from $1,000", () => {
49 assert.deepEqual(parsePrepay({ amount: "100" }), { ok: true, value: { amountCents: 10_000, method: "card" } });
50 assert.deepEqual(parsePrepay({ amount: "100", custom: "250" }), {
51 ok: true,
52 value: { amountCents: 25_000, method: "card" },
53 });
54 assert.equal(parsePrepay({ custom: "10" }).ok, false);
55 assert.equal(parsePrepay({ custom: "20000" }).ok, false);
56 assert.equal(parsePrepay({ custom: "500", method: "bank_transfer" }).ok, false);
57 assert.deepEqual(parsePrepay({ custom: "20000", method: "bank_transfer" }), {
58 ok: true,
59 value: { amountCents: 2_000_000, method: "bank_transfer" },
60 });
61 assert.equal(parsePrepay({}).ok, false);
62});
63
64test("caps stay in range, and empty goes back to the default", () => {
65 assert.deepEqual(parseCaps({ run: "", issue: "" }), { ok: true, value: { runCapMicros: null, issueCapMicros: null } });
66 assert.deepEqual(parseCaps({ run: "0.10", issue: "1000" }), {
67 ok: true,
68 value: { runCapMicros: 100_000, issueCapMicros: 1_000_000_000 },
69 });
70 assert.equal(parseCaps({ run: "0.05" }).ok, false);
71 assert.equal(parseCaps({ issue: "2000" }).ok, false);
72 assert.equal(parseCaps({ run: "two" }).ok, false);
73});
74
75test("a limit request needs an amount, a reason and a monthly spend; an overage only a reason", () => {
76 assert.deepEqual(parseLimitRequest({ kind: "limit", amount: "5000", reason: " A launch ", expected: "3000" }), {
77 ok: true,
78 value: { kind: "limit", amountMicros: 5_000_000_000, reason: "A launch", expectedMonthlyMicros: 3_000_000_000 },
79 });
80 assert.equal(parseLimitRequest({ kind: "limit", amount: "5000", reason: "" , expected: "1" }).ok, false);
81 assert.equal(parseLimitRequest({ kind: "limit", reason: "x", expected: "1" }).ok, false);
82 assert.deepEqual(parseLimitRequest({ kind: "overage", reason: "A loop ran all night" }), {
83 ok: true,
84 value: { kind: "overage", amountMicros: 0, reason: "A loop ran all night", expectedMonthlyMicros: 0 },
85 });
86});
87
88test("a spend limit is automatic, fixed, the full amount available, or the one-time raise", () => {
89 assert.deepEqual(parseSpendLimit({ mode: "automatic" }), { ok: true, value: { micros: null, useFull: false, raiseOnce: false } });
90 assert.deepEqual(parseSpendLimit({ mode: "fixed", limit: "500" }), {
91 ok: true,
92 value: { micros: 500_000_000, useFull: false, raiseOnce: false },
93 });
94 assert.equal(parseSpendLimit({ mode: "fixed", limit: "0.5" }).ok, false);
95 assert.deepEqual(parseSpendLimit({ mode: "full" }), { ok: true, value: { micros: null, useFull: true, raiseOnce: false } });
96 assert.deepEqual(parseSpendLimit({ mode: "raise" }), { ok: true, value: { micros: null, useFull: false, raiseOnce: true } });
97 assert.deepEqual(parseSpendLimit({ mode: "raise", limit: "150" }), {
98 ok: true,
99 value: { micros: 150_000_000, useFull: false, raiseOnce: true },
100 });
101});
102
103test("the spend limit's range: up to the highest ceiling plus prepaid, then once to twice it, then ask", () => {
104 // A first month at $100, nothing prepaid, the raise unused: $100 alone, $200 once.
105 const range = spendRange({ availableMicros: 100_000_000, ceilingMicros: 100_000_000, raiseOnceMicros: 200_000_000, raisedAt: null });
106 assert.deepEqual(range, { selfServeMicros: 100_000_000, raiseOnceMicros: 200_000_000, raisedAt: null });
107 assert.equal(spendPath(80_000_000, range), "self");
108 assert.equal(spendPath(100_000_000, range), "self");
109 assert.equal(spendPath(150_000_000, range), "raise");
110 assert.equal(spendPath(200_000_000, range), "raise");
111 assert.equal(spendPath(200_000_001, range), "ask");
112 // The raise used: only asking goes further.
113 const used = spendRange({ availableMicros: 200_000_000, ceilingMicros: 200_000_000, raiseOnceMicros: null, raisedAt: "2026-10-01T00:00:00Z" });
114 assert.equal(used.raiseOnceMicros, null);
115 assert.equal(spendPath(300_000_000, used), "ask");
116 // No ceiling: anything goes.
117 assert.equal(spendPath(1e12, spendRange({ availableMicros: null, ceilingMicros: null, raiseOnceMicros: null })), "self");
118 // A raise no higher than what is available is no raise.
119 assert.equal(spendRange({ availableMicros: 300_000_000, ceilingMicros: 100_000_000, raiseOnceMicros: 300_000_000 }).raiseOnceMicros, null);
120});
121
122test("the plan card says where the workspace stands", () => {
123 const free = { plan: "free" as const, trialMicrosLeft: 0, trialVerified: false, firstMonth: false };
124 assert.deepEqual(planStatus(null, free), { kind: "free", label: "Free" });
125 assert.equal(planStatus(null, { ...free, trialVerified: true, trialMicrosLeft: 5_000_000 }).kind, "trial");
126 assert.equal(planStatus(null, { ...free, trialVerified: true }).kind, "free");
127 const subscription = { feature: "plan" as const, status: "active" as const, periodEnd: null, startedBy: "a", startedAt: "" };
128 assert.deepEqual(planStatus({ on: true, subscription }, { ...free, plan: "paid", firstMonth: true }), {
129 kind: "paid",
130 label: "On the g1t plan, first month",
131 });
132 assert.equal(planStatus({ on: true, subscription: { ...subscription, status: "canceling" } }, { ...free, plan: "paid" }).kind, "canceling");
133 assert.equal(planStatus({ on: false, subscription: { ...subscription, status: "past_due" } }, free).kind, "past_due");
134 assert.equal(planStatus({ on: true, included: true, subscription: null }, free).kind, "comped");
135 assert.equal(planStatus(null, { ...free, plan: "internal" }).kind, "comped");
136 assert.equal(planStatus(null, { ...free, plan: "enterprise" }).kind, "enterprise");
137});
138
139test("the overview's Usage card reads the month by what pays", () => {
140 const usage = {
141 free: false,
142 spentMicros: 14_000_000,
143 usedMicros: 11_000_000,
144 byTask: [
145 { key: "review", micros: 2_000_000, runs: 4 },
146 { key: "implement", micros: 12_000_000, runs: 9 },
147 { key: "plan", micros: 0, runs: 1 },
148 ],
149 };
150 const entitlements = { includedMicros: 10_000_000, includedUsedMicros: 10_000_000, trialMicrosLeft: 0 };
151 const plan = usageGlance({ usage, status: { kind: "paid", label: "" }, entitlements, limit: { spentMicros: 4_000_000, spendLimitMicros: 200_000_000 }, trialMicros: 5_000_000 });
152 assert.equal(plan.kind, "plan");
153 assert.deepEqual(plan.credit, { label: "Included usage", usedMicros: 10_000_000, ofMicros: 10_000_000 });
154 assert.deepEqual(plan.onDemand, { micros: 4_000_000, limitMicros: 200_000_000 });
155 // Most first, and nothing that cost nothing.
156 assert.deepEqual(plan.lines.map((line) => line.label), ["Making changes", "Reviews"]);
157 const trial = usageGlance({ usage, status: { kind: "trial", label: "" }, entitlements: { ...entitlements, trialMicrosLeft: 1_500_000 }, limit: null, trialMicros: 5_000_000 });
158 assert.deepEqual(trial.credit, { label: "Trial credit", usedMicros: 3_500_000, ofMicros: 5_000_000 });
159 assert.equal(trial.onDemand, null);
160 assert.equal(usageGlance({ usage, status: { kind: "comped", label: "" }, entitlements, limit: null, trialMicros: 0 }).kind, "comped");
161 assert.equal(usageGlance({ usage, status: { kind: "free", label: "" }, entitlements, limit: null, trialMicros: 0 }).kind, "forge");
162 // While g1t charges nothing, usage is shown at cost.
163 const beta = usageGlance({ usage: { ...usage, free: true }, status: { kind: "paid", label: "" }, entitlements, limit: null, trialMicros: 0 });
164 assert.equal(beta.kind, "beta");
165 assert.equal(beta.spentMicros, 11_000_000);
166});
167
168test("a meter's share stays between 0 and 1", () => {
169 assert.equal(share(5, 10), 0.5);
170 assert.equal(share(15, 10), 1);
171 assert.equal(share(5, 0), 0);
172 assert.equal(share(5, null), 0);
173});
174
175test("after a card check the owner reads whether the trial started", () => {
176 assert.match(cardCheckResult({ trialVerified: true, trialMicrosLeft: 5_000_000 }, 5_000_000), /[$]5 trial is ready/);
177 assert.match(cardCheckResult({ trialVerified: true, trialMicrosLeft: 0 }, 5_000_000), /Prepaid cards can still pay for the plan/);
178 assert.match(cardCheckResult({ trialVerified: false, trialMicrosLeft: 0 }, 5_000_000), /did not finish/);
179});
180
181test("alerts read as sentences", () => {
182 const alert = { meter: "included", level: 90, usedMicros: 9_000_000, limitMicros: 10_000_000, message: "" };
183 assert.equal(alertText(alert), "90% of the plan's included usage: $9.00 of $10.00.");
184 assert.equal(alertText({ ...alert, meter: "spend_limit", level: 100 }), "All of your spend limit: $9.00 of $10.00.");
185 assert.equal(alertTone(50), "ok");
186 assert.equal(alertTone(75), "warning");
187 assert.equal(alertTone(100), "stopped");
188});
189
190test("a workspace needs attention when paused, on a spike, or at 90%", () => {
191 assert.equal(needsAttention(null), false);
192 assert.equal(needsAttention({ paused: null, spike: null, alerts: [] }), false);
193 assert.equal(needsAttention({ paused: "Spend spike", spike: null, alerts: [] }), true);
194 const spike = { id: "s", status: "continued", hourMicros: 1, averageMicros: 1, detectedAt: "" };
195 assert.equal(needsAttention({ paused: null, spike, alerts: [] }), false);
196 assert.equal(needsAttention({ paused: null, spike: { ...spike, status: "open" }, alerts: [] }), true);
197 const at = (level: number) => ({ meter: "spend_limit", level, usedMicros: 0, limitMicros: 0, message: "" });
198 assert.equal(needsAttention({ paused: null, spike: null, alerts: [at(75)] }), false);
199 assert.equal(needsAttention({ paused: null, spike: null, alerts: [at(90)] }), true);
200});
201
202test("a request says where it stands", () => {
203 const request = {
204 id: "r",
205 workspace: "acme",
206 kind: "limit",
207 amountMicros: 1,
208 reason: "",
209 expectedMonthlyMicros: 0,
210 status: "open",
211 createdBy: "a",
212 createdAt: "",
213 };
214 assert.equal(requestStatus(request), "Waiting for an answer");
215 assert.equal(requestStatus({ ...request, status: "approved", decidedMicros: 2_000_000_000 }), "Approved at $2,000");
216 assert.equal(requestStatus({ ...request, status: "declined" }), "Declined");
217});
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas218
219const METERS = [
220 { key: "agents", label: "Agents & sandboxes", micros: 1_200_000, quantity: "4 runs" },
221 { key: "builds", label: "Builds", micros: 0, quantity: null },
222 { key: "requests", label: "Requests & CPU", micros: 0, quantity: null },
223 { key: "domains", label: "Custom domains", micros: 120_000, quantity: "1 custom domain" },
224 { key: "git_storage", label: "Git operations & storage", micros: 0, quantity: "1,200 git operations" },
225 { key: "search_scans", label: "Search & security scans", micros: 3_000, quantity: null },
226];
227
228test("the plan shows every meter, with no quota beside any of them", () => {
229 const shown = shownMeters(METERS, true);
230 assert.deepEqual(
231 shown.map((m) => m.key),
232 ["agents", "builds", "requests", "domains", "git_storage", "search_scans"],
233 );
234 for (const meter of shown) assert.ok(!/ of |apps up/i.test(`${meter.label} ${meter.quantity ?? ""}`));
235});
236
237test("a free workspace sees only what it can use, and anything it used anyway", () => {
238 assert.deepEqual(
239 shownMeters(METERS, false).map((m) => m.key),
240 ["agents", "domains", "git_storage", "search_scans"],
241 );
242});
243
244test("storage reads in powers of ten", () => {
245 assert.equal(gigabytes(1_000_000_000), "1 GB");
246 assert.equal(gigabytes(1_500_000_000), "1.5 GB");
247 assert.equal(gigabytes(500_000_000), "500 MB");
248});
Usage lists Other once: kinds of work without words of their own are added into it249
250test("kinds of work without words of their own are one Other, listed once and last", () => {
251 const slices = [
252 { key: "check", micros: 5, runs: 50 },
253 { key: "review", micros: 3, runs: 5 },
254 { key: "other", micros: 1, runs: 7 },
255 { key: "implement", micros: 9, runs: 4 },
256 { key: "answer", micros: 2, runs: 11 },
257 ];
258 assert.deepEqual(foldTasks(slices), [
259 { key: "review", micros: 3, runs: 5 },
260 { key: "implement", micros: 9, runs: 4 },
261 { key: "other", micros: 8, runs: 68 },
262 ]);
263 assert.deepEqual(foldTasks([{ key: "plan", micros: 1, runs: 1 }]), [{ key: "plan", micros: 1, runs: 1 }]);
264});
Usage, Billing settings and prepaid AI credit; fixes from the UX audit265
266test("AI credit is bought in whole dollars from $10 to $1,000", async () => {
267 const { parseAiPurchase } = await import("./billing.ts");
268 assert.deepEqual(parseAiPurchase({ amount: "25" }), { ok: true, value: { amountCents: 2_500 } });
269 assert.deepEqual(parseAiPurchase({ amount: "custom", custom: "$150" }), { ok: true, value: { amountCents: 15_000 } });
270 assert.equal(parseAiPurchase({ amount: "custom", custom: "5" }).ok, false);
271 assert.equal(parseAiPurchase({ amount: "custom", custom: "12.50" }).ok, false);
272 assert.equal(parseAiPurchase({ amount: "custom", custom: "" }).ok, false);
273});
274
275test("auto-reload's amounts are checked before billing sees them", async () => {
276 const { parseAiReload } = await import("./billing.ts");
277 assert.deepEqual(parseAiReload({ enabled: "on", threshold: "10", target: "25", monthly: "100" }), {
278 ok: true,
279 value: { enabled: true, thresholdMicros: 10_000_000, targetMicros: 25_000_000, monthlyMaxMicros: 100_000_000 },
280 });
281 assert.equal(parseAiReload({ threshold: "10", target: "15", monthly: "100" }).ok, false);
282 assert.equal(parseAiReload({ threshold: "10", target: "25", monthly: "10" }).ok, false);
283 assert.equal(parseAiReload({ threshold: "10", target: "25.5", monthly: "100" }).ok, false);
284});
285
286test("budget alerts keep the levels ticked, highest first", async () => {
287 const { parseBudgetAlerts } = await import("./billing.ts");
288 assert.deepEqual(parseBudgetAlerts({ alerts: ["50", "100", "75", "12"], pause: "on", webhook: "" }), {
289 ok: true,
290 value: { alerts: [100, 75, 50], pauseAtLimit: true, webhook: null },
291 });
292 assert.equal(parseBudgetAlerts({ alerts: [], webhook: "http://x.test" }).ok, false);
293 assert.deepEqual(parseBudgetAlerts({ alerts: [], webhook: "https://hooks.acme.test/g1t" }).ok, true);
294});
295
296test("invoice details read from the form, with the country in capitals", async () => {
297 const { parseInvoiceDetails } = await import("./billing.ts");
298 const form = new FormData();
299 form.set("email", "billing@acme.test");
300 form.set("country", "de");
301 form.set("taxIdType", "eu_vat");
302 form.set("taxId", "DE123456789");
303 const parsed = parseInvoiceDetails(form);
304 assert.ok(parsed.ok);
305 assert.equal(parsed.value.address.country, "DE");
306 form.set("taxId", "");
307 assert.equal(parseInvoiceDetails(form).ok, false);
308});