| 1 | // Tests for the signup-first join page's pure decision helpers (prejoin.js). |
| 2 | import test from "node:test"; |
| 3 | import assert from "node:assert/strict"; |
| 4 | import { |
| 5 | PREJOIN_STASH_KEY, offerPrejoin, prejoinStash, readPrejoinReturn, homeSigninURL, |
| 6 | autoEnrolOnClaim, |
| 7 | } from "../web/static/prejoin.js"; |
| 8 | |
| 9 | const HOME = "https://home.example.com"; |
| 10 | const base = { kind: "join", homeOrigin: HOME, embed: false, native: false, signedUp: false }; |
| 11 | |
| 12 | test("offerPrejoin: fresh join/setup kinds with a home server", () => { |
| 13 | assert.equal(offerPrejoin(base), true); |
| 14 | assert.equal(offerPrejoin({ ...base, kind: "setup" }), true); |
| 15 | }); |
| 16 | |
| 17 | test("offerPrejoin: never for established or unsupported contexts", () => { |
| 18 | for (const kind of ["device", "adopt", "adopt-admin"]) { |
| 19 | assert.equal(offerPrejoin({ ...base, kind }), false); |
| 20 | } |
| 21 | assert.equal(offerPrejoin({ ...base, homeOrigin: "" }), false); |
| 22 | assert.equal(offerPrejoin({ ...base, homeOrigin: "__HOME_ORIGIN__" }), false); |
| 23 | assert.equal(offerPrejoin({ ...base, embed: true }), false); |
| 24 | assert.equal(offerPrejoin({ ...base, native: true }), false); |
| 25 | assert.equal(offerPrejoin({ ...base, signedUp: true }), false); |
| 26 | }); |
| 27 | |
| 28 | // The create-a-space journey: home hands over /join/setup?src=home moments |
| 29 | // after a sign-in there (and having already saved the space to that account). |
| 30 | // Leading with the sign-in door again reads as the flow forgetting. |
| 31 | test("offerPrejoin: a setup link from a signed-in home page never leads with sign-in", () => { |
| 32 | assert.equal(offerPrejoin({ ...base, kind: "setup", fromHome: true }), false); |
| 33 | assert.equal(offerPrejoin({ ...base, kind: "join", fromHome: true }), false); |
| 34 | assert.equal(offerPrejoin({ ...base, kind: "setup", fromHome: false }), true); |
| 35 | }); |
| 36 | |
| 37 | // One sign-in per space: an arrival that already signed in on the way here |
| 38 | // enrols itself at claim time, so nothing asks again later. |
| 39 | test("autoEnrolOnClaim: the two already-signed-in arrivals, and nobody else", () => { |
| 40 | const at = { homeOrigin: HOME, embed: false }; |
| 41 | assert.equal(autoEnrolOnClaim({ ...at, signedUp: true }), true); |
| 42 | assert.equal(autoEnrolOnClaim({ ...at, fromHome: true }), true); |
| 43 | assert.equal(autoEnrolOnClaim({ ...at }), false, "a cold joiner still meets the ask"); |
| 44 | assert.equal(autoEnrolOnClaim({ ...at, fromHome: true, embed: true }), false); |
| 45 | assert.equal(autoEnrolOnClaim({ signedUp: true, fromHome: true, homeOrigin: "", embed: false }), false); |
| 46 | assert.equal(autoEnrolOnClaim({ signedUp: true, homeOrigin: "__HOME_ORIGIN__", embed: false }), false); |
| 47 | }); |
| 48 | |
| 49 | test("round trip: stash then read back on a genuine return", () => { |
| 50 | const now = 1_000_000; |
| 51 | const stored = prejoinStash({ frag: "e1.SECRET~Ann~bkey", nonce: "n0nce", now }); |
| 52 | const r = readPrejoinReturn({ search: "?su=n0nce", storedJSON: stored, now: now + 5000 }); |
| 53 | assert.deepEqual(r, { signedUp: true, frag: "e1.SECRET~Ann~bkey" }); |
| 54 | }); |
| 55 | |
| 56 | test("readPrejoinReturn: no marker, forged nonce, stale stash, clock skew", () => { |
| 57 | const now = 1_000_000; |
| 58 | const stored = prejoinStash({ frag: "f", nonce: "good", now }); |
| 59 | assert.deepEqual(readPrejoinReturn({ search: "", storedJSON: stored, now }), |
| 60 | { signedUp: false, frag: "" }); |
| 61 | assert.deepEqual(readPrejoinReturn({ search: "?su=evil", storedJSON: stored, now }), |
| 62 | { signedUp: false, frag: "" }); |
| 63 | assert.deepEqual(readPrejoinReturn({ search: "?su=good", storedJSON: stored, now: now + 61 * 60 * 1000 }), |
| 64 | { signedUp: false, frag: "" }); |
| 65 | assert.deepEqual(readPrejoinReturn({ search: "?su=good", storedJSON: stored, now: now - 1000 }), |
| 66 | { signedUp: false, frag: "" }); |
| 67 | assert.deepEqual(readPrejoinReturn({ search: "?su=good", storedJSON: "not json", now }), |
| 68 | { signedUp: false, frag: "" }); |
| 69 | assert.deepEqual(readPrejoinReturn({ search: "?su=good", storedJSON: "", now }), |
| 70 | { signedUp: false, frag: "" }); |
| 71 | }); |
| 72 | |
| 73 | test("homeSigninURL: return URL rides the fragment, encoded, no invite secret", () => { |
| 74 | const url = homeSigninURL(HOME, "https://chat.example.com", "/join/abc", "n+once"); |
| 75 | assert.equal(url, |
| 76 | HOME + "/#joinret=" + encodeURIComponent("https://chat.example.com/join/abc?su=" + encodeURIComponent("n+once"))); |
| 77 | assert.equal(homeSigninURL(HOME + "/", "https://c.example.com", "/join/x", "n"), |
| 78 | HOME + "/#joinret=" + encodeURIComponent("https://c.example.com/join/x?su=n")); |
| 79 | }); |
| 80 | |
| 81 | test("stash key is stable", () => assert.equal(PREJOIN_STASH_KEY, "lchat_prejoin")); |
| 82 | |