""" Tests pour les routes metrics. """ import pytest import re from unittest.mock import patch, AsyncMock, MagicMock from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession pytestmark = pytest.mark.unit class TestGetMetrics: """Tests pour GET /api/metrics.""" @pytest.mark.asyncio async def test_get_metrics(self, client: AsyncClient): """Récupération des métriques globales.""" response = await client.get("/api/metrics") assert response.status_code == 200 data = response.json() assert "online_hosts" in data or "total_tasks" in data class TestGetAllHostsMetrics: """Tests pour GET /api/metrics/all-hosts.""" @pytest.mark.asyncio async def test_get_all_hosts_metrics_empty(self, client: AsyncClient, db_session: AsyncSession): """Métriques vides.""" response = await client.get("/api/metrics/all-hosts") assert response.status_code == 200 data = response.json() assert isinstance(data, dict) @pytest.mark.asyncio async def test_get_all_hosts_metrics_with_data(self, client: AsyncClient, db_session: AsyncSession, host_factory): """Métriques avec données.""" from app.crud.host_metrics import HostMetricsRepository host = await host_factory.create(db_session, name="metrics-test-host") repo = HostMetricsRepository(db_session) await repo.create( host_id=host.id, metric_type="system_info", cpu_usage_percent=50.0, memory_usage_percent=60.0 ) await db_session.commit() response = await client.get("/api/metrics/all-hosts") assert response.status_code == 200 data = response.json() assert host.id in data assert data[host.id]["last_collected"] is not None assert data[host.id]["last_collected"] == data[host.id]["collected_at"] assert re.search(r"[+-]\d{2}:\d{2}$", data[host.id]["last_collected"]) is not None class TestGetCollectionSchedule: """Tests pour GET /api/metrics/collection-schedule.""" @pytest.mark.asyncio async def test_get_schedule_default(self, client: AsyncClient): """Intervalle par défaut.""" response = await client.get("/api/metrics/collection-schedule") assert response.status_code == 200 data = response.json() assert "interval" in data @pytest.mark.asyncio async def test_get_schedule_with_setting(self, client: AsyncClient, db_session: AsyncSession): """Intervalle configuré.""" from app.crud.app_setting import AppSettingRepository repo = AppSettingRepository(db_session) await repo.set("metrics_collection_interval", "15min") await db_session.commit() response = await client.get("/api/metrics/collection-schedule") assert response.status_code == 200 data = response.json() assert data["interval"] == "15min" class TestSetCollectionSchedule: """Tests pour POST /api/metrics/collection-schedule.""" @pytest.mark.asyncio async def test_set_schedule_valid(self, client: AsyncClient): """Définition d'un intervalle valide.""" response = await client.post( "/api/metrics/collection-schedule", json={"interval": "30min"} ) assert response.status_code == 200 data = response.json() assert data["interval"] == "30min" @pytest.mark.asyncio async def test_set_schedule_triggers_reschedule(self, client: AsyncClient): """Le changement d'intervalle doit rescheduler le job de collecte.""" with patch( "app.routes.metrics.apply_interval_using_global_scheduler", autospec=True, ) as mock_apply: response = await client.post( "/api/metrics/collection-schedule", json={"interval": "5min"} ) assert response.status_code == 200 mock_apply.assert_called_once_with("5min") @pytest.mark.asyncio async def test_set_schedule_off(self, client: AsyncClient): """Désactivation de la collecte.""" response = await client.post( "/api/metrics/collection-schedule", json={"interval": "off"} ) assert response.status_code == 200 data = response.json() assert data["interval"] == "off" @pytest.mark.asyncio async def test_set_schedule_invalid(self, client: AsyncClient): """Intervalle invalide.""" response = await client.post( "/api/metrics/collection-schedule", json={"interval": "invalid"} ) assert response.status_code == 400 class TestGetHostMetrics: """Tests pour GET /api/metrics/{host_id}.""" @pytest.mark.asyncio async def test_get_host_metrics_no_data(self, client: AsyncClient): """Hôte sans métriques.""" response = await client.get("/api/metrics/nonexistent-host") assert response.status_code == 200 data = response.json() assert data["collection_status"] == "no_data" @pytest.mark.asyncio async def test_get_host_metrics_with_data(self, client: AsyncClient, db_session: AsyncSession, host_factory): """Hôte avec métriques.""" from app.crud.host_metrics import HostMetricsRepository host = await host_factory.create(db_session, name="host-with-metrics") repo = HostMetricsRepository(db_session) await repo.create( host_id=host.id, metric_type="system_info", cpu_usage_percent=45.5, memory_usage_percent=70.0, disk_root_usage_percent=55.0 ) await db_session.commit() response = await client.get(f"/api/metrics/{host.id}") assert response.status_code == 200 data = response.json() assert data["host_id"] == host.id assert data["cpu_usage_percent"] == 45.5 assert data["last_collected"] is not None assert data["last_collected"] == data["collected_at"] assert re.search(r"[+-]\d{2}:\d{2}$", data["last_collected"]) is not None class TestMetricsWithErrors: """Tests pour les cas d'erreur des métriques.""" @pytest.mark.asyncio async def test_get_all_hosts_metrics_exception(self, client: AsyncClient): """Gestion des exceptions pour all-hosts.""" with patch("app.routes.metrics.HostMetricsRepository") as mock_repo: mock_repo.return_value.get_all_latest = AsyncMock(side_effect=Exception("DB error")) response = await client.get("/api/metrics/all-hosts") # Should return empty dict on error assert response.status_code == 200 assert response.json() == {} @pytest.mark.asyncio async def test_get_collection_schedule_exception(self, client: AsyncClient): """Gestion des exceptions pour collection-schedule.""" with patch("app.routes.metrics.AppSettingRepository") as mock_repo: mock_repo.return_value.get = AsyncMock(side_effect=Exception("DB error")) response = await client.get("/api/metrics/collection-schedule") # Should return default "off" on error assert response.status_code == 200 assert response.json()["interval"] == "off" @pytest.mark.asyncio async def test_set_collection_schedule_exception(self, client: AsyncClient): """Gestion des exceptions pour set collection-schedule.""" with patch("app.routes.metrics.AppSettingRepository") as mock_repo: mock_repo.return_value.set = AsyncMock(side_effect=Exception("DB error")) response = await client.post( "/api/metrics/collection-schedule", json={"interval": "5min"} ) assert response.status_code == 500 @pytest.mark.asyncio async def test_get_host_metrics_exception(self, client: AsyncClient): """Gestion des exceptions pour host metrics.""" with patch("app.routes.metrics.HostMetricsRepository") as mock_repo: mock_repo.return_value.get_latest_for_host = AsyncMock(side_effect=Exception("DB error")) response = await client.get("/api/metrics/test-host") assert response.status_code == 200 data = response.json() assert data["collection_status"] == "error" assert "error_message" in data @pytest.mark.asyncio async def test_get_host_metrics_with_error_message(self, client: AsyncClient, db_session: AsyncSession, host_factory): """Métriques avec message d'erreur.""" from app.crud.host_metrics import HostMetricsRepository host = await host_factory.create(db_session, name="host-with-error") repo = HostMetricsRepository(db_session) await repo.create( host_id=host.id, metric_type="system_info", error_message="Connection failed" ) await db_session.commit() response = await client.get(f"/api/metrics/{host.id}") assert response.status_code == 200 data = response.json() assert data["collection_status"] == "failed" assert data["error_message"] == "Connection failed" class TestSetCollectionScheduleAllIntervals: """Tests pour tous les intervalles valides.""" @pytest.mark.asyncio async def test_set_schedule_5min(self, client: AsyncClient): """Intervalle 5min.""" response = await client.post( "/api/metrics/collection-schedule", json={"interval": "5min"} ) assert response.status_code == 200 assert response.json()["interval"] == "5min" @pytest.mark.asyncio async def test_set_schedule_15min(self, client: AsyncClient): """Intervalle 15min.""" response = await client.post( "/api/metrics/collection-schedule", json={"interval": "15min"} ) assert response.status_code == 200 assert response.json()["interval"] == "15min" @pytest.mark.asyncio async def test_set_schedule_1h(self, client: AsyncClient): """Intervalle 1h.""" response = await client.post( "/api/metrics/collection-schedule", json={"interval": "1h"} ) assert response.status_code == 200 assert response.json()["interval"] == "1h" @pytest.mark.asyncio async def test_set_schedule_6h(self, client: AsyncClient): """Intervalle 6h.""" response = await client.post( "/api/metrics/collection-schedule", json={"interval": "6h"} ) assert response.status_code == 200 assert response.json()["interval"] == "6h" @pytest.mark.asyncio async def test_set_schedule_12h(self, client: AsyncClient): """Intervalle 12h.""" response = await client.post( "/api/metrics/collection-schedule", json={"interval": "12h"} ) assert response.status_code == 200 assert response.json()["interval"] == "12h" @pytest.mark.asyncio async def test_set_schedule_24h(self, client: AsyncClient): """Intervalle 24h.""" response = await client.post( "/api/metrics/collection-schedule", json={"interval": "24h"} ) assert response.status_code == 200 assert response.json()["interval"] == "24h"