# ABOUTME: Tests for health and metrics endpoints. # ABOUTME: Covers /healthz DB check and /metrics Prometheus format. import os from unittest.mock import MagicMock import pytest def make_test_settings( csrf_secret: str = "test-secret", trusted_proxy_ips: str = "127.0.0.1", metrics_enabled: bool = True, ): """Create Settings for testing by setting env vars temporarily.""" from animaltrack.config import Settings old_env = os.environ.copy() try: os.environ["CSRF_SECRET"] = csrf_secret os.environ["TRUSTED_PROXY_IPS"] = trusted_proxy_ips os.environ["METRICS_ENABLED"] = str(metrics_enabled).lower() return Settings() finally: os.environ.clear() os.environ.update(old_env) class TestHealthzEndpoint: """Tests for /healthz endpoint.""" @pytest.fixture def client(self, seeded_db): """Create a test client for the app.""" from starlette.testclient import TestClient from animaltrack.web.app import create_app settings = make_test_settings(trusted_proxy_ips="testclient") app, rt = create_app(settings=settings, db=seeded_db) return TestClient(app, raise_server_exceptions=False) def test_healthz_returns_200_when_db_healthy(self, client): """GET /healthz returns 200 OK when database is healthy.""" resp = client.get("/healthz") assert resp.status_code == 200 assert resp.text == "OK" def test_healthz_returns_503_when_db_fails(self, seeded_db): """GET /healthz returns 503 when database query fails.""" from starlette.testclient import TestClient from animaltrack.web.app import create_app # Create a mock db that raises on execute mock_db = MagicMock() mock_db.execute.side_effect = Exception("Database connection lost") settings = make_test_settings(trusted_proxy_ips="testclient") app, rt = create_app(settings=settings, db=mock_db) client = TestClient(app, raise_server_exceptions=False) resp = client.get("/healthz") assert resp.status_code == 503 assert "Database error" in resp.text class TestMetricsEndpoint: """Tests for /metrics Prometheus endpoint.""" @pytest.fixture def client_metrics_enabled(self, seeded_db): """Create a test client with metrics enabled.""" from starlette.testclient import TestClient from animaltrack.web.app import create_app settings = make_test_settings( trusted_proxy_ips="testclient", metrics_enabled=True, ) app, rt = create_app(settings=settings, db=seeded_db) return TestClient(app, raise_server_exceptions=False) @pytest.fixture def client_metrics_disabled(self, seeded_db): """Create a test client with metrics disabled.""" from starlette.testclient import TestClient from animaltrack.web.app import create_app settings = make_test_settings( trusted_proxy_ips="testclient", metrics_enabled=False, ) app, rt = create_app(settings=settings, db=seeded_db) return TestClient(app, raise_server_exceptions=False) def test_metrics_returns_200_when_enabled(self, client_metrics_enabled): """GET /metrics returns 200 when metrics_enabled=True.""" resp = client_metrics_enabled.get("/metrics") assert resp.status_code == 200 def test_metrics_returns_404_when_disabled(self, client_metrics_disabled): """GET /metrics returns 404 when metrics_enabled=False.""" resp = client_metrics_disabled.get("/metrics") assert resp.status_code == 404 def test_metrics_returns_prometheus_format(self, client_metrics_enabled): """GET /metrics returns valid Prometheus text format.""" resp = client_metrics_enabled.get("/metrics") # Check Content-Type content_type = resp.headers.get("content-type", "") assert "text/plain" in content_type # Check body contains Prometheus format elements body = resp.text assert "# HELP" in body assert "# TYPE" in body assert "animaltrack_up" in body def test_metrics_includes_db_health_metric(self, client_metrics_enabled): """GET /metrics includes database health gauge.""" resp = client_metrics_enabled.get("/metrics") body = resp.text assert "animaltrack_db_healthy" in body assert "# TYPE animaltrack_db_healthy gauge" in body