Files
pointeuse-optimisator/app/tests/test_autosave_astreinte.py
Jacquin Antoine f50d1ef7fc Astreinte : supprimer une intervention l'enregistre immédiatement
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
2026-09-10 17:05:00 +02:00

129 lines
5.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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