feat: production launch — email audit, team invites, security headers, router fixes
- Fix loyalty & monitoring router bugs (_get_router → named routers) - Implement team invitation email with send_template + seed templates (en/fr/de) - Add SecurityHeadersMiddleware (nosniff, HSTS, referrer-policy, permissions-policy) - Build email audit admin page: service, schemas, API, page route, menu, i18n, HTML, JS - Clean stale TODO in platform-menu-config.js - Add 67 tests (unit + integration) covering all new functionality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
55
tests/unit/middleware/test_security_headers.py
Normal file
55
tests/unit/middleware/test_security_headers.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# tests/unit/middleware/test_security_headers.py
|
||||
"""Unit tests for SecurityHeadersMiddleware."""
|
||||
|
||||
import pytest
|
||||
|
||||
from middleware.security_headers import SecurityHeadersMiddleware
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSecurityHeadersMiddleware:
|
||||
"""Tests for security headers on responses."""
|
||||
|
||||
def test_nosniff_header(self, client):
|
||||
"""X-Content-Type-Options: nosniff is present."""
|
||||
response = client.get("/health")
|
||||
assert response.headers.get("X-Content-Type-Options") == "nosniff"
|
||||
|
||||
def test_frame_options_header(self, client):
|
||||
"""X-Frame-Options: SAMEORIGIN is present."""
|
||||
response = client.get("/health")
|
||||
assert response.headers.get("X-Frame-Options") == "SAMEORIGIN"
|
||||
|
||||
def test_referrer_policy_header(self, client):
|
||||
"""Referrer-Policy is set correctly."""
|
||||
response = client.get("/health")
|
||||
assert (
|
||||
response.headers.get("Referrer-Policy")
|
||||
== "strict-origin-when-cross-origin"
|
||||
)
|
||||
|
||||
def test_permissions_policy_header(self, client):
|
||||
"""Permissions-Policy restricts camera/mic/geo."""
|
||||
response = client.get("/health")
|
||||
assert (
|
||||
response.headers.get("Permissions-Policy")
|
||||
== "camera=(), microphone=(), geolocation=()"
|
||||
)
|
||||
|
||||
def test_no_hsts_on_http(self, client):
|
||||
"""HSTS header is NOT set for plain HTTP requests."""
|
||||
response = client.get("/health")
|
||||
# TestClient uses http by default
|
||||
assert "Strict-Transport-Security" not in response.headers
|
||||
|
||||
def test_headers_on_api_routes(self, client):
|
||||
"""Security headers are present on API routes too."""
|
||||
response = client.get("/api/v1/health")
|
||||
assert response.headers.get("X-Content-Type-Options") == "nosniff"
|
||||
assert response.headers.get("X-Frame-Options") == "SAMEORIGIN"
|
||||
|
||||
def test_headers_on_404(self, client):
|
||||
"""Security headers are present even on 404 responses."""
|
||||
response = client.get("/nonexistent-path-that-returns-404")
|
||||
assert response.headers.get("X-Content-Type-Options") == "nosniff"
|
||||
assert response.headers.get("Referrer-Policy") == "strict-origin-when-cross-origin"
|
||||
Reference in New Issue
Block a user