Files
simplex-chat/apps/simplex-badge-service/web/test/store.test.ts
T

326 lines
15 KiB
TypeScript

import { timedTest } from "./boot.js";
import assert from "node:assert/strict";
import { type OrderRecord } from "../src/domain.js";
import { Store } from "../src/store.js";
const storeTest = timedTest(2000);
class MemoryStorage {
map = new Map<string, string>();
failWrites = false;
getItem(k: string) { return this.map.get(k) ?? null; }
setItem(k: string, v: string) { if (this.failWrites) throw new Error("QuotaExceeded"); this.map.set(k, v); }
removeItem(k: string) { this.map.delete(k); }
}
const order = (id: string, over: Partial<OrderRecord> = {}): OrderRecord => ({
orderId: id, badgeType: "legend", months: 12,
createdAt: new Date(Date.parse("2026-08-24T11:02:19Z") + Number(id)).toISOString(),
status: "open", ...over,
});
storeTest("store: session round-trips the step and the draft", () => {
const s = new Store(new MemoryStorage());
assert.equal(s.session().step, "tier");
s.saveSession({ step: "months", priceId: "price_legend" });
assert.equal(s.session().step, "months");
assert.equal(s.session().priceId, "price_legend");
s.clearSession();
assert.equal(s.session().priceId, undefined);
});
storeTest("store: orders upsert by id and keep a stored code", () => {
const s = new Store(new MemoryStorage());
s.saveOrder(order("1", { code: "SB-CODE" }));
s.saveOrder(order("1", { status: "paid" }));
const got = s.orders()[0]!;
assert.equal(got.status, "paid");
assert.equal(got.code, "SB-CODE", "a later record must not clear the code");
});
storeTest("store: the cap drops the oldest entry holding no code", () => {
const s = new Store(new MemoryStorage());
for (let i = 0; i < 50; i++) s.saveOrder(order(String(i), i < 10 ? {} : { code: "C" + i }));
s.saveOrder(order("999", { code: "NEW" }));
const ids = s.orders().map((o) => o.orderId);
assert.equal(ids.length, 50);
assert.ok(!ids.includes("0"), "the oldest codeless entry is dropped");
assert.ok(ids.includes("999"));
});
storeTest("store: with every entry holding a code the new one is not stored", () => {
const s = new Store(new MemoryStorage());
for (let i = 0; i < 50; i++) s.saveOrder(order(String(i), { code: "C" + i }));
assert.equal(s.saveOrder(order("999", { code: "NEW" })), false);
assert.equal(s.orders().length, 50);
});
storeTest("store: a failing storage degrades without throwing", () => {
const mem = new MemoryStorage();
const s = new Store(mem);
mem.failWrites = true;
assert.equal(s.saveOrder(order("1", { code: "C" })), false);
assert.equal(s.saveSession({ step: "months" }), false);
assert.deepEqual(s.orders(), []);
assert.equal(s.session().step, "tier");
});
storeTest("store: corruption is replaced, not parsed", () => {
const mem = new MemoryStorage();
mem.map.set("sb.orders.v1", "{not json");
const s = new Store(mem);
assert.deepEqual(s.orders(), []);
assert.equal(s.saveOrder(order("1")), true);
assert.equal(s.orders().length, 1);
});
storeTest("store: newestOpen finds the resumable order", () => {
const s = new Store(new MemoryStorage());
s.saveOrder(order("1", { status: "expired" }));
s.saveOrder(order("2", { status: "open" }));
s.saveOrder(order("3", { status: "paid", code: "C" }));
assert.equal(s.newestOpen()?.orderId, "2");
});
storeTest("store: shape-corrupted but parseable entries are dropped, not thrown", () => {
const mem = new MemoryStorage();
mem.map.set("sb.orders.v1", JSON.stringify([{ foo: 1 }, { bar: 2 }]));
const s = new Store(mem);
assert.deepEqual(s.orders(), [], "malformed elements are dropped rather than surfaced");
assert.equal(s.saveOrder(order("1")), true, "the write path must not throw on corrupted shape");
assert.equal(s.orders().length, 1);
});
storeTest("store: orders sort chronologically across second- and millisecond-precision timestamps", () => {
const s = new Store(new MemoryStorage());
const secOrder: OrderRecord = {
orderId: "sec", badgeType: "legend", months: 12,
createdAt: "2026-08-24T11:02:19Z", status: "open",
};
const msOrder: OrderRecord = {
orderId: "ms", badgeType: "legend", months: 12,
createdAt: "2026-08-24T11:02:19.900Z", status: "open",
};
s.saveOrder(secOrder);
s.saveOrder(msOrder);
assert.deepEqual(
s.orders().map((o) => o.orderId),
["ms", "sec"],
"the .900Z record is chronologically newer despite sorting lexicographically before the second-precision string",
);
});
storeTest("store: an unparseable createdAt sorts oldest instead of poisoning the comparator", () => {
// Date.parse answers NaN for an unreadable createdAt, and NaN makes every comparison false, leaving the sort order undefined.
const mem = new MemoryStorage();
mem.map.set("sb.orders.v1", JSON.stringify([
{ orderId: "undated", badgeType: "legend", months: 12, createdAt: "whenever", status: "open" },
{ orderId: "older", badgeType: "legend", months: 12, createdAt: "2026-08-20T00:00:00Z", status: "open" },
]));
const s = new Store(mem);
s.saveOrder(order("1", { status: "open", createdAt: "2026-08-26T00:00:00Z" }));
assert.deepEqual(s.orders().map((o) => o.orderId), ["1", "older", "undated"]);
assert.equal(s.newestOpen()?.orderId, "1", "the newest DATED open order is the one resumed");
});
storeTest("store: a stored record keeps the fields that read and drops the ones that do not", () => {
const mem = new MemoryStorage();
mem.map.set("sb.orders.v1", JSON.stringify([{
orderId: "corrupt", createdAt: "2026-08-24T11:00:00Z", status: "open",
code: "SB-KEEP-ME", badgeType: null, months: "12",
currency: 42, paidInFull: "yes", cryptoCurrency: "eth", amount: 42000,
}]));
const stored = new Store(mem).orders();
assert.equal(stored.length, 1, "a record with a code is never dropped over a bad field");
const o = stored[0]!;
assert.equal(o.code, "SB-KEEP-ME");
assert.equal(o.amount, 42000, "and what does read is kept");
for (const key of ["currency", "paidInFull", "cryptoCurrency"] as const) {
assert.equal(o[key], undefined, `${key} does not read, so it is not there to be believed`);
}
assert.equal(o.badgeType, "");
assert.equal(o.months, 0);
});
storeTest("store: a record with nothing to point at is dropped whole", () => {
const mem = new MemoryStorage();
mem.map.set("sb.orders.v1", JSON.stringify([
{ orderId: "no-status", createdAt: "2026-08-24T11:00:00Z" },
{ orderId: "bad-status", createdAt: "2026-08-24T11:00:00Z", status: "refunded" },
{ orderId: "", createdAt: "2026-08-24T11:00:00Z", status: "open" },
{ createdAt: "2026-08-24T11:00:00Z", status: "open" },
"not-an-object",
null,
]));
assert.deepEqual(new Store(mem).orders(), [], "an order with no id or no state has no row to draw");
mem.map.set("sb.orders.v1", JSON.stringify({ orderId: "not-a-list" }));
assert.deepEqual(new Store(mem).orders(), []);
});
storeTest("store: a session or theme outside the known set falls back to the default", () => {
const mem = new MemoryStorage();
mem.map.set("sb.session.v1", JSON.stringify({ step: "elsewhere", priceId: 12, method: "paypal" }));
mem.map.set("sb.theme.v1", JSON.stringify("neon"));
assert.deepEqual(new Store(mem).session(), { step: "tier" });
assert.equal(new Store(mem).theme(), "system");
mem.map.set("sb.session.v1", JSON.stringify({ step: "months", priceId: "price_legend", method: "paypal" }));
assert.deepEqual(new Store(mem).session(), { step: "months", priceId: "price_legend" },
"a good step keeps what reads beside it, and only that");
});
storeTest("store: saveOrder replaces the stored entry rather than merging into it", () => {
const s = new Store(new MemoryStorage());
s.saveOrder(order("1", { address: "48HqK2Xm", cryptoAmountPaid: "1.482", paidInFull: true, amount: 42000 }));
s.saveOrder(order("1", { status: "expired" }));
const stored = s.order("1")!;
for (const key of ["address", "cryptoAmountPaid", "paidInFull", "amount"] as const) {
assert.equal(stored[key], undefined, `${key} must not survive a write that leaves it out`);
}
assert.equal(stored.status, "expired");
});
storeTest("store: the two facts only this browser holds survive a write that omits them", () => {
const s = new Store(new MemoryStorage());
s.saveOrder(order("1", { code: "SB-CODE" }));
assert.equal(s.markSubmitted("1"), true);
s.saveOrder(order("1", { status: "paid" }));
assert.equal(s.order("1")!.code, "SB-CODE");
assert.equal(s.order("1")!.submitted, true);
});
storeTest("store: the app that opened the page round-trips the session, and a value outside the contract reads as none", () => {
const mem = new MemoryStorage();
const s = new Store(mem);
s.saveSession({ app: "mobile" });
s.saveSession({ step: "months", priceId: "price_legend" });
assert.equal(new Store(mem).session().app, "mobile", "a later step and a reload keep it");
mem.setItem("sb.session.v1", JSON.stringify({ step: "tier", app: "true" }));
assert.equal(s.session().app, undefined, "the stored value is the page's own word, never the URL's");
});
storeTest("store: an order keeps the app it was bought from through a write that omits it and a reload", () => {
const mem = new MemoryStorage();
const s = new Store(mem);
s.saveOrder(order("1", { app: "desktop" }));
s.saveOrder(order("1", { status: "paid" }));
assert.equal(s.order("1")!.app, "desktop", "the service never sends it, so its answer cannot clear it");
assert.equal(new Store(mem).order("1")!.app, "desktop");
s.saveOrder(order("2"));
assert.equal(s.order("2")!.app, undefined, "and it is not a page-wide flag");
});
storeTest("store: markSubmitted is per order, sticky, and survives everything but Forget", () => {
const mem = new MemoryStorage();
const s = new Store(mem);
s.saveOrder(order("1", { code: "SB-CODE" }));
s.saveOrder(order("2"));
assert.equal(s.markSubmitted("1"), true);
assert.equal(s.markSubmitted("absent"), false, "there is no order to mark");
s.clearSession();
s.saveOrder(order("1", { status: "open" })); // a later 200 for the same order
assert.equal(s.order("1")!.submitted, true, "a plain upsert must not take it back off");
assert.equal(s.order("1")!.code, "SB-CODE");
assert.equal(s.order("2")!.submitted, undefined, "and it is not a page-wide flag");
// A reload is a fresh Store over the same storage.
assert.equal(new Store(mem).order("1")!.submitted, true);
});
storeTest("store: markCanceled is per order, sticky, and survives a later server answer", () => {
const mem = new MemoryStorage();
const s = new Store(mem);
s.saveOrder(order("1"));
s.saveOrder(order("2"));
assert.equal(s.markCanceled("1"), true);
assert.equal(s.markCanceled("absent"), false, "there is no order to mark");
assert.equal(s.order("1")!.canceled, true);
s.saveOrder(order("1", { status: "expired" })); // the cancel's own 200, applied to the record
assert.equal(s.order("1")!.canceled, true, "a plain upsert must not take it back off");
assert.equal(s.order("2")!.canceled, undefined, "and it is not a page-wide flag");
assert.equal(new Store(mem).order("1")!.canceled, true, "and it survives a reload");
});
storeTest("store: the cap evicts the oldest codeless entry even when an older entry holds a code", () => {
const s = new Store(new MemoryStorage());
for (let i = 0; i < 50; i++) s.saveOrder(order(String(i), i % 2 === 0 ? { code: "C" + i } : {}));
s.saveOrder(order("999", { code: "NEW" }));
const ids = s.orders().map((o) => o.orderId);
assert.equal(ids.length, 50);
assert.ok(ids.includes("0"), "the oldest entry overall holds a code and must survive");
assert.ok(!ids.includes("1"), "the oldest codeless entry is evicted, not the oldest overall");
assert.ok(ids.includes("999"));
});
storeTest("store: a store that loses its writes never claims to hold the code", () => {
const durable = new Store(new MemoryStorage());
durable.saveOrder(order("1", { code: "SB-CODE" }));
assert.equal(durable.holdsCode("1", "SB-CODE"), true);
const losing = new Store(new MemoryStorage(), false);
losing.saveOrder(order("1", { code: "SB-CODE" }));
assert.equal(losing.order("1")?.code, "SB-CODE", "it answers with what it was handed");
assert.equal(losing.holdsCode("1", "SB-CODE"), false, "and still does not claim to hold it");
assert.equal(durable.holdsCode("1", undefined), false, "no code is not a held code");
assert.equal(durable.holdsCode("2", "SB-CODE"), false);
});
storeTest("store: a full list cannot hold another code, and says so before the money", () => {
const s = new Store(new MemoryStorage());
assert.equal(s.canHoldACode(), true, "an empty store has room");
for (let i = 0; i < 50; i++) s.saveOrder(order(String(i), { code: "C" + i }));
assert.equal(s.saveOrder(order("999", { code: "NEW" })), false, "the cap really is reached");
assert.equal(s.canHoldACode(), false, "and the buyer can be told before paying");
const roomy = new Store(new MemoryStorage());
for (let i = 0; i < 50; i++) roomy.saveOrder(order(String(i), i === 7 ? {} : { code: "C" + i }));
assert.equal(roomy.canHoldACode(), true, "one codeless entry is room enough");
assert.equal(new Store(new MemoryStorage(), false).canHoldACode(), false, "and a losing store never has room");
});
storeTest("store: forgetting one key does not depend on the other succeeding", () => {
class RefusesOrders extends MemoryStorage {
override removeItem(k: string): void {
if (k === "sb.orders.v1") throw new Error("SecurityError");
super.removeItem(k);
}
}
const storage = new RefusesOrders();
const s = new Store(storage);
s.saveOrder(order("1", { code: "SB-CODE" }));
s.saveSession({ step: "checkout" });
s.forgetEverything();
assert.equal(storage.getItem("sb.session.v1"), null, "the session goes even though the orders key threw");
});
storeTest("store: forgetting everything is countable, so a write awaited across it can be dropped", () => {
const s = new Store(new MemoryStorage());
const before = s.wipeCount;
s.saveOrder(order("1"));
assert.equal(s.wipeCount, before, "an ordinary write is not a wipe");
s.forgetEverything();
assert.equal(s.wipeCount, before + 1, "and the wipe is");
s.forgetEverything();
assert.equal(s.wipeCount, before + 2, "each one counts: two answers may be in flight");
});
storeTest("store: a card-return is returned within the window, cleared on read, and dropped when stale", () => {
const s = new Store(new MemoryStorage());
const NOW = 1_000_000;
const WINDOW = 60_000;
assert.equal(s.takeCardReturn(WINDOW, NOW), undefined, "nothing remembered yet");
s.rememberCardReturn("inv_1", NOW);
assert.equal(s.takeCardReturn(WINDOW, NOW + WINDOW), "inv_1", "at the edge of the window it is the return");
assert.equal(s.takeCardReturn(WINDOW, NOW + WINDOW), undefined, "and it was cleared on the read");
s.rememberCardReturn("inv_2", NOW);
assert.equal(s.takeCardReturn(WINDOW, NOW + WINDOW + 1), undefined, "past the window it is a stale attempt");
s.rememberCardReturn("inv_3", NOW);
s.clearCardReturn();
assert.equal(s.takeCardReturn(WINDOW, NOW), undefined, "an explicit clear forgets it");
});