Files
team-tryouts/app/api.py
GGThedandClaude Opus 5 ad3dea6a15 fix(web): une erreur sur un point JSON ne renvoie plus une page HTML
STD-09, trouve en recroisant l'audit anterieur -- celui mene sur le miroir
GitHub, jamais repasse depuis qu'on a decouvert que ce n'etait pas la bonne
source.

Sept gestionnaires d'erreur portaient chacun leur copie d'une liste de
prefixes d'URL decidant "JSON ou page HTML". Les copies avaient derive --
trois testaient /users/coach-availability, quatre non -- et toutes
manquaient les memes points. Un fetch() qui recoit une page d'erreur HTML
leve en la parsant : sur le calendrier, les listes de selections et
d'equipes restaient vides, sans message dans la page et sans rien dans le
journal.

Deux choses apprises en ecrivant le test, aucune n'etait dans le constat.

L'approche par prefixe ne pouvait pas etre reparee. Trois des seize vues
JSON sont a des chemins qu'aucun prefixe ne distingue des pages HTML
voisines -- /matches/<id>/toggle-presence/<id> et ses deux cousins, que les
gabarits appellent justement en fetch(). Les vues se declarent donc
elles-memes (@json_endpoint, app/api.py), et un test parcourt la carte des
URL pour verifier qu'aucune vue appelant jsonify n'a ete oubliee.

Et surtout : @login_required n'atteint jamais le gestionnaire 401.
Flask-Login intercepte avant et redirige. Les seize points JSON repondaient
donc a une session expiree par une 302 vers un formulaire HTML, quoi que
dise la liste de prefixes. Reecrire la liste seule aurait eu l'air d'un
correctif sans rien changer.

Au passage, le message flash de ce gestionnaire etait la seule chaine de
l'application qui n'avait jamais ete traduite.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-11 19:36:25 -04:00

48 lines
1.8 KiB
Python

"""Marking the views whose answers — including their failures — are JSON.
STD-09. Deciding "JSON or HTML page" from the URL path could not work here,
and the audit's own recommendation ("gestion d'erreurs API par préfixe d'URL
codé en dur", fix the prefixes) would not have fixed it either. Three of the
sixteen JSON views sit at paths no prefix can single out:
/matches/<int:match_id>/toggle-presence/<int:participant_id>
/team-matches/<int:match_id>/toggle-presence/<int:participant_id>
/teams/<int:team_id>/toggle_status/<int:player_id>
They are interleaved with the HTML routes of the same blueprints, and the
templates fetch them. Any prefix wide enough to catch them catches every
page of the section with them.
So the view says so itself. `wants_json_response()` in app.py reads the mark
off the registered view function, and `tests/test_api_error_format.py` walks
the URL map to prove that every view calling `jsonify` carries it — the
mechanism that was missing before was not a better list, it was anything at
all that checked the list.
Usage — directly under the route decorator, above `login_required`, so the
mark lands on the object the route registers::
@matches_bp.route('/api/events')
@json_endpoint
@login_required
def api_events():
...
"""
def json_endpoint(view):
"""Mark a view as answering in JSON, errors included.
Args:
view: The view function, already wrapped by any decorator below this
one (`login_required` in every current case).
Returns:
The same object, with the mark set. Nothing is wrapped: an extra
wrapper here would be one more thing between Flask and the view for
no gain, and `functools.wraps` copying `__dict__` is exactly the
detail that would make this fragile.
"""
view.returns_json = True
return view