"""Tests for the health check router.""" import pytest from httpx import AsyncClient from app.models.server import ServerStatus @pytest.mark.asyncio async def test_health_check_returns_200_when_online(client: AsyncClient) -> None: """``GET /api/v1/health`` must return HTTP 200 when fail2ban is online.""" response = await client.get("/api/v1/health") assert response.status_code == 200 @pytest.mark.asyncio async def test_health_check_returns_503_when_offline(client: AsyncClient) -> None: """``GET /api/v1/health`` must return HTTP 503 when fail2ban is offline.""" client._transport.app.state.server_status = ServerStatus(online=False) response = await client.get("/api/v1/health") assert response.status_code == 503 @pytest.mark.asyncio async def test_health_check_returns_ok_status_when_online(client: AsyncClient) -> None: """``GET /api/v1/health`` must contain ``status: ok`` when fail2ban is online.""" response = await client.get("/api/v1/health") data: dict[str, object] = response.json() assert data["status"] == "ok" assert data["fail2ban"] == "online" @pytest.mark.asyncio async def test_health_check_returns_unavailable_when_offline(client: AsyncClient) -> None: """``GET /api/v1/health`` must contain ``status: unavailable`` when fail2ban is offline.""" client._transport.app.state.server_status = ServerStatus(online=False) response = await client.get("/api/v1/health") data: dict[str, object] = response.json() assert data["status"] == "unavailable" assert data["fail2ban"] == "offline" @pytest.mark.asyncio async def test_health_check_content_type_is_json(client: AsyncClient) -> None: """``GET /api/v1/health`` must set the ``Content-Type`` header to JSON.""" response = await client.get("/api/v1/health") assert "application/json" in response.headers.get("content-type", "") @pytest.mark.asyncio async def test_health_check_includes_database_status(client: AsyncClient) -> None: """``GET /api/v1/health`` must include database status field.""" response = await client.get("/api/v1/health") data: dict[str, object] = response.json() assert "database" in data assert data["database"] in ("ok", "error") @pytest.mark.asyncio async def test_health_check_includes_scheduler_status(client: AsyncClient) -> None: """``GET /api/v1/health`` must include scheduler status field.""" response = await client.get("/api/v1/health") data: dict[str, object] = response.json() assert "scheduler" in data assert data["scheduler"] in ("running", "stopped", "unknown") @pytest.mark.asyncio async def test_health_check_includes_cache_status(client: AsyncClient) -> None: """``GET /api/v1/health`` must include cache status field.""" response = await client.get("/api/v1/health") data: dict[str, object] = response.json() assert "cache" in data assert data["cache"] in ("initialised", "uninitialised") @pytest.mark.asyncio async def test_health_check_includes_components_list(client: AsyncClient) -> None: """``GET /api/v1/health`` must include components list.""" response = await client.get("/api/v1/health") data: dict[str, object] = response.json() assert "components" in data assert isinstance(data["components"], list) @pytest.mark.asyncio async def test_health_check_offline_adds_fail2ban_to_components( client: AsyncClient, ) -> None: """When fail2ban is offline, it must appear in the components list.""" client._transport.app.state.server_status = ServerStatus(online=False) response = await client.get("/api/v1/health") data: dict[str, object] = response.json() assert data["status"] == "unavailable" components: list[dict[str, object]] = data["components"] # type: ignore[assignment] assert any(c.get("name") == "fail2ban" and c.get("healthy") is False for c in components) @pytest.mark.asyncio async def test_health_check_online_returns_empty_components(client: AsyncClient) -> None: """When all components are healthy, components list must be empty.""" response = await client.get("/api/v1/health") data: dict[str, object] = response.json() assert data["status"] == "ok" assert data["components"] == []