"""Tests for the deprecation header middleware.""" from datetime import UTC, datetime, timedelta from pathlib import Path import pytest from httpx import ASGITransport, AsyncClient from app.main import create_app from app.middleware.deprecation import ( _DEPRECATED_ENDPOINTS, _is_deprecated, register_deprecated_endpoint, ) def _make_utc(days_from_now: int) -> datetime: return datetime.now(UTC) + timedelta(days=days_from_now) @pytest.fixture def clean_registry() -> list: """Clear the deprecated endpoints registry before and after each test.""" original = list(_DEPRECATED_ENDPOINTS) _DEPRECATED_ENDPOINTS.clear() yield _DEPRECATED_ENDPOINTS _DEPRECATED_ENDPOINTS.clear() _DEPRECATED_ENDPOINTS.extend(original) class TestIsDeprecated: def test_path_matches_registered_prefix(self, clean_registry: list) -> None: register_deprecated_endpoint("/api/v1/jails", _make_utc(180)) assert _is_deprecated("/api/v1/jails") is not None assert _is_deprecated("/api/v1/jails/test-jail") is not None def test_path_does_not_match_unregistered_prefix(self, clean_registry: list) -> None: register_deprecated_endpoint("/api/v1/jails", _make_utc(180)) assert _is_deprecated("/api/v1/bans") is None def test_empty_registry_returns_none(self, clean_registry: list) -> None: assert _is_deprecated("/api/v1/jails") is None class TestDeprecationHeadersIntegration: @pytest.mark.asyncio async def test_deprecated_endpoint_gets_headers(self, clean_registry: list, tmp_path: Path) -> None: register_deprecated_endpoint("/api/v1/jails", _make_utc(180), successor_url="/api/v2/jails") from app.config import Settings config_dir = tmp_path / "fail2ban" config_dir.mkdir() settings = Settings( database_path="/tmp/test.db", fail2ban_socket="/tmp/fake.sock", fail2ban_config_dir=str(config_dir), session_secret="test-secret-key-do-not-use-in-production", session_duration_minutes=60, timezone="UTC", log_level="debug", ) app = create_app(settings=settings) async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get("/api/v1/jails") # 307 = setup redirect (app redirects unauthenticated/unconfigured requests) assert response.status_code in (200, 307, 401, 403, 404) assert "Deprecation" in response.headers or "Sunset" in response.headers @pytest.mark.asyncio async def test_non_deprecated_endpoint_no_headers(self, clean_registry: list, tmp_path: Path) -> None: register_deprecated_endpoint("/api/v1/jails", _make_utc(180)) from app.config import Settings config_dir = tmp_path / "fail2ban" config_dir.mkdir() settings = Settings( database_path="/tmp/test.db", fail2ban_socket="/tmp/fake.sock", fail2ban_config_dir=str(config_dir), session_secret="test-secret-key-do-not-use-in-production", session_duration_minutes=60, timezone="UTC", log_level="debug", ) app = create_app(settings=settings) async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get("/api/v1/bans") # No Deprecation header on non-deprecated path assert "Deprecation" not in response.headers