Validate equipment and GPS form input at the API, not just the browser - #571
Open
brickbots wants to merge 1 commit into
Open
Validate equipment and GPS form input at the API, not just the browser#571brickbots wants to merge 1 commit into
brickbots wants to merge 1 commit into
Conversation
…the browser
The equipment handlers parsed with bare float()/int() inside
`except Exception: logger.error(...)` and then rendered the success
template regardless, so an unreadable value reported "Eyepiece added"
and saved nothing:
POST /equipment/add_eyepiece/-1 focal_length_mm=7,5
-> HTTP 200 + "Eyepiece added, restart your PiFinder to use"
-> eyepiece count unchanged
/gps/update had no try/except at all, so the same input reached the user
as an unhandled 500. A decimal comma is the easiest trigger — PiFinder
ships de/es/fr/zh — but any unparseable value did it, including the
blank instrument name from #569.
Field rules now live in one table in equipment.py: the edit forms render
them into their client-side check and the API re-checks them before
anything reaches config. Measurements are floats throughout, so a 279.4mm
aperture is enterable and a config carrying one is loadable (#291); whole
millimetres still display as "1000", not "1000.0", via format_measurement.
- equipment: measurements are validated floats; limits + name length live
beside the dataclasses (ADR 0027)
- server: parse_measurement/parse_name/*_from_form; failures re-render the
edit form with the message and the values that were typed; route indexes
are range-checked instead of raising IndexError as a 500; the DeepskyLog
import skips records it can't read rather than writing them through
- gps: parse everything before locking anything, so a bad clock entry
can't half-apply a position; gps.html gets #536's normalizeDecimal,
which never reached it, and locations.html's two decimal->DMS bypasses
are fixed
- config: an undecodable equipment section logs and falls back to the
defaults instead of aborting main() before the UI comes up (#291)
Covered at the request level (the Selenium suite runs en-US and
structurally can't catch a decimal-comma bug): 68 new tests, and the
equipment + locations web suites still pass against a live PiFinder.
Fixes #569
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #569. Also fixes the GPS-form 500 from the same 2.6.1 Gate 6 pass, and the config-load crash from #291.
The two defects
The equipment forms failed silently. Both handlers parsed with bare
float()/int()insideexcept Exception: logger.error(...)and then fell through to the success template:/gps/updatehad no try/except at all, so the same input reached the user as an unhandled 500. #536 fixed this class for/locations; itsnormalizeDecimalhelper appears zero times inviews/gps.html.A decimal comma is the easiest trigger — PiFinder ships de/es/fr/zh — but anything unreadable did it, including the blank instrument name in #569's description.
The rules
Field rules live in one table in
equipment.py. The edit forms render them into their client-side check and the API re-checks them before anything reaches config; the ranges are shared so the two can't drift. Tabulated indocs/ax/equipment/CONTEXT.md, with the reasoning in ADR 0027.makenameaperture_mmfocal_length_mmobstruction_percmount_typealt/az|equatorialmakenamefocal_length_mmafovfield_stopTwo rules that aren't ranges: a blank field is not a zero (only
obstruction_percandfield_stophave a documented zero meaning), and a rejected entry never reports success — the form comes back with the message and the values that were typed.focal_length_mm: int on Telescope, float on EyepieceSettled as part of the issue: all measurements are floats. Optics are fractional — an 11" SCT is 279.4mm, a reducer turns 2032mm into 1280.2mm. That also closes #291, where an aperture of
"279.5"written into config made the PiFinder unbootable:Equipment.from_dictraisedinvalid literal for int()insidemain()before the UI came up. Whole millimetres still display as1000, not1000.0, through aformat_measurementJinja filter, and no migration is needed — the stored values are already numbers the float fields read.What changed
equipment.py— measurements are floats;TELESCOPE_LIMITS/EYEPIECE_LIMITS/MOUNT_TYPES/NAME_MAX_LENGTHandformat_measurement()beside the dataclasses.server.py—parse_measurement/parse_name/eyepiece_from_form/telescope_from_form; bothadd_*handlers re-render the edit form onValueError; every<id>route range-checks its index instead of raisingIndexErroras a 500; the DeepskyLog import re-checks the same limits and skips (and counts) records it can't read.gps_update()— parses everything before locking anything, so a bad clock entry can't half-apply a position; re-rendersgps.htmlwith the error.equipment_validation.html), numeric inputs switched totype=text inputmode=decimalper fix(web): accept comma or period decimal separator when saving locations #536,normalizeDecimalported intogps.html, and the two decimal→DMS bypasses atlocations.html:282,288fixed.config.py— an undecodable equipment section logs and falls back to the shipped defaults rather than aborting the boot.Two incidental fixes in files this touches: the eyepiece dedup searched
telescopesfor anEyepiece, and the GPS altitude field's input listener rewrote the longitude DMS fields.Testing
68 new tests at the request level — a Flask test client POSTing
7,5runs in CI, unlike the web suite, which runs en-US and structurally cannot catch this.pytest -m "smoke or unit"→ 1227 passed (baseline 1159 at4a83d25b)test_web_equipment.py+test_web_locations.pyagainst a live PiFinder → 14 passed, 1 skipped7.5/8.0, garbage re-renders the form with "must be a number",/gps/updatewith34,22returns 302 (was 500), and the out-of-range and bad-index cases return 200 with a messagei18n
18 new msgids added to de/es/fr/zh, tagged
AI-TRANSLATED (claude): needs human review,.morecompiled. The.podiffs are additive only (nopybabel updatechurn —messages.potis gitignored here, so the.podiff is the only record).Not included
/gpsis kept and fixed rather than retired — it's the only place with the date/time control. G6.5 (theVolumemenu-index drift) and the web suite's missing starting-language guard are deliberately left out; both are test-only and written up in the 2.6.1 test plan.🤖 Generated with Claude Code