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.

137 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { alertLetter, bindingSender, confirmLetter, memorySender, render, unsubscribeHeaders, updateLetter } from "./email.ts";
5import { atom, feedItems, jsonFeed } from "./feed.ts";
6import { chosenParts, hashToken, newToken, normalizeEmail, parseParts, readUnsubscribeToken, unsubscribeToken, wants } from "./subscribers.ts";
7
8test("addresses are trimmed, lowercased and checked", () => {
9 assert.equal(normalizeEmail(" Ana@Example.COM "), "ana@example.com");
10 assert.equal(normalizeEmail("nope"), null);
11 assert.equal(normalizeEmail("a@b"), null);
12 assert.equal(normalizeEmail("a b@example.com"), null);
13 assert.equal(normalizeEmail(42), null);
14});
15
16test("confirmation tokens: random, and only their hash is kept", async () => {
17 const a = newToken();
18 assert.match(a, /^[A-Za-z0-9_-]{43}$/);
19 assert.notEqual(newToken(), a);
20 const hash = await hashToken(a);
21 assert.match(hash, /^[0-9a-f]{64}$/);
22 assert.equal(await hashToken(a), hash, "the same token hashes the same");
23 assert.ok(!hash.includes(a));
24});
25
26test("unsubscribe links are signed, not stored, and refuse tampering", async () => {
27 const token = await unsubscribeToken("secret", "sub-1");
28 assert.ok(token.startsWith("sub-1."));
29 assert.equal(await readUnsubscribeToken("secret", token), "sub-1");
30 assert.equal(await readUnsubscribeToken("other", token), null, "another secret");
31 assert.equal(await readUnsubscribeToken("secret", token.replace("sub-1", "sub-2")), null, "another subscriber");
32 assert.equal(await readUnsubscribeToken("secret", `${token}x`), null);
33 assert.equal(await readUnsubscribeToken("secret", "garbage"), null);
34});
35
36test("per-part subscriptions", () => {
37 assert.equal(chosenParts([], ["git", "api"]), null, "none chosen is everything");
38 assert.equal(chosenParts(["git", "api"], ["git", "api"]), null, "all chosen is everything");
39 assert.deepEqual(chosenParts(["git", "git", "zzz"], ["git", "api"]), ["git"]);
40 assert.equal(parseParts(null), null);
41 assert.deepEqual(parseParts('["git"]'), ["git"]);
42 assert.equal(parseParts("not json"), null);
43 assert.equal(wants(null, ["api"]), true);
44 assert.equal(wants(["git"], ["api"]), false);
45 assert.equal(wants(["git"], ["api", "git"]), true);
46 assert.equal(wants(["git"], []), true, "news about no part in particular goes to everyone");
47});
48
49test("letters: escaped HTML, plain text, and an unsubscribe link in every update", () => {
50 const { text, html } = render(
51 updateLetter({
52 heading: "Identified: <Pushes>",
53 text: "A bad deploy.",
54 url: "https://status.g1t.sh/incidents/a",
55 affects: ["Git"],
56 unsubscribe: "https://status.g1t.sh/unsubscribe?token=x",
57 }),
58 );
59 assert.ok(html.includes("Identified: &lt;Pushes&gt;") && !html.includes("<Pushes>"));
60 assert.ok(text.includes("Unsubscribe: https://status.g1t.sh/unsubscribe?token=x"));
61 assert.ok(text.includes("Affects: Git."));
62 assert.equal(unsubscribeHeaders("https://u")["List-Unsubscribe-Post"], "List-Unsubscribe=One-Click");
63 assert.ok(render(confirmLetter("https://c", ["Git"])).text.includes("affecting Git"));
64 assert.ok(
65 render(
66 alertLetter({
67 title: "Detected: API not answering",
68 lines: ["API has not answered on 3 checks in a row since 5 Oct 12:00 UTC."],
69 link: "https://sudo.g1t.sh/incidents/x",
70 }),
71 ).text.includes("https://sudo.g1t.sh/incidents/x"),
72 );
73});
74
75test("senders: the binding when there is one, none otherwise", async () => {
76 assert.equal(bindingSender(undefined), null);
77 const calls: unknown[] = [];
78 const sender = bindingSender({ send: async (m) => void calls.push(m) }, "Status <s@g1t.sh>");
79 await sender!.send({ to: "a@b.co", subject: "Hi", text: "t", html: "h" });
80 assert.deepEqual(calls, [{ to: "a@b.co", from: "Status <s@g1t.sh>", subject: "Hi", text: "t", html: "h" }]);
81 const memory = memorySender();
82 await memory.send({ to: "a@b.co", subject: "Hi", text: "t", html: "h" });
83 assert.equal(memory.sent.length, 1);
84});
85
86test("feeds: every public update and maintenance notice, newest first, in Atom and JSON Feed", () => {
87 const incidents = [
88 {
89 id: "a",
90 title: "Pushes & pulls failing",
91 impact: "down" as const,
92 status: "resolved" as const,
93 components: ["git"],
94 component_impacts: [{ key: "git", impact: "major_outage" as const }],
95 started_at: "2026-10-05T11:00:00Z",
96 resolved_at: "2026-10-05T12:00:00Z",
97 url: "https://status.g1t.sh/incidents/a",
98 postmortem_published_at: null,
99 updates: [
100 { id: "u2", at: "2026-10-05T12:00:00Z", status: "resolved" as const, text: "Fixed." },
101 { id: "u1", at: "2026-10-05T11:05:00Z", status: "investigating" as const, text: "Looking <now>." },
102 ],
103 },
104 ];
105 const maintenance = [
106 {
107 id: "m",
108 title: "Upgrade",
109 message: "m",
110 components: ["git"],
111 starts_at: "2026-10-06T02:00:00Z",
112 ends_at: "2026-10-06T03:00:00Z",
113 state: "scheduled" as const,
114 url: "https://status.g1t.sh/maintenance/m",
115 updates: [{ id: "mu", at: "2026-10-05T11:30:00Z", text: "Scheduled." }],
116 },
117 ];
118 const items = feedItems(incidents, maintenance);
119 assert.deepEqual(
120 items.map((i) => i.title),
121 ["Resolved: Pushes & pulls failing", "Maintenance: Upgrade", "Investigating: Pushes & pulls failing"],
122 );
123 assert.equal(items[0]!.url, "https://status.g1t.sh/incidents/a#update-u2");
124 const xml = atom(items, { origin: "https://status.g1t.sh", title: "g1t status", updated: "2026-10-05T13:00:00Z" });
125 assert.ok(xml.startsWith('<?xml version="1.0" encoding="utf-8"?>\n<feed xmlns="http://www.w3.org/2005/Atom">'));
126 assert.ok(xml.includes("<title>Resolved: Pushes &amp; pulls failing</title>"));
127 assert.ok(xml.includes("Looking &lt;now&gt;."));
128 assert.ok(xml.includes("<updated>2026-10-05T12:00:00Z</updated>"));
129 assert.equal((xml.match(/<entry>/g) ?? []).length, 3);
130 const json = jsonFeed(items, { origin: "https://status.g1t.sh", title: "g1t status", updated: "" }) as {
131 version: string;
132 items: { id: string; content_text: string }[];
133 };
134 assert.equal(json.version, "https://jsonfeed.org/version/1.1");
135 assert.equal(json.items[2]!.content_text, "Looking <now>.");
136 assert.equal(feedItems(incidents, maintenance, 1).length, 1);
137});