"""Regression tests for the global CSRF interceptor (F.7c). Background: previously, dashboard pages defined a local ``getCsrf()`` JS helper that read the dashboard CSRF cookie via ``document.cookie``. The cookie is ``HttpOnly``, so JS could never read it and every non-GET fetch sent an empty ``X-CSRF-Token`` header — leading to 403 errors when toggling tools or changing the access scope. The fix moved CSRF handling into a single global interceptor in ``head_assets.html`` that reads from a ```` tag rendered server-side in ``base.html``. This file pins down the contract so the bug cannot silently regress: 1. Every dashboard page renders a non-empty ```` tag. 2. The global ``htmx:configRequest`` + ``window.fetch`` interceptor is present in ``head_assets.html``. 3. No dashboard template defines its own ``getCsrf()`` helper or sets an explicit ``x-csrf-token`` header from JS — those would short-circuit the global interceptor again. """ from __future__ import annotations from pathlib import Path import pytest from starlette.testclient import TestClient import core.dashboard.routes as routes_module import core.database as db_module from core.database import Database TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "core" / "templates" DASHBOARD_DIR = TEMPLATES_DIR / "dashboard" # ── Static template checks ────────────────────────────────────── class TestStaticTemplateContract: """Lint-style checks that don't require a running app.""" def test_head_assets_has_global_csrf_interceptor(self): head = (DASHBOARD_DIR / "partials" / "head_assets.html").read_text() # The HTMX hook assert "htmx:configRequest" in head, "missing global HTMX CSRF hook" # The fetch monkey-patch assert "window.fetch" in head, "missing global fetch CSRF hook" # Both must read from the meta tag — not the cookie assert 'meta[name="csrf-token"]' in head, "interceptor must read from meta tag" # Sanity: must set the X-CSRF-Token header assert "X-CSRF-Token" in head def test_base_template_renders_csrf_meta_tag(self): base = (DASHBOARD_DIR / "base.html").read_text() assert '", 1)[0]