Skip to content
120 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { money } from "./money.ts";
5import { byProject, columns, defaultGrain, isStaff, monthSpan, pendingSentence, productStyle, quantity, receipt, resolveRange, ticks, usageCsv } from "./usage.ts";
6
7const axisMoney = (micros: number) => money(micros, { compact: true });
8
9const NOW = new Date("2026-10-08T15:00:00Z");
10
11test("periods resolve to whole UTC days, both ends included", () => {
12 assert.deepEqual(resolveRange(null, {}, NOW), { period: "cycle", from: "2026-10-01", until: "2026-10-08" });
13 // The current cycle is the one "this month" Home asks billing for.
14 assert.deepEqual(resolveRange("cycle", {}, NOW), { period: "cycle", ...monthSpan(NOW) });
15 assert.deepEqual(monthSpan(NOW), { from: "2026-10-01", until: "2026-10-08" });
16 assert.deepEqual(resolveRange("last_cycle", {}, NOW), { period: "last_cycle", from: "2026-09-01", until: "2026-09-30" });
17 assert.deepEqual(resolveRange("7d", {}, NOW), { period: "7d", from: "2026-10-02", until: "2026-10-08" });
18 assert.deepEqual(resolveRange("custom", { from: "2026-10-05", until: "2026-09-20" }, NOW), { period: "custom", from: "2026-09-20", until: "2026-10-05" });
19 // A custom range that is not days falls back to the cycle.
20 assert.equal(resolveRange("custom", { from: "yesterday", until: "2026-10-01" }, NOW).period, "cycle");
21 assert.equal(resolveRange("nonsense", {}, NOW).period, "cycle");
22});
23
24test("columns cover every day, stack products, and add up when cumulative", () => {
25 const days = [
26 { day: "2026-10-01", product: "deployments", micros: 2_000_000 },
27 { day: "2026-10-01", product: "sandboxes", micros: 500_000 },
28 { day: "2026-10-03", product: "deployments", micros: 1_000_000 },
29 ];
30 const daily = columns(days, "2026-10-01", "2026-10-03");
31 assert.deepEqual(daily.map((c) => c.total), [2_500_000, 0, 1_000_000]);
32 assert.equal(daily[0]!.parts.sandboxes, 500_000);
33 const running = columns(days, "2026-10-01", "2026-10-03", "day", true);
34 assert.deepEqual(running.map((c) => c.total), [2_500_000, 2_500_000, 3_500_000]);
35 assert.equal(running[2]!.parts.deployments, 3_000_000);
36 // Weeks start on Monday: Oct 1 2026 is a Thursday.
37 const weekly = columns(days, "2026-10-01", "2026-10-08", "week");
38 assert.deepEqual(weekly.map((c) => c.key), ["2026-09-28", "2026-10-05"]);
39 assert.equal(weekly[0]!.total, 3_500_000);
40 assert.equal(columns(days, "2026-09-15", "2026-10-03", "month").length, 2);
41 assert.equal(defaultGrain("2026-10-01", "2026-10-31"), "day");
42 assert.equal(defaultGrain("2026-07-01", "2026-10-01"), "week");
43});
44
45test("the money axis has clean ticks, never the same label twice", () => {
46 assert.deepEqual(ticks(0), [0]);
47 const t = ticks(3_320_000);
48 assert.equal(t[0], 0);
49 assert.ok(t[t.length - 1]! >= 3_320_000);
50 const labels = t.map(axisMoney);
51 assert.equal(new Set(labels).size, labels.length, labels.join(" "));
52 const small = ticks(4_000).map(axisMoney);
53 assert.equal(new Set(small).size, small.length, small.join(" "));
54 assert.equal(axisMoney(2_000_000), "$2");
55 assert.equal(axisMoney(1_500_000_000), "$1.5K");
56});
57
58test("quantities read in their units", () => {
59 assert.equal(quantity(1_234_567, "tokens"), "1.2M tokens");
60 assert.equal(quantity(3_725, "seconds"), "1h 2m");
61 assert.equal(quantity(504_000_000, "bytes"), "504 MB");
62 assert.equal(quantity(1, "entries"), "1 entry");
63});
64
65test("the receipt shows usage at price, then what paid, then what is charged", () => {
66 const lines = receipt(
67 { priceMicros: 3_320_000, discountMicros: 3_320_000, includedMicros: 0, creditsMicros: 0, chargedMicros: 0, pendingMicros: 0, costMicros: 0 },
68 100,
69 );
70 assert.deepEqual(lines.map((l) => l.label), ["Usage at price", "Discount (100%)", "Charged"]);
71});
72
73test("grouping by project adds a project's meters together", () => {
74 const meter = (label: string, parts: [string, number][]) => ({
75 key: label, label, product: "deployments", unit: "entries", quantity: 0, micros: 0, daily: [],
76 byProject: parts.map(([project, micros]) => ({ project, micros, quantity: 0 })),
77 });
78 const rows = byProject({
79 products: [{ key: "deployments", label: "Deployments", micros: 0, meters: [meter("Requests", [["acme/web", 300], ["acme/api", 500]]), meter("Domains", [["acme/web", 400]])] }],
80 });
81 assert.deepEqual(rows.map((r) => [r.project, r.micros]), [["acme/web", 700], ["acme/api", 500]]);
82});
83
84test("the CSV has a row per day and meter with anything on it", () => {
85 const csv = usageCsv({
86 from: "2026-10-01",
87 until: "2026-10-02",
88 products: [{ key: "sandboxes", label: "Sandboxes", micros: 0, meters: [{ key: "sandbox_time", label: "Sandbox time", product: "sandboxes", unit: "seconds", quantity: 0, micros: 0, daily: [0, 1_500_000], byProject: [] }] }],
89 });
90 assert.equal(csv, "day,product,meter,usd\n2026-10-02,Sandboxes,Sandbox time,1.500000\n");
91});
92
93test("test-mode hints are for g1t's own people", () => {
94 assert.ok(isStaff({ workspaces: [{ slug: "flagon-io" }] }));
95 assert.ok(!isStaff({ workspaces: [{ slug: "acme" }] }));
96 assert.ok(!isStaff(null));
97});
98
99test("the pending note says what the close will charge, with the fraction of a cent that explains a gap", () => {
100 // A comped workspace: $0.009 pending, nothing of it charged.
101 assert.equal(
102 pendingSentence({ pendingMicros: 9_000, pendingChargedMicros: 0 }),
103 "$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.",
104 );
105 assert.equal(
106 pendingSentence({ pendingMicros: 9_000, pendingChargedMicros: 6_300 }),
107 "$0.009 of it is metered this month and not yet closed; $0.0063 of it will be charged when the month closes, after your discount and included usage.",
108 );
109 assert.equal(
110 pendingSentence({ pendingMicros: 1_340_000, pendingChargedMicros: 1_340_000 }),
111 "$1.34 of it is metered this month and not yet closed; all of it will be charged when the month closes, after your discount and included usage.",
112 );
113 // An older billing, with no split yet.
114 assert.equal(pendingSentence({ pendingMicros: 9_000 }), "$0.009 of it is metered this month and not yet closed; it is charged when the month closes.");
115});
116
117test("products no longer offered still read in older usage", () => {
118 assert.equal(productStyle("agent").label, "Agent");
119 assert.equal(productStyle("sandboxes").label, "Sandboxes");
120});