"""FlowDeck — PWA service worker tests (v6.0.0). Validates that the service worker, static manifest and PWA icons are served correctly (status codes, content types) and stay in sync with the manifest. """ import json import re from pathlib import Path from conftest import anon ROOT = Path(__file__).resolve().parent.parent def _token(user_id, login): from app.auth.session import SessionManager return SessionManager.create_session({"id": user_id, "login": login, "full_name": login.title(), "is_admin": 1}) def _create_user_ws(client, login="pwatest", ws_name="PWA WS"): """Insert a user + workspace and return an authenticated session cookie.""" from app.db import get_conn with get_conn() as conn: cur = conn.execute( "INSERT INTO users (login, full_name, email) VALUES (?, ?, ?)", (login, login.title(), f"{login}@test.com"), ) user_id = cur.lastrowid conn.execute( "INSERT INTO workspaces (name, owner_id) VALUES (?, ?)", (ws_name, user_id), ) conn.commit() return _token(user_id, login) def test_sw_served_at_top_level(client): """GET /sw.js returns the JS source so registration at scope '/' works.""" resp = client.get("/sw.js") assert resp.status_code == 200 assert "javascript" in resp.headers.get("content-type", "") def test_sw_served_via_static_mount(client): resp = client.get("/static/sw.js") assert resp.status_code == 200 assert "javascript" in resp.headers.get("content-type", "") def test_sw_registration_present_on_landing(client): anon(client) """Anonymous entry point (/) registers the SW (assets are public).""" resp = client.get("/") assert resp.status_code in (200, 302) body = resp.text assert "serviceWorker" in body assert "/sw.js" in body def test_sw_registration_with_background_sync_in_base(client): """base.html pages register /sw.js and the background-sync tag.""" session = _create_user_ws(client) resp = client.get("/local-workspace", cookies={"flowdeck_session": session}) assert resp.status_code == 200 body = resp.text assert "serviceWorker" in body assert "/sw.js" in body assert "sync-flowdeck" in body def test_base_has_pwa_meta_tags(client): anon(client) resp = client.get("/") body = resp.text assert 'rel="manifest"' in body assert "theme-color" in body assert "apple-touch-icon" in body def test_manifest_self_consistent(client): """Every icon listed in the served manifest exists on disk.""" resp = client.get("/manifest.json") assert resp.status_code == 200 manifest = resp.json() for icon in manifest["icons"]: path = ROOT / icon["src"].lstrip("/") assert path.is_file(), f"manifest references missing icon: {icon['src']}" pwa_manifest = ROOT / "static" / "manifest.json" assert pwa_manifest.is_file() assert json.loads(pwa_manifest.read_text(encoding="utf-8")) == manifest def test_sw_precache_urls_exist(): """Every URL listed in the SW precache maps to a served static asset.""" sw = (ROOT / "static" / "sw.js").read_text(encoding="utf-8") urls = re.findall(r"'(/static/[^']+)'", sw) assert urls, "no static URLs found in precache list" for url in urls: file_path = ROOT / url.split("?")[0].lstrip("/") assert file_path.is_file(), f"precache URL missing on disk: {url}" def test_sw_cache_strategies_defined(): """The SW must define both cache-first and network-first helpers.""" sw = (ROOT / "static" / "sw.js").read_text(encoding="utf-8") assert "cacheFirst" in sw assert "networkFirst" in sw assert "CACHE_NAME" in sw assert "addEventListener('install'" in sw assert "addEventListener('activate'" in sw assert "addEventListener('fetch'" in sw assert "addEventListener('sync'" in sw