From a9b0168c7196cb50aa16775c5d2f7ec6c4925e8c Mon Sep 17 00:00:00 2001 From: Oliver Hofmann Date: Sun, 10 May 2026 10:11:55 +0200 Subject: [PATCH] Add GET /api/logs/{name} endpoint to admin API --- backend/admin.py | 12 +++++++ backend/tests/test_admin_logs.py | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 backend/tests/test_admin_logs.py diff --git a/backend/admin.py b/backend/admin.py index 600db28..ed68dc7 100644 --- a/backend/admin.py +++ b/backend/admin.py @@ -169,6 +169,18 @@ async def get_ollama_models( except Exception: return {"models": [], "reachable": False} +@app.get("/api/logs/{name}") +async def get_log_lines(name: str, _ = Depends(require_admin_auth)): + if name not in ("usage", "error"): + raise HTTPException(status_code=400, detail="name must be 'usage' or 'error'") + log_file = Path(os.getenv("LOG_FILE", "logs/usage.log")) + path = log_file if name == "usage" else log_file.parent / "error.log" + try: + lines = path.read_text(encoding="utf-8").splitlines() + return {"lines": lines[-10:]} + except FileNotFoundError: + return {"lines": []} + # Statisches Frontend ausliefern (nur im Produktivbetrieb, wenn dist/ existiert) _dist = Path(__file__).parent.parent / "frontend" / "dist" if _dist.exists(): diff --git a/backend/tests/test_admin_logs.py b/backend/tests/test_admin_logs.py new file mode 100644 index 0000000..e55f2a6 --- /dev/null +++ b/backend/tests/test_admin_logs.py @@ -0,0 +1,59 @@ +import os +import pytest +from fastapi.testclient import TestClient + +os.environ.setdefault("ADMIN_PASSWORD", "test-admin-pw") +os.environ.setdefault("OLLAMA_URL", "http://127.0.0.1:9999") + + +@pytest.fixture +def client(tmp_path): + log_file = tmp_path / "usage.log" + log_file.write_text("\n".join(f"Zeile {i}" for i in range(1, 16)) + "\n") + (tmp_path / "error.log").write_text("Fehler A\nFehler B\n") + os.environ["LOG_FILE"] = str(log_file) + + from database import Base, engine + Base.metadata.drop_all(bind=engine) + Base.metadata.create_all(bind=engine) + + from admin import app + yield TestClient(app, raise_server_exceptions=False) + + Base.metadata.drop_all(bind=engine) + os.environ.pop("LOG_FILE", None) + + +AUTH = {"Authorization": "Bearer test-admin-pw"} + + +def test_logs_usage_returns_last_10_lines(client): + resp = client.get("/api/logs/usage", headers=AUTH) + assert resp.status_code == 200 + lines = resp.json()["lines"] + assert len(lines) == 10 + assert lines[-1] == "Zeile 15" + assert lines[0] == "Zeile 6" + + +def test_logs_error_returns_content(client): + resp = client.get("/api/logs/error", headers=AUTH) + assert resp.status_code == 200 + assert resp.json()["lines"] == ["Fehler A", "Fehler B"] + + +def test_logs_missing_file_returns_empty(client, tmp_path): + os.environ["LOG_FILE"] = str(tmp_path / "nonexistent.log") + resp = client.get("/api/logs/usage", headers=AUTH) + assert resp.status_code == 200 + assert resp.json()["lines"] == [] + + +def test_logs_invalid_name_returns_400(client): + resp = client.get("/api/logs/secret", headers=AUTH) + assert resp.status_code == 400 + + +def test_logs_requires_auth(client): + resp = client.get("/api/logs/usage") + assert resp.status_code == 401