argus/test/plans.test.mjs

126 lines
5.8 KiB
JavaScript
Raw Normal View History

// 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`);
}
});