Skip to content
84 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { readFileSync } from "node:fs";
3import { test } from "node:test";
4
5import { CONFIRM_PATH, afterConfirming, confirmGate, confirmedLine, openWhilePending, pageOf } from "./confirm-gate.ts";
6
7const pending = { verified: false };
8const confirmed = { verified: true };
9
10test("a pending account is sent to confirm its address from every app page", () => {
11 assert.equal(confirmGate("/", "", pending), CONFIRM_PATH);
12 assert.equal(confirmGate("/acme", "", pending), `${CONFIRM_PATH}?next=%2Facme`);
13 assert.equal(confirmGate("/workspaces/new", "?next=/x", pending), `${CONFIRM_PATH}?next=%2Fworkspaces%2Fnew%3Fnext%3D%252Fx`);
14 assert.equal(confirmGate("/settings/tokens", "", pending), `${CONFIRM_PATH}?next=%2Fsettings%2Ftokens`);
15 assert.equal(confirmGate("/invite/g1t-abcd", "", pending), `${CONFIRM_PATH}?next=%2Finvite%2Fg1t-abcd`);
16 assert.equal(confirmGate("/acme/rocket/pulls", "?state=open", pending), `${CONFIRM_PATH}?next=%2Facme%2Frocket%2Fpulls%3Fstate%3Dopen`);
17});
18
19test("client navigations are gated as the page they load", () => {
20 assert.equal(pageOf("/acme.data"), "/acme");
21 assert.equal(pageOf("/_root.data"), "/");
22 assert.equal(confirmGate("/acme/rocket.data", "?_routes=routes%2Frepo%2Flayout", pending), `${CONFIRM_PATH}?next=%2Facme%2Frocket`);
23 assert.equal(confirmGate("/_root.data", "", pending), CONFIRM_PATH);
24 assert.equal(confirmGate("/confirm-email.data", "", pending), null);
25});
26
27test("the pages confirming needs, and the public pages about g1t, stay open", () => {
28 for (const path of [
29 "/confirm-email",
30 "/verify",
31 "/logout",
32 "/login",
33 "/login/two-factor",
34 "/register",
35 "/forgot",
36 "/reset",
37 "/policies",
38 "/policies/terms",
39 "/security",
40 "/support",
41 "/status",
42 "/pricing",
43 "/auth/github/callback",
44 "/.well-known/security.txt",
45 ]) {
46 assert.equal(openWhilePending(path), true, path);
47 assert.equal(confirmGate(path, "", pending), null, path);
48 }
49 for (const path of ["/", "/explore", "/search", "/settings", "/settings/emails", "/device", "/oauth/authorize", "/new", "/inbox"]) {
50 assert.equal(openWhilePending(path), false, path);
51 }
52});
53
54test("confirmed accounts, visitors and non-people are never gated", () => {
55 assert.equal(confirmGate("/acme", "", confirmed), null);
56 assert.equal(confirmGate("/acme", "", null), null);
57 assert.equal(confirmGate("/acme", "", { kind: "workspace", verified: false }), null);
58 assert.equal(confirmGate("/acme", "", { kind: "user" }), `${CONFIRM_PATH}?next=%2Facme`);
59});
60
61test("what the page says once confirmed, and where it goes", () => {
62 assert.equal(confirmedLine({}), "Your email address is confirmed.");
63 assert.equal(confirmedLine({ joined: "acme" }), "Your email address is confirmed, and you have joined acme.");
64 assert.match(confirmedLine({ inviteLapsed: "Your email address is confirmed. The invite … was revoked" }), /revoked/);
65 assert.equal(afterConfirming("/", "acme"), "/acme");
66 assert.equal(afterConfirming("/acme/rocket", "acme"), "/acme/rocket");
67 assert.equal(afterConfirming("", null), "/");
68});
69
70test("confirming an account whose invite names a workspace goes on to accept or decline it", () => {
71 assert.match(confirmedLine({ invitedTo: "flagon-io" }), /invited to join flagon-io: accept or decline/);
72 assert.equal(afterConfirming("/flagon-io", null, "flagon-io"), "/invitations");
73 assert.equal(afterConfirming("/", null, null), "/");
74});
75
76test("the code field is a one-time code typed with a number pad", () => {
77 const page = readFileSync(new URL("../routes/confirm-email.tsx", import.meta.url), "utf8");
78 const input = /<Input[\s\S]*?name="code"[\s\S]*?\/>/.exec(page)?.[0] ?? "";
79 assert.match(input, /autoComplete="one-time-code"/);
80 assert.match(input, /inputMode="numeric"/);
81 assert.match(input, /pattern="\[0-9 -\]\*"/);
82 // Either route: the page says the link in the email works too.
83 assert.match(page, /link in the email/);
84});