g1t/apps/web/app/lib/invites.test.ts

108 lines5,227 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { CONTACT } from "./legal.ts";
5import {
6 HAVE_AN_INVITE,
7 INVITES_CONTACT,
8 cleanCode,
9 inviteFor,
10 inviteLink,
11 inviteState,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas12 landingFor,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look13 looksAutomated,
14 moreInvitesMailto,
15 remainingLine,
16 signUpCopy,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas17 suggestUsername,
18 welcomeCookie,
19 clearWelcome,
20 welcomes,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look21} from "./invites.ts";
22
23const CODE = "g1t-k7m2-q9xd-4hpw-abcd-0123-4567-89ef-ghjk";
24
25test("while invite-only, nobody is offered a plain sign-up", () => {
26 assert.deepEqual(signUpCopy(true), { primary: "Request access", secondary: "Have an invite?" });
27 assert.deepEqual(signUpCopy(false), { primary: "Sign up", secondary: null });
28 assert.equal(HAVE_AN_INVITE, "/register#invite");
29});
30
31test("asking for more invites goes to support with the [g1t Invites] subject", () => {
32 assert.equal(INVITES_CONTACT, CONTACT.support);
33 assert.equal(moreInvitesMailto(), "mailto:hey@flagon.io?subject=%5Bg1t%20Invites%5D%20More%20invites");
34 assert.equal(
35 moreInvitesMailto("acme"),
36 "mailto:hey@flagon.io?subject=%5Bg1t%20Invites%5D%20More%20invites%20for%20acme",
37 );
38});
39
40test("an invite link is on g1t.sh unless told otherwise", () => {
41 assert.equal(inviteLink(CODE), `https://g1t.sh/invite/${CODE}`);
42 assert.equal(inviteLink(CODE, "http://localhost:8787/"), `http://localhost:8787/invite/${CODE}`);
43});
44
45test("a pasted link or code is tidied to the code", () => {
46 assert.equal(cleanCode(CODE), CODE);
47 assert.equal(cleanCode(` ${CODE} `), CODE);
48 assert.equal(cleanCode(`https://g1t.sh/invite/${CODE}`), CODE);
49 assert.equal(cleanCode(`https://g1t.sh/register?invite=${CODE}&next=%2F`), CODE);
50 assert.equal(cleanCode("g1t-k7m2 q9xd"), "g1t-k7m2q9xd");
51 assert.equal(cleanCode(null), "");
52 assert.equal(cleanCode("x".repeat(500)).length, 80);
53});
54
55test("each invite says where it stands and whom it is for", () => {
56 const base = { redeemedBy: null, email: null, workspace: null };
57 assert.deepEqual(inviteState({ ...base, status: "pending" }), { label: "Pending", tone: "pending" });
58 assert.deepEqual(inviteState({ ...base, status: "redeemed", redeemedBy: "ada" }), { label: "Joined as @ada", tone: "done" });
59 assert.deepEqual(inviteState({ ...base, status: "expired" }), { label: "Expired", tone: "dead" });
60 assert.deepEqual(inviteState({ ...base, status: "revoked" }), { label: "Revoked", tone: "dead" });
61 assert.equal(inviteFor({ ...base, status: "pending" }), "Anyone with the link");
62 assert.equal(inviteFor({ ...base, status: "pending", email: "ada@example.com", workspace: "acme" }), "ada@example.com · joins acme");
63});
64
65test("what is left reads plainly", () => {
66 assert.equal(remainingLine({ limit: 5, used: 2, remaining: 3 }), "3 of 5 invites left");
67 assert.equal(remainingLine({ limit: 1, used: 0, remaining: 1 }), "1 of 1 invite left");
68 assert.equal(remainingLine({ limit: 5, used: 5, remaining: 0 }), "You have used all 5 of your invites");
69 assert.equal(remainingLine({ limit: null, used: 40, remaining: null }), "No limit on your invites");
70});
71
72test("bots that fill the hidden field or answer instantly are turned away", () => {
73 const form = (fields: Record<string, string>) => ({ get: (name: string) => fields[name] ?? null });
74 const now = 1_000_000;
75 assert.equal(looksAutomated(form({ website: "http://spam.example" }), now), true);
76 assert.equal(looksAutomated(form({ started: String(now - 200) }), now), true);
77 assert.equal(looksAutomated(form({ started: String(now - 10_000) }), now), false);
78 assert.equal(looksAutomated(form({}), now), false);
79 assert.equal(looksAutomated(form({ website: " " }), now), false);
80});
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas81
82test("a username is suggested from the invited address", () => {
83 assert.equal(suggestUsername("ada.lovelace@example.com"), "ada-lovelace");
84 assert.equal(suggestUsername("Margaret_Hamilton+g1t@example.com"), "margaret-hamilton");
85 assert.equal(suggestUsername("--x--@example.com"), "x");
86 assert.equal(suggestUsername(`${"a".repeat(38)}.b@example.com`), "a".repeat(38));
87 assert.equal(suggestUsername("...@example.com"), "");
88 assert.equal(suggestUsername(null), "");
89});
90
91test("an invite lands in its workspace, else its repository", () => {
92 assert.equal(landingFor({ workspace: { slug: "Flagon-IO" }, repository: null }), "flagon-io");
93 assert.equal(landingFor({ workspace: null, repository: { name: "flagon-io/g1t" } }), "flagon-io/g1t");
94 assert.equal(landingFor({ workspace: null, repository: null }), null);
95});
96
97test("the welcome is for one place, and ends", () => {
98 const set = welcomeCookie("flagon-io/g1t", true);
99 assert.match(set, /^g1t_welcome=flagon-io%2Fg1t; Path=\/; Max-Age=300; HttpOnly; SameSite=Lax; Secure$/);
100 const header = `a=1; ${set.split(";")[0]}; b=2`;
101 assert.equal(welcomes(header, "flagon-io/g1t"), true);
102 assert.equal(welcomes(header, "flagon-io"), false);
103 assert.equal(welcomes("g1t_welcome=flagon-io", "Flagon-IO"), true);
104 assert.equal(welcomes("g1t_welcome=%E0%A4%A", "flagon-io"), false);
105 assert.equal(welcomes("g1t_welcome=..%2F..%2Fx", "../../x"), false);
106 assert.equal(welcomes(null, "flagon-io"), false);
107 assert.match(clearWelcome(false), /^g1t_welcome=; Path=\/; Max-Age=0; HttpOnly; SameSite=Lax$/);
108});