Add multi-tenant cloud mode: self-serve purchase, credit metering, core-decoupling
Introduces RECAP_MODE=multi alongside single-mode self-host: - Tenant auth + accounts (magic-link via System SMTP), per-tenant credit pool, anonymous trial minting with per-IP/-64 caps - Self-serve Pro/Max purchase: inline Lightning (BTCPay) + card (Zaprite), prepaid 30-day periods, expiry-reminder emails - Core-decoupling: relay owns cloud tier/expiry keyed by Recaps user-id - SQLite (better-sqlite3) schema for multi-mode; filesystem unchanged for single - StartOS actions/versions through 0.2.155
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
// Pure-logic tests for the expiry-reminder cadence: which reminder kind
|
||||
// (if any) applies to a relay subscription row right now. The send/dedup
|
||||
// path hits SQLite + SMTP and isn't unit-tested here; this nails the
|
||||
// decision that drives it.
|
||||
|
||||
import { test, describe } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { reminderKindFor } from "../subscription-reminders.js";
|
||||
|
||||
const sub = (days_left, expired = false) => ({
|
||||
tier: "pro",
|
||||
expires_at: "2026-07-01T00:00:00.000Z",
|
||||
expired,
|
||||
days_left,
|
||||
});
|
||||
|
||||
describe("reminderKindFor", () => {
|
||||
test("upcoming: smallest crossed threshold wins", () => {
|
||||
assert.equal(reminderKindFor(sub(8)), null); // beyond 7d
|
||||
assert.equal(reminderKindFor(sub(7)), "upcoming_7d");
|
||||
assert.equal(reminderKindFor(sub(5)), "upcoming_7d");
|
||||
assert.equal(reminderKindFor(sub(2)), "upcoming_7d");
|
||||
assert.equal(reminderKindFor(sub(1)), "upcoming_1d");
|
||||
assert.equal(reminderKindFor(sub(0)), "upcoming_1d"); // expires today, not yet lapsed
|
||||
});
|
||||
|
||||
test("lapsed: only within the lapsed window", () => {
|
||||
assert.equal(reminderKindFor(sub(0, true)), "lapsed");
|
||||
assert.equal(reminderKindFor(sub(-1, true)), "lapsed");
|
||||
assert.equal(reminderKindFor(sub(-3, true)), "lapsed");
|
||||
assert.equal(reminderKindFor(sub(-4, true)), null); // aged out (window=3)
|
||||
});
|
||||
|
||||
test("null-safe / malformed input", () => {
|
||||
assert.equal(reminderKindFor(null), null);
|
||||
assert.equal(reminderKindFor({}), null);
|
||||
assert.equal(reminderKindFor({ expired: false }), null); // no days_left
|
||||
assert.equal(reminderKindFor({ days_left: "soon" }), null);
|
||||
});
|
||||
|
||||
test("respects custom thresholds", () => {
|
||||
const upcoming = [
|
||||
{ days: 14, kind: "upcoming_14d" },
|
||||
{ days: 3, kind: "upcoming_3d" },
|
||||
];
|
||||
assert.equal(reminderKindFor(sub(14), { upcoming }), "upcoming_14d");
|
||||
assert.equal(reminderKindFor(sub(3), { upcoming }), "upcoming_3d");
|
||||
assert.equal(reminderKindFor(sub(15), { upcoming }), null);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user