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.tsBlame149 lines · 1 contributor
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
72 it("when BUILD_SHA is set, that exact value appears as SW_VERSION", async () => {
73 process.env.BUILD_SHA = "abc123deadbeef";
74 const res = await app.request("/sw.js");
75 const body = await res.text();
76 expect(body).toContain('const SW_VERSION = "abc123deadbeef"');
77 });
78
79 it("when BUILD_SHA is unset, falls back to a non-empty dev-mode string", () => {
80 delete process.env.BUILD_SHA;
81 const v = buildSwVersion();
82 expect(typeof v).toBe("string");
83 expect(v.length).toBeGreaterThan(0);
84 expect(v.startsWith("dev-")).toBe(true);
85 });
86
87 it("buildVersionedServiceWorker pins CURRENT_CACHE to SW_VERSION", () => {
88 const src = buildVersionedServiceWorker("v9");
89 expect(src).toContain('const SW_VERSION = "v9"');
90 expect(src).toContain("const CURRENT_CACHE = CACHE_PREFIX + SW_VERSION");
91 });
92
93 it("buildVersionedServiceWorker escapes quotes + backslashes safely", () => {
94 const src = buildVersionedServiceWorker('weird"version\\stuff');
95 // The literal in the output must round-trip via the JS string parser —
96 // i.e. the embedded quote is backslash-escaped, no raw close on the
97 // string would prematurely terminate the version constant.
98 expect(src).toContain('const SW_VERSION = "weird\\"version\\\\stuff"');
99 });
100
101 it("content-type stays application/javascript", async () => {
102 const res = await app.request("/sw.js");
103 expect(res.headers.get("content-type") || "").toContain(
104 "application/javascript"
105 );
106 });
107
108 it("service-worker-allowed header is /", async () => {
109 const res = await app.request("/sw.js");
110 expect(res.headers.get("service-worker-allowed")).toBe("/");
111 });
112});
113
114describe("pwa cache-bust (S2) — /version alias", () => {
115 let _savedSha: string | undefined;
116 let _savedTime: string | undefined;
117
118 beforeEach(() => {
119 _savedSha = process.env.BUILD_SHA;
120 _savedTime = process.env.BUILD_TIME;
121 });
122
123 afterEach(() => {
124 if (_savedSha === undefined) delete process.env.BUILD_SHA;
125 else process.env.BUILD_SHA = _savedSha;
126 if (_savedTime === undefined) delete process.env.BUILD_TIME;
127 else process.env.BUILD_TIME = _savedTime;
128 });
129
130 it("returns {sha, buildAt} with BUILD_SHA + BUILD_TIME echoed", async () => {
131 process.env.BUILD_SHA = "deadbeef1234";
132 process.env.BUILD_TIME = "2026-05-14T00:00:00Z";
133 const res = await app.request("/version");
134 expect(res.status).toBe(200);
135 const body = (await res.json()) as Record<string, unknown>;
136 expect(body.sha).toBe("deadbeef1234");
137 expect(body.buildAt).toBe("2026-05-14T00:00:00Z");
138 });
139
140 it("falls back to {sha: 'dev', buildAt: null} when env unset", async () => {
141 delete process.env.BUILD_SHA;
142 delete process.env.BUILD_TIME;
143 const res = await app.request("/version");
144 expect(res.status).toBe(200);
145 const body = (await res.json()) as Record<string, unknown>;
146 expect(body.sha).toBe("dev");
147 expect(body.buildAt).toBeNull();
148 });
149});