L'utilisateur avait raison de contester mon précédent correctif : le
problème n'était pas l'absence de champs. Reproduit précisément : créer un
objet avec un champ de type "Relation vers un autre objet" SANS avoir de
cible valide sélectionnée (ex. le tout premier objet créé dans un jeu — le
sélecteur "Objet lié" est alors vide, faute d'un autre objet à pointer)
plantait toute la requête avec une ValueError ("invalid literal for int()
with base 10: ''") dans create_definition() / add_field_to_definition()
(int(relation_definition_id) sans filet). Comme le crash survient APRÈS
l'INSERT de la ligne _definitions mais AVANT le commit(), rien n'était
jamais persisté (transaction perdue à la fermeture de la connexion) :
l'objet entier disparaissait, pas seulement son champ "Relation" — d'où
"le panneau recharge la page sans créer d'objet" alors que des champs
avaient bien été renseignés.
Correctif (routes, pas la couche db) : un champ "Relation" dont la cible
n'est ni choisie ni un id valide est maintenant simplement IGNORÉ (comme
une ligne sans nom, déjà le cas), dans les deux endroits qui construisent
ce payload :
- routes/objects/parse_field_rows.py (panneau "+ Nouvel objet")
- routes/objects/object_field_add.py (panneau "+ Ajouter un champ" d'un
objet déjà créé — même risque de crash dans add_field_to_definition)
object_field_edit.py/update_field.py avaient déjà la bonne garde
("if relation_definition_id" avant le int()) — rien à y changer.
Deux nouveaux tests, confirmés en échec sur l'ancien code (git stash,
même ValueError reproduite) puis au vert avec le correctif. 133 tests au
vert au total.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
3.6 KiB
Python
72 lines
3.6 KiB
Python
"""Régression : le panneau "+ Nouvel objet" du tableau de bord (game_dashboard.html)
|
|
soumet le formulaire dès qu'on tape un nom et clique "Créer l'objet",
|
|
SANS avoir forcément cliqué "+ Ajouter un champ" au moins une fois avant
|
|
(les champs se posent typiquement APRÈS, depuis le panneau "Modifier un
|
|
objet"). routes/objects/object_new.py exigeait pourtant au moins un champ
|
|
pour créer quoi que ce soit ("if name and fields"), sans le moindre
|
|
message d'erreur en cas d'échec — juste un aller-retour silencieux vers le
|
|
tableau de bord, vécu comme "le panneau recharge la page sans créer
|
|
d'objet"."""
|
|
import db
|
|
|
|
|
|
def test_object_created_with_a_name_but_no_field(client, game):
|
|
resp = client.post(f"/game/{game}/objects/new", data={"object_name": "Compteur"}, follow_redirects=False)
|
|
assert resp.status_code == 302
|
|
assert "/objects/" in resp.headers["Location"]
|
|
|
|
definitions = db.list_definitions(game)
|
|
assert len(definitions) == 1
|
|
assert definitions[0]["name"] == "Compteur"
|
|
assert db.get_definition(game, definitions[0]["id"])["fields"] == []
|
|
|
|
|
|
def test_object_not_created_without_a_name(client, game):
|
|
resp = client.post(f"/game/{game}/objects/new", data={"object_name": " "}, follow_redirects=False)
|
|
assert resp.status_code == 302
|
|
assert db.list_definitions(game) == []
|
|
|
|
|
|
def test_relation_field_without_a_chosen_target_does_not_crash_the_whole_request(client, game):
|
|
"""Régression : "Relation vers un autre objet" est TOUJOURS une option
|
|
du sélecteur de type (db/constants.py), même quand il n'existe encore
|
|
AUCUN autre objet à pointer (ex. le tout premier objet créé dans un
|
|
jeu) — le sélecteur "Objet lié" est alors vide, et
|
|
create_definition() faisait int("") sans filet, plantant TOUTE la
|
|
requête (500) avant même d'atteindre son commit() : l'objet n'était
|
|
donc PAS créé du tout, pas seulement son champ "Relation" — vécu comme
|
|
"le panneau recharge la page sans créer d'objet" alors que des champs
|
|
avaient bien été renseignés. Le champ "Relation" invalide doit être
|
|
ignoré (comme une ligne sans nom), pas faire échouer tout le reste."""
|
|
resp = client.post(f"/game/{game}/objects/new", data={
|
|
"object_name": "Personnage",
|
|
"field_name[]": ["nom", "arme"],
|
|
"field_type[]": ["texte", "relation"],
|
|
"field_relation[]": ["", ""],
|
|
"field_required[]": ["0", "0"],
|
|
"field_min[]": ["", ""],
|
|
"field_max[]": ["", ""],
|
|
}, follow_redirects=False)
|
|
assert resp.status_code == 302
|
|
definitions = db.list_definitions(game)
|
|
assert len(definitions) == 1
|
|
fields = db.get_definition(game, definitions[0]["id"])["fields"]
|
|
assert [f["name"] for f in fields] == ["nom"]
|
|
|
|
|
|
def test_add_relation_field_without_a_chosen_target_is_ignored_not_a_crash(client, game):
|
|
"""Même garde, côté "+ Ajouter un champ" du panneau d'un objet déjà
|
|
créé (object_field_add.py / add_field_to_definition.py)."""
|
|
resp = client.post(f"/game/{game}/objects/new", data={
|
|
"object_name": "Personnage", "field_name[]": ["nom"], "field_type[]": ["texte"],
|
|
"field_relation[]": [""], "field_required[]": ["0"], "field_min[]": [""], "field_max[]": [""],
|
|
}, follow_redirects=False)
|
|
def_id = int(resp.headers["Location"].rstrip("/").split("/")[-1])
|
|
|
|
resp = client.post(f"/game/{game}/objects/{def_id}/fields/add", data={
|
|
"field_name": "arme", "field_type": "relation", "field_relation": "",
|
|
}, follow_redirects=False)
|
|
assert resp.status_code == 302
|
|
fields = db.get_definition(game, def_id)["fields"]
|
|
assert [f["name"] for f in fields] == ["nom"]
|