import datetime as dt import uuid from app.models import Role from tests.factories import dt_at, make_artist, make_position, make_shift, make_volunteer def _url(edition, suffix=""): return f"/editions/{edition.id}/solver{suffix}" def test_suggests_a_free_volunteer_for_an_understaffed_shift(client, edition, session): position = make_position(session, edition) shift = make_shift( session, edition, position, start=dt_at(10, 14), end=dt_at(10, 18), min_staff=1, ideal_staff=1, ) volunteer = make_volunteer(session, edition, email="v@ex.fr") resp = client.post(_url(edition, "/suggestions")) assert resp.status_code == 200 body = resp.json() assert body["shifts_considered"] == 1 assert body["volunteers_considered"] == 1 suggestion = next(s for s in body["suggestions"] if s["shift_id"] == str(shift.id)) assert suggestion["volunteer_ids"] == [str(volunteer.id)] def test_fully_staffed_shift_has_no_suggestion(client, edition, session): from app.models import Assignment position = make_position(session, edition) shift = make_shift( session, edition, position, start=dt_at(10, 14), end=dt_at(10, 18), min_staff=1, ideal_staff=1, ) volunteer = make_volunteer(session, edition, email="v@ex.fr") session.add( Assignment(edition_id=edition.id, volunteer_id=volunteer.id, shift_id=shift.id) ) session.commit() resp = client.post(_url(edition, "/suggestions")) assert resp.status_code == 200 shift_ids = {s["shift_id"] for s in resp.json()["suggestions"]} assert str(shift.id) not in shift_ids def test_avoids_volunteer_whose_wished_artist_would_be_missed(client, edition, session): position = make_position(session, edition) shift = make_shift( session, edition, position, start=dt_at(10, 14), end=dt_at(10, 18), min_staff=1, ideal_staff=1, ) artist = make_artist( session, edition, performance_date=dt.date(2026, 7, 10), start_time=dt.time(15, 0), end_time=dt.time(16, 0), ) fan = make_volunteer(session, edition, email="fan@ex.fr") assert client.patch( f"/editions/{edition.id}/volunteers/{fan.id}", json={"artist_ids": [str(artist.id)]}, ).status_code == 200 indiff = make_volunteer(session, edition, email="indiff@ex.fr") resp = client.post(_url(edition, "/suggestions")) suggestion = next( s for s in resp.json()["suggestions"] if s["shift_id"] == str(shift.id) ) assert suggestion["volunteer_ids"] == [str(indiff.id)] def test_chain_penalty_applies_across_different_positions(client, edition, session): from app.models import Assignment, PositionCategory # Deux postes différents, même jour, repos suffisant (8 h pile) entre les # deux créneaux : seul le chaînage (n'importe quel poste) doit # différencier les deux bénévoles pour le second créneau. bar = make_position(session, edition, name="Bar") entrance = make_position(session, edition, name="Entrée", category=PositionCategory.WELCOME) morning_shift = make_shift( session, edition, bar, start=dt_at(10, 8), end=dt_at(10, 12), min_staff=1, ideal_staff=1, ) evening_shift = make_shift( session, edition, entrance, start=dt_at(10, 20), end=dt_at(10, 23), min_staff=1, ideal_staff=1, ) chained = make_volunteer(session, edition, email="chained@ex.fr") session.add( Assignment(edition_id=edition.id, volunteer_id=chained.id, shift_id=morning_shift.id) ) session.commit() fresh = make_volunteer(session, edition, email="fresh@ex.fr") resp = client.post(_url(edition, "/suggestions")) suggestion = next( s for s in resp.json()["suggestions"] if s["shift_id"] == str(evening_shift.id) ) assert suggestion["volunteer_ids"] == [str(fresh.id)] def test_unknown_edition_is_404(client): assert client.post(f"/editions/{uuid.uuid4()}/solver/suggestions").status_code == 404 def test_volunteer_role_forbidden(edition, session, make_account, as_account): account = make_account(Role.VOLUNTEER, email="v@e.fr") api = as_account(account) assert api.post(_url(edition, "/suggestions")).status_code == 403 def test_does_not_persist_any_assignment(client, edition, session): from sqlmodel import select from app.models import Assignment position = make_position(session, edition) make_shift(session, edition, position, start=dt_at(10, 14), end=dt_at(10, 18)) make_volunteer(session, edition, email="v@ex.fr") client.post(_url(edition, "/suggestions")) assert session.exec(select(Assignment)).all() == []