"""Tests for the ALLOWED_HOSTS host validation.""" import pytest def _run_before_request(app, path: str, host: str): """Invoke the registered before_request hooks for the given path/host. Returns the Flask response tuple (response, status) if a hook blocked the request, or None if the request passed through. We test the hook directly (instead of via the HTTP test client) because the test app config sets SERVER_NAME="localhost.localdomain", which makes Flask's routing layer reject foreign Host headers with 404 before any before_request hook runs. """ with app.test_request_context(path, headers={"Host": host}): for func in app.before_request_funcs.get(None, []): result = func() if result is not None: return result return None @pytest.fixture(autouse=True) def _restore_allowed_hosts(app): """Restore the original ALLOWED_HOSTS after each test. The app fixture is session-scoped, so mutations to app.config persist across tests/files. This ensures host validation doesn't leak into other test files. """ original = app.config.get("ALLOWED_HOSTS") yield app.config["ALLOWED_HOSTS"] = original @pytest.mark.integration class TestHostValidation: """Test the before_request ALLOWED_HOSTS enforcement.""" def test_allowed_host_accepted(self, app): """Requests with an allowed Host header pass through.""" app.config["ALLOWED_HOSTS"] = ["example.com", "www.example.com"] result = _run_before_request(app, "/api/boards", "example.com") assert result is None def test_allowed_host_with_port_accepted(self, app): """Host header with a port is stripped and validated.""" app.config["ALLOWED_HOSTS"] = ["example.com"] result = _run_before_request(app, "/api/boards", "example.com:8000") assert result is None def test_disallowed_host_rejected(self, app): """Requests with a disallowed Host header are rejected with 404.""" app.config["ALLOWED_HOSTS"] = ["example.com"] result = _run_before_request(app, "/api/boards", "192.168.1.135:8000") assert result is not None assert result[1] == 404 assert result[0].get_json()["error"] == "Not found" def test_unknown_domain_rejected(self, app): """Unknown domains are rejected, not just IPs.""" app.config["ALLOWED_HOSTS"] = ["example.com"] result = _run_before_request(app, "/api/boards", "evil-site.com") assert result is not None assert result[1] == 404 def test_no_allowed_hosts_permissive(self, app): """Empty ALLOWED_HOSTS disables the check (backwards compatible).""" app.config["ALLOWED_HOSTS"] = [] result = _run_before_request(app, "/api/boards", "192.168.1.135:8000") assert result is None def test_case_insensitive_host_match(self, app): """Host matching is case-insensitive.""" app.config["ALLOWED_HOSTS"] = ["example.com"] result = _run_before_request(app, "/api/boards", "EXAMPLE.COM") assert result is None def test_health_endpoint_exempt(self, app): """Health checks are exempt from host validation.""" app.config["ALLOWED_HOSTS"] = ["example.com"] result = _run_before_request(app, "/health/", "localhost:8000") assert result is None def test_parse_list_helper(self): """_parse_list splits, trims, lowercases, and drops empties.""" from app.config import _parse_list assert _parse_list("") == [] assert _parse_list(None) == [] assert _parse_list("Example.com, WWW.Example.com , ,other.org") == [ "example.com", "www.example.com", "other.org", ]