flagon-io/g1t

public

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/apps/status/src/render.test.ts

174 lines8,639 bytesCodeBlame
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { StatusIncident, StatusMaintenance } from "@g1t/contracts";
5
6import { components } from "./components.ts";
7import { type DayRow, buildPage } from "./model.ts";
8import { byMonth, dayDetail, escape, prose, renderBadge, renderHistory, renderIncident, renderMaintenance, renderMessage, renderPage, renderSubscribe } from "./render.ts";
9
10const NOW = new Date("2026-10-05T12:00:00Z");
11const OPTIONS = { siteUrl: "https://g1t.sh", supportUrl: "https://g1t.sh/support", ogImage: "", selfUrl: "https://status.g1t.sh/", now: NOW, email: true };
12const parts = components(
13 {
14 SITE_URL: "https://g1t.sh",
15 API_URL: "https://api.g1t.sh",
16 DOCS_URL: "https://docs.g1t.sh",
17 MODELS_URL: "https://models.g1t.sh",
18 PROBE_REPO: "flagon-io/g1t",
19 },
20 false,
21);
22
23function page(states: Record<string, "up" | "degraded" | "down">, incidents: StatusIncident[] = [], maintenance: StatusMaintenance[] = []) {
24 const current = new Map(
25 Object.entries(states).map(([k, state]) => [k, { state, detail: state === "down" ? "Failed: HTTP 502" : "Answered in 80 ms", latency_ms: 80 }]),
26 );
27 const days: DayRow[] = [
28 { component: "api", day: "2026-10-05", checks: 720, up: 719, degraded: 0, down: 1, latency_total: 71900, latency_count: 719 },
29 ];
30 return buildPage({ parts, current, checkedAt: "2026-10-05T11:59:40Z", days, incidents, maintenance, now: NOW });
31}
32
33const INCIDENT: StatusIncident = {
34 id: "abc",
35 title: "Sign-in <failing>",
36 impact: "down",
37 status: "identified",
38 components: ["site"],
39 component_impacts: [{ key: "site", impact: "major_outage" }],
40 started_at: "2026-10-05T11:40:00Z",
41 resolved_at: null,
42 url: "https://status.g1t.sh/incidents/abc",
43 postmortem_published_at: null,
44 updates: [{ id: "u1", at: "2026-10-05T11:45:00Z", status: "identified", text: "A bad deploy.\n\nRolling back." }],
45};
46
47const MAINT: StatusMaintenance = {
48 id: "m1",
49 title: "Database upgrade",
50 message: "Pushes pause for up to five minutes.",
51 components: ["git"],
52 starts_at: "2026-10-06T02:00:00Z",
53 ends_at: "2026-10-06T03:00:00Z",
54 state: "scheduled",
55 url: "https://status.g1t.sh/maintenance/m1",
56 updates: [{ id: "mu1", at: "2026-10-05T10:00:00Z", text: "Pushes pause for up to five minutes." }],
57};
58
59test("escape covers HTML's specials", () => {
60 assert.equal(escape(`<a href="x">'&'</a>`), "&lt;a href=&quot;x&quot;&gt;&#39;&amp;&#39;&lt;/a&gt;");
61});
62
63test("the page: banner, every part with its bar, and no incidents", () => {
64 const html = renderPage(page({ site: "up", api: "up", git: "up", docs: "up", agents: "up" }), OPTIONS);
65 assert.match(html, /<h1>All systems normal<\/h1>/);
66 for (const name of ["Website and sign-in", "API", "Git and repositories", "Documentation", "Sandboxes"]) assert.ok(html.includes(name), name);
67 assert.equal((html.match(/<ol class="bar"/g) ?? []).length, 5, "Sandboxes has no bar");
68 assert.ok(html.includes("No incidents in the last 14 days"));
69 assert.ok(html.includes('action="/subscribe"'), "a subscribe form");
70 assert.ok(html.includes('href="/feed.xml"'));
71 assert.match(html, /99\.86% uptime/);
72 assert.match(html, /aria-label="g1t"/);
73 assert.doesNotMatch(html, /Happening now/);
74});
75
76test("an outage and an open incident are said at the top, escaped", () => {
77 const html = renderPage(
78 page({ site: "down", api: "up", git: "up", docs: "up", agents: "up" }, [
79 INCIDENT,
80 ]),
81 OPTIONS,
82 );
83 assert.match(html, /<h1>Major outage<\/h1>/);
84 assert.match(html, /Happening now/);
85 assert.ok(html.includes("Sign-in &lt;failing&gt;"));
86 assert.ok(!html.includes("<failing>"));
87 assert.match(html, /<p>A bad deploy\.<\/p><p>Rolling back\.<\/p>/);
88 assert.match(html, /Identified · Major outage/);
89 assert.ok(html.includes('href="/incidents/abc"'), "the title links to its page");
90});
91
92test("a stale page says the checks stopped", () => {
93 const model = page({ api: "up" });
94 const html = renderPage({ ...model, stale: true }, OPTIONS);
95 assert.match(html, /have not run since/);
96});
97
98test("a day's detail names what happened", () => {
99 assert.equal(
100 dayDetail({ day: "2026-10-05", state: "up", uptime: 99.86, checks: 720, failed: 1, slow: 0, avg_ms: 100, incidents: ["Pushes failing"] }),
101 "5 Oct 2026 · 99.86% answered · 1 failed check of 720 · average 100 ms · Incident: Pushes failing",
102 );
103 assert.equal(
104 dayDetail({ day: "2026-10-01", state: "none", uptime: null, checks: 0, failed: 0, slow: 0, avg_ms: null, incidents: [] }),
105 "1 Oct 2026 · No data",
106 );
107});
108
109test("the badge is an SVG coloured by state", () => {
110 const svg = renderBadge("degraded", "Partial outage");
111 assert.match(svg, /^<svg/);
112 assert.match(svg, /#ffbd8c/);
113 assert.match(svg, /partial outage/);
114});
115
116test("maintenance: upcoming, then in progress with its parts under maintenance", () => {
117 const ahead = renderPage(page({ site: "up", api: "up", git: "up" }, [], [MAINT]), OPTIONS);
118 assert.ok(ahead.includes("Upcoming maintenance") && ahead.includes("Database upgrade") && !ahead.includes("Maintenance in progress"));
119 const during = { ...OPTIONS, now: new Date("2026-10-06T02:30:00Z") };
120 const model = buildPage({
121 parts,
122 current: new Map([["git", { state: "down" as const, detail: "Failed: HTTP 503", latency_ms: null }], ["site", { state: "up" as const, detail: "ok", latency_ms: 1 }]]),
123 checkedAt: "2026-10-06T02:29:40Z",
124 days: [],
125 incidents: [],
126 maintenance: [MAINT],
127 now: during.now,
128 });
129 const html = renderPage(model, during);
130 assert.ok(html.includes("Maintenance in progress"));
131 assert.ok(html.includes("<h1>Under maintenance</h1>"));
132 assert.ok(html.includes('class="state s-maintenance"'));
133});
134
135test("an incident's page: every update, its parts' impact, and the postmortem once published", () => {
136 const names = new Map([["site", "Website and sign-in"]]);
137 const resolved = { ...INCIDENT, status: "resolved" as const, resolved_at: "2026-10-05T12:40:00Z", postmortem_published_at: "2026-10-06T09:00:00Z" };
138 const pm = { summary: "A deploy broke sign-in.", impact: "", timeline: "- 11:40 declared\n- 12:40 resolved", root_cause: "A <bad> cookie.", went_well: "", went_badly: "", action_items: "- Canary deploys", published_at: "2026-10-06T09:00:00Z" };
139 const html = renderIncident(resolved, pm, names, { ...OPTIONS, selfUrl: resolved.url });
140 assert.ok(html.includes('id="update-u1"'));
141 assert.ok(html.includes("Website and sign-in"));
142 assert.ok(html.includes("Major outage"));
143 assert.ok(html.includes('id="postmortem"'));
144 assert.ok(html.includes("<h3>Root cause</h3>") && html.includes("A &lt;bad&gt; cookie."));
145 assert.ok(!html.includes("<h3>Impact</h3>"), "empty sections are left out");
146 assert.ok(html.includes("<ul><li>Canary deploys</li></ul>"));
147 assert.ok(html.includes("lasted 1h 00m"));
148 const draft = renderIncident(resolved, null, names, OPTIONS);
149 assert.ok(!draft.includes('id="postmortem"') && draft.includes("If a postmortem is written"));
150});
151
152test("prose: paragraphs and lists, escaped", () => {
153 assert.equal(prose("One\ntwo\n\n- a\n- <b>"),"<p>One<br>two</p><ul><li>a</li><li>&lt;b&gt;</li></ul>");
154});
155
156test("history: months newest first, empty ones said so", () => {
157 const old = { ...INCIDENT, id: "old", started_at: "2026-08-10T00:00:00Z", resolved_at: "2026-08-10T01:00:00Z" };
158 const groups = byMonth([INCIDENT, old], [MAINT]);
159 assert.deepEqual(groups.map((g) => g.month), ["2026-10", "2026-08"]);
160 const html = renderHistory([INCIDENT, old], [MAINT], OPTIONS, 3);
161 assert.ok(html.includes("October 2026") && html.includes("September 2026") && html.includes("August 2026"));
162 assert.ok(html.includes("No incidents or maintenance."));
163 assert.ok(html.indexOf("October 2026") < html.indexOf("August 2026"));
164 assert.ok(html.includes('href="/maintenance/m1"'));
165});
166
167test("subscribe pages: the form, its parts, and a confirm button rather than a link that acts", () => {
168 const html = renderSubscribe(OPTIONS, [{ key: "git", name: "Git" }]);
169 assert.ok(html.includes('name="components" value="git"') && html.includes('name="website"'));
170 assert.ok(renderSubscribe({ ...OPTIONS, email: false }, []).includes("not available"));
171 const confirm = renderMessage(OPTIONS, { title: "Confirm", text: "One more step", form: { action: "/subscribe/confirm", fields: { token: 'a"b' }, button: "Confirm subscription" } });
172 assert.ok(confirm.includes('method="post" action="/subscribe/confirm"') && confirm.includes('value="a&quot;b"'));
173 assert.ok(renderMaintenance(MAINT, new Map([["git", "Git"]]), OPTIONS).includes("Affects Git"));
174});