"""Jours fériés marqués à la main : stockage, décompte (dû 0, fenêtre d'astreinte 0h00 → 0h00) et ratios d'astreinte éditables depuis la page Réglages.""" import sys # DATA_DIR temporaire est positionné par conftest.py avant l'import de models. import models # noqa: F401 sys.path.insert(0, ".") import main from fastapi.testclient import TestClient from calcul import compute_week, heures_dues client = TestClient(main.app) HEURES_JOUR_MIN = 468 # 39h / 5j 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 _pointage(date, astreinte_events=None): return {"date": date, "matin_entree": None, "matin_sortie": None, "aprem_entree": None, "aprem_sortie": None, "astreinte_events": astreinte_events or []} # ── Stockage ────────────────────────────────────────────────────────────────── def test_toggle_ferie_persiste(): models.toggle_ferie("2026-07-15", "u-ferie") assert models.load_feries("u-ferie") == ["2026-07-15"] models.toggle_ferie("2026-07-15", "u-ferie") assert models.load_feries("u-ferie") == [] # ── Décompte ────────────────────────────────────────────────────────────────── def test_heures_dues_ferie_zero(): """Un mercredi marqué férié ne doit aucune heure, congés ou pas.""" assert heures_dues("2026-07-15", [], HEURES_JOUR_MIN, ferie=True) == 0 assert heures_dues("2026-07-15", [], HEURES_JOUR_MIN, ferie=True, retour_min=10 * 60) == 0 def test_compute_week_ferie_du_zero_jour_complet(freeze_today): """Mercredi marqué F : dû 0, jour complet d'office, badge férié, heures pointées toujours comptées (delta positif).""" pointages = [ _pointage("2026-07-13", None), _pointage("2026-07-14", None), _pointage("2026-07-15", None), _pointage("2026-07-16", None), _pointage("2026-07-17", None), ] pointages[2].update(matin_entree="09:00", matin_sortie="12:24", aprem_entree="13:30", aprem_sortie="17:54") result = compute_week(pointages, [], HEURES_JOUR_MIN, {}, [], None, {"2026-07-15"}) mercredi = result["jours"][2] assert mercredi["ferie"] is True assert mercredi["du_min"] == 0 assert mercredi["is_complete"] is True assert mercredi["travaille_min"] == HEURES_JOUR_MIN # pointé : compte en delta assert result["jours"][1]["ferie"] is False assert result["jours"][3]["ferie"] is False def test_compute_week_ferie_etend_fenetre_astreinte(freeze_today): """AS mardi + F mercredi : l'intervention de mercredi 20:00→21:00 (après la borne 18:00 des jours ouvrés) compte — Nuit ×1,5 = 1h30 pondérées.""" pointages = [ _pointage("2026-07-13", None), _pointage("2026-07-14", None), _pointage("2026-07-15", [{"debut": "20:00", "fin": "21:00"}]), _pointage("2026-07-16", None), _pointage("2026-07-17", None), ] result = compute_week(pointages, [], HEURES_JOUR_MIN, {}, ["2026-07-14"], None, {"2026-07-15"}) assert result["jours"][2]["astreinte_min"] == 60 assert result["astreinte"]["total_pondere_min"] == 90 # ── Routes ──────────────────────────────────────────────────────────────────── def test_route_toggle_ferie(): cookies = _login("u-ferie-route") r = client.post("/ferie/2026-07-15/toggle", cookies=cookies, follow_redirects=False) assert r.status_code == 303 assert models.load_feries("u-ferie-route") == ["2026-07-15"] r = client.get("/semaine/2026/29", cookies=cookies, follow_redirects=False) assert r.status_code == 200 assert "ferie-on" in r.text # bouton F actif assert "ferie-badge" in r.text # badge « férié » sur la ligne du jour client.post("/ferie/2026-07-15/toggle", cookies=cookies, follow_redirects=False) assert models.load_feries("u-ferie-route") == [] def test_settings_astreinte_facteurs_editables(): """POST de règles valides : enregistrées et rechargées ; règle au nom vide supprimée ; ratio invalide rejette tout sans rien changer.""" cookies = _login("u-facteurs") avant = models.load_facteurs_astreinte() data = { "f_nom_0": "Nuit", "f_debut_0": "20:00", "f_fin_0": "08:00", "f_facteur_0": "1.5", "f_jours_0": ["semaine", "dim"], "f_nom_1": "Garde", "f_debut_1": "18:00", "f_fin_1": "09:00", "f_facteur_1": "2,5", "f_jours_1": ["sam"], } r = client.post("/settings/astreinte-facteurs", cookies=cookies, data=data, follow_redirects=False) assert r.status_code == 303 assert "facteurs_saved=1" in r.headers["location"] facteurs = models.load_facteurs_astreinte() assert [f["nom"] for f in facteurs] == ["Nuit", "Garde"] assert facteurs[1]["facteur"] == 2.5 # virgule acceptée assert facteurs[1]["jours"] == ["sam"] # suppression par nom vidé : la règle disparaît r = client.post("/settings/astreinte-facteurs", cookies=cookies, data={ "f_nom_0": "Nuit", "f_debut_0": "20:00", "f_fin_0": "08:00", "f_facteur_0": "1.5", "f_jours_0": ["semaine"], "f_nom_1": "", "f_debut_1": "18:00", "f_fin_1": "09:00", "f_facteur_1": "2", }, follow_redirects=False) assert r.status_code == 303 assert [f["nom"] for f in models.load_facteurs_astreinte()] == ["Nuit"] # ratio invalide : rejet, rien n'est modifié r = client.post("/settings/astreinte-facteurs", cookies=cookies, data={ "f_nom_0": "Bug", "f_debut_0": "20:00", "f_fin_0": "08:00", "f_facteur_0": "0", "f_jours_0": ["semaine"], }, follow_redirects=False) assert r.status_code == 303 assert "facteurs_error=1" in r.headers["location"] assert [f["nom"] for f in models.load_facteurs_astreinte()] == ["Nuit"] # remise aux règles par défaut pour les autres tests models.save_facteurs_astreinte(avant)