Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
275 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 filterInvitations,
6 filterMembers,
7 invitationCounts,
8 invitationFilter,
9 invitationFilterOf,
10 invitationTone,
11 inviteKind,
12 G1T_INVITES,
13 bringIntoChoices,
14 inviteActions,
15 inviteDraft,
16 leftBadge,
17 remainingLine,
18 memberFilters,
19 invitePageCopy,
20 workspaceInviteCopy,
21 cleanCode,
22 cleanProof,
23 invitePath,
24 inviteSignUpCopy,
25 inviteFor,
26 inviteLink,
27 inviteState,
28 landingFor,
29 looksAutomated,
30 signUpCopy,
31 suggestUsername,
32 welcomeCookie,
33 clearWelcome,
34 welcomes,
35} from "./invites.ts";
36
37const CODE = "g1t-k7m2-q9xd-4hpw-abcd-0123-4567-89ef-ghjk";
38
39test("the Invitations page sorts every state under one filter, and counts them", () => {
40 const statuses = ["pending", "awaiting_confirmation", "awaiting_answer", "redeemed", "declined", "expired", "revoked"] as const;
41 const invites = statuses.map((status) => ({ status }));
42 assert.equal(invitationFilterOf("pending"), "pending");
43 assert.equal(invitationFilterOf("awaiting_confirmation"), "pending");
44 assert.equal(invitationFilterOf("awaiting_answer"), "pending");
45 assert.equal(invitationFilterOf("redeemed"), "accepted");
46 assert.equal(invitationFilterOf("declined"), "declined");
47 assert.equal(invitationFilterOf("expired"), "expired");
48 assert.equal(invitationFilterOf("revoked"), "revoked");
49 assert.deepEqual(invitationCounts(invites), { all: 7, pending: 3, accepted: 1, declined: 1, expired: 1, revoked: 1 });
50 assert.equal(filterInvitations(invites, "all").length, 7);
51 assert.deepEqual(filterInvitations(invites, "pending").map((invite) => invite.status), ["pending", "awaiting_confirmation", "awaiting_answer"]);
52 assert.deepEqual(filterInvitations(invites, "accepted").map((invite) => invite.status), ["redeemed"]);
53 // The query names a filter, or means All.
54 assert.equal(invitationFilter("pending"), "pending");
55 assert.equal(invitationFilter("failed"), "all");
56 assert.equal(invitationFilter(null), "all");
57 // Waiting is amber, joined green, a no red, and over is quiet.
58 assert.equal(invitationTone("awaiting_answer"), "warn");
59 assert.equal(invitationTone("redeemed"), "success");
60 assert.equal(invitationTone("revoked"), "danger");
61 assert.equal(invitationTone("expired"), "neutral");
62});
63
64test("the Members page filters by role, two-factor, team and words", () => {
65 const members = [
66 { username: "ada", name: "Ada Lovelace", role: "owner" as const, two_factor: true },
67 { username: "grace", name: "Grace Hopper", role: "member" as const, two_factor: false },
68 { username: "linus", name: null, role: "member" as const, two_factor: true },
69 ];
70 const teams = { ada: [{ slug: "core" }], grace: [{ slug: "web" }, { slug: "core" }] };
71 assert.deepEqual(memberFilters(""), { role: "all", twoFactor: "all", team: null, query: "" });
72 assert.deepEqual(memberFilters("?role=owner&2fa=off&team=Core&q=%20ada%20"), { role: "owner", twoFactor: "off", team: "core", query: "ada" });
73 assert.deepEqual(memberFilters("?role=admin&2fa=maybe"), { role: "all", twoFactor: "all", team: null, query: "" });
74 const names = (filters: string) => filterMembers(members, memberFilters(filters), teams).map((member) => member.username);
75 assert.deepEqual(names(""), ["ada", "grace", "linus"]);
76 assert.deepEqual(names("?role=member"), ["grace", "linus"]);
77 assert.deepEqual(names("?2fa=off"), ["grace"]);
78 assert.deepEqual(names("?team=core"), ["ada", "grace"]);
79 assert.deepEqual(names("?q=hopper"), ["grace"]);
80 assert.deepEqual(names("?q=@LIN"), ["linus"]);
81 assert.deepEqual(names("?role=owner&team=web"), []);
82});
83
84test("everyone is offered a plain sign-up", () => {
85 assert.deepEqual(signUpCopy(), { primary: "Sign up", secondary: null });
86});
87
88test("an invite link is on g1t.sh unless told otherwise", () => {
89 assert.equal(inviteLink(CODE), `https://g1t.sh/invite/${CODE}`);
90 assert.equal(inviteLink(CODE, "http://localhost:8787/"), `http://localhost:8787/invite/${CODE}`);
91});
92
93const PROOF = "4f9c2a7e0b13d5c84f9c2a7e0b13d5c84f9c2a7e0b13d5c84f9c2a7e0b13d5c8";
94
95test("an invite email's proof is kept only when it looks like one, and goes along to the invite's page", () => {
96 assert.equal(cleanProof(PROOF), PROOF);
97 assert.equal(cleanProof(` ${PROOF.toUpperCase()} `), PROOF);
98 assert.equal(cleanProof("not-a-proof"), null);
99 assert.equal(cleanProof("abc"), null);
100 assert.equal(cleanProof("a".repeat(500)), null);
101 assert.equal(cleanProof(null), null);
102 assert.equal(invitePath(CODE, PROOF), `/invite/${CODE}?proof=${PROOF}`);
103 assert.equal(invitePath(CODE, null), `/invite/${CODE}`);
104 assert.equal(invitePath(CODE), `/invite/${CODE}`);
105});
106
107test("signing up from the invite email says the address is confirmed already; otherwise the code step applies", () => {
108 const base = { address: "ada@example.com", emailProven: false, workspace: { name: "Flagon, Inc." }, repository: null };
109 const proven = inviteSignUpCopy({ ...base, emailProven: true });
110 assert.equal(proven.intro, "You can join Flagon, Inc. as soon as you create it: accept the invitation then.");
111 assert.match(proven.confirmed ?? "", /^ada@example\.com is confirmed: you came here from the invite we emailed to it/);
112 assert.match(proven.hint, /confirmed already/);
113 assert.doesNotMatch(proven.hint, /code/);
114
115 // No proof (a code typed in, or a link passed on): nothing new is said.
116 const plain = inviteSignUpCopy(base);
117 assert.equal(plain.intro, "You can join Flagon, Inc. as soon as you confirm your email: accept the invitation then.");
118 assert.equal(plain.confirmed, null);
119 assert.equal(plain.hint, "Your invite was sent here. We email it a code to confirm it before you start.");
120
121 // An invite for anyone with the code has no address to prove.
122 const open = inviteSignUpCopy({ ...base, address: null, emailProven: true, workspace: null });
123 assert.equal(open.confirmed, null);
124 assert.equal(open.intro, "It takes a minute.");
125 assert.equal(open.hint, "We email it a code to confirm it before you start.");
126
127 const repo = inviteSignUpCopy({ ...base, workspace: null, repository: { name: "flagon-io/g1t" }, emailProven: true });
128 assert.equal(repo.intro, "You get flagon-io/g1t as soon as you create it.");
129});
130
131test("a pasted link or code is tidied to the code", () => {
132 assert.equal(cleanCode(CODE), CODE);
133 assert.equal(cleanCode(` ${CODE} `), CODE);
134 assert.equal(cleanCode(`https://g1t.sh/invite/${CODE}`), CODE);
135 assert.equal(cleanCode(`https://g1t.sh/register?invite=${CODE}&next=%2F`), CODE);
136 assert.equal(cleanCode("g1t-k7m2 q9xd"), "g1t-k7m2q9xd");
137 assert.equal(cleanCode(null), "");
138 assert.equal(cleanCode("x".repeat(500)).length, 80);
139});
140
141test("each invite says where it stands and whom it is for", () => {
142 const base = { redeemedBy: null, email: null, workspace: null };
143 assert.deepEqual(inviteState({ ...base, status: "pending" }), { label: "Pending", tone: "pending" });
144 assert.deepEqual(inviteState({ ...base, status: "redeemed", redeemedBy: "ada" }), { label: "Joined as @ada", tone: "done" });
145 assert.deepEqual(inviteState({ ...base, status: "awaiting_confirmation", redeemedBy: "ada" }), {
146 label: "@ada is confirming their email",
147 tone: "pending",
148 });
149 assert.deepEqual(inviteState({ ...base, status: "expired" }), { label: "Expired", tone: "dead" });
150 assert.deepEqual(inviteState({ ...base, status: "revoked" }), { label: "Revoked", tone: "dead" });
151 assert.equal(inviteFor({ ...base, status: "pending" }), "Anyone with the link");
152 assert.equal(inviteFor({ ...base, status: "pending", email: "ada@example.com", workspace: "acme" }), "ada@example.com");
153 assert.equal(inviteFor({ ...base, status: "pending", invitee: "daweazl", workspace: "flagon-io" }), "@daweazl");
154 // Which kind each is, beside whom it is for.
155 assert.deepEqual(inviteKind({ workspace: null }), { kind: "g1t", label: "Invite to g1t" });
156 assert.deepEqual(inviteKind({ workspace: "flagon-io" }), { kind: "workspace", label: "Invite to join flagon-io" });
157 assert.deepEqual(inviteState({ ...base, status: "awaiting_answer", redeemedBy: "daweazl" }), { label: "Waiting for @daweazl to accept", tone: "pending" });
158 assert.deepEqual(inviteState({ ...base, status: "declined", invitee: "daweazl" }), { label: "@daweazl declined", tone: "dead" });
159});
160
161test("bots that fill the hidden field or answer instantly are turned away", () => {
162 const form = (fields: Record<string, string>) => ({ get: (name: string) => fields[name] ?? null });
163 const now = 1_000_000;
164 assert.equal(looksAutomated(form({ website: "http://spam.example" }), now), true);
165 assert.equal(looksAutomated(form({ started: String(now - 200) }), now), true);
166 assert.equal(looksAutomated(form({ started: String(now - 10_000) }), now), false);
167 assert.equal(looksAutomated(form({}), now), false);
168 assert.equal(looksAutomated(form({ website: " " }), now), false);
169});
170
171test("a username is suggested from the invited address", () => {
172 assert.equal(suggestUsername("ada.lovelace@example.com"), "ada-lovelace");
173 assert.equal(suggestUsername("Margaret_Hamilton+g1t@example.com"), "margaret-hamilton");
174 assert.equal(suggestUsername("--x--@example.com"), "x");
175 assert.equal(suggestUsername(`${"a".repeat(38)}.b@example.com`), "a".repeat(38));
176 assert.equal(suggestUsername("...@example.com"), "");
177 assert.equal(suggestUsername(null), "");
178});
179
180test("an invite lands in its workspace, else its repository", () => {
181 assert.equal(landingFor({ workspace: { slug: "Flagon-IO" }, repository: null }), "flagon-io");
182 assert.equal(landingFor({ workspace: null, repository: { name: "flagon-io/g1t" } }), "flagon-io/g1t");
183 assert.equal(landingFor({ workspace: null, repository: null }), null);
184});
185
186test("the welcome is for one place, and ends", () => {
187 const set = welcomeCookie("flagon-io/g1t", true);
188 assert.match(set, /^g1t_welcome=flagon-io%2Fg1t; Path=\/; Max-Age=300; HttpOnly; SameSite=Lax; Secure$/);
189 const header = `a=1; ${set.split(";")[0]}; b=2`;
190 assert.equal(welcomes(header, "flagon-io/g1t"), true);
191 assert.equal(welcomes(header, "flagon-io"), false);
192 assert.equal(welcomes("g1t_welcome=flagon-io", "Flagon-IO"), true);
193 assert.equal(welcomes("g1t_welcome=%E0%A4%A", "flagon-io"), false);
194 assert.equal(welcomes("g1t_welcome=..%2F..%2Fx", "../../x"), false);
195 assert.equal(welcomes(null, "flagon-io"), false);
196 assert.match(clearWelcome(false), /^g1t_welcome=; Path=\/; Max-Age=0; HttpOnly; SameSite=Lax$/);
197});
198
199test("the two invites say which they are", () => {
200 // A workspace's People page: an invitation to accept or decline, which signs up whoever has no account.
201 const copy = workspaceInviteCopy("Flagon, Inc.");
202 assert.equal(copy.heading, "Invite to Flagon, Inc.");
203 assert.match(copy.hint, /invitation to join Flagon, Inc\..*join only if they accept/);
204 assert.match(copy.hint, /If they do not have a g1t account yet, they sign up from the invitation first/);
205 assert.doesNotMatch(copy.hint, /one of yours|shared invites/);
206});
207
208test("an invite's page names the invite it is", () => {
209 const base = { kind: "account" as const, invitedBy: { username: "syntaqx" }, workspace: null, repository: null, hasAccount: false };
210 const g1t = invitePageCopy(base, false);
211 assert.equal(`${g1t.before}${g1t.place ?? ""}${g1t.after}`, "@syntaqx invited you to g1t");
212 assert.match(g1t.about, /Sign up from this invite and your address is filled in for you\. It does not add you to anyone's workspace/);
213 const join = invitePageCopy({ ...base, workspace: { name: "Flagon, Inc." } }, false);
214 assert.equal(`${join.before}${join.place}${join.after}`, "@syntaqx invited you to join Flagon, Inc. on g1t");
215 assert.equal(join.place, "Flagon, Inc.");
216 assert.match(join.about, /invitation to join Flagon, Inc\., which you accept or decline/);
217 assert.match(join.about, /You do not have a g1t account yet, so it also lets you make one/);
218 // Someone with an account, or signed in, just accepts.
219 assert.match(invitePageCopy({ ...base, kind: "workspace", workspace: { name: "Flagon, Inc." }, hasAccount: true }, false).about, /Accepting joins you to Flagon, Inc\./);
220 assert.equal(invitePageCopy({ ...base, invitedBy: null }, false).before, "The g1t team invited you to g1t");
221});
222
223test("Invite a friend never reads as a gate on signing up", () => {
224 for (const words of Object.values(G1T_INVITES)) {
225 assert.doesNotMatch(words, /invite-only|waitlist|request access|need an invite|held for/i, words);
226 }
227 assert.match(G1T_INVITES.about, /Anyone can sign up for g1t/);
228 assert.match(G1T_INVITES.out, /can still sign up/);
229});
230
231test("a friend can also be invited only to a paid workspace the person owns", () => {
232 const memberships = [
233 { slug: "Acme", name: "Acme Labs", role: "owner" as const },
234 { slug: "hobby", name: null, role: "owner" as const },
235 { slug: "flagon-io", name: "Flagon, Inc.", role: "member" as const },
236 ];
237 assert.deepEqual(bringIntoChoices(memberships, ["hobby"]), [{ slug: "acme", name: "Acme Labs" }]);
238 // Nothing paid that they own: no box at all.
239 assert.deepEqual(bringIntoChoices(memberships, ["acme", "hobby"]), []);
240});
241
242test("the form sends a workspace only when its box is ticked and it was offered", () => {
243 const form = (fields: Record<string, string>) => new Map(Object.entries(fields));
244 const offered = [{ slug: "acme", name: "Acme Labs" }];
245 assert.deepEqual(inviteDraft(form({ email: " ada@example.com ", message: " " }), offered), { email: "ada@example.com", message: null });
246 assert.deepEqual(inviteDraft(form({ email: "ada@example.com", join: "acme", join_role: "owner" }), offered), { email: "ada@example.com", message: null });
247 assert.deepEqual(inviteDraft(form({ email: "ada@example.com", message: "Come build", also_join: "on", join: "acme", join_role: "owner" }), offered), {
248 email: "ada@example.com",
249 message: "Come build",
250 join: "acme",
251 joinRole: "owner",
252 });
253 // A workspace not offered (free, or not theirs) is dropped.
254 assert.deepEqual(inviteDraft(form({ email: "ada@example.com", also_join: "on", join: "hobby" }), offered), { email: "ada@example.com", message: null });
255});
256
257test("how many invites are left reads plainly, and the menu shows the count", () => {
258 assert.equal(remainingLine({ limit: 20, remaining: 18 }), "18 of 20 invites left");
259 assert.equal(remainingLine({ limit: 20, remaining: 0 }), "You have used all 20 of your invites");
260 assert.equal(remainingLine({ limit: 1, remaining: 1 }), "1 of 1 invite left");
261 assert.equal(remainingLine({ limit: 0, remaining: 0 }), "Invites to g1t are turned off on this installation");
262 assert.equal(leftBadge(18), "18 left");
263 assert.equal(leftBadge(0), "0 left");
264 assert.equal(leftBadge(null), null);
265 assert.equal(leftBadge(undefined), null);
266});
267
268test("only a pending invite is sent again or copied; one waiting on its friend can still be revoked", () => {
269 assert.deepEqual(inviteActions({ status: "pending", code: "g1t-abcd" }), { revoke: true, resend: true, copy: true });
270 assert.deepEqual(inviteActions({ status: "pending", code: null }), { revoke: true, resend: true, copy: false });
271 assert.deepEqual(inviteActions({ status: "awaiting_confirmation", code: null }), { revoke: true, resend: false, copy: false });
272 for (const status of ["redeemed", "expired", "revoked", "declined"] as const) {
273 assert.deepEqual(inviteActions({ status, code: null }), { revoke: false, resend: false, copy: false }, status);
274 }
275});