""" Tests pour les routes de gestion des logs. Couvre: - Liste des logs - Création de logs - Suppression des logs """ import pytest from unittest.mock import patch, AsyncMock from httpx import AsyncClient pytestmark = pytest.mark.unit class TestGetLogs: """Tests pour GET /api/logs.""" async def test_list_logs_empty(self, client: AsyncClient, db_session): """Liste vide quand pas de logs.""" response = await client.get("/api/logs") assert response.status_code == 200 assert isinstance(response.json(), list) async def test_list_logs_with_data(self, client: AsyncClient, db_session): """Liste les logs depuis la BD.""" from app.crud.log import LogRepository repo = LogRepository(db_session) await repo.create(level="INFO", message="Test log 1", source="test") await repo.create(level="ERROR", message="Test log 2", source="test") await db_session.commit() response = await client.get("/api/logs") assert response.status_code == 200 logs = response.json() assert len(logs) >= 2 async def test_list_logs_with_pagination(self, client: AsyncClient, db_session): """Pagination fonctionne.""" from app.crud.log import LogRepository repo = LogRepository(db_session) for i in range(5): await repo.create(level="INFO", message=f"Log {i}", source="test") await db_session.commit() response = await client.get("/api/logs?limit=2&offset=0") assert response.status_code == 200 logs = response.json() assert isinstance(logs, list) async def test_list_logs_filter_by_level(self, client: AsyncClient, db_session): """Filtre par niveau.""" from app.crud.log import LogRepository repo = LogRepository(db_session) await repo.create(level="INFO", message="Info log", source="test") await repo.create(level="ERROR", message="Error log", source="test") await db_session.commit() response = await client.get("/api/logs?level=ERROR") assert response.status_code == 200 async def test_list_logs_structure(self, client: AsyncClient, db_session): """Vérifie la structure de réponse.""" from app.crud.log import LogRepository repo = LogRepository(db_session) await repo.create(level="INFO", message="Structured log", source="test") await db_session.commit() response = await client.get("/api/logs") assert response.status_code == 200 logs = response.json() if logs: log = logs[0] assert "id" in log assert "level" in log assert "message" in log class TestCreateLog: """Tests pour POST /api/logs.""" @pytest.mark.asyncio async def test_create_log_success(self, client: AsyncClient, db_session): """Création de log réussie.""" with patch("app.routes.logs.ws_manager") as mock_ws: mock_ws.broadcast = AsyncMock() response = await client.post( "/api/logs", params={ "level": "INFO", "message": "Test log message", "source": "test" } ) assert response.status_code == 200 data = response.json() assert data["level"] == "INFO" assert data["message"] == "Test log message" @pytest.mark.asyncio async def test_create_log_broadcasts_ws(self, client: AsyncClient, db_session): """Création broadcast via WebSocket.""" with patch("app.routes.logs.ws_manager") as mock_ws: mock_ws.broadcast = AsyncMock() await client.post( "/api/logs", params={ "level": "WARNING", "message": "Warning message" } ) mock_ws.broadcast.assert_called_once() call_args = mock_ws.broadcast.call_args[0][0] assert call_args["type"] == "new_log" class TestClearLogs: """Tests pour DELETE /api/logs.""" @pytest.mark.asyncio async def test_clear_logs_success(self, client: AsyncClient, db_session): """Suppression de tous les logs.""" from app.crud.log import LogRepository repo = LogRepository(db_session) await repo.create(level="INFO", message="To delete", source="test") await db_session.commit() response = await client.delete("/api/logs") assert response.status_code == 200 assert "supprimés" in response.json()["message"] class TestCreateLogWithHost: """Tests pour POST /api/logs avec host_id.""" @pytest.mark.asyncio async def test_create_log_with_host_id(self, client: AsyncClient, db_session, host_factory): """Création de log avec host_id.""" host = await host_factory.create(db_session, name="log-host") with patch("app.routes.logs.ws_manager") as mock_ws: mock_ws.broadcast = AsyncMock() response = await client.post( "/api/logs", params={ "level": "INFO", "message": "Host log", "source": "test", "host_id": host.id } ) assert response.status_code == 200 data = response.json() assert data["host"] == host.id @pytest.mark.asyncio async def test_create_log_error_level(self, client: AsyncClient, db_session): """Création de log niveau ERROR.""" with patch("app.routes.logs.ws_manager") as mock_ws: mock_ws.broadcast = AsyncMock() response = await client.post( "/api/logs", params={ "level": "error", # lowercase "message": "Error message" } ) assert response.status_code == 200 data = response.json() assert data["level"] == "ERROR" # Should be uppercased class TestListLogsFilterBySource: """Tests pour GET /api/logs avec filtre source.""" @pytest.mark.asyncio async def test_list_logs_filter_by_source(self, client: AsyncClient, db_session): """Filtre par source.""" from app.crud.log import LogRepository repo = LogRepository(db_session) await repo.create(level="INFO", message="Scheduler log", source="scheduler") await repo.create(level="INFO", message="Task log", source="task") await db_session.commit() response = await client.get("/api/logs?source=scheduler") assert response.status_code == 200 logs = response.json() for log in logs: if log.get("source"): assert log["source"] == "scheduler" @pytest.mark.asyncio async def test_list_logs_combined_filters(self, client: AsyncClient, db_session): """Filtres combinés level + source.""" from app.crud.log import LogRepository repo = LogRepository(db_session) await repo.create(level="ERROR", message="Error from scheduler", source="scheduler") await repo.create(level="INFO", message="Info from scheduler", source="scheduler") await repo.create(level="ERROR", message="Error from task", source="task") await db_session.commit() response = await client.get("/api/logs?level=ERROR&source=scheduler") assert response.status_code == 200 class TestClearLogsVerify: """Tests pour vérifier que DELETE /api/logs supprime bien les logs.""" @pytest.mark.asyncio async def test_clear_logs_verify_empty(self, client: AsyncClient, db_session): """Vérifier que les logs sont bien supprimés.""" from app.crud.log import LogRepository repo = LogRepository(db_session) await repo.create(level="INFO", message="Log 1", source="test") await repo.create(level="INFO", message="Log 2", source="test") await db_session.commit() # Clear await client.delete("/api/logs") # Verify empty response = await client.get("/api/logs") assert response.status_code == 200 # Logs should be empty or significantly reduced logs = response.json() assert isinstance(logs, list)