Files
flowdeck/tests/test_service_worker.py
bruno d125eb399e
FlowDeck CI / lint (push) Successful in 1m49s
FlowDeck CI / test (push) Successful in 21m47s
FlowDeck CI / docker (push) Successful in 1m45s
fix: A3–A8 — bloc « fallback admin » de l'audit sécurité (401 sans session, ACL, CSRF)
- A3 : helper `_require_user_id()` (401 sans session) sur profile/password/token/forge ; `PUT /api/user/password` exige `current_password` vérifié ; `/api/user` sorti de la liste CSRF exemptée
- A4 : `POST /api/v1/token` → 401 sans session, chemin legacy `user_id=0` supprimé
- A5 : CRUD membres d'espace (POST/PUT/DELETE) : session + rôle admin de l'espace (ou admin global), placeholder user créé en `is_admin=0`, GET membres sans session → 401
- A6 : `_require_view` → 404 et `_require_edit` → 401 quand il n'y a pas de session (fin du legacy single-user sur les collections)
- A7 : création ET lecture de page → 401 sans session (PermissionManager conservé) ; `/board/api/pages` sorti de `EXCLUDED_PATHS` ; header CSRF manquant ajouté sur setItemIcon (local_workspace)
- A8 : seed admin sans mot de passe codé en dur — aléatoire au premier boot loggé une fois, ou `FLOWDECK_ADMIN_PASSWORD` ; re-seed seulement si absent
- tests : client connecté par défaut via `_TestSessionAuth` (session + CSRF injectés à la volée, jamais dans le cookie jar → plus de CookieConflict), helper `anon()` sur les 40 tests d'anonymat ; 1016/1016 verts, `ruff check app tests` OK
2026-09-30 22:04:13 -04:00

117 lines
3.9 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
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