Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

pwa-cache-bust.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

pwa-cache-bust.test.tsBlame157 lines · 2 contributors
b1be050CC LABS App1/**
2 * Block S2 — service worker cache-bust pinned to deploy SHA.
3 *
4 * Covers the `/sw.js` handler extension that injects `const SW_VERSION`
5 * derived from `process.env.BUILD_SHA` (with a `dev-<pid>` fallback) and
6 * the cache-prefix invalidation logic that purges previous-version
7 * caches on activate.
8 *
9 * No mock pollution: this suite only manipulates `process.env.BUILD_SHA`
10 * via `beforeEach` / `afterEach` save+restore so the rest of the test
11 * run sees the original env. No DB stubs needed — the handler is pure.
12 */
13
14import { describe, it, expect, beforeEach, afterEach } from "bun:test";
15import app from "../app";
16import {
17 buildSwVersion,
18 buildVersionedServiceWorker,
19 _resetSwShaWarningForTests,
20} from "../routes/pwa";
21
22describe("pwa cache-bust (S2) — GET /sw.js", () => {
23 let _savedSha: string | undefined;
24
25 beforeEach(() => {
26 _savedSha = process.env.BUILD_SHA;
27 delete process.env.BUILD_SHA;
28 _resetSwShaWarningForTests();
29 });
30
31 afterEach(() => {
32 if (_savedSha === undefined) delete process.env.BUILD_SHA;
33 else process.env.BUILD_SHA = _savedSha;
34 _resetSwShaWarningForTests();
35 });
36
37 it("returns 200 + Cache-Control: no-store", async () => {
38 const res = await app.request("/sw.js");
39 expect(res.status).toBe(200);
40 const cc = res.headers.get("cache-control") || "";
41 expect(cc).toContain("no-store");
42 });
43
44 it("response body contains a non-empty SW_VERSION literal", async () => {
45 const res = await app.request("/sw.js");
46 const body = await res.text();
47 const m = body.match(/const SW_VERSION = "([^"]+)"/);
48 expect(m).not.toBeNull();
49 expect((m as RegExpMatchArray)[1].length).toBeGreaterThan(0);
50 });
51
52 it("body calls self.skipWaiting() on install", async () => {
53 const res = await app.request("/sw.js");
54 const body = await res.text();
55 expect(body).toContain("self.skipWaiting()");
56 });
57
58 it("body calls clients.claim() on activate", async () => {
59 const res = await app.request("/sw.js");
60 const body = await res.text();
61 expect(body).toContain("clients.claim()");
62 });
63
64 it("body purges stale gluecron-* caches via caches.delete(...)", async () => {
65 const res = await app.request("/sw.js");
66 const body = await res.text();
67 expect(body).toContain("caches.delete");
68 expect(body).toContain("CACHE_PREFIX");
69 expect(body).toContain('"gluecron-"');
70 });
71
44fe49bClaude72 // 2026-05-16 — PWA rip-out. /sw.js now self-unregisters on activate so
73 // any browser with the old SW installed recovers automatically.
74 it("body unregisters itself on activate", async () => {
75 const res = await app.request("/sw.js");
76 const body = await res.text();
77 expect(body).toContain("self.registration.unregister");
78 });
79
b1be050CC LABS App80 it("when BUILD_SHA is set, that exact value appears as SW_VERSION", async () => {
81 process.env.BUILD_SHA = "abc123deadbeef";
82 const res = await app.request("/sw.js");
83 const body = await res.text();
84 expect(body).toContain('const SW_VERSION = "abc123deadbeef"');
85 });
86
87 it("when BUILD_SHA is unset, falls back to a non-empty dev-mode string", () => {
88 delete process.env.BUILD_SHA;
89 const v = buildSwVersion();
90 expect(typeof v).toBe("string");
91 expect(v.length).toBeGreaterThan(0);
92 expect(v.startsWith("dev-")).toBe(true);
93 });
94
95 it("buildVersionedServiceWorker pins CURRENT_CACHE to SW_VERSION", () => {
96 const src = buildVersionedServiceWorker("v9");
97 expect(src).toContain('const SW_VERSION = "v9"');
98 expect(src).toContain("const CURRENT_CACHE = CACHE_PREFIX + SW_VERSION");
99 });
100
101 it("buildVersionedServiceWorker escapes quotes + backslashes safely", () => {
102 const src = buildVersionedServiceWorker('weird"version\\stuff');
103 // The literal in the output must round-trip via the JS string parser —
104 // i.e. the embedded quote is backslash-escaped, no raw close on the
105 // string would prematurely terminate the version constant.
106 expect(src).toContain('const SW_VERSION = "weird\\"version\\\\stuff"');
107 });
108
109 it("content-type stays application/javascript", async () => {
110 const res = await app.request("/sw.js");
111 expect(res.headers.get("content-type") || "").toContain(
112 "application/javascript"
113 );
114 });
115
116 it("service-worker-allowed header is /", async () => {
117 const res = await app.request("/sw.js");
118 expect(res.headers.get("service-worker-allowed")).toBe("/");
119 });
120});
121
122describe("pwa cache-bust (S2) — /version alias", () => {
123 let _savedSha: string | undefined;
124 let _savedTime: string | undefined;
125
126 beforeEach(() => {
127 _savedSha = process.env.BUILD_SHA;
128 _savedTime = process.env.BUILD_TIME;
129 });
130
131 afterEach(() => {
132 if (_savedSha === undefined) delete process.env.BUILD_SHA;
133 else process.env.BUILD_SHA = _savedSha;
134 if (_savedTime === undefined) delete process.env.BUILD_TIME;
135 else process.env.BUILD_TIME = _savedTime;
136 });
137
138 it("returns {sha, buildAt} with BUILD_SHA + BUILD_TIME echoed", async () => {
139 process.env.BUILD_SHA = "deadbeef1234";
140 process.env.BUILD_TIME = "2026-05-14T00:00:00Z";
141 const res = await app.request("/version");
142 expect(res.status).toBe(200);
143 const body = (await res.json()) as Record<string, unknown>;
144 expect(body.sha).toBe("deadbeef1234");
145 expect(body.buildAt).toBe("2026-05-14T00:00:00Z");
146 });
147
148 it("falls back to {sha: 'dev', buildAt: null} when env unset", async () => {
149 delete process.env.BUILD_SHA;
150 delete process.env.BUILD_TIME;
151 const res = await app.request("/version");
152 expect(res.status).toBe(200);
153 const body = (await res.json()) as Record<string, unknown>;
154 expect(body.sha).toBe("dev");
155 expect(body.buildAt).toBeNull();
156 });
157});