Files
BanGUI/backend/tests/test_deprecation_middleware.py
Lukas 65fe747cba feat(backend): add deprecation middleware and API versioning support
- Add deprecation middleware for warning headers on sunset endpoints
- Add jails_v2 router for API v2 migration path
- Update CI workflow with new test coverage
- Update API versioning documentation
- Remove completed tasks from Tasks.md
2026-05-04 00:03:52 +02:00

89 lines
3.3 KiB
Python

"""Tests for the deprecation header middleware."""
from datetime import UTC, datetime, timedelta
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) -> None:
register_deprecated_endpoint("/api/v1/jails", _make_utc(180), successor_url="/api/v2/jails")
settings = pytest.importorskip("app.config").Settings(
database_path="/tmp/test.db",
fail2ban_socket="/tmp/fake.sock",
fail2ban_config_dir="/tmp/fail2ban",
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) -> None:
register_deprecated_endpoint("/api/v1/jails", _make_utc(180))
settings = pytest.importorskip("app.config").Settings(
database_path="/tmp/test.db",
fail2ban_socket="/tmp/fake.sock",
fail2ban_config_dir="/tmp/fail2ban",
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