Merge remote-tracking branch 'forgejo/qa/date-422-live' into qa/frontend-qa-batch
This commit is contained in:
commit
831f10d657
2 changed files with 28 additions and 2 deletions
|
|
@ -71,6 +71,18 @@ def test_api_version_reports_backend_contract(client):
|
|||
assert body["backend_version"] == "2"
|
||||
|
||||
|
||||
def test_malformed_date_is_rejected(client):
|
||||
# A malformed or non-calendar date must be a 422, not a 500: fromisoformat
|
||||
# raises ValueError on these and the handler used to leak it as a crash.
|
||||
for bad in ("notadate", "2026-13-40", "2026-02-30"):
|
||||
for route in ("grade", "day"):
|
||||
r = client.get(f"/thermograph/api/v2/{route}", params={**Q, "date": bad})
|
||||
assert r.status_code == 422, (route, bad, r.status_code)
|
||||
# A valid date still works.
|
||||
assert client.get("/thermograph/api/v2/grade",
|
||||
params={**Q, "date": "2026-07-11"}).status_code == 200
|
||||
|
||||
|
||||
def test_day_detail_and_ladders(client, history):
|
||||
r = client.get("/thermograph/api/v2/day", params=Q)
|
||||
assert r.status_code == 200
|
||||
|
|
|
|||
|
|
@ -79,6 +79,20 @@ def _weather_fetch_error(e) -> HTTPException:
|
|||
return HTTPException(status_code=502, detail=f"weather data fetch failed: {e}")
|
||||
|
||||
|
||||
def _parse_target_date(date: str | None, default: datetime.date) -> datetime.date:
|
||||
"""Parse the ``date`` query param (YYYY-MM-DD) into a date, falling back to
|
||||
``default`` when it's absent. A malformed or non-calendar date (e.g. 2026-13-40)
|
||||
is a client error, not a crash: surface it as a 422 rather than let
|
||||
fromisoformat's ValueError become a 500."""
|
||||
if not date:
|
||||
return default
|
||||
try:
|
||||
return datetime.date.fromisoformat(date[:10])
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=422,
|
||||
detail=f"invalid date: {date!r} (expected YYYY-MM-DD)")
|
||||
|
||||
|
||||
# --- background neighbor warming ---------------------------------------------
|
||||
# /cell?neighbors=1 asks the server to warm the 8 grid cells around the request
|
||||
# (the frontend used to fire 8 staggered prefetch requests, mirroring grid.py's
|
||||
|
|
@ -492,7 +506,7 @@ def api_grade(
|
|||
description="days to grade after the target (observed, or forecast when future; "
|
||||
"the forecast reaches ~7 days out so future days cap there)"),
|
||||
):
|
||||
target = datetime.date.fromisoformat(date[:10]) if date else datetime.date.today()
|
||||
target = _parse_target_date(date, datetime.date.today())
|
||||
cell = grid.snap(lat, lon)
|
||||
|
||||
with audit.RunAudit(
|
||||
|
|
@ -580,7 +594,7 @@ def api_day(
|
|||
) as run:
|
||||
history, cache_meta, _ = _fetch_history(run, cell)
|
||||
last = history["date"].max()
|
||||
target = datetime.date.fromisoformat(date[:10]) if date else last
|
||||
target = _parse_target_date(date, last)
|
||||
run.set(target_date=target.isoformat())
|
||||
|
||||
def build(place):
|
||||
|
|
|
|||
Loading…
Reference in a new issue