| 1 | // Tests for the band-price minting tool's pure parts — the committed table |
| 2 | // must stay the doc's matrix. node test/stripe-band-prices.test.mjs |
| 3 | import assert from "node:assert/strict"; |
| 4 | import { BAND_TABLE, priceLabel, netEURYear } from "../tools/stripe-band-prices.mjs"; |
| 5 | |
| 6 | let failures = 0; |
| 7 | async function test(name, fn) { |
| 8 | try { |
| 9 | await fn(); |
| 10 | console.log("PASS " + name); |
| 11 | } catch (e) { |
| 12 | console.log("FAIL " + name + " — " + (e && e.message)); |
| 13 | failures++; |
| 14 | } |
| 15 | } |
| 16 | |
| 17 | await test("the table is the elevens ladder — 11 prices in all", () => { |
| 18 | // 3 base + 8 band prices: nominally 3 × 4, minus Business's solidarity |
| 19 | // (deliberately absent — a hardship price aimed at businesses never |
| 20 | // parsed; Reduced at €5.50/person covers nonprofits/education). |
| 21 | const tiers = Object.keys(BAND_TABLE); |
| 22 | assert.deepEqual(tiers, ["family", "community", "business"]); |
| 23 | const bandCount = tiers.reduce((n, t) => n + Object.keys(BAND_TABLE[t].bands).length, 0); |
| 24 | assert.equal(tiers.length + bandCount, 11); |
| 25 | assert.ok(!BAND_TABLE.business.bands.solidarity, "business must not offer solidarity"); |
| 26 | // Spot-pin the ladder's load-bearing figures (decimal cents). |
| 27 | assert.equal(BAND_TABLE.family.base, "550"); |
| 28 | assert.equal(BAND_TABLE.community.base, "11"); |
| 29 | assert.equal(BAND_TABLE.business.base, "1100"); |
| 30 | assert.equal(BAND_TABLE.business.bands.supporter, "1500"); |
| 31 | assert.equal(BAND_TABLE.business.bands.reduced, "550"); |
| 32 | // The included-members identity: €0.11 × the 100-member floor = the €11 |
| 33 | // minimum, and every band scales it (supporter €15, reduced €5.50). |
| 34 | assert.equal(BAND_TABLE.community.minSeats, 100); |
| 35 | assert.equal(Number(BAND_TABLE.community.base) * BAND_TABLE.community.minSeats, 1100); |
| 36 | // Bands only ever use the fixed vocabulary; per-unit tiers name their unit. |
| 37 | for (const t of tiers) { |
| 38 | for (const b of Object.keys(BAND_TABLE[t].bands)) { |
| 39 | assert.ok(["supporter", "reduced", "solidarity"].includes(b), `${t}: unknown band ${b}`); |
| 40 | } |
| 41 | assert.ok([null, "member", "seat"].includes(BAND_TABLE[t].unit)); |
| 42 | } |
| 43 | }); |
| 44 | |
| 45 | await test("priceLabel: whole euros bare, cents as a pair, sub-cent to three places", () => { |
| 46 | assert.equal(priceLabel("550", null), "€5.50/year"); |
| 47 | assert.equal(priceLabel("1100", "seat"), "€11/seat/year"); |
| 48 | assert.equal(priceLabel("11", "member"), "€0.11/member/year"); |
| 49 | assert.equal(priceLabel("5.5", "member"), "€0.055/member/year"); |
| 50 | assert.equal(priceLabel("139900", null), "€1,399/year"); |
| 51 | }); |
| 52 | |
| 53 | await test("netEURYear: the EEA fee model — flat absorbs the fixed fee, per-unit doesn't", () => { |
| 54 | // Flat annual: one charge — €5.50 → 5.50 − 0.0825 − 0.25 = 5.168. |
| 55 | assert.equal(netEURYear("550", null), 5.168); |
| 56 | // Per-unit rates only carry the percentage; the €0.25 lands once per space. |
| 57 | assert.equal(netEURYear("1100", "seat"), 10.835); |
| 58 | assert.equal(netEURYear("11", "member"), 0.108); |
| 59 | }); |
| 60 | |
| 61 | console.log(failures === 0 ? "\nALL PASSED" : `\n${failures} FAILED`); |
| 62 | process.exit(failures === 0 ? 0 : 1); |
| 63 | |