From c01f821680267579a559ef681328c6490cdf2107 Mon Sep 17 00:00:00 2001
From: Julius Scheuerer <95489434+JuliusScheuerer@users.noreply.github.com>
Date: Wed, 25 Mar 2026 20:12:04 +0100
Subject: [PATCH] Fix CSP blocking inline translations and broken routes.py
indentation (#5 followup)
The i18n PR (#5) introduced two bugs: (1) broken indentation in
_template_response() causing SyntaxError at import, and (2) an inline
-
+
diff --git a/tests/conftest.py b/tests/conftest.py
index 2f12f0e..5f8b8ef 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,9 +1,30 @@
"""Shared test fixtures."""
+from __future__ import annotations
+
import pytest
from fastapi.testclient import TestClient
from document_anonymizer.api.app import app
+from document_anonymizer.security.rate_limiter import RateLimiterMiddleware
+
+
+def _find_rate_limiter(app_obj: object) -> RateLimiterMiddleware | None:
+ """Walk the middleware stack to find the RateLimiterMiddleware instance."""
+ current: object | None = getattr(app_obj, "middleware_stack", None)
+ while current is not None:
+ if isinstance(current, RateLimiterMiddleware):
+ return current
+ current = getattr(current, "app", None)
+ return None
+
+
+@pytest.fixture(autouse=True)
+def _reset_rate_limiter() -> None:
+ """Clear rate limiter state before each test to prevent 429 cascade."""
+ limiter = _find_rate_limiter(app)
+ if limiter is not None:
+ limiter._requests.clear()
@pytest.fixture
diff --git a/tests/test_web/test_routes.py b/tests/test_web/test_routes.py
index 746b1ae..9164bc9 100644
--- a/tests/test_web/test_routes.py
+++ b/tests/test_web/test_routes.py
@@ -287,6 +287,36 @@ def test_csp_header_present(self) -> None:
assert "frame-ancestors 'none'" in csp
assert "font-src 'self'" in csp
+ def test_csp_nonce_in_script_src(self) -> None:
+ """CSP script-src must include a per-request nonce."""
+ r = client.get("/")
+ csp = r.headers["Content-Security-Policy"]
+ assert "'nonce-" in csp
+
+ def test_csp_nonce_differs_per_request(self) -> None:
+ """Each request must get a unique CSP nonce."""
+ r1 = client.get("/")
+ r2 = client.get("/")
+ csp1 = r1.headers["Content-Security-Policy"]
+ csp2 = r2.headers["Content-Security-Policy"]
+ assert csp1 != csp2
+
+ def test_csp_nonce_matches_inline_script(self) -> None:
+ """The nonce in the CSP header must match the nonce on the inline script tag."""
+ import re
+
+ r = client.get("/")
+ csp = r.headers["Content-Security-Policy"]
+ csp_match = re.search(r"'nonce-([^']+)'", csp)
+ assert csp_match is not None
+ csp_nonce = csp_match.group(1)
+
+ html_match = re.search(r'