diff --git a/routstr/core/admin.py b/routstr/core/admin.py index d5eba742..cc140f6c 100644 --- a/routstr/core/admin.py +++ b/routstr/core/admin.py @@ -1760,6 +1760,34 @@ async def get_logs_api( } +@admin_router.get( + "/api/logs/request/{request_id}", dependencies=[Depends(require_admin_api)] +) +async def get_logs_by_request_id_api( + request: Request, + request_id: str, + date: str | None = None, + limit: int = Query(default=200, ge=1, le=1000), +) -> dict[str, object]: + """ + Get every log entry belonging to a single request ID, oldest first. + """ + log_entries = log_manager.search_logs( + date=date, + request_id=request_id, + limit=limit, + ) + log_entries.sort(key=lambda entry: str(entry.get("asctime", ""))) + + return { + "logs": log_entries, + "total": len(log_entries), + "request_id": request_id, + "date": date, + "limit": limit, + } + + @admin_router.get("/api/logs/dates", dependencies=[Depends(require_admin_api)]) async def get_log_dates_api(request: Request) -> dict[str, object]: logs_dir = Path("logs") diff --git a/tests/unit/test_admin_logs_by_request_id.py b/tests/unit/test_admin_logs_by_request_id.py new file mode 100644 index 00000000..5391654a --- /dev/null +++ b/tests/unit/test_admin_logs_by_request_id.py @@ -0,0 +1,39 @@ +from typing import Any +from unittest.mock import Mock, patch + +import pytest + +from routstr.core.admin import get_logs_by_request_id_api + + +@pytest.mark.asyncio +async def test_returns_entries_for_request_id_oldest_first() -> None: + entries = [ + {"asctime": "2026-01-01 10:00:02", "message": "done", "request_id": "req-1"}, + {"asctime": "2026-01-01 10:00:01", "message": "start", "request_id": "req-1"}, + ] + + with patch("routstr.core.admin.log_manager") as log_manager: + log_manager.search_logs.return_value = entries + result: dict[str, Any] = await get_logs_by_request_id_api( + request=Mock(), request_id="req-1", date=None, limit=200 + ) + + log_manager.search_logs.assert_called_once_with( + date=None, request_id="req-1", limit=200 + ) + assert result["total"] == 2 + assert result["request_id"] == "req-1" + assert [entry["message"] for entry in result["logs"]] == ["start", "done"] # type: ignore[index,union-attr] + + +@pytest.mark.asyncio +async def test_returns_empty_list_for_unknown_request_id() -> None: + with patch("routstr.core.admin.log_manager") as log_manager: + log_manager.search_logs.return_value = [] + result = await get_logs_by_request_id_api( + request=Mock(), request_id="missing", date="2026-01-01", limit=10 + ) + + assert result["logs"] == [] + assert result["total"] == 0