Skip to content
217 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { readFileSync } from "node:fs";
3import { test } from "node:test";
4
5import { CONTACT } from "./legal.ts";
6import {
7 HAVE_AN_INVITE,
8 OWN_WORKSPACE,
9 bringIntoChoices,
10 INVITES_CONTACT,
11 cleanCode,
12 cleanProof,
13 invitePath,
14 inviteSignUpCopy,
15 inviteFor,
16 inviteLink,
17 inviteState,
18 landingFor,
19 looksAutomated,
20 moreInvitesMailto,
21 remainingLine,
22 sharedDomainsHint,
23 sharedInviteLine,
24 sharedInviteLink,
25 signUpCopy,
26 suggestUsername,
27 welcomeCookie,
28 clearWelcome,
29 welcomes,
30} from "./invites.ts";
31
32const CODE = "g1t-k7m2-q9xd-4hpw-abcd-0123-4567-89ef-ghjk";
33
34test("while invite-only, nobody is offered a plain sign-up", () => {
35 // Sign up everywhere; only the sign-up page says registration takes an invite.
36 assert.deepEqual(signUpCopy(), { primary: "Sign up", secondary: null });
37 assert.equal(HAVE_AN_INVITE, "/register#invite");
38});
39
40test("asking for more invites goes to support with the [g1t Invites] subject", () => {
41 assert.equal(INVITES_CONTACT, CONTACT.support);
42 assert.equal(moreInvitesMailto(), "mailto:hey@flagon.io?subject=%5Bg1t%20Invites%5D%20More%20invites");
43 assert.equal(
44 moreInvitesMailto("acme"),
45 "mailto:hey@flagon.io?subject=%5Bg1t%20Invites%5D%20More%20invites%20for%20acme",
46 );
47});
48
49test("an invite link is on g1t.sh unless told otherwise", () => {
50 assert.equal(inviteLink(CODE), `https://g1t.sh/invite/${CODE}`);
51 assert.equal(inviteLink(CODE, "http://localhost:8787/"), `http://localhost:8787/invite/${CODE}`);
52});
53
54const PROOF = "4f9c2a7e0b13d5c84f9c2a7e0b13d5c84f9c2a7e0b13d5c84f9c2a7e0b13d5c8";
55
56test("an invite email's proof is kept only when it looks like one, and goes along to the invite's page", () => {
57 assert.equal(cleanProof(PROOF), PROOF);
58 assert.equal(cleanProof(` ${PROOF.toUpperCase()} `), PROOF);
59 assert.equal(cleanProof("not-a-proof"), null);
60 assert.equal(cleanProof("abc"), null);
61 assert.equal(cleanProof("a".repeat(500)), null);
62 assert.equal(cleanProof(null), null);
63 assert.equal(invitePath(CODE, PROOF), `/invite/${CODE}?proof=${PROOF}`);
64 assert.equal(invitePath(CODE, null), `/invite/${CODE}`);
65 assert.equal(invitePath(CODE), `/invite/${CODE}`);
66});
67
68test("signing up from the invite email says the address is confirmed already; otherwise the code step applies", () => {
69 const base = { address: "ada@example.com", emailProven: false, workspace: { name: "Flagon, Inc." }, repository: null };
70 const proven = inviteSignUpCopy({ ...base, emailProven: true });
71 assert.equal(proven.intro, "You can join Flagon, Inc. as soon as you create it: accept the invitation then.");
72 assert.match(proven.confirmed ?? "", /^ada@example\.com is confirmed: you came here from the invite we emailed to it/);
73 assert.match(proven.hint, /confirmed already/);
74 assert.doesNotMatch(proven.hint, /code/);
75
76 // No proof (a code typed in, or a link passed on): nothing new is said.
77 const plain = inviteSignUpCopy(base);
78 assert.equal(plain.intro, "You can join Flagon, Inc. as soon as you confirm your email: accept the invitation then.");
79 assert.equal(plain.confirmed, null);
80 assert.equal(plain.hint, "Your invite was sent here. We email it a code to confirm it before you start.");
81
82 // An invite for anyone with the code has no address to prove.
83 const open = inviteSignUpCopy({ ...base, address: null, emailProven: true, workspace: null });
84 assert.equal(open.confirmed, null);
85 assert.equal(open.intro, "It takes a minute.");
86 assert.equal(open.hint, "We email it a code to confirm it before you start.");
87
88 const repo = inviteSignUpCopy({ ...base, workspace: null, repository: { name: "flagon-io/g1t" }, emailProven: true });
89 assert.equal(repo.intro, "You get flagon-io/g1t as soon as you create it.");
90});
91
92test("a pasted link or code is tidied to the code", () => {
93 assert.equal(cleanCode(CODE), CODE);
94 assert.equal(cleanCode(` ${CODE} `), CODE);
95 assert.equal(cleanCode(`https://g1t.sh/invite/${CODE}`), CODE);
96 assert.equal(cleanCode(`https://g1t.sh/register?invite=${CODE}&next=%2F`), CODE);
97 assert.equal(cleanCode("g1t-k7m2 q9xd"), "g1t-k7m2q9xd");
98 assert.equal(cleanCode(null), "");
99 assert.equal(cleanCode("x".repeat(500)).length, 80);
100});
101
102test("each invite says where it stands and whom it is for", () => {
103 const base = { redeemedBy: null, email: null, workspace: null };
104 assert.deepEqual(inviteState({ ...base, status: "pending" }), { label: "Pending", tone: "pending" });
105 assert.deepEqual(inviteState({ ...base, status: "redeemed", redeemedBy: "ada" }), { label: "Joined as @ada", tone: "done" });
106 assert.deepEqual(inviteState({ ...base, status: "awaiting_confirmation", redeemedBy: "ada" }), {
107 label: "@ada is confirming their email",
108 tone: "pending",
109 });
110 assert.deepEqual(inviteState({ ...base, status: "expired" }), { label: "Expired", tone: "dead" });
111 assert.deepEqual(inviteState({ ...base, status: "revoked" }), { label: "Revoked", tone: "dead" });
112 assert.equal(inviteFor({ ...base, status: "pending" }), "Anyone with the link");
113 assert.equal(inviteFor({ ...base, status: "pending", email: "ada@example.com", workspace: "acme" }), "ada@example.com · invited to acme");
114 assert.equal(inviteFor({ ...base, status: "pending", invitee: "daweazl", workspace: "flagon-io" }), "@daweazl · invited to flagon-io");
115 assert.deepEqual(inviteState({ ...base, status: "awaiting_answer", redeemedBy: "daweazl" }), { label: "Waiting for @daweazl to accept", tone: "pending" });
116 assert.deepEqual(inviteState({ ...base, status: "declined", invitee: "daweazl" }), { label: "@daweazl declined", tone: "dead" });
117});
118
119test("what is left reads plainly", () => {
120 assert.equal(remainingLine({ limit: 5, used: 2, remaining: 3 }), "3 of 5 invites left");
121 assert.equal(remainingLine({ limit: 1, used: 0, remaining: 1 }), "1 of 1 invite left");
122 assert.equal(remainingLine({ limit: 5, used: 5, remaining: 0 }), "You have used all 5 of your invites");
123 assert.equal(remainingLine({ limit: null, used: 40, remaining: null }), "No limit on your invites");
124});
125
126test("bots that fill the hidden field or answer instantly are turned away", () => {
127 const form = (fields: Record<string, string>) => ({ get: (name: string) => fields[name] ?? null });
128 const now = 1_000_000;
129 assert.equal(looksAutomated(form({ website: "http://spam.example" }), now), true);
130 assert.equal(looksAutomated(form({ started: String(now - 200) }), now), true);
131 assert.equal(looksAutomated(form({ started: String(now - 10_000) }), now), false);
132 assert.equal(looksAutomated(form({}), now), false);
133 assert.equal(looksAutomated(form({ website: " " }), now), false);
134});
135
136test("a username is suggested from the invited address", () => {
137 assert.equal(suggestUsername("ada.lovelace@example.com"), "ada-lovelace");
138 assert.equal(suggestUsername("Margaret_Hamilton+g1t@example.com"), "margaret-hamilton");
139 assert.equal(suggestUsername("--x--@example.com"), "x");
140 assert.equal(suggestUsername(`${"a".repeat(38)}.b@example.com`), "a".repeat(38));
141 assert.equal(suggestUsername("...@example.com"), "");
142 assert.equal(suggestUsername(null), "");
143});
144
145test("an invite lands in its workspace, else its repository", () => {
146 assert.equal(landingFor({ workspace: { slug: "Flagon-IO" }, repository: null }), "flagon-io");
147 assert.equal(landingFor({ workspace: null, repository: { name: "flagon-io/g1t" } }), "flagon-io/g1t");
148 assert.equal(landingFor({ workspace: null, repository: null }), null);
149});
150
151test("the welcome is for one place, and ends", () => {
152 const set = welcomeCookie("flagon-io/g1t", true);
153 assert.match(set, /^g1t_welcome=flagon-io%2Fg1t; Path=\/; Max-Age=300; HttpOnly; SameSite=Lax; Secure$/);
154 const header = `a=1; ${set.split(";")[0]}; b=2`;
155 assert.equal(welcomes(header, "flagon-io/g1t"), true);
156 assert.equal(welcomes(header, "flagon-io"), false);
157 assert.equal(welcomes("g1t_welcome=flagon-io", "Flagon-IO"), true);
158 assert.equal(welcomes("g1t_welcome=%E0%A4%A", "flagon-io"), false);
159 assert.equal(welcomes("g1t_welcome=..%2F..%2Fx", "../../x"), false);
160 assert.equal(welcomes(null, "flagon-io"), false);
161 assert.match(clearWelcome(false), /^g1t_welcome=; Path=\/; Max-Age=0; HttpOnly; SameSite=Lax$/);
162});
163
164test("a shared invite link names its group above the sign-up form", () => {
165 assert.equal(sharedInviteLine("Cloudflare judges"), "Invited as part of Cloudflare judges");
166 assert.equal(sharedInviteLine(" Hacker News readers "), "Invited as part of Hacker News readers");
167 // A one-person invite has no group, and says nothing of the kind.
168 assert.equal(sharedInviteLine(null), null);
169 assert.equal(sharedInviteLine(undefined), null);
170 assert.equal(sharedInviteLine(" "), null);
171});
172
173test("a shared invite link is sign-up with its code filled in", () => {
174 assert.equal(sharedInviteLink(CODE), `https://g1t.sh/register?invite=${CODE}`);
175 assert.equal(sharedInviteLink(CODE, "http://localhost:5173/"), `http://localhost:5173/register?invite=${CODE}`);
176 // The register page reads the code back out of its own link.
177 assert.equal(cleanCode(sharedInviteLink(CODE)), CODE);
178});
179
180test("a shared link limited to domains says which, on the email field", () => {
181 assert.equal(sharedDomainsHint([]), undefined);
182 assert.equal(sharedDomainsHint(null), undefined);
183 assert.equal(sharedDomainsHint(["cloudflare.com"]), "This invite is for addresses at cloudflare.com. Use yours there.");
184 assert.equal(
185 sharedDomainsHint(["a.com", "b.com", "c.com"]),
186 "This invite is for addresses at a.com, b.com or c.com. Use yours there.",
187 );
188});
189
190test("an own invite brings its person into a workspace you own that can add people, the current one first", () => {
191 const memberships = [
192 { slug: "flagon-io", name: "Flagon, Inc.", role: "owner" as const },
193 { slug: "side", name: "side", role: "owner" as const },
194 { slug: "friends", name: "Friends", role: "member" as const },
195 ];
196 // The current workspace is chosen; free ones and ones you only belong to are not offered.
197 const here = bringIntoChoices(memberships, ["side"], "flagon-io");
198 assert.deepEqual(here.options, [{ slug: "flagon-io", name: "Flagon, Inc." }]);
199 assert.equal(here.chosen, "flagon-io");
200 assert.equal(here.note, null);
201 // In a free workspace: not offered, and the form says why; no workspace is chosen.
202 const free = bringIntoChoices(memberships, ["side"], "side");
203 assert.equal(free.chosen, OWN_WORKSPACE);
204 assert.match(free.note ?? "", /side is on the free plan, so it cannot add people/);
205 // In one you are only a member of.
206 assert.match(bringIntoChoices(memberships, [], "friends").note ?? "", /Only the owners of friends/);
207 // No workspace at all: their own.
208 assert.deepEqual(bringIntoChoices([], [], null), { options: [], chosen: OWN_WORKSPACE, note: null });
209});
210
211test("the invites form offers each workspace and no workspace, named for what it does", () => {
212 const section = readFileSync(new URL("../components/invites-section.tsx", import.meta.url), "utf8");
213 assert.match(section, /Bring them into/);
214 assert.match(section, /name="join"/);
215 assert.match(section, /No workspace — they'll get their own/);
216 assert.match(section, /defaultValue=\{bringInto\.chosen\}/);
217});