// The plan catalogue and bundle pricing. // // Most of these assert honesty properties rather than arithmetic: that a free // option is never priced, that only a plan where WE hold the bytes is flagged as // creating an obligation, and that every recurring plan tells the buyer what // happens to their NAME when hosting lapses. Those are the claims that get // quietly wrong when copy is edited months later, and they are exactly the ones // no one notices until a customer is upset. import test from "node:test"; import assert from "node:assert/strict"; import { HOSTING_PLANS, VPS_REFERRALS, PLAN_KIND, planById, paidPlans, freePlans, quoteBundle, } from "../src/lib/plans.js"; import { quotePrice } from "../src/lib/orders.js"; import { REGISTRAR } from "../src/registrar-config.js"; const NAME_PRICE = quotePrice({ serviceFeeSats: REGISTRAR.serviceFee.sats }); test("only one plan is actually hosting-as-a-service", () => { const managed = HOSTING_PLANS.filter((p) => p.kind === PLAN_KIND.managed); assert.equal(managed.length, 1, "selling more 'plans' than we run is how a storefront starts lying"); assert.equal(managed[0].id, "sia-managed-1y"); assert.equal(managed[0].weStore, true); }); test("every free plan is genuinely free and stores nothing of ours", () => { for (const p of freePlans()) { assert.equal(p.priceSats, 0n, `${p.id} is listed free but priced`); assert.equal(p.weStore, false, `${p.id} is listed free but we hold the bytes`); assert.equal(p.recurring, false, `${p.id} is free but recurring — that cannot both be true`); } assert.ok(freePlans().length >= 3, "most options cost the buyer nothing; the page should say so"); }); test("paid plans are exactly the ones where we store bytes", () => { const paid = paidPlans().map((p) => p.id).sort(); const stores = HOSTING_PLANS.filter((p) => p.weStore).map((p) => p.id).sort(); assert.deepEqual(paid, stores, "we should charge for storage and nothing else"); }); test("every recurring plan explains what happens to the NAME on lapse", () => { for (const p of HOSTING_PLANS.filter((x) => x.recurring)) { assert.ok(p.onLapse, `${p.id} is recurring but says nothing about lapsing`); assert.match(p.onLapse, /name is unaffected/i, "the flagship claim must survive a lapsed subscription"); assert.match(p.onLapse, /repoint/i, "the buyer needs to know the remedy is free and in their hands"); } }); test("non-recurring plans do not carry lapse language", () => { for (const p of HOSTING_PLANS.filter((x) => !x.recurring)) { assert.equal(p.onLapse, null, `${p.id} cannot lapse, so it must not imply it can`); } }); test("referrals are not plans and carry no price of ours", () => { const ids = new Set(HOSTING_PLANS.map((p) => p.id)); for (const r of VPS_REFERRALS) { assert.ok(!ids.has(r.id), `${r.id} appears in both lists — a referral is not something we sell`); assert.ok(r.url.startsWith("https://"), "referral needs a real destination"); assert.equal(r.priceSats, undefined, "we do not price other people's products"); } }); // ---------- bundle pricing ---------- test("a name on its own is just the name price", () => { const q = quoteBundle({ namePriceSats: NAME_PRICE }); assert.equal(q.totalSats, NAME_PRICE); assert.equal(q.lines.length, 1); assert.equal(q.plan, null); assert.equal(q.createsHostingObligation, false); assert.equal(q.recurring, false); }); test("name + managed hosting is one payment with itemised lines", () => { const plan = planById("sia-managed-1y"); const q = quoteBundle({ namePriceSats: NAME_PRICE, planId: "sia-managed-1y" }); assert.equal(q.totalSats, NAME_PRICE + plan.priceSats); assert.equal(q.lines.length, 2, "one payment, two lines — not two orders to reconcile"); assert.equal(q.lines[0].id, "name"); assert.match(q.lines[1].label, /Managed Sia hosting/); assert.match(q.lines[1].label, /1y/, "the period belongs in the label, where the buyer reads it"); }); test("choosing a free plan adds no charge and no obligation", () => { for (const p of freePlans()) { const q = quoteBundle({ namePriceSats: NAME_PRICE, planId: p.id }); assert.equal(q.totalSats, NAME_PRICE, `${p.id} must not add cost`); assert.equal(q.lines.length, 1, `${p.id} must not add a billing line`); assert.equal(q.createsHostingObligation, false, `${p.id} must not make us a host`); } }); test("only the managed plan flags a hosting obligation", () => { // This flag is what the UI keys the "we will be storing your files" disclosure // off. Getting it wrong either hides a real liability or scares people off a // free option that never touches our infrastructure. const managed = quoteBundle({ namePriceSats: NAME_PRICE, planId: "sia-managed-1y" }); assert.equal(managed.createsHostingObligation, true); assert.equal(managed.recurring, true); const onchain = quoteBundle({ namePriceSats: NAME_PRICE, planId: "onchain" }); assert.equal(onchain.createsHostingObligation, false); }); test("an unknown plan is refused rather than silently ignored", () => { // Silently dropping it would sell a name, take the money, and quietly not // provide the hosting the buyer picked. assert.throws(() => quoteBundle({ namePriceSats: NAME_PRICE, planId: "sia-managed-10y" }), /unknown hosting plan/); }); test("bundle totals stay bigint throughout", () => { const q = quoteBundle({ namePriceSats: NAME_PRICE, planId: "sia-managed-1y" }); assert.equal(typeof q.totalSats, "bigint"); for (const l of q.lines) assert.equal(typeof l.sats, "bigint", `${l.id} must not drift to Number`); }); test("every plan maps to a real BNS record type", () => { const valid = new Set(["h", "u", "ip", "s3", "tls"]); for (const p of HOSTING_PLANS) { assert.ok(valid.has(p.record), `${p.id} points at "${p.record}", which the resolver does not implement`); } });