Disponibilités déclarées supprimées (table `availability`, migration 0022, API, éditeur de la fiche bénévole, badge de couverture du panneau d'affectation, colonnes des statistiques) ainsi que les règles OUTSIDE_AVAILABILITY, DAILY_HOURS_EXCEEDED et INSUFFICIENT_REST, les malus de disponibilité et de repos du solveur et le filtre « Surchargés » de la vue bénévoles. Seul le chevauchement reste contrôlé. La case « je suis disponible » du formulaire public est conservée. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 lines
4.9 KiB
Python
146 lines
4.9 KiB
Python
import datetime as dt
|
||
|
||
from tests.factories import (
|
||
dt_at,
|
||
make_position,
|
||
make_shift,
|
||
make_volunteer,
|
||
)
|
||
|
||
|
||
def _url(edition, suffix=""):
|
||
return f"/editions/{edition.id}/assignments{suffix}"
|
||
|
||
|
||
def _setup(session, edition):
|
||
position = make_position(session, edition)
|
||
shift = make_shift(
|
||
session, edition, position, start=dt_at(10, 14), end=dt_at(10, 18)
|
||
)
|
||
volunteer = make_volunteer(session, edition, email="v@ex.fr")
|
||
return volunteer, shift
|
||
|
||
|
||
def test_create_assignment_clean(client, edition, session, day_parts):
|
||
volunteer, shift = _setup(session, edition)
|
||
resp = client.post(
|
||
_url(edition),
|
||
json={"volunteer_id": str(volunteer.id), "shift_id": str(shift.id)},
|
||
)
|
||
assert resp.status_code == 201
|
||
body = resp.json()
|
||
assert body["warnings"] == []
|
||
assert body["assignment"]["source"] == "MANUAL"
|
||
assert body["assignment"]["locked"] is False
|
||
|
||
|
||
def test_long_day_without_rest_raises_no_warning(client, edition, session, day_parts):
|
||
# Plus de limite d'heures par jour ni de repos minimal : 14h–18h puis
|
||
# 18h–04h (14 h d'affilée) passe sans avertissement.
|
||
volunteer, shift = _setup(session, edition)
|
||
position = make_position(session, edition, name="Bar")
|
||
following = make_shift(
|
||
session, edition, position, start=dt_at(10, 18), end=dt_at(11, 4)
|
||
)
|
||
for s in (shift, following):
|
||
resp = client.post(
|
||
_url(edition),
|
||
json={"volunteer_id": str(volunteer.id), "shift_id": str(s.id)},
|
||
)
|
||
assert resp.status_code == 201
|
||
assert resp.json()["warnings"] == []
|
||
|
||
|
||
def test_create_assignment_blocked_by_overlap(client, edition, session, day_parts):
|
||
volunteer, shift = _setup(session, edition)
|
||
assert client.post(
|
||
_url(edition),
|
||
json={"volunteer_id": str(volunteer.id), "shift_id": str(shift.id)},
|
||
).status_code == 201
|
||
|
||
position = make_position(session, edition, name="Parking")
|
||
overlapping = make_shift(
|
||
session, edition, position, start=dt_at(10, 16), end=dt_at(10, 20)
|
||
)
|
||
resp = client.post(
|
||
_url(edition),
|
||
json={
|
||
"volunteer_id": str(volunteer.id),
|
||
"shift_id": str(overlapping.id),
|
||
},
|
||
)
|
||
assert resp.status_code == 422
|
||
detail = resp.json()["detail"]
|
||
assert detail["code"] == "error.validation_failed"
|
||
assert any(v["code"] == "SHIFT_OVERLAP" for v in detail["violations"])
|
||
# Rien n'a été enregistré pour ce créneau.
|
||
listed = client.get(
|
||
_url(edition), params={"shift_id": str(overlapping.id)}
|
||
).json()
|
||
assert listed == []
|
||
|
||
|
||
def test_create_duplicate_assignment_conflicts(client, edition, session, day_parts):
|
||
volunteer, shift = _setup(session, edition)
|
||
body = {"volunteer_id": str(volunteer.id), "shift_id": str(shift.id)}
|
||
assert client.post(_url(edition), json=body).status_code == 201
|
||
resp = client.post(_url(edition), json=body)
|
||
assert resp.status_code == 409
|
||
assert resp.json()["detail"]["code"] == "assignment.duplicate"
|
||
|
||
|
||
def test_assignment_wrong_edition_is_422(client, edition, session, day_parts):
|
||
from app.models import Edition
|
||
|
||
other = Edition(
|
||
year=2027, start_date=dt.date(2027, 7, 1), end_date=dt.date(2027, 7, 4)
|
||
)
|
||
session.add(other)
|
||
session.commit()
|
||
session.refresh(other)
|
||
stranger = make_volunteer(session, other, email="s@ex.fr")
|
||
_, shift = _setup(session, edition)
|
||
|
||
resp = client.post(
|
||
_url(edition),
|
||
json={"volunteer_id": str(stranger.id), "shift_id": str(shift.id)},
|
||
)
|
||
assert resp.status_code == 422
|
||
assert resp.json()["detail"]["code"] == "assignment.wrong_edition"
|
||
|
||
|
||
def test_validate_endpoint_does_not_persist(client, edition, session, day_parts):
|
||
volunteer, shift = _setup(session, edition)
|
||
resp = client.post(
|
||
_url(edition, "/validate"),
|
||
json={"volunteer_id": str(volunteer.id), "shift_id": str(shift.id)},
|
||
)
|
||
assert resp.status_code == 200
|
||
body = resp.json()
|
||
assert body["blocking"] is False
|
||
assert body["violations"] == []
|
||
assert client.get(_url(edition)).json() == []
|
||
|
||
|
||
def test_patch_locked_keeps_assignment(client, edition, session, day_parts):
|
||
volunteer, shift = _setup(session, edition)
|
||
aid = client.post(
|
||
_url(edition),
|
||
json={"volunteer_id": str(volunteer.id), "shift_id": str(shift.id)},
|
||
).json()["assignment"]["id"]
|
||
|
||
resp = client.patch(_url(edition, f"/{aid}"), json={"locked": True})
|
||
assert resp.status_code == 200
|
||
assert resp.json()["assignment"]["locked"] is True
|
||
assert resp.json()["warnings"] == []
|
||
|
||
|
||
def test_delete_assignment(client, edition, session, day_parts):
|
||
volunteer, shift = _setup(session, edition)
|
||
aid = client.post(
|
||
_url(edition),
|
||
json={"volunteer_id": str(volunteer.id), "shift_id": str(shift.id)},
|
||
).json()["assignment"]["id"]
|
||
assert client.delete(_url(edition, f"/{aid}")).status_code == 204
|
||
assert client.get(_url(edition, f"/{aid}")).status_code == 404
|