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