Le bouton × retirait la ligne de l'écran sans déclencher la sauvegarde :
au rechargement, l'intervention supprimée réapparaissait. La suppression
enregistre maintenant le jour dès le clic (asDelEvent → asSaveDay).
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
129 lines
5.7 KiB
Python
129 lines
5.7 KiB
Python
"""Auto-enregistrement des interventions d'astreinte : une ligne en cours de
|
||
saisie (début sans fin, ou l'inverse) survit au cycle save/re-rendu — le calcul
|
||
l'ignore tant que la paire est incomplète, mais la valeur saisie reste stockée
|
||
et affichée, et le bloc dépliant ne se referme pas (restauration après le
|
||
settle HTMX, cf. semaine.html)."""
|
||
import sys
|
||
|
||
import models # noqa: F401 (DATA_DIR temporaire posé par conftest)
|
||
|
||
sys.path.insert(0, ".")
|
||
import main
|
||
from fastapi.testclient import TestClient
|
||
|
||
client = TestClient(main.app)
|
||
|
||
|
||
def _login(uid: str) -> dict:
|
||
import bcrypt
|
||
models.save_auth(uid, email=f"{uid}@x.fr",
|
||
password_hash=bcrypt.hashpw(b"x" * 12, bcrypt.gensalt()).decode())
|
||
r = client.post("/login/password",
|
||
data={"email": f"{uid}@x.fr", "password": "x" * 12},
|
||
follow_redirects=False)
|
||
assert r.status_code == 302, r.text
|
||
return {"user_id": r.cookies.get("user_id")}
|
||
|
||
|
||
def _events(uid: str, date: str):
|
||
from datetime import date as d_
|
||
d = d_.fromisoformat(date)
|
||
iso = d.isocalendar()
|
||
row = next((p for p in models.load_week_pointages(iso.year, iso.week, uid)
|
||
if p["date"] == date), {})
|
||
return row.get("astreinte_events")
|
||
|
||
|
||
def test_ligne_partielle_conservee_entre_deux_saves():
|
||
uid = "u-as-partiel"
|
||
cookies = _login(uid)
|
||
models.toggle_astreinte("2026-07-14", uid) # mardi AS
|
||
|
||
# 1er save auto : début saisi, fin pas encore — la ligne doit survivre
|
||
r = client.post("/pointage/2026-07-14", cookies=cookies,
|
||
data={"astreinte_debut": ["22:00"], "astreinte_fin": [""],
|
||
"astreinte_desc": [""]}, follow_redirects=False)
|
||
assert r.status_code == 200, r.text
|
||
assert _events(uid, "2026-07-14") == [{"debut": "22:00", "fin": None, "desc": None}]
|
||
# La valeur saisie est réaffichée dans le bloc re-rendu
|
||
assert 'value="22:00"' in r.text
|
||
|
||
# 2e save : la fin complète la paire — l'événement devient calculable
|
||
r = client.post("/pointage/2026-07-14", cookies=cookies,
|
||
data={"astreinte_debut": ["22:00"], "astreinte_fin": ["23:30"],
|
||
"astreinte_desc": [""]}, follow_redirects=False)
|
||
assert r.status_code == 200, r.text
|
||
assert _events(uid, "2026-07-14") == [{"debut": "22:00", "fin": "23:30", "desc": None}]
|
||
assert "astreinte : 1h30" in r.text # décompte arrondi au quart d'heure
|
||
|
||
|
||
def test_event_partiel_ignore_par_les_calculs():
|
||
"""Un début sans fin ne déclenche ni décompte ni repos de 11h."""
|
||
uid = "u-as-partiel-calc"
|
||
cookies = _login(uid)
|
||
models.toggle_astreinte("2026-07-14", uid)
|
||
|
||
client.post("/pointage/2026-07-14", cookies=cookies,
|
||
data={"astreinte_debut": ["23:00"], "astreinte_fin": [""],
|
||
"astreinte_desc": [""]}, follow_redirects=False)
|
||
r = client.get("/semaine/2026/29", cookies=cookies, follow_redirects=False)
|
||
assert "astreinte :" not in r.text # rien de décompté
|
||
assert "retour ≥" not in r.text # pas de retour décalé
|
||
|
||
|
||
def test_ligne_entierement_vide_non_conservee():
|
||
uid = "u-as-vide"
|
||
cookies = _login(uid)
|
||
models.toggle_astreinte("2026-07-14", uid)
|
||
|
||
client.post("/pointage/2026-07-14", cookies=cookies,
|
||
data={"astreinte_debut": ["", "22:00"], "astreinte_fin": ["", "23:00"],
|
||
"astreinte_desc": ["", ""]}, follow_redirects=False)
|
||
# La ligne vide n'est pas stockée, la ligne complète l'est
|
||
assert _events(uid, "2026-07-14") == [{"debut": "22:00", "fin": "23:00", "desc": None}]
|
||
|
||
|
||
def test_suppression_dune_ligne_persiste():
|
||
"""Le bouton × retire la ligne du DOM puis enregistre le jour : le POST ne
|
||
contient plus que les lignes restantes, la supprimée doit disparaître."""
|
||
uid = "u-as-del"
|
||
cookies = _login(uid)
|
||
models.toggle_astreinte("2026-07-14", uid)
|
||
|
||
client.post("/pointage/2026-07-14", cookies=cookies,
|
||
data={"astreinte_debut": ["22:00", "23:00"], "astreinte_fin": ["22:30", "23:30"],
|
||
"astreinte_desc": ["", ""]}, follow_redirects=False)
|
||
assert len(_events(uid, "2026-07-14") or []) == 2
|
||
|
||
# La 1re ligne est supprimée : seule la 2e reste dans le POST
|
||
r = client.post("/pointage/2026-07-14", cookies=cookies,
|
||
data={"astreinte_debut": ["23:00"], "astreinte_fin": ["23:30"],
|
||
"astreinte_desc": [""]}, follow_redirects=False)
|
||
assert r.status_code == 200, r.text
|
||
assert _events(uid, "2026-07-14") == [{"debut": "23:00", "fin": "23:30", "desc": None}]
|
||
|
||
# Suppression de la dernière ligne : plus aucun champ astreinte dans le POST
|
||
r = client.post("/pointage/2026-07-14", cookies=cookies,
|
||
data={"astreinte_debut": [], "astreinte_fin": [],
|
||
"astreinte_desc": []}, follow_redirects=False)
|
||
assert r.status_code == 200, r.text
|
||
assert _events(uid, "2026-07-14") == []
|
||
|
||
|
||
def test_bouton_suppression_câble_lenregistrement():
|
||
"""Le × doit déclencher la sauvegarde du jour (asDelEvent), sans quoi la
|
||
ligne ne disparaît que visuellement et revient au rechargement."""
|
||
uid = "u-as-del-html"
|
||
cookies = _login(uid)
|
||
models.toggle_astreinte("2026-07-14", uid)
|
||
client.post("/pointage/2026-07-14", cookies=cookies,
|
||
data={"astreinte_debut": ["22:00"], "astreinte_fin": ["22:30"],
|
||
"astreinte_desc": [""]}, follow_redirects=False)
|
||
|
||
r = client.get("/semaine/2026/29", cookies=cookies, follow_redirects=False)
|
||
assert r.status_code == 200, r.text
|
||
# Le bouton rendu appelle asDelEvent, défini (avec asSaveDay) dans la page
|
||
assert 'onclick="asDelEvent(this)"' in r.text
|
||
assert "window.asDelEvent" in r.text
|
||
assert "window.asSaveDay" in r.text
|