From 44b02d985af5463c96e45559a12dc8c372d7feae Mon Sep 17 00:00:00 2001 From: emi Date: Sun, 26 Jul 2026 06:17:26 +0000 Subject: [PATCH] fix(frontend): Weekly view must always send an explicit date to /grade --- frontend/tests/unit/test_app_js_grade_date.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 frontend/tests/unit/test_app_js_grade_date.py diff --git a/frontend/tests/unit/test_app_js_grade_date.py b/frontend/tests/unit/test_app_js_grade_date.py new file mode 100644 index 0000000..0687c5d --- /dev/null +++ b/frontend/tests/unit/test_app_js_grade_date.py @@ -0,0 +1,57 @@ +"""Regression guard for the grade-URL "today" date bug: runGrade() in app.js used +to omit `date` from the /api/v2/grade request whenever the selected date equaled +the viewer's local today, so the backend fell back to ITS OWN UTC today +(api_grade's `date` param default in backend/web/app.py) -- a day behind local +midnight for any viewer east of UTC. Reported from Kaunas (UTC+3): asking for +the Weekly tab on 7/26 rendered 7/25. + +There is no JS/DOM test harness in this repo to drive app.js and assert on the +real fetch() URL it builds: static/package.json declares zero dependencies (no +jest/playwright/node test runner), no such runner is wired into scripts/test.sh +or any workflow, and server/**/*_test.go (the Go suite CI actually gates on) +covers the Go SSR layer only, never static/*.js's runtime behavior. So this +can't be a behavioral test. What it CAN do, hermetically, through the same +served-asset route test_pages.py already exercises (`GET {B}/app.js`), is treat +the real shipped source as data and assert the structural property the fix +guarantees: runGrade() builds exactly one grade URL and always includes an +explicit `date=`, rather than branching on whether the selected date is "today" +and omitting it in that case. + +This fails against the pre-fix source and passes against the fixed source -- +a source guard, not a substitute for a real behavioral test. +""" +import re + +B = "/thermograph" # matches THERMOGRAPH_BASE set in tests/conftest.py + + +def _run_grade_body(client) -> str: + r = client.get(f"{B}/app.js") + assert r.status_code == 200 + src = r.text + m = re.search(r"async function runGrade\(\) \{\n(.*?)\n\}\n", src, re.DOTALL) + assert m, "runGrade() not found in app.js -- update this test if it moved/was renamed" + return m.group(1) + + +def test_run_grade_always_sends_explicit_date(client): + """The historical bug: `dateInput.value === todayISO() ? grade?q : grade?q&date=...` + omitted `date` for "today", so the backend's own UTC-today fallback rendered the + previous day for any UTC+ viewer between their local midnight and UTC midnight.""" + body = _run_grade_body(client) + assert not re.search(r"dateInput\.value\s*===\s*todayISO\(\)", body), \ + "runGrade() still branches on today to decide whether to send date=" + assert body.count("grade?") == 1, \ + "runGrade() should build exactly one grade URL, not two divergent branches" + assert re.search(r"date=\$\{[^}]+\}", body), \ + "runGrade() must always build the URL with an explicit date= param" + + +def test_run_grade_never_sends_an_empty_date(client): + """A cleared native date field reports dateInput.value === "" (day.js guards the + same input for the same reason). An empty date= hits the identical server-UTC + fallback this fix exists to avoid, so runGrade() must fall back to the viewer's + own local today rather than ever send the param empty.""" + body = _run_grade_body(client) + assert re.search(r"dateInput\.value\s*\|\|\s*todayISO\(\)", body), \ + "runGrade() should fall back to todayISO() when dateInput.value is empty"