fix(pwa): BUG-005 chargement mobile via Cloudflare (SW network-first + no-cache)
CI / lint (push) Successful in 58s
CI / security (push) Successful in 40s
CI / test (push) Successful in 1m19s
CI / build (push) Successful in 37s
CI / e2e (push) Successful in 10m53s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 58s
CI / security (push) Successful in 40s
CI / test (push) Successful in 1m19s
CI / build (push) Successful in 37s
CI / e2e (push) Successful in 10m53s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* ObsiGate - Service Worker caching policy guards.
|
||||
*
|
||||
* Regression tests for the mobile/Cloudflare stale-load bug: the SW used
|
||||
* cache-first with a fixed cache name and wrong precache paths, so old JS kept
|
||||
* being served even after flushing the browser/Cloudflare caches (the SW Cache
|
||||
* Storage is a separate store). These tests read the source and assert the
|
||||
* network-first policy, versioned caches and corrected paths stay in place.
|
||||
*
|
||||
* Usage: node tests/frontend/sw.test.mjs
|
||||
*/
|
||||
|
||||
import { strict as assert } from "node:assert";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const FRONTEND = path.resolve(__dirname, "..", "..", "frontend");
|
||||
|
||||
const sw = readFileSync(path.join(FRONTEND, "sw.js"), "utf-8");
|
||||
const indexHtml = readFileSync(path.join(FRONTEND, "index.html"), "utf-8");
|
||||
const sync = readFileSync(path.join(FRONTEND, "js", "sync.js"), "utf-8");
|
||||
|
||||
let testCount = 0;
|
||||
let passCount = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
testCount++;
|
||||
try {
|
||||
fn();
|
||||
console.log(` \u2713 ${name}`);
|
||||
passCount++;
|
||||
} catch (e) {
|
||||
console.log(` \u2717 ${name}`);
|
||||
console.log(` ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Service worker ─────────────────────────────────────────────────────────
|
||||
test("service worker uses a versioned cache name", () => {
|
||||
assert.ok(/const\s+SW_VERSION\s*=/.test(sw), "SW_VERSION constant present");
|
||||
assert.ok(sw.includes("obsigate-code-${SW_VERSION}"), "code cache is versioned");
|
||||
assert.ok(sw.includes("obsigate-runtime-${SW_VERSION}"), "runtime cache is versioned");
|
||||
assert.ok(sw.includes("obsigate-api-${SW_VERSION}"), "api cache is versioned");
|
||||
});
|
||||
|
||||
test("legacy fixed cache name is gone", () => {
|
||||
assert.ok(!/obsigate-v1['"]/.test(sw), "must not keep the fixed obsigate-v1 cache name");
|
||||
});
|
||||
|
||||
test("code/navigation requests are network-first", () => {
|
||||
assert.ok(/function\s+networkFirst\s*\(/.test(sw), "networkFirst strategy present");
|
||||
assert.ok(/isCodeRequest\s*\(/.test(sw), "code-request classifier present");
|
||||
// The fetch handler must route code requests through networkFirst.
|
||||
assert.ok(/isCodeRequest\([\s\S]*?networkFirst\(/.test(sw), "code requests use networkFirst");
|
||||
assert.ok(/request\.mode\s*===\s*['"]navigate['"]/.test(sw), "navigations are treated as code");
|
||||
});
|
||||
|
||||
test("precache uses real /static paths", () => {
|
||||
assert.ok(sw.includes("'/static/js/app.js'"), "app.js precached at /static/js");
|
||||
assert.ok(sw.includes("'/static/style.css'"), "style.css precached at /static");
|
||||
assert.ok(!sw.includes("/frontend/js/"), "no stale /frontend/js path in precache");
|
||||
});
|
||||
|
||||
test("precache failures are non-fatal", () => {
|
||||
assert.ok(/PRECACHE_URLS\.map[\s\S]*?\.catch\(/.test(sw), "per-URL catch so a 404 does not abort install");
|
||||
});
|
||||
|
||||
test("worker activates immediately and claims clients", () => {
|
||||
assert.ok(sw.includes("skipWaiting()"), "skipWaiting present");
|
||||
assert.ok(sw.includes("clients.claim()"), "clients.claim present");
|
||||
});
|
||||
|
||||
// ── index.html migration ───────────────────────────────────────────────────
|
||||
test("index.html uses a one-time keyed migration, not a session kill-switch", () => {
|
||||
assert.ok(indexHtml.includes("obsigate-sw-migration"), "keyed migration guard present");
|
||||
assert.ok(!indexHtml.includes("_sw_killed"), "legacy session kill-switch removed");
|
||||
assert.ok(!/getRegistrations\(\)[\s\S]{0,200}unregister/.test(indexHtml), "does not unregister on every session");
|
||||
});
|
||||
|
||||
// ── sync.js registration ───────────────────────────────────────────────────
|
||||
test("sync.js reloads once when a new worker takes control", () => {
|
||||
assert.ok(sync.includes("controllerchange"), "controllerchange listener present");
|
||||
assert.ok(sync.includes("SKIP_WAITING"), "asks the waiting worker to activate");
|
||||
});
|
||||
|
||||
console.log(`\n${passCount}/${testCount} service-worker tests passed`);
|
||||
if (passCount !== testCount) {
|
||||
process.exit(1);
|
||||
}
|
||||
+35
-1
@@ -719,4 +719,38 @@ class TestSecretRedactor:
|
||||
def test_redact_plain_text(self):
|
||||
from backend.secret_redactor import redact_file_content
|
||||
result = redact_file_content("hello world this is safe")
|
||||
assert result == "hello world this is safe"
|
||||
assert result == "hello world this is safe"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Static / PWA caching (Cloudflare / mobile freshness)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
class TestStaticCaching:
|
||||
"""Unhashed assets must revalidate (``no-cache``) to avoid stale builds.
|
||||
|
||||
This is the server-side half of the fix for mobile loads behind
|
||||
Cloudflare: without ``Cache-Control`` the edge/browser could serve an old
|
||||
build indefinitely.
|
||||
"""
|
||||
|
||||
def test_spa_index_revalidates(self, client):
|
||||
resp = client.get("/")
|
||||
assert resp.status_code == 200
|
||||
assert "no-cache" in resp.headers.get("cache-control", "")
|
||||
|
||||
def test_service_worker_revalidates(self, client):
|
||||
resp = client.get("/sw.js")
|
||||
assert resp.status_code == 200
|
||||
assert "no-cache" in resp.headers.get("cache-control", "")
|
||||
|
||||
def test_manifest_revalidates(self, client):
|
||||
resp = client.get("/manifest.json")
|
||||
assert resp.status_code == 200
|
||||
assert "no-cache" in resp.headers.get("cache-control", "")
|
||||
|
||||
def test_static_asset_revalidates(self, client):
|
||||
resp = client.get("/static/style.css")
|
||||
assert resp.status_code == 200
|
||||
assert "no-cache" in resp.headers.get("cache-control", "")
|
||||
Reference in New Issue
Block a user