import datetime as dt import pytest from sqlalchemy import text from sqlalchemy.exc import IntegrityError from app.models import ( Assignment, AssignmentSource, Availability, AvailabilityStatus, DayPart, Edition, Position, PositionCategory, Shift, Skill, Volunteer, ) EXPECTED_TABLES = { "edition", "day_part", "skill", "artist", "volunteer", "volunteer_skill", "volunteer_artist", "volunteer_position_wish", "account", "magic_link_token", "position", "shift", "shift_required_skill", "availability", "assignment", } def _edition(session) -> Edition: e = Edition( year=2026, start_date=dt.date(2026, 7, 8), end_date=dt.date(2026, 7, 11), retention_days=365, ) session.add(e) session.commit() session.refresh(e) return e def test_metadata_has_expected_tables(): from sqlmodel import SQLModel assert set(SQLModel.metadata.tables) == EXPECTED_TABLES def test_create_core_graph(session): edition = _edition(session) volunteer = Volunteer( edition_id=edition.id, email="alex@example.org", first_name="Alex", last_name="Martin", ) skill = Skill(edition_id=edition.id, code="SST", label="Secourisme") volunteer.skills.append(skill) position = Position( edition_id=edition.id, name="Bar principal", category=PositionCategory.BAR ) session.add(volunteer) session.add(position) session.commit() session.refresh(volunteer) session.refresh(position) # Lien N-N bénévole <-> compétence. assert [s.code for s in volunteer.skills] == ["SST"] shift = Shift( edition_id=edition.id, position_id=position.id, starts_at=dt.datetime(2026, 7, 9, 18, 0), ends_at=dt.datetime(2026, 7, 9, 23, 0), min_staff=2, ideal_staff=4, ) session.add(shift) session.commit() session.refresh(volunteer) session.refresh(shift) assignment = Assignment( edition_id=edition.id, volunteer_id=volunteer.id, shift_id=shift.id ) session.add(assignment) session.commit() session.refresh(assignment) # Valeurs par défaut appliquées. assert assignment.source == AssignmentSource.MANUAL assert assignment.locked is False # Relations. assert shift.position.id == position.id assert volunteer.assignments[0].id == assignment.id def test_enum_round_trip(session): edition = _edition(session) day_part = DayPart( edition_id=edition.id, code="EVENING", label="Soirée", start_time=dt.time(19, 0), end_time=dt.time(6, 0), order=2, ) volunteer = Volunteer( edition_id=edition.id, first_name="Sam", last_name="Nuit" ) session.add_all([day_part, volunteer]) session.commit() session.refresh(day_part) session.refresh(volunteer) avail = Availability( edition_id=edition.id, volunteer_id=volunteer.id, date=dt.date(2026, 7, 9), day_part_id=day_part.id, status=AvailabilityStatus.IF_NEEDED, ) session.add(avail) session.commit() session.expire_all() reloaded = session.get(Availability, avail.id) assert reloaded.status is AvailabilityStatus.IF_NEEDED def test_volunteer_email_unique_per_edition(session): edition = _edition(session) session.add( Volunteer( edition_id=edition.id, email="dup@example.org", first_name="A", last_name="A", ) ) session.commit() session.add( Volunteer( edition_id=edition.id, email="dup@example.org", first_name="B", last_name="B", ) ) with pytest.raises(IntegrityError): session.commit() def test_multiple_volunteers_without_email_allowed(session): edition = _edition(session) session.add_all( [ Volunteer(edition_id=edition.id, first_name="A", last_name="A"), Volunteer(edition_id=edition.id, first_name="B", last_name="B"), ] ) session.commit() # NULL n'entre pas dans la contrainte d'unicité def test_deleting_volunteer_cascades_to_assignments(session): edition = _edition(session) volunteer = Volunteer( edition_id=edition.id, first_name="Cascade", last_name="Test" ) position = Position( edition_id=edition.id, name="Accueil", category=PositionCategory.WELCOME ) session.add_all([volunteer, position]) session.commit() session.refresh(volunteer) session.refresh(position) shift = Shift( edition_id=edition.id, position_id=position.id, starts_at=dt.datetime(2026, 7, 9, 9, 0), ends_at=dt.datetime(2026, 7, 9, 12, 0), ) session.add(shift) session.commit() session.refresh(shift) assignment = Assignment( edition_id=edition.id, volunteer_id=volunteer.id, shift_id=shift.id ) session.add(assignment) session.commit() assignment_id = assignment.id volunteer_id = volunteer.id # Suppression au niveau base : la cascade ON DELETE doit s'appliquer. session.execute( text("DELETE FROM volunteer WHERE id = :id"), {"id": volunteer_id} ) session.commit() session.expire_all() assert session.get(Assignment, assignment_id) is None # Le créneau, lui, subsiste. assert session.get(Shift, shift.id) is not None