| 1 | /** |
| 2 | * One month, one number: Home's "this month so far" and Usage's "This |
| 3 | * range" show the same figure for the same month, formatted the same way. Each surface's arithmetic is the |
| 4 | * pure function its loader or component calls; the loaders only fetch. |
| 5 | */ |
| 6 | import assert from "node:assert/strict"; |
| 7 | import { test } from "node:test"; |
| 8 | |
| 9 | import type { Statement, UsageReport } from "@g1t/contracts"; |
| 10 | |
| 11 | import { spendIn } from "./home.ts"; |
| 12 | import { money } from "./money.ts"; |
| 13 | import { monthSpan, pendingSentence, receipt, resolveRange } from "./usage.ts"; |
| 14 | |
| 15 | const NOW = new Date("2026-10-10T18:30:00Z"); |
| 16 | |
| 17 | /** A comped workspace in October: $10.401 on the ledger, $0.009 of storage metered and not yet closed, a 100% discount. */ |
| 18 | const report = { |
| 19 | from: "2026-10-01", |
| 20 | until: "2026-10-10", |
| 21 | totals: { |
| 22 | priceMicros: 10_410_000, |
| 23 | discountMicros: 10_410_000, |
| 24 | includedMicros: 0, |
| 25 | creditsMicros: 0, |
| 26 | chargedMicros: 0, |
| 27 | pendingMicros: 9_000, |
| 28 | pendingChargedMicros: 0, |
| 29 | costMicros: 8_670_000, |
| 30 | }, |
| 31 | days: [], |
| 32 | products: [], |
| 33 | projects: [], |
| 34 | discountPercent: 100, |
| 35 | aiCreditMicros: 0, |
| 36 | creditMicros: 0, |
| 37 | plan: "paid", |
| 38 | free: false, |
| 39 | } satisfies UsageReport; |
| 40 | |
| 41 | /** The same month's statement: the ledger's lines only, which is why Home no longer reads its total for the month. */ |
| 42 | const statement = { |
| 43 | groups: [{ key: "2026-10-09", label: "", chargedMicros: 0, lines: [{ kind: "Sandboxes", count: 3, chargedMicros: 0, costMicros: 8_670_000, priceMicros: 10_401_000 }] }], |
| 44 | totals: { chargedMicros: 0, paidMicros: 0, costMicros: 8_670_000, entries: 3, priceMicros: 10_401_000 }, |
| 45 | } satisfies Pick<Statement, "groups" | "totals">; |
| 46 | |
| 47 | test("every page asks billing for the same month", () => { |
| 48 | const month = monthSpan(NOW); |
| 49 | assert.deepEqual(month, { from: "2026-10-01", until: "2026-10-10" }); |
| 50 | // Home (lib/home.server.ts `loadSpend`) and Usage (routes/workspace/usage.tsx `readFilters`). |
| 51 | const { from, until } = resolveRange("cycle", {}, NOW); |
| 52 | assert.deepEqual({ from, until }, month); |
| 53 | }); |
| 54 | |
| 55 | test("Home and Usage show one figure for the month", () => { |
| 56 | // Home's "this month so far" (lib/home.server.ts `loadSpend` hands `spendIn` the report's usage at price; components/home.tsx `SpendCard`). |
| 57 | const home = spendIn([statement], { from: Date.parse("2026-10-09T00:00:00Z"), now: NOW.getTime() }, report.totals.priceMicros); |
| 58 | const homeMonth = money(home.monthMicros!); |
| 59 | // Usage's "This range", first line (components/usage.tsx `IncludedAndCredit`: `receipt`, then `money(line.micros)`). |
| 60 | const lines = receipt(report.totals, report.discountPercent); |
| 61 | const usage = money(lines[0]!.micros); |
| 62 | |
| 63 | assert.equal(homeMonth, "$10.41"); |
| 64 | assert.equal(usage, homeMonth); |
| 65 | |
| 66 | // The statement alone, which Home used to show, is the $0.009 short that read as a rounding difference. |
| 67 | assert.equal(money(statement.totals.priceMicros), "$10.40"); |
| 68 | |
| 69 | // The rest of Usage's receipt for the same report, as the page writes each line. |
| 70 | assert.deepEqual( |
| 71 | lines.map((line) => `${line.label} ${line.minus ? "−" : ""}${money(line.micros)}`), |
| 72 | ["Usage at price $10.41", "Discount (100%) −$10.41", "Charged $0.00"], |
| 73 | ); |
| 74 | assert.equal( |
| 75 | pendingSentence(report.totals), |
| 76 | "$0.009 of it is metered this month and not yet closed; nothing will be charged when the month closes, after your discount and included usage.", |
| 77 | ); |
| 78 | }); |