| 1 | import assert from "node:assert/strict"; |
| 2 | import { readFileSync, readdirSync } from "node:fs"; |
| 3 | import { DatabaseSync } from "node:sqlite"; |
| 4 | import { test } from "node:test"; |
| 5 | |
| 6 | // Complimentary enrollment against the complete migrated schema, using the |
| 7 | // production SQL: the allocation that posts usage, and the terms change as |
| 8 | // `admin_set_complimentary` writes it. |
| 9 | const source = (name) => readFileSync(new URL(`../services/billing/src/${name}.rs`, import.meta.url), "utf8"); |
| 10 | const sqlFile = (name) => readFileSync(new URL(`../services/billing/src/${name}.sql`, import.meta.url), "utf8"); |
| 11 | const constant = (file, name) => { |
| 12 | const match = source(file).match(new RegExp(`const ${name}: &str = "([\\s\\S]*?)";`)); |
| 13 | assert.ok(match, `${file}::${name}`); |
| 14 | return match[1]; |
| 15 | }; |
| 16 | const prepares = (file, from, to) => { |
| 17 | const text = source(file).split(from)[1].split(to)[0]; |
| 18 | return [...text.matchAll(/\.prepare\(\s*"([\s\S]*?)"\s*,?\s*\)/g)].map((m) => m[1]); |
| 19 | }; |
| 20 | const migrations = new URL("../services/billing/migrations/", import.meta.url); |
| 21 | const names = readdirSync(migrations) |
| 22 | .filter((n) => n.endsWith(".sql")) |
| 23 | .sort(); |
| 24 | function database(before = null) { |
| 25 | const db = new DatabaseSync(":memory:"); |
| 26 | for (const name of names.filter((n) => before === null || n < before)) db.exec(readFileSync(new URL(name, migrations), "utf8")); |
| 27 | return db; |
| 28 | } |
| 29 | function batch(db, writes) { |
| 30 | db.exec("BEGIN"); |
| 31 | try { |
| 32 | for (const [sql, args] of writes) db.prepare(sql).run(...args); |
| 33 | db.exec("COMMIT"); |
| 34 | } catch (e) { |
| 35 | db.exec("ROLLBACK"); |
| 36 | throw e; |
| 37 | } |
| 38 | } |
| 39 | const at = "2026-10-14T12:00:00.000Z"; |
| 40 | function usage(id, reference, workspace, gross, { plan = 10_000_000, trial = 1, given = 0, when = at } = {}) { |
| 41 | const insert = prepares("storage", "pub(crate) async fn post_usage_with(", "/// Takes `charged`")[0]; |
| 42 | const balance = prepares("storage", "pub(crate) fn balance_if_posted(", "/// Writes down")[0]; |
| 43 | const month = when.slice(0, 7); |
| 44 | return [ |
| 45 | [insert, [id, workspace, -gross, "Usage", `${workspace}/repo`, "sandbox", gross, reference, when, 0, 0, 0, 0, null]], |
| 46 | [sqlFile("allocate_usage"), [id, workspace, month, plan, trial, `${workspace}/repo`, 1_000_000, 1_000_000, given]], |
| 47 | [constant("allocation", "PLAN"), [id, month]], |
| 48 | [constant("allocation", "GIFT"), [id, month]], |
| 49 | [constant("allocation", "TRIAL"), [id]], |
| 50 | [constant("allocation", "OSS_POOL"), [id, month]], |
| 51 | [constant("allocation", "OSS_REPO"), [id, month, `${workspace}/repo`]], |
| 52 | [balance, [workspace, when, id]], |
| 53 | ]; |
| 54 | } |
| 55 | const enrolledAt = "2026-10-01T00:00:00.000Z"; |
| 56 | function enroll(db, workspace, allowance, { unlimited = 0, overage = "stop" } = {}) { |
| 57 | db.prepare( |
| 58 | `INSERT INTO complimentary (workspace, grant_id, status, allowance_micros, unlimited, fee_cents, note, created_by, created_at, created_ms, updated_by, updated_at, subscription_id, overage) |
| 59 | VALUES (?, ?, 'active', ?, ?, 0, 'partner', 'staff', ?, 0, 'staff', ?, ?, ?)`, |
| 60 | ).run(workspace, `cmp_${workspace}`, allowance, unlimited, enrolledAt, enrolledAt, `sub_${workspace}`, overage); |
| 61 | db.prepare( |
| 62 | `INSERT INTO complimentary_periods (id, workspace, grant_id, starts_at, allowance_micros, unlimited, fee_cents, note, created_by, created_at, overage) |
| 63 | VALUES (?, ?, ?, ?, ?, ?, 0, 'partner', 'staff', ?, ?)`, |
| 64 | ).run(`p_${workspace}`, workspace, `cmp_${workspace}`, enrolledAt, allowance, unlimited, enrolledAt, overage); |
| 65 | } |
| 66 | const termsChange = prepares("complimentary", "// One batch: the open period closes", "self.audit("); |
| 67 | function changeTerms(db, workspace, allowance, when, overage = "stop") { |
| 68 | assert.equal(termsChange.length, 3); |
| 69 | batch(db, [ |
| 70 | [termsChange[0], [workspace, when]], |
| 71 | [termsChange[1], [`p_${when}`, workspace, `cmp_${workspace}`, when, allowance, 0, 0, "changed", "staff", overage]], |
| 72 | [termsChange[2], [workspace, allowance, 0, 0, "changed", "staff", when, JSON.stringify({ op: "terms", period: `p_${when}` }), overage]], |
| 73 | ]); |
| 74 | } |
| 75 | const line = (db, id) => ({ |
| 76 | ...db.prepare("SELECT amount_micros, credit_micros, gift_micros, trial_micros, oss_micros, given_micros FROM ledger WHERE id=?").get(id), |
| 77 | }); |
| 78 | const giftUsed = (db, workspace, month = "2026-10") => |
| 79 | db.prepare("SELECT used FROM allowance_use WHERE kind='complimentary' AND scope=? AND month=?").get(workspace, month)?.used ?? 0; |
| 80 | |
| 81 | test("a complimentary allowance pays first and alone, and past it g1t covers rather than charging", () => { |
| 82 | const db = database(); |
| 83 | enroll(db, "acme", 10_000_000); |
| 84 | db.prepare("INSERT INTO trial_grants (workspace, month, granted_micros, used_micros, created_at) VALUES ('acme', '2026-10', 5000000, 0, ?)").run(at); |
| 85 | batch(db, usage("a", "run/a", "acme", 6_000_000)); |
| 86 | assert.deepEqual(line(db, "a"), { amount_micros: 0, credit_micros: 0, gift_micros: 6_000_000, trial_micros: 0, oss_micros: 0, given_micros: 0 }); |
| 87 | // Running work that outlasts the allowance: the rest is g1t's, recorded |
| 88 | // apart from the allowance, and nothing is owed. |
| 89 | batch(db, usage("b", "run/b", "acme", 6_000_000)); |
| 90 | assert.deepEqual(line(db, "b"), { amount_micros: 0, credit_micros: 0, gift_micros: 4_000_000, trial_micros: 0, oss_micros: 0, given_micros: 2_000_000 }); |
| 91 | assert.equal(giftUsed(db, "acme"), 10_000_000); |
| 92 | assert.equal(db.prepare("SELECT balance_micros FROM accounts WHERE workspace='acme'").get().balance_micros, 0); |
| 93 | // Plan credit, the trial and the pools were never touched. |
| 94 | assert.equal(db.prepare("SELECT COUNT(*) n FROM allowance_use WHERE kind IN ('plan_credit','oss_pool','oss_repo')").get().n, 0); |
| 95 | assert.equal(db.prepare("SELECT used_micros FROM trial_grants WHERE workspace='acme'").get().used_micros, 0); |
| 96 | // A duplicate delivery consumes nothing more. |
| 97 | batch(db, usage("dup", "run/b", "acme", 6_000_000)); |
| 98 | assert.equal(giftUsed(db, "acme"), 10_000_000); |
| 99 | assert.equal(db.prepare("SELECT COUNT(*) n FROM ledger").get().n, 2); |
| 100 | db.close(); |
| 101 | }); |
| 102 | |
| 103 | test("changing the amount keeps the month's consumption; lowering it never creates a debt", () => { |
| 104 | const db = database(); |
| 105 | enroll(db, "acme", 10_000_000); |
| 106 | batch(db, usage("a", "run/a", "acme", 8_000_000, { when: "2026-10-10T00:00:00.000Z" })); |
| 107 | changeTerms(db, "acme", 5_000_000, "2026-10-11T00:00:00.000Z"); |
| 108 | assert.equal(db.prepare("SELECT COUNT(*) n FROM complimentary_periods WHERE workspace='acme' AND ends_at IS NULL").get().n, 1); |
| 109 | assert.equal(db.prepare("SELECT allowance_micros FROM complimentary WHERE workspace='acme'").get().allowance_micros, 5_000_000); |
| 110 | // Lowered below what was used: nothing more from it, nothing owed. |
| 111 | batch(db, usage("b", "run/b", "acme", 1_000_000, { when: "2026-10-12T00:00:00.000Z" })); |
| 112 | assert.deepEqual(line(db, "b"), { amount_micros: 0, credit_micros: 0, gift_micros: 0, trial_micros: 0, oss_micros: 0, given_micros: 1_000_000 }); |
| 113 | assert.equal(giftUsed(db, "acme"), 8_000_000, "the earlier draw is kept, not reset"); |
| 114 | // Raised: only what is over the month's use is left. |
| 115 | changeTerms(db, "acme", 20_000_000, "2026-10-13T00:00:00.000Z"); |
| 116 | batch(db, usage("c", "run/c", "acme", 15_000_000, { when: "2026-10-14T00:00:00.000Z" })); |
| 117 | assert.equal(line(db, "c").gift_micros, 12_000_000); |
| 118 | assert.equal(line(db, "c").given_micros, 3_000_000); |
| 119 | assert.equal(giftUsed(db, "acme"), 20_000_000); |
| 120 | // A new month starts the allowance again, with no rollover. |
| 121 | batch(db, usage("d", "run/d", "acme", 25_000_000, { when: "2026-11-01T00:00:01.000Z" })); |
| 122 | assert.equal(line(db, "d").gift_micros, 20_000_000); |
| 123 | assert.equal(giftUsed(db, "acme", "2026-11"), 20_000_000); |
| 124 | assert.equal(db.prepare("SELECT SUM(amount_micros) n FROM ledger").get().n, 0, "nothing was ever charged"); |
| 125 | db.close(); |
| 126 | }); |
| 127 | |
| 128 | test("late usage draws on the period it happened in, not the one it is posted in", () => { |
| 129 | const db = database(); |
| 130 | enroll(db, "acme", 10_000_000); |
| 131 | // Converted to the plan on the 15th. |
| 132 | db.exec("UPDATE complimentary_periods SET ends_at='2026-10-15T00:00:00.000Z'; UPDATE complimentary SET status='ended', ended_reason='converted'"); |
| 133 | batch(db, usage("before", "run/before", "acme", 3_000_000, { when: "2026-10-14T23:00:00.000Z" })); |
| 134 | assert.equal(line(db, "before").gift_micros, 3_000_000, "gifted usage is never repriced after conversion"); |
| 135 | assert.equal(line(db, "before").amount_micros, 0); |
| 136 | batch(db, usage("after", "run/after", "acme", 3_000_000, { when: "2026-10-15T01:00:00.000Z" })); |
| 137 | assert.deepEqual(line(db, "after"), { amount_micros: 0, credit_micros: 3_000_000, gift_micros: 0, trial_micros: 0, oss_micros: 0, given_micros: 0 }); |
| 138 | db.close(); |
| 139 | }); |
| 140 | |
| 141 | test("charged past the allowance: g1t pays the allowance, the workspace's own card the rest", () => { |
| 142 | const db = database(); |
| 143 | enroll(db, "friend", 10_000_000, { overage: "charge" }); |
| 144 | // No card yet: past the allowance it is g1t's, never a debt. |
| 145 | batch(db, usage("z", "run/z", "friend", 12_000_000, { when: "2026-11-02T00:00:00.000Z" })); |
| 146 | assert.deepEqual(line(db, "z"), { amount_micros: 0, credit_micros: 0, gift_micros: 10_000_000, trial_micros: 0, oss_micros: 0, given_micros: 2_000_000 }); |
| 147 | db.prepare("INSERT INTO card_checks (workspace, setup_intent, payment_method, checked_by, checked_at) VALUES ('friend', 'seti_1', 'pm_1', 'owner', ?)").run( |
| 148 | at, |
| 149 | ); |
| 150 | batch(db, usage("a", "run/a", "friend", 6_000_000)); |
| 151 | batch(db, usage("b", "run/b", "friend", 7_000_000)); |
| 152 | // 4 of 7 from the allowance, 3 charged; nothing covered by g1t. |
| 153 | assert.deepEqual(line(db, "b"), { amount_micros: -3_000_000, credit_micros: 0, gift_micros: 4_000_000, trial_micros: 0, oss_micros: 0, given_micros: 0 }); |
| 154 | assert.equal(db.prepare("SELECT balance_micros FROM accounts WHERE workspace='friend'").get().balance_micros, -3_000_000); |
| 155 | // Switched to stop mid-month: from then, past the allowance is g1t's. |
| 156 | changeTerms(db, "friend", 10_000_000, "2026-10-15T00:00:00.000Z", "stop"); |
| 157 | batch(db, usage("c", "run/c", "friend", 1_000_000, { when: "2026-10-16T00:00:00.000Z" })); |
| 158 | assert.deepEqual(line(db, "c"), { amount_micros: 0, credit_micros: 0, gift_micros: 0, trial_micros: 0, oss_micros: 0, given_micros: 1_000_000 }); |
| 159 | // Usage from before the switch, posted late, keeps the period it happened in. |
| 160 | batch(db, usage("d", "run/d", "friend", 2_000_000, { when: "2026-10-14T00:00:00.000Z" })); |
| 161 | assert.equal(line(db, "d").amount_micros, -2_000_000); |
| 162 | db.close(); |
| 163 | }); |
| 164 | |
| 165 | test("an enrollment's fee paid while enrolled is not evidence of the plan", () => { |
| 166 | const db = database(); |
| 167 | enroll(db, "acme", 10_000_000); |
| 168 | db.exec("UPDATE complimentary_periods SET ends_at='2026-10-15T00:00:00.000Z'"); |
| 169 | const evidence = db.prepare(constant("storage", "PLAN_EVIDENCE")); |
| 170 | db.exec("UPDATE complimentary_periods SET fee_cents = 2500"); |
| 171 | db.prepare("INSERT INTO plan_payments (invoice_id, workspace, amount_micros, paid_at) VALUES ('in_fee', 'acme', 25000000, '2026-10-02T00:00:00.000Z')").run(); |
| 172 | assert.equal(evidence.get("acme", "2026-10-01", "2026-11-01", "2026-10").had, 0); |
| 173 | // The conversion's first plan payment, made while the period is still open, is the plan. |
| 174 | db.prepare( |
| 175 | "INSERT INTO plan_payments (invoice_id, workspace, amount_micros, paid_at) VALUES ('in_plan', 'acme', 21000000, '2026-10-14T23:59:00.000Z')", |
| 176 | ).run(); |
| 177 | assert.equal(evidence.get("acme", "2026-10-01", "2026-11-01", "2026-10").had, 1); |
| 178 | db.close(); |
| 179 | }); |
| 180 | |
| 181 | test("an unlimited enrollment pays everything and still records what it used", () => { |
| 182 | const db = database(); |
| 183 | enroll(db, "flagon-io", 0, { unlimited: 1 }); |
| 184 | batch(db, usage("a", "run/a", "flagon-io", 900_000_000)); |
| 185 | assert.equal(line(db, "a").gift_micros, 900_000_000); |
| 186 | assert.equal(line(db, "a").given_micros, 0); |
| 187 | assert.equal(giftUsed(db, "flagon-io"), 900_000_000); |
| 188 | db.close(); |
| 189 | }); |
| 190 | |
| 191 | test("an interrupted posting consumes no complimentary allowance", () => { |
| 192 | const db = database(); |
| 193 | enroll(db, "acme", 10_000_000); |
| 194 | db.exec("CREATE TRIGGER fail_balance BEFORE INSERT ON accounts WHEN NEW.workspace='acme' BEGIN SELECT RAISE(ABORT, 'injected failure'); END"); |
| 195 | assert.throws(() => batch(db, usage("a", "run/a", "acme", 4_000_000)), /injected failure/); |
| 196 | assert.equal(giftUsed(db, "acme"), 0); |
| 197 | db.exec("DROP TRIGGER fail_balance"); |
| 198 | batch(db, usage("b", "run/a", "acme", 4_000_000)); |
| 199 | assert.equal(giftUsed(db, "acme"), 4_000_000); |
| 200 | db.close(); |
| 201 | }); |
| 202 | |
| 203 | test("one open period per workspace", () => { |
| 204 | const db = database(); |
| 205 | enroll(db, "acme", 1); |
| 206 | assert.throws( |
| 207 | () => |
| 208 | db |
| 209 | .prepare( |
| 210 | `INSERT INTO complimentary_periods (id, workspace, grant_id, starts_at, allowance_micros, note, created_by, created_at) |
| 211 | VALUES ('second', 'acme', 'cmp_acme', ?, 1, 'x', 'staff', ?)`, |
| 212 | ) |
| 213 | .run(at, at), |
| 214 | /UNIQUE/, |
| 215 | ); |
| 216 | db.close(); |
| 217 | }); |
| 218 | |
| 219 | test("migration 0065 returns every workspace but flagon-io to Free, with an audit entry", () => { |
| 220 | const db = database("0065"); |
| 221 | const account = db.prepare( |
| 222 | "INSERT INTO billing_accounts (id, kind, name, terms_kind, discount_percent, team_granted, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?, 'staff', ?)", |
| 223 | ); |
| 224 | account.run("ws_flagon-io-x", "workspace", "flagon-io-x", "comped", 100, 0, at); |
| 225 | db.prepare("UPDATE billing_accounts SET terms_kind='custom' WHERE id='ws_flagon-io'").run(); |
| 226 | account.run("ws_partner", "workspace", "partner", "comped", 100, 0, at); |
| 227 | account.run("ws_given", "workspace", "given", "standard", 0, 1, at); |
| 228 | account.run("ws_plain", "workspace", "plain", "standard", 0, 0, at); |
| 229 | db.prepare("INSERT INTO account_members (workspace, account_id, added_by, added_at) VALUES ('member', 'ent_x', 'staff', ?)").run(at); |
| 230 | // One on an enterprise invoice still being made: kept, not a failed deploy. |
| 231 | db.prepare("INSERT INTO account_members (workspace, account_id, added_by, added_at) VALUES ('invoicing', 'ent_y', 'staff', ?)").run(at); |
| 232 | db.prepare("INSERT INTO enterprise_invoice_attempts (account_id, id, payload, created_ms) VALUES ('ent_y', 'att', ?, 1)").run( |
| 233 | JSON.stringify({ lines: [{ workspace: "invoicing", amountMicros: 1 }] }), |
| 234 | ); |
| 235 | const flagonBefore = db.prepare("SELECT * FROM billing_accounts WHERE id='ws_flagon-io'").get(); |
| 236 | db.exec( |
| 237 | readFileSync( |
| 238 | new URL( |
| 239 | names.find((n) => n.startsWith("0065")), |
| 240 | migrations, |
| 241 | ), |
| 242 | "utf8", |
| 243 | ), |
| 244 | ); |
| 245 | const terms = Object.fromEntries( |
| 246 | db |
| 247 | .prepare("SELECT id, terms_kind, discount_percent, team_granted FROM billing_accounts") |
| 248 | .all() |
| 249 | .map((r) => [r.id, [r.terms_kind, r.discount_percent, r.team_granted]]), |
| 250 | ); |
| 251 | assert.deepEqual({ ...db.prepare("SELECT * FROM billing_accounts WHERE id='ws_flagon-io'").get() }, { ...flagonBefore }, "flagon-io is untouched"); |
| 252 | assert.deepEqual(terms["ws_flagon-io-x"], ["standard", 0, 0], "only flagon-io itself is kept"); |
| 253 | assert.deepEqual(terms.ws_partner, ["standard", 0, 0]); |
| 254 | assert.deepEqual(terms.ws_given, ["standard", 0, 0]); |
| 255 | assert.deepEqual(terms.ws_plain, ["standard", 0, 0]); |
| 256 | assert.deepEqual( |
| 257 | db |
| 258 | .prepare("SELECT workspace FROM account_members") |
| 259 | .all() |
| 260 | .map((r) => r.workspace), |
| 261 | ["invoicing"], |
| 262 | ); |
| 263 | const audited = db |
| 264 | .prepare("SELECT account FROM admin_actions WHERE by='billing' ORDER BY account") |
| 265 | .all() |
| 266 | .map((r) => r.account); |
| 267 | assert.deepEqual(audited, ["ent_x", "ws_flagon-io-x", "ws_given", "ws_partner"]); |
| 268 | db.close(); |
| 269 | }); |