Files
flowdeck/tests/test_service_worker.py
T
bruno b5207216f1 feat: v6.0.0 PWA offline support
- manifest + icones, service worker (precache, network-first, Background Sync)

- module client FlowOffline (IndexedDB, queue, delta, flush) + hook editeur

- endpoints /api/v2/sync/{delta,batch,status} + moteur de sync (conflits LWW/orpheline/copie offline)

- migrations offline_sync_queue + sync_version (triggers)

- UI offline (banner, badge sync, toasts, icone dirty) + doc /help

- tests pytest (sync, migrations, SW, offline) + E2E Playwright; bump 6.0.0
2026-09-18 13:05:40 -04:00

113 lines
3.8 KiB
Python

"""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
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):
"""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):
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