Files
pointeuse-optimisator/app/tests/test_ferie.py
Jacquin Antoine e9983a28c7 Refonte de l'interface : sauvegarde automatique, menu d'état unique et panneau aujourd'hui
- Chaque heure validée enregistre le jour (plus de bouton ✓), avec un témoin
  « enregistré » et conservation du focus entre les champs
- Les boutons MA/AM/J/F/AS deviennent un bouton d'état unique à pastilles
  ouvrant un menu déroulant (congés, férié, astreinte)
- Panneau Aujourd'hui : sortie cible en grand et progression travaillé/dû
  de la semaine, rafraîchis à chaque saisie
- Polish général : coins arrondis, ombres douces, hiérarchie des soldes,
  états de focus visibles

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-09 20:34:27 +02:00

146 lines
6.5 KiB
Python
Raw 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.

"""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 'cg-chip st-ferie">F<' in r.text # pastille F active dans le bouton d'état
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)