| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { CONFIRM_OVER_MICROS, CREDIT_PRESETS, grantSummary, parseCreditAmount, parseCreditForm, parseExpiry, parseMonth, shortDay } from "./credits.ts"; |
| 5 | import { usd } from "./money.ts"; |
| 6 | |
| 7 | const NOW = new Date("2026-10-07T12:00:00Z"); |
| 8 | |
| 9 | function form(values: Record<string, string>): FormData { |
| 10 | const data = new FormData(); |
| 11 | for (const [name, value] of Object.entries(values)) data.set(name, value); |
| 12 | return data; |
| 13 | } |
| 14 | |
| 15 | test("the presets are $10, $20, $25, $50 and $100, each one step", () => { |
| 16 | assert.deepEqual([...CREDIT_PRESETS], [10, 20, 25, 50, 100]); |
| 17 | for (const dollars of CREDIT_PRESETS) { |
| 18 | assert.deepEqual(parseCreditAmount(String(dollars), ""), { ok: true, value: dollars * 1_000_000 }); |
| 19 | assert.ok(dollars * 1_000_000 <= CONFIRM_OVER_MICROS, "no preset needs the slug typed"); |
| 20 | } |
| 21 | // Only the presets offered; anything else is Custom. |
| 22 | assert.equal(parseCreditAmount("15", "").ok, false); |
| 23 | assert.deepEqual(parseCreditAmount("custom", "$12.40"), { ok: true, value: 12_400_000 }); |
| 24 | assert.equal(parseCreditAmount("custom", "").ok, false); |
| 25 | assert.equal(parseCreditAmount("custom", "0").ok, false); |
| 26 | assert.equal(parseCreditAmount("custom", "10000.01").ok, false); |
| 27 | assert.deepEqual(parseCreditAmount("custom", "10,000"), { ok: true, value: 10_000_000_000 }); |
| 28 | }); |
| 29 | |
| 30 | test("an expiry is never, 30, 90 or 365 days, or the end of a chosen day", () => { |
| 31 | assert.deepEqual(parseExpiry("none", "", NOW), { ok: true, value: null }); |
| 32 | assert.deepEqual(parseExpiry("", "", NOW), { ok: true, value: null }); |
| 33 | assert.deepEqual(parseExpiry("30", "", NOW), { ok: true, value: "2026-11-06T23:59:59Z" }); |
| 34 | assert.deepEqual(parseExpiry("90", "", NOW), { ok: true, value: "2027-01-05T23:59:59Z" }); |
| 35 | assert.deepEqual(parseExpiry("365", "", NOW), { ok: true, value: "2027-10-07T23:59:59Z" }); |
| 36 | assert.deepEqual(parseExpiry("date", "2027-01-05", NOW), { ok: true, value: "2027-01-05T23:59:59Z" }); |
| 37 | assert.equal(parseExpiry("date", "", NOW).ok, false); |
| 38 | assert.equal(parseExpiry("date", "2026-10-01", NOW).ok, false); |
| 39 | assert.equal(parseExpiry("date", "2040-01-01", NOW).ok, false); |
| 40 | assert.equal(parseExpiry("7", "", NOW).ok, false); |
| 41 | }); |
| 42 | |
| 43 | test("a small credit is one step; over $100 the slug is typed out", () => { |
| 44 | const small = parseCreditForm(form({ preset: "25", kind: "promotional", expires: "90", note: "Welcome to g1t" }), "acme", NOW); |
| 45 | assert.deepEqual(small, { |
| 46 | ok: true, |
| 47 | value: { amountMicros: 25_000_000, kind: "promotional", expiresAt: "2027-01-05T23:59:59Z", refundFor: null, refundDay: null, note: "Welcome to g1t" }, |
| 48 | }); |
| 49 | // $100 is still one step. |
| 50 | assert.ok(parseCreditForm(form({ preset: "100", kind: "goodwill", note: "Sorry" }), "acme", NOW).ok); |
| 51 | const big = form({ preset: "custom", amount: "250", kind: "goodwill", note: "Sorry about the outage" }); |
| 52 | const refused = parseCreditForm(big, "acme", NOW); |
| 53 | assert.equal(refused.ok, false); |
| 54 | assert.match(refused.ok ? "" : refused.error, /type the workspace's slug, acme/); |
| 55 | big.set("confirmation", "acme"); |
| 56 | assert.equal(parseCreditForm(big, "acme", NOW).ok, true); |
| 57 | }); |
| 58 | |
| 59 | test("a credit needs a kind and a note", () => { |
| 60 | assert.equal(parseCreditForm(form({ preset: "10", note: "x" }), "acme", NOW).ok, false); |
| 61 | assert.equal(parseCreditForm(form({ preset: "10", kind: "gift", note: "x" }), "acme", NOW).ok, false); |
| 62 | assert.equal(parseCreditForm(form({ preset: "10", kind: "goodwill" }), "acme", NOW).ok, false); |
| 63 | }); |
| 64 | |
| 65 | test("a refund says what it is for, never expires, and refunds a day that was", () => { |
| 66 | const refund = parseCreditForm( |
| 67 | form({ preset: "20", kind: "refund", refundFor: "the failed runs on Oct 2", refundDay: "2026-10-02", expires: "30", note: "Sorry" }), |
| 68 | "acme", |
| 69 | NOW, |
| 70 | ); |
| 71 | // The expiry chosen before switching to Refund is ignored. |
| 72 | assert.deepEqual(refund, { |
| 73 | ok: true, |
| 74 | value: { amountMicros: 20_000_000, kind: "refund", expiresAt: null, refundFor: "the failed runs on Oct 2", refundDay: "2026-10-02", note: "Sorry" }, |
| 75 | }); |
| 76 | assert.equal(parseCreditForm(form({ preset: "20", kind: "refund", note: "Sorry" }), "acme", NOW).ok, false); |
| 77 | assert.equal(parseCreditForm(form({ preset: "20", kind: "refund", refundFor: "x", refundDay: "2026-10-09", note: "Sorry" }), "acme", NOW).ok, false); |
| 78 | assert.equal(parseCreditForm(form({ preset: "20", kind: "refund", refundFor: "x", refundDay: "2026-02-30", note: "Sorry" }), "acme", NOW).ok, false); |
| 79 | }); |
| 80 | |
| 81 | test("a grant reads in a line, as the Billing page says it", () => { |
| 82 | const open = { amountMicros: 25_000_000, leftMicros: 12_400_000, expiresAt: "2027-01-05T23:59:59Z", state: "open" as const }; |
| 83 | assert.equal(grantSummary(open, usd, NOW), "$25.00 credit, $12.40 left, expires Jan 5, 2027"); |
| 84 | assert.equal(grantSummary({ ...open, expiresAt: "2026-11-05T23:59:59Z" }, usd, NOW), "$25.00 credit, $12.40 left, expires Nov 5"); |
| 85 | assert.equal(grantSummary({ ...open, expiresAt: null }, usd), "$25.00 credit, $12.40 left"); |
| 86 | assert.equal(grantSummary({ ...open, state: "used", leftMicros: 0 }, usd), "$25.00 credit, all used"); |
| 87 | assert.equal(grantSummary({ ...open, state: "expired", leftMicros: 0 }, usd), "$25.00 credit, expired"); |
| 88 | assert.equal(shortDay("2026-11-05T23:59:59Z", NOW), "Nov 5"); |
| 89 | }); |
| 90 | |
| 91 | test("a month filter is a month", () => { |
| 92 | assert.equal(parseMonth("2026-10"), "2026-10"); |
| 93 | assert.equal(parseMonth("2026-13"), null); |
| 94 | assert.equal(parseMonth(null), null); |
| 95 | }); |