"""Integration tests for security headers middleware.""" import tempfile from pathlib import Path import pytest from fastapi.testclient import TestClient @pytest.fixture def test_app(monkeypatch): """Create test FastAPI app with test configuration.""" # Set up test environment with tempfile.TemporaryDirectory() as tmpdir: db_path = Path(tmpdir) / "test.db" # Set required environment variables monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.setenv("GONDULF_DATABASE_URL", f"sqlite:///{db_path}") monkeypatch.setenv("GONDULF_DEBUG", "true") # Import app AFTER setting env vars from gondulf.main import app yield app @pytest.fixture def client(test_app): """FastAPI test client.""" return TestClient(test_app) class TestSecurityHeaders: """Test security headers middleware.""" def test_x_frame_options_header(self, client): """Test X-Frame-Options header is present.""" response = client.get("/health") assert "X-Frame-Options" in response.headers assert response.headers["X-Frame-Options"] == "DENY" def test_x_content_type_options_header(self, client): """Test X-Content-Type-Options header is present.""" response = client.get("/health") assert "X-Content-Type-Options" in response.headers assert response.headers["X-Content-Type-Options"] == "nosniff" def test_x_xss_protection_header(self, client): """Test X-XSS-Protection header is present.""" response = client.get("/health") assert "X-XSS-Protection" in response.headers assert response.headers["X-XSS-Protection"] == "1; mode=block" def test_csp_header(self, client): """Test Content-Security-Policy header is present and configured correctly.""" response = client.get("/health") assert "Content-Security-Policy" in response.headers csp = response.headers["Content-Security-Policy"] assert "default-src 'self'" in csp assert "style-src 'self' 'unsafe-inline'" in csp assert "img-src 'self' https:" in csp assert "frame-ancestors 'none'" in csp def test_referrer_policy_header(self, client): """Test Referrer-Policy header is present.""" response = client.get("/health") assert "Referrer-Policy" in response.headers assert response.headers["Referrer-Policy"] == "strict-origin-when-cross-origin" def test_permissions_policy_header(self, client): """Test Permissions-Policy header is present.""" response = client.get("/health") assert "Permissions-Policy" in response.headers policy = response.headers["Permissions-Policy"] assert "geolocation=()" in policy assert "microphone=()" in policy assert "camera=()" in policy def test_hsts_header_not_in_debug_mode(self, client): """Test HSTS header is NOT present in debug mode.""" # This test assumes DEBUG=True in test environment # In production, DEBUG=False and HSTS should be present response = client.get("/health") # Check current mode from Config from gondulf.config import Config if Config.DEBUG: # HSTS should NOT be present in debug mode assert "Strict-Transport-Security" not in response.headers else: # HSTS should be present in production mode assert "Strict-Transport-Security" in response.headers assert ( "max-age=31536000" in response.headers["Strict-Transport-Security"] ) assert ( "includeSubDomains" in response.headers["Strict-Transport-Security"] ) def test_headers_on_all_endpoints(self, client): """Test security headers are present on all endpoints.""" endpoints = [ "/", "/health", "/.well-known/oauth-authorization-server", ] for endpoint in endpoints: response = client.get(endpoint) # All endpoints should have security headers assert "X-Frame-Options" in response.headers assert "X-Content-Type-Options" in response.headers assert "Content-Security-Policy" in response.headers def test_headers_on_error_responses(self, client): """Test security headers are present even on error responses.""" # Request non-existent endpoint (404) response = client.get("/nonexistent") assert response.status_code == 404 # Security headers should still be present assert "X-Frame-Options" in response.headers assert "X-Content-Type-Options" in response.headers