Files
benevoles/backend/tests/test_shift_generation.py
T
AdamThomasandClaude Sonnet 5 bc165757ca Badges : corrections de lint (E501) et couverture du smoke test
Lignes trop longues détectées par ruff (badges.py, tests) ; ajout de
/badges au smoke test frontend (rendu SSR de chaque écran protégé).

Vérifié dans ce commit : pytest (248 passés), ruff check, alembic
check, tsc --noEmit, eslint, vite build, smoke — tous verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 20:37:24 +02:00

102 lines
3.9 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.
"""Génération des créneaux d'un poste à partir des créneaux types."""
import datetime as dt
def _positions_url(edition):
return f"/editions/{edition.id}/positions"
def _create_position(client, edition, name="Bar"):
return client.post(
_positions_url(edition), json={"name": name, "category": "BAR"}
).json()
def test_creating_a_position_generates_one_shift_per_day_and_type(client, edition):
position = _create_position(client, edition)
shifts = client.get(
f"/editions/{edition.id}/shifts", params={"position_id": position["id"]}
).json()
# 4 jours (2026-07-09..12) x 3 créneaux types.
assert len(shifts) == 12
assert all(s["min_staff"] == 0 and s["ideal_staff"] == 0 for s in shifts)
assert all(s["day_part_id"] is not None for s in shifts)
def test_generate_is_idempotent(client, edition):
position = _create_position(client, edition)
resp = client.post(
f"{_positions_url(edition)}/{position['id']}/generate-shifts"
)
assert resp.status_code == 200
assert resp.json()["created"] == 0
def test_extending_the_edition_then_generating_adds_the_new_days(client, edition):
position = _create_position(client, edition)
client.patch(f"/editions/{edition.id}", json={"end_date": "2026-07-14"})
resp = client.post(
f"{_positions_url(edition)}/{position['id']}/generate-shifts"
)
# 2 jours de plus x 3 créneaux types.
assert resp.json()["created"] == 6
shifts = client.get(
f"/editions/{edition.id}/shifts", params={"position_id": position["id"]}
).json()
assert len(shifts) == 18
def test_adding_a_day_part_then_generating_adds_it_everywhere(client, edition):
position = _create_position(client, edition)
client.post(
f"/editions/{edition.id}/day-parts",
json={"label": "Nuit", "start_time": "02:00", "end_time": "05:00"},
)
resp = client.post(
f"{_positions_url(edition)}/{position['id']}/generate-shifts"
)
assert resp.json()["created"] == 4 # 1 par jour
def test_day_part_before_the_day_boundary_rolls_to_the_next_calendar_day(client, edition):
"""La frontière du jour est le début du créneau type de plus petit `order`
(ici MORNING, 06 h — voir CLAUDE.md § 3 et `validation.engine._day_boundary`).
Un créneau type qui commence avant cette heure (ex. `Nuit` 02 h–05 h,
suite de `Soirée` 19 h–00 h) est donc la fin de la nuit du jour
*précédent* : il doit être généré sur le jour suivant, pas sur `day`."""
position = _create_position(client, edition)
client.post(
f"/editions/{edition.id}/day-parts",
json={"label": "Nuit", "start_time": "02:00", "end_time": "05:00", "order": 3},
)
client.post(f"{_positions_url(edition)}/{position['id']}/generate-shifts")
shifts = client.get(
f"/editions/{edition.id}/shifts", params={"position_id": position["id"]}
).json()
nuit_shifts = [
s for s in shifts if dt.time.fromisoformat(s["starts_at"][11:19]) == dt.time(2, 0)
]
assert len(nuit_shifts) == 4
nuit_dates = sorted(dt.date.fromisoformat(s["starts_at"][:10]) for s in nuit_shifts)
# Édition du 9 au 12/07 : la nuit du 9 (Soirée 19 h) se termine le 10 à
# 05 h, ..., la nuit du 12 se termine le 13 — jamais le 9 lui-même.
assert nuit_dates == [dt.date(2026, 7, d) for d in (10, 11, 12, 13)]
def test_generation_does_not_touch_staffing_already_set(client, edition):
position = _create_position(client, edition)
shift = client.get(
f"/editions/{edition.id}/shifts", params={"position_id": position["id"]}
).json()[0]
client.patch(
f"/editions/{edition.id}/shifts/{shift['id']}",
json={"min_staff": 3, "ideal_staff": 5},
)
client.post(f"{_positions_url(edition)}/{position['id']}/generate-shifts")
again = client.get(f"/editions/{edition.id}/shifts/{shift['id']}").json()
assert again["min_staff"] == 3 and again["ideal_staff"] == 5