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