From 5ff9b63d0af4a6880ac51347c6fb05912edf82fa Mon Sep 17 00:00:00 2001 From: Guido Date: Sat, 22 Aug 2026 23:47:10 +0200 Subject: [PATCH] fix: Fehler am Kurzbefehl-Endpunkt sind auf dem Telefon lesbar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der Kurzbefehl zeigt das Feld "message" an. Fehlerantworten liefern aber "detail" – die Mitteilung auf dem Telefon blieb also leer. Man sah nicht einmal, DASS etwas schiefging: Die Marke wurde als benutzt vermerkt, auf dem Server kam trotzdem nichts an, und die Ursache war von aussen unsichtbar. Antworten unter /api/quick liefern jetzt zusaetzlich "message" mit einem lesbaren Text. Bei fehlenden Pflichtfeldern nennt er sie beim Namen und erinnert daran, dass der Anfragetext beim Datei-Kurzbefehl 'Formular' sein muss. Co-Authored-By: Claude Opus 5 --- backend/app/main.py | 41 ++++++++++++++++++++++++++++++++++++- backend/tests/test_quick.py | 32 +++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/backend/app/main.py b/backend/app/main.py index 54121ae..083dff9 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -10,7 +10,11 @@ import threading from contextlib import asynccontextmanager from pathlib import Path -from fastapi import APIRouter, Depends, FastAPI, HTTPException +from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request +from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse +from starlette.exceptions import HTTPException as StarletteHTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles @@ -155,6 +159,41 @@ api.include_router(quick.router) api.include_router(inbox.router) api.include_router(mail.router) +# Fehler an den Kurzbefehl-Endpunkten zusätzlich als „message" ausliefern. +# Der Kurzbefehl auf dem Telefon zeigt genau dieses Feld an; bei einem Fehler +# steht sonst nur „detail" in der Antwort und die Mitteilung bleibt leer – +# man sieht dann nicht, DASS etwas schiefging, geschweige denn was. +def _ist_kurzbefehl(request: Request) -> bool: + return request.url.path.startswith("/api/quick/") + + +@app.exception_handler(StarletteHTTPException) +async def http_fehler(request: Request, exc: StarletteHTTPException): + inhalt = {"detail": exc.detail} + if _ist_kurzbefehl(request): + inhalt["message"] = str(exc.detail) + inhalt["ok"] = False + return JSONResponse(status_code=exc.status_code, content=inhalt, + headers=getattr(exc, "headers", None)) + + +@app.exception_handler(RequestValidationError) +async def eingabe_fehler(request: Request, exc: RequestValidationError): + inhalt = {"detail": exc.errors()} + if _ist_kurzbefehl(request): + fehlend = [ + ".".join(str(t) for t in f.get("loc", ()) if t not in ("body", "query")) + for f in exc.errors() + ] + inhalt["message"] = ("Die Anfrage ist unvollständig: " + + ", ".join(f for f in fehlend if f) + + ". Beim Datei-Kurzbefehl muss der Anfragetext " + + "'Formular' sein, mit den Feldern file (Datei) " + + "und account_id.") + inhalt["ok"] = False + return JSONResponse(status_code=422, content=jsonable_encoder(inhalt)) + + app.include_router(api) diff --git a/backend/tests/test_quick.py b/backend/tests/test_quick.py index 0e84e17..38f2ac2 100644 --- a/backend/tests/test_quick.py +++ b/backend/tests/test_quick.py @@ -307,3 +307,35 @@ def test_leere_und_zu_grosse_dateien_werden_abgewiesen(admin): files={"file": ("gross.csv", io.BytesIO(b"x" * (6 * 1024 * 1024)), "text/csv")}, data={"account_id": str(konto["id"])}, headers=kopf) assert riesig.status_code == 413 + + +def test_fehler_am_kurzbefehl_sind_im_feld_message_lesbar(admin): + """Der Kurzbefehl zeigt „message" an. Steht dort bei einem Fehler nichts, + sieht man auf dem Telefon nur eine leere Mitteilung – und sucht den Fehler + dann auf dem Server statt im Kurzbefehl.""" + client = _fremd() + konto = _konto(admin, name="Meldungskonto") + marke = _marke(admin, konto["id"]) + kopf = {"X-Certain-Token": marke["token"]} + import io + + # Datei da, aber account_id vergessen: Pflichtfeld fehlt. + r = client.post("/api/quick/statement", + files={"file": ("a.csv", io.BytesIO(b"Datum;Betrag\n"), "text/csv")}, + headers=kopf) + assert r.status_code == 422 + assert "account_id" in r.json()["message"] + assert "Formular" in r.json()["message"] + + # Falsche Marke. + r = client.post("/api/quick/booking", json={"amount": "1"}, + headers={"X-Certain-Token": "falsch"}) + assert r.status_code == 401 + assert "message" in r.json() + + # Leere Datei. + r = client.post("/api/quick/statement", + files={"file": ("leer.csv", io.BytesIO(b""), "text/csv")}, + data={"account_id": str(konto["id"])}, headers=kopf) + assert r.status_code == 400 + assert "leer" in r.json()["message"].lower()