import datetime as dt from tests.factories import ( declare_available_all_day, dt_at, make_position, make_shift, make_volunteer, ) def _url(edition, suffix=""): return f"/editions/{edition.id}/assignments{suffix}" def _setup(session, edition, *, available=True): 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") if available: declare_available_all_day( session, edition, volunteer, dt.date(2026, 7, 10) ) 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_create_assignment_returns_warnings(client, edition, session, day_parts): # Bénévole non déclaré disponible sur le créneau. volunteer, shift = _setup(session, edition, available=False) resp = client.post( _url(edition), json={"volunteer_id": str(volunteer.id), "shift_id": str(shift.id)}, ) assert resp.status_code == 201 codes = {w["code"] for w in resp.json()["warnings"]} assert "OUTSIDE_AVAILABILITY" in codes 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, available=False) 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 any(v["code"] == "OUTSIDE_AVAILABILITY" for v in body["violations"]) assert client.get(_url(edition)).json() == [] def test_patch_locked_keeps_assignment_and_returns_warnings( client, edition, session, day_parts ): volunteer, shift = _setup(session, edition, available=False) 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 any( w["code"] == "OUTSIDE_AVAILABILITY" for w in 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