Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

124 linesCodeBlameRaw
1/**
2 * A workspace's meter on Node's SQLite standing in for the Durable
3 * Object's: its projects share one day's share, and on the plan the
4 * month's total is reported to billing until billing takes it.
5 */
6import assert from "node:assert/strict";
7import { DatabaseSync } from "node:sqlite";
8import test from "node:test";
9
10import { NOTE_AFTER_MS, NOTE_RETRY_MS, WorkspaceMeter, noteAt } from "./meter.ts";
11
12function state() {
13 const db = new DatabaseSync(":memory:");
14 const box = { alarm: null as number | null };
15 const sql = {
16 exec(query: string, ...args: unknown[]) {
17 if (
18 !args.length &&
19 query
20 .trim()
21 .split(";")
22 .filter((q) => q.trim()).length > 1
23 ) {
24 db.exec(query);
25 return { toArray: () => [] };
26 }
27 const rows = db.prepare(query).all(...(args as never[]));
28 return { toArray: () => rows };
29 },
30 };
31 const s = {
32 storage: {
33 sql,
34 getAlarm: async () => box.alarm,
35 setAlarm: async (t: number) => void (box.alarm = t),
36 },
37 } as unknown as DurableObjectState;
38 return { s, box };
39}
40
41const NOW = Date.UTC(2026, 9, 13, 12);
42
43test("a free workspace's projects share one day's share, and a new day starts over", async () => {
44 const { s, box } = state();
45 const meter = new WorkspaceMeter(s);
46 await meter.add("acme", 60, NOW);
47 await meter.add("acme", 30, NOW);
48 assert.equal(meter.todayBytes(NOW), 90);
49 assert.equal(meter.over(10, 100, NOW), false);
50 assert.equal(meter.over(11, 100, NOW), true);
51 assert.equal(meter.over(1e9, null, NOW), false);
52 assert.equal(meter.over(0, 100, NOW + 24 * 3600_000), false);
53 // Free: nothing is reported, so no alarm.
54 assert.equal(box.alarm, null);
55});
56
57test("the plan last said holds; never said, it is unknown", () => {
58 const meter = new WorkspaceMeter(state().s);
59 assert.equal(meter.standing(), null);
60 meter.setStanding("paid");
61 assert.equal(meter.standing(), "paid");
62 meter.setStanding("held");
63 assert.equal(meter.standing(), "held");
64});
65
66test("on the plan, the month's total is reported, again when billing did not take it, and once it has, not again", async () => {
67 const { s, box } = state();
68 const meter = new WorkspaceMeter(s);
69 meter.setStanding("paid");
70 await meter.add("acme", 500, NOW);
71 assert.equal(box.alarm, NOW + NOTE_AFTER_MS);
72 const sent: [string, string, number][] = [];
73 let up = false;
74 const send = async (workspace: string, month: string, bytes: number) => {
75 sent.push([workspace, month, bytes]);
76 if (!up) throw new Error("billing answered 503");
77 return true;
78 };
79 box.alarm = null;
80 await meter.note(send, NOW);
81 assert.equal(box.alarm, NOW + NOTE_RETRY_MS, "tried again later");
82 up = true;
83 box.alarm = null;
84 await meter.note(send, NOW);
85 await meter.note(send, NOW);
86 assert.deepEqual(sent, [
87 ["acme", "2026-10", 500],
88 ["acme", "2026-10", 500],
89 ]);
90 assert.equal(box.alarm, null);
91 // More arrives: the new total.
92 await meter.add("acme", 100, NOW);
93 await meter.note(send, NOW);
94 assert.deepEqual(sent.at(-1), ["acme", "2026-10", 600]);
95});
96
97test("the last report of a month goes before it ends", () => {
98 const end = Date.UTC(2026, 10, 1);
99 assert.equal(noteAt(end - 2 * 60_000), end - 60_000);
100 assert.equal(noteAt(NOW), NOW + NOTE_AFTER_MS);
101});
102
103test("a month's last bytes are reported under that month after it ends; older months are not", async () => {
104 const { s, box } = state();
105 const meter = new WorkspaceMeter(s);
106 meter.setStanding("paid");
107 const end = Date.UTC(2026, 10, 1);
108 await meter.add("acme", 800, Date.UTC(2026, 8, 20));
109 await meter.add("acme", 700, end - 30_000);
110 const sent: [string, string, number][] = [];
111 const send = async (workspace: string, month: string, bytes: number) => {
112 sent.push([workspace, month, bytes]);
113 return true;
114 };
115 box.alarm = null;
116 // The alarm fires after the month is over: October's total goes as
117 // October's, September's (two months back) is left as it was.
118 await meter.note(send, end + 60_000);
119 assert.deepEqual(sent, [["acme", "2026-10", 700]]);
120 await meter.add("acme", 5, end + 120_000);
121 await meter.note(send, end + 180_000);
122 assert.deepEqual(sent.at(-1), ["acme", "2026-11", 5]);
123 assert.equal(sent.length, 2);
124});