eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1// Tests for the passkey-backup banner visibility rule. node test/backup.test.mjs
2import assert from "node:assert/strict";
3import { autoRestoreRequested, backupBannerState, parseReturnNonce, parseRestoreReturn, REMIND_RUNGS_MS } from "../web/static/backup.js";
4
5let failures = 0;
6async function test(name, fn) {
7 try {
8 await fn();
9 console.log("PASS " + name);
10 } catch (e) {
11 console.log("FAIL " + name + " — " + (e && e.message));
12 failures++;
13 }
14}
15
16const DAY = 24 * 60 * 60 * 1000;
17const now = 1_700_000_000_000; // a fixed "now" so snooze comparisons are deterministic
18
19await test("home space links request automatic restore by exact marker", () => {
20 assert.equal(autoRestoreRequested("?restore=1"), true);
21 assert.equal(autoRestoreRequested("?thread=7&restore=1"), true);
22 assert.equal(autoRestoreRequested("?restore=0"), false);
23 assert.equal(autoRestoreRequested("?restored=1"), false);
24});
25
26await test("not enrolled + not snoozed → show", () => {
27 assert.equal(backupBannerState({ enrolled: false, dismissed: false, snoozedUntil: 0, now }), "show");
28});
29
30await test("enrolled → hidden", () => {
31 assert.equal(backupBannerState({ enrolled: true, dismissed: false, snoozedUntil: 0, now }), "hidden");
32});
33
34await test("snoozed in the future → hidden", () => {
35 assert.equal(backupBannerState({ enrolled: false, dismissed: false, snoozedUntil: now + 7 * DAY, now }), "hidden");
36});
37
38await test("snooze expired → show", () => {
39 assert.equal(backupBannerState({ enrolled: false, dismissed: false, snoozedUntil: now - DAY, now }), "show");
40});
41
42await test("permanently dismissed → hidden", () => {
43 assert.equal(backupBannerState({ enrolled: false, dismissed: true, snoozedUntil: 0, now }), "hidden");
44});
45
46await test("enrolled wins even if the snooze has expired", () => {
47 assert.equal(backupBannerState({ enrolled: true, dismissed: false, snoozedUntil: now - DAY, now }), "hidden");
48});
49
50// The deferral schedule: a setup-time skip keeps the banner quiet until each
51// escalating rung (24h → 72h → 1w) is due; past the rungs, classic rules apply.
52await test("setup skip: quiet before the 24h rung, shows once due", () => {
53 const base = { enrolled: false, dismissed: false, snoozedUntil: 0, deferredAt: now, remindStage: 0 };
54 assert.equal(backupBannerState({ ...base, now: now + DAY - 1 }), "hidden");
55 assert.equal(backupBannerState({ ...base, now: now + DAY }), "show");
56});
57
58await test("setup skip: each dismissal advances to the next rung", () => {
59 const base = { enrolled: false, dismissed: false, snoozedUntil: 0, deferredAt: now };
60 assert.equal(backupBannerState({ ...base, remindStage: 1, now: now + DAY + 1 }), "hidden", "72h rung not yet due");
61 assert.equal(backupBannerState({ ...base, remindStage: 1, now: now + 3 * DAY }), "show");
62 assert.equal(backupBannerState({ ...base, remindStage: 2, now: now + 6 * DAY }), "hidden", "final rung not yet due");
63 assert.equal(backupBannerState({ ...base, remindStage: 2, now: now + 7 * DAY }), "show");
64});
65
66await test("setup skip: past the final rung the classic snooze rule owns it", () => {
67 const base = { enrolled: false, dismissed: false, deferredAt: now, remindStage: REMIND_RUNGS_MS.length };
68 assert.equal(backupBannerState({ ...base, snoozedUntil: now + 30 * DAY, now: now + 8 * DAY }), "hidden");
69 assert.equal(backupBannerState({ ...base, snoozedUntil: 0, now: now + 8 * DAY }), "show");
70});
71
72await test("setup skip: enrolment or opt-out silences the schedule", () => {
73 const due = now + 2 * DAY;
74 assert.equal(backupBannerState({ enrolled: true, dismissed: false, snoozedUntil: 0, deferredAt: now, remindStage: 0, now: due }), "hidden");
75 assert.equal(backupBannerState({ enrolled: false, dismissed: true, snoozedUntil: 0, deferredAt: now, remindStage: 0, now: due }), "hidden");
76});
77
78// parseReturnNonce: pull our one-time nonce out of the /enroll return fragment.
79await test("return nonce parses from a lone marker", () => {
80 assert.equal(parseReturnNonce("#lchat_backup=abc123"), "abc123");
81});
82await test("return nonce is URL-decoded", () => {
83 assert.equal(parseReturnNonce("#lchat_backup=a%2Fb%2Bc"), "a/b+c");
84});
85await test("return nonce parses when not first in the fragment", () => {
86 assert.equal(parseReturnNonce("#foo=1&lchat_backup=xyz"), "xyz");
87});
88await test("no marker → empty", () => {
89 assert.equal(parseReturnNonce("#something-else"), "");
90 assert.equal(parseReturnNonce(""), "");
91 assert.equal(parseReturnNonce("#lchat_backupZZZ=nope"), "");
92});
93
94// parseRestoreReturn: pull { nonce, wrapped } out of the /restore return fragment.
95function b64url(s) {
96 return Buffer.from(s, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
97}
98await test("restore return parses nonce + wrapped", () => {
99 const h = "#lchat_restore=" + b64url(JSON.stringify({ nonce: "n1", wrapped: "w1" }));
100 assert.deepEqual(parseRestoreReturn(h), { nonce: "n1", wrapped: "w1" });
101});
102await test("restore return: no marker → null", () => {
103 assert.equal(parseRestoreReturn("#something"), null);
104 assert.equal(parseRestoreReturn(""), null);
105});
106await test("restore return: malformed base64 → null", () => {
107 assert.equal(parseRestoreReturn("#lchat_restore=%%%"), null);
108});
109await test("restore return: valid base64 but missing fields → null", () => {
110 assert.equal(parseRestoreReturn("#lchat_restore=" + b64url(JSON.stringify({ nonce: "n" }))), null);
111});
112
113console.log(failures === 0 ? "\nALL PASSED" : `\n${failures} FAILED`);
114process.exit(failures === 0 ? 0 : 1);
115