- 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
113 lines
3.8 KiB
Python
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
|