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