fix: Fehler am Kurzbefehl-Endpunkt sind auf dem Telefon lesbar
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 <noreply@anthropic.com>
This commit is contained in:
+40
-1
@@ -10,7 +10,11 @@ import threading
|
|||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from pathlib import Path
|
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.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
@@ -155,6 +159,41 @@ api.include_router(quick.router)
|
|||||||
api.include_router(inbox.router)
|
api.include_router(inbox.router)
|
||||||
api.include_router(mail.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)
|
app.include_router(api)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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")},
|
files={"file": ("gross.csv", io.BytesIO(b"x" * (6 * 1024 * 1024)), "text/csv")},
|
||||||
data={"account_id": str(konto["id"])}, headers=kopf)
|
data={"account_id": str(konto["id"])}, headers=kopf)
|
||||||
assert riesig.status_code == 413
|
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user