g1t/apps/status/src/subscribers.test.ts
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.
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import { alertLetter, bindingSender, confirmLetter, memorySender, render, unsubscribeHeaders, updateLetter } from "./email.ts"; | |
| 5 | import { atom, feedItems, jsonFeed } from "./feed.ts"; | |
| 6 | import { chosenParts, hashToken, newToken, normalizeEmail, parseParts, readUnsubscribeToken, unsubscribeToken, wants } from "./subscribers.ts"; | |
| 7 | ||
| 8 | test("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 | ||
| 16 | test("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 | ||
| 26 | test("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 | ||
| 36 | test("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 | ||
| 49 | test("letters: escaped HTML, plain text, and an unsubscribe link in every update", () => { | |
| 50 | const { text, html } = render(updateLetter({ heading: "Identified: <Pushes>", text: "A bad deploy.", url: "https://status.g1t.sh/incidents/a", affects: ["Git"], unsubscribe: "https://status.g1t.sh/unsubscribe?token=x" })); | |
| 51 | assert.ok(html.includes("Identified: <Pushes>") && !html.includes("<Pushes>")); | |
| 52 | assert.ok(text.includes("Unsubscribe: https://status.g1t.sh/unsubscribe?token=x")); | |
| 53 | assert.ok(text.includes("Affects: Git.")); | |
| 54 | assert.equal(unsubscribeHeaders("https://u")["List-Unsubscribe-Post"], "List-Unsubscribe=One-Click"); | |
| 55 | assert.ok(render(confirmLetter("https://c", ["Git"])).text.includes("affecting Git")); | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 56 | assert.ok(render(alertLetter({ title: "Detected: API not answering", lines: ["API has not answered on 3 checks in a row since 5 Oct 12:00 UTC."], link: "https://sudo.g1t.sh/incidents/x" })).text.includes("https://sudo.g1t.sh/incidents/x")); |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 57 | }); |
| 58 | ||
| 59 | test("senders: the binding when there is one, none otherwise", async () => { | |
| 60 | assert.equal(bindingSender(undefined), null); | |
| 61 | const calls: unknown[] = []; | |
| 62 | const sender = bindingSender({ send: async (m) => void calls.push(m) }, "Status <s@g1t.sh>"); | |
| 63 | await sender!.send({ to: "a@b.co", subject: "Hi", text: "t", html: "h" }); | |
| 64 | assert.deepEqual(calls, [{ to: "a@b.co", from: "Status <s@g1t.sh>", subject: "Hi", text: "t", html: "h" }]); | |
| 65 | const memory = memorySender(); | |
| 66 | await memory.send({ to: "a@b.co", subject: "Hi", text: "t", html: "h" }); | |
| 67 | assert.equal(memory.sent.length, 1); | |
| 68 | }); | |
| 69 | ||
| 70 | test("feeds: every public update and maintenance notice, newest first, in Atom and JSON Feed", () => { | |
| 71 | const incidents = [ | |
| 72 | { | |
| 73 | id: "a", | |
| 74 | title: "Pushes & pulls failing", | |
| 75 | impact: "down" as const, | |
| 76 | status: "resolved" as const, | |
| 77 | components: ["git"], | |
| 78 | component_impacts: [{ key: "git", impact: "major_outage" as const }], | |
| 79 | started_at: "2026-10-05T11:00:00Z", | |
| 80 | resolved_at: "2026-10-05T12:00:00Z", | |
| 81 | url: "https://status.g1t.sh/incidents/a", | |
| 82 | postmortem_published_at: null, | |
| 83 | updates: [ | |
| 84 | { id: "u2", at: "2026-10-05T12:00:00Z", status: "resolved" as const, text: "Fixed." }, | |
| 85 | { id: "u1", at: "2026-10-05T11:05:00Z", status: "investigating" as const, text: "Looking <now>." }, | |
| 86 | ], | |
| 87 | }, | |
| 88 | ]; | |
| 89 | const maintenance = [ | |
| 90 | { id: "m", title: "Upgrade", message: "m", components: ["git"], starts_at: "2026-10-06T02:00:00Z", ends_at: "2026-10-06T03:00:00Z", state: "scheduled" as const, url: "https://status.g1t.sh/maintenance/m", updates: [{ id: "mu", at: "2026-10-05T11:30:00Z", text: "Scheduled." }] }, | |
| 91 | ]; | |
| 92 | const items = feedItems(incidents, maintenance); | |
| 93 | assert.deepEqual(items.map((i) => i.title), ["Resolved: Pushes & pulls failing", "Maintenance: Upgrade", "Investigating: Pushes & pulls failing"]); | |
| 94 | assert.equal(items[0]!.url, "https://status.g1t.sh/incidents/a#update-u2"); | |
| 95 | const xml = atom(items, { origin: "https://status.g1t.sh", title: "g1t status", updated: "2026-10-05T13:00:00Z" }); | |
| 96 | assert.ok(xml.startsWith('<?xml version="1.0" encoding="utf-8"?>\n<feed xmlns="http://www.w3.org/2005/Atom">')); | |
| 97 | assert.ok(xml.includes("<title>Resolved: Pushes & pulls failing</title>")); | |
| 98 | assert.ok(xml.includes("Looking <now>.")); | |
| 99 | assert.ok(xml.includes("<updated>2026-10-05T12:00:00Z</updated>")); | |
| 100 | assert.equal((xml.match(/<entry>/g) ?? []).length, 3); | |
| 101 | const json = jsonFeed(items, { origin: "https://status.g1t.sh", title: "g1t status", updated: "" }) as { version: string; items: { id: string; content_text: string }[] }; | |
| 102 | assert.equal(json.version, "https://jsonfeed.org/version/1.1"); | |
| 103 | assert.equal(json.items[2]!.content_text, "Looking <now>."); | |
| 104 | assert.equal(feedItems(incidents, maintenance, 1).length, 1); | |
| 105 | }); |