Files
benevoles/backend/tests/test_position_sheets_api.py
AdamThomasandClaude Opus 5.5 cc8c2e61ab
ci-cd / build-backend (push) Successful in 29s
ci-cd / build-frontend (push) Successful in 7s
ci-cd / deploy (push) Successful in 11s
Fiches de poste : message de l'organisation en bas de chaque page
Edition.volunteer_message imprimé en pied de chaque page (suite d'une
feuille comprise), la marge du bas agrandie de sa hauteur ; rien sans
message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:11:33 +01:00

199 lines
7.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Feuilles de poste (PDF) — API."""
import base64
import datetime as dt
from app.models import Assignment, Role
from tests.factories import dt_at, make_artist, make_position, make_shift, make_volunteer
# PNG 1×1 valide (transparent), le plus petit possible.
_PNG = base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY"
"42YAAAAASUVORK5CYII="
)
def _url(edition):
return f"/editions/{edition.id}/position-sheets/generate"
def test_is_coordinator_only(edition, make_account, as_account):
lead = make_account(Role.LEAD, email="lead@e.fr")
client = as_account(lead)
resp = client.post(_url(edition), json={})
assert resp.status_code == 403
def test_rejects_when_no_position_matches(client, edition):
resp = client.post(
_url(edition),
json={"position_ids": ["00000000-0000-0000-0000-000000000000"]},
)
assert resp.status_code == 422
assert resp.json()["detail"]["code"] == "position_sheets.no_positions"
def test_rejects_when_no_real_need_to_print(client, edition, session):
# Créneaux générés à 0/0, jamais remplis : rien à imprimer.
make_position(session, edition, name="Bar central")
resp = client.post(_url(edition), json={})
assert resp.status_code == 422
assert resp.json()["detail"]["code"] == "position_sheets.no_shifts"
def test_generates_a_pdf_with_assigned_volunteers(client, edition, session):
position = make_position(session, edition, name="Bar central")
shift = make_shift(
session, edition, position, start=dt_at(9, 19), end=dt_at(9, 22),
min_staff=1, ideal_staff=2,
)
volunteer = make_volunteer(session, edition, first_name="Alex", last_name="Martin")
session.add(
Assignment(edition_id=edition.id, volunteer_id=volunteer.id, shift_id=shift.id)
)
session.commit()
resp = client.post(_url(edition), json={})
assert resp.status_code == 200
assert resp.headers["content-type"] == "application/pdf"
assert resp.content.startswith(b"%PDF")
def test_generates_a_pdf_with_a_referent(client, edition, session, make_account):
position = make_position(session, edition, name="Bar central")
make_shift(
session, edition, position, start=dt_at(9, 19), end=dt_at(9, 22),
min_staff=1, ideal_staff=2,
)
referent_volunteer = make_volunteer(session, edition, first_name="Sam", last_name="Nuit")
referent_account = make_account(
Role.LEAD, volunteer_id=referent_volunteer.id, email="referent@e.fr"
)
client.patch(
f"/editions/{edition.id}/positions/{position.id}",
json={"lead_account_id": str(referent_account.id)},
)
resp = client.post(_url(edition), json={})
assert resp.status_code == 200
assert resp.content.startswith(b"%PDF")
def test_includes_a_volunteer_photo_when_present(client, edition, session):
position = make_position(session, edition, name="Bar central")
shift = make_shift(
session, edition, position, start=dt_at(9, 19), end=dt_at(9, 22),
min_staff=1, ideal_staff=2,
)
with_photo = make_volunteer(session, edition, first_name="Alex", last_name="Martin")
with_photo.photo = _PNG
with_photo.photo_content_type = "image/png"
without_photo = make_volunteer(session, edition, first_name="Sam", last_name="Nuit")
session.add_all(
[
Assignment(edition_id=edition.id, volunteer_id=with_photo.id, shift_id=shift.id),
Assignment(edition_id=edition.id, volunteer_id=without_photo.id, shift_id=shift.id),
]
)
session.commit()
resp = client.post(_url(edition), json={})
assert resp.status_code == 200
assert resp.content.startswith(b"%PDF")
def test_includes_the_day_lineup_across_all_positions(client, edition, session):
bar = make_position(session, edition, name="Bar central")
welcome = make_position(session, edition, name="Accueil")
make_shift(
session, edition, bar, start=dt_at(9, 19), end=dt_at(9, 22),
min_staff=1, ideal_staff=2,
)
make_shift(
session, edition, welcome, start=dt_at(9, 9), end=dt_at(9, 13),
min_staff=1, ideal_staff=2,
)
# Passage le 9 (jour des deux créneaux) : doit apparaître sur les deux
# feuilles, pas seulement celle du poste « Régie ».
make_artist(
session, edition, name="Chilla",
performance_date=dt.date(2026, 7, 9),
start_time=dt.time(20, 0), end_time=dt.time(21, 0),
)
# Passage un autre jour : ne doit apparaître sur aucune des deux.
make_artist(
session, edition, name="Hors sujet",
performance_date=dt.date(2026, 7, 11),
start_time=dt.time(20, 0), end_time=dt.time(21, 0),
)
resp = client.post(_url(edition), json={})
assert resp.status_code == 200
assert resp.content.startswith(b"%PDF")
def test_position_ids_filter_scopes_the_sheets(client, edition, session):
bar = make_position(session, edition, name="Bar central")
welcome = make_position(session, edition, name="Accueil")
make_shift(
session, edition, bar, start=dt_at(9, 19), end=dt_at(9, 22),
min_staff=1, ideal_staff=2,
)
make_shift(
session, edition, welcome, start=dt_at(9, 9), end=dt_at(9, 13),
min_staff=1, ideal_staff=2,
)
resp = client.post(_url(edition), json={"position_ids": [str(bar.id)]})
assert resp.status_code == 200
resp = client.post(_url(edition), json={"position_ids": [str(welcome.id)]})
assert resp.status_code == 200
def test_a_night_that_passes_midnight_stays_on_its_festival_day(client, edition, session):
# Début de journée = 06:00 (premier créneau type de la fixture) : 00h–02h
# le 10 appartient encore à la soirée du 9 — une seule page, pas deux.
from io import BytesIO
from pypdf import PdfReader
position = make_position(session, edition, name="Bar de nuit")
for start, end in [
(dt_at(9, 22), dt_at(9, 23)),
(dt_at(9, 23), dt_at(10, 0)),
(dt_at(10, 0), dt_at(10, 1)),
(dt_at(10, 1), dt_at(10, 2)),
]:
make_shift(session, edition, position, start=start, end=end)
resp = client.post(_url(edition), json={})
assert resp.status_code == 200
reader = PdfReader(BytesIO(resp.content))
assert len(reader.pages) == 1
text = reader.pages[0].extract_text()
assert "jeudi 9 juillet" in text.lower()
assert "01:00" in text
def test_organisation_message_is_passed_as_the_page_footer(client, edition, session, monkeypatch):
import app.api.position_sheets as sheets_api
captured = {}
original = sheets_api.generate_position_sheets_pdf
def spy(sheets, footer=None):
captured["footer"] = footer
return original(sheets, footer)
monkeypatch.setattr(sheets_api, "generate_position_sheets_pdf", spy)
position = make_position(session, edition)
make_shift(session, edition, position, start=dt_at(9, 19), end=dt_at(9, 22))
assert client.post(_url(edition), json={}).status_code == 200
assert captured["footer"] is None
client.patch(f"/editions/{edition.id}", json={"volunteer_message": "Repas à 18h30"})
assert client.post(_url(edition), json={}).status_code == 200
assert captured["footer"].message == "Repas à 18h30"