Skip to content
174 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 EXTERNAL_MAX_AGE_MS,
6 KEEP_DAYS,
7 RETRY_AFTER_MS,
8 attemptKey,
9 dayOf,
10 dueForRefresh,
11 expired,
12 externalAttemptKey,
13 externalShotKey,
14 isCurrent,
15 isFresh,
16 isPublicHost,
17 parseExternalShot,
18 parseShot,
19 shotKey,
20 shouldAttempt,
21} from "./screenshot.ts";
22
23test("an external production is visited only at a public address", () => {
24 for (const host of ["g1t.sh", "www.g1t.sh", "example.com", "app.example.co.uk", "8.8.8.8", "acme-web.vercel.app"]) {
25 assert.ok(isPublicHost(host), host);
26 }
27 for (const host of [
28 "localhost",
29 "app.localhost",
30 "printer.local",
31 "db.internal",
32 "intranet",
33 "127.0.0.1",
34 "10.1.2.3",
35 "172.16.0.1",
36 "172.31.255.255",
37 "192.168.1.1",
38 "169.254.169.254",
39 "100.64.0.1",
40 "0.0.0.0",
41 "224.0.0.1",
42 "999.1.1.1",
43 "2130706433",
44 "0x7f.0.0.1",
45 "[::1]",
46 "::1",
47 "fe80::1",
48 // g1t's own services, but g1t.sh itself.
49 "api.g1t.sh",
50 "og.g1t.sh",
51 "sudo.g1t.sh",
52 "g1tusercontent.com",
53 "x.g1tusercontent.com",
54 "web-acme.g1t.page",
55 "g1t.page",
56 ]) {
57 assert.equal(isPublicHost(host), false, host);
58 }
59});
60
61test("an external request names a public http(s) address and a commit or a day", () => {
62 const commit = "abc1234def5678abc1234def5678abc1234def56";
63 assert.deepEqual(parseExternalShot({ url: "https://G1T.sh/#top", commit }), { host: "g1t.sh", url: "https://g1t.sh/", commit });
64 assert.deepEqual(parseExternalShot({ url: "http://example.com/app?x=1", commit: "2026-10-11", since: "2026-10-11T00:00:00Z" }), {
65 host: "example.com",
66 url: "http://example.com/app?x=1",
67 commit: "2026-10-11",
68 since: "2026-10-11T00:00:00Z",
69 });
70 for (const url of [
71 "ftp://example.com/",
72 "file:///etc/passwd",
73 "javascript:alert(1)",
74 "https://user:pass@example.com/",
75 "https://example.com:8443/",
76 "http://127.0.0.1/",
77 "http://169.254.169.254/latest/meta-data",
78 "https://api.g1t.sh/",
79 "https://web-acme.g1t.page/",
80 "not a url",
81 ]) {
82 assert.equal(parseExternalShot({ url, commit }), null, url);
83 }
84 assert.equal(parseExternalShot({ url: "https://example.com/", commit: "main" }), null);
85 assert.equal(parseExternalShot({ url: "https://example.com/", commit: "2026-13-45" }), null);
86 assert.equal(parseExternalShot({ url: "https://example.com/" }), null);
87 assert.equal(parseExternalShot(null), null);
88});
89
90test("external screenshots are kept apart, by host", () => {
91 assert.equal(externalShotKey("g1t.sh"), "external/g1t.sh.jpg");
92 assert.equal(externalAttemptKey("g1t.sh"), "attempts/external/g1t.sh");
93 assert.equal(dayOf(Date.parse("2026-10-11T23:59:00Z")), "2026-10-11");
94});
95
96test("an external screenshot stands for a day, and not from before the deploy", () => {
97 const now = Date.parse("2026-10-11T12:00:00Z");
98 const kept = { capturedAt: "2026-10-11T06:00:00Z" };
99 assert.ok(isFresh(kept, now));
100 assert.ok(isFresh(kept, now, "2026-10-11T05:00:00Z"));
101 assert.ok(!isFresh(kept, now, "2026-10-11T07:00:00Z"));
102 assert.ok(!isFresh({ capturedAt: new Date(now - EXTERNAL_MAX_AGE_MS).toISOString() }, now));
103 assert.ok(!isFresh(null, now));
104});
105
106test("the daily refresh takes the missing and the stalest first, up to its limit", () => {
107 const now = Date.parse("2026-10-11T12:00:00Z");
108 const targets = [
109 { name: "fresh", capturedAt: "2026-10-11T06:00:00Z" },
110 { name: "two days", capturedAt: "2026-10-09T06:00:00Z" },
111 { name: "missing", capturedAt: null },
112 { name: "a week", capturedAt: "2026-10-04T06:00:00Z" },
113 ];
114 assert.deepEqual(
115 dueForRefresh(targets, now).map((t) => t.name),
116 ["missing", "a week", "two days"],
117 );
118 assert.deepEqual(
119 dueForRefresh(targets, now, 1).map((t) => t.name),
120 ["missing"],
121 );
122});
123
124const commit = "abc1234def5678abc1234def5678abc1234def56";
125
126test("only apps on g1t.page, with a commit, are taken", () => {
127 assert.deepEqual(parseShot({ host: "Web-Acme.g1t.page", commit: commit.toUpperCase() }), { host: "web-acme.g1t.page", commit });
128 for (const host of [
129 "web-acme.g1t.sh",
130 "example.com",
131 "g1t.page",
132 "a.b.g1t.page",
133 "domains.g1t.page",
134 "-web.g1t.page",
135 "web-acme.g1t.page.evil.com",
136 "web acme.g1t.page",
137 ]) {
138 assert.equal(parseShot({ host, commit }), null, host);
139 }
140 assert.equal(parseShot({ host: "web-acme.g1t.page", commit: "main" }), null);
141 assert.equal(parseShot({ host: "web-acme.g1t.page" }), null);
142 assert.equal(parseShot(null), null);
143});
144
145test("keys are by hostname", () => {
146 assert.equal(shotKey("web-acme.g1t.page"), "production/web-acme.g1t.page.jpg");
147 assert.equal(attemptKey("web-acme.g1t.page"), "attempts/web-acme.g1t.page");
148});
149
150test("a failed attempt at a commit waits before it is tried again", () => {
151 const now = Date.parse("2026-10-05T12:00:00Z");
152 assert.equal(shouldAttempt(null, commit, now), true);
153 const just = { commit, at: new Date(now - 1000).toISOString() };
154 assert.equal(shouldAttempt(just, commit, now), false);
155 assert.equal(shouldAttempt(just, "fff0000", now), true);
156 assert.equal(shouldAttempt({ commit, at: new Date(now - RETRY_AFTER_MS).toISOString() }, commit, now), true);
157});
158
159test("a screenshot is current when it shows the commit and was taken after the deploy", () => {
160 const kept = { commit, capturedAt: "2026-10-06T10:00:00Z" };
161 assert.ok(isCurrent(kept, { host: "a.g1t.page", commit }));
162 assert.ok(isCurrent(kept, { host: "a.g1t.page", commit, since: "2026-10-06T09:00:00Z" }));
163 // Resumed (deployed again at the same commit) after it was taken.
164 assert.ok(!isCurrent(kept, { host: "a.g1t.page", commit, since: "2026-10-06T11:00:00Z" }));
165 assert.ok(!isCurrent(kept, { host: "a.g1t.page", commit: "f".repeat(40) }));
166 assert.deepEqual(parseShot({ host: "a-b.g1t.page", commit, since: "2026-10-06T11:00:00Z" })?.since, "2026-10-06T11:00:00Z");
167 assert.equal(parseShot({ host: "a-b.g1t.page", commit, since: "nope" })?.since, undefined);
168});
169
170test("kept objects expire after KEEP_DAYS", () => {
171 const now = Date.parse("2026-10-06T00:00:00Z");
172 assert.ok(expired(new Date(now - (KEEP_DAYS + 1) * 86_400_000), now));
173 assert.ok(!expired(new Date(now - 86_400_000), now));
174});