"""Integration tests for HTTPS enforcement 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 TestHTTPSEnforcement: """Test HTTPS enforcement middleware.""" def test_https_allowed_in_production(self, client, monkeypatch): """Test HTTPS requests are allowed in production mode.""" # Simulate production mode from gondulf.config import Config monkeypatch.setattr(Config, "DEBUG", False) # HTTPS request should succeed # Note: TestClient uses http by default, so this test is illustrative # In real production, requests come from a reverse proxy (nginx) with HTTPS # Use root endpoint instead of health as it doesn't require database response = client.get("/") assert response.status_code == 200 def test_http_localhost_allowed_in_debug(self, client, monkeypatch): """Test HTTP to localhost is allowed in debug mode.""" from gondulf.config import Config monkeypatch.setattr(Config, "DEBUG", True) # HTTP to localhost should succeed in debug mode # Use root endpoint instead of health as it doesn't require database response = client.get("http://localhost:8000/") assert response.status_code == 200 def test_https_always_allowed(self, client): """Test HTTPS requests are always allowed regardless of mode.""" # HTTPS should work in both debug and production # Use root endpoint instead of health as it doesn't require database response = client.get("/") # TestClient doesn't enforce HTTPS, but middleware should allow it assert response.status_code == 200 def test_health_endpoint_exempt_from_https_in_production( self, client, monkeypatch ): """Test /health endpoint is accessible via HTTP in production mode. Docker health checks and load balancers call the health endpoint directly without going through the reverse proxy, so it must work over HTTP. The key assertion is that we don't get a 301 redirect to HTTPS. """ from gondulf.config import Config monkeypatch.setattr(Config, "DEBUG", False) monkeypatch.setattr(Config, "TRUST_PROXY", False) # HTTP request to /health should NOT redirect to HTTPS response = client.get( "http://localhost:8000/health", follow_redirects=False ) # Should NOT be 301 redirect - actual status depends on DB state (200/503) assert response.status_code != 301 # Verify it reached the health endpoint (not redirected) assert response.status_code in (200, 503) def test_health_endpoint_head_request_in_production(self, client, monkeypatch): """Test HEAD request to /health is not redirected in production. Docker health checks may use HEAD requests. The key is that the middleware doesn't redirect to HTTPS - the actual endpoint behavior (405 Method Not Allowed) is separate from HTTPS enforcement. """ from gondulf.config import Config monkeypatch.setattr(Config, "DEBUG", False) monkeypatch.setattr(Config, "TRUST_PROXY", False) # HEAD request to /health should NOT redirect to HTTPS response = client.head( "http://localhost:8000/health", follow_redirects=False ) # Should NOT be 301 redirect assert response.status_code != 301 def test_metrics_endpoint_exempt_from_https_in_production( self, client, monkeypatch ): """Test /metrics endpoint is accessible via HTTP in production mode. Monitoring systems may call metrics directly without HTTPS. """ from gondulf.config import Config monkeypatch.setattr(Config, "DEBUG", False) monkeypatch.setattr(Config, "TRUST_PROXY", False) # HTTP request to /metrics should not be redirected # (endpoint may not exist yet, but should not redirect to HTTPS) response = client.get( "http://localhost:8000/metrics", follow_redirects=False ) # Should return 404 (not found) not 301 (redirect to HTTPS) assert response.status_code != 301