Files
benevoles/backend/tests/test_position_sheets_pdf.py
T
AdamThomasandClaude Opus 5.5 c6ea7378c7
ci-cd / build-backend (push) Successful in 34s
ci-cd / build-frontend (push) Successful in 19s
ci-cd / deploy (push) Successful in 11s
Fiches de poste rapides, badges sans initiales, vue par bénévole sur l'axe des temps
Étape 62 : les fiches de poste intégraient la photo d'origine de chaque
bénévole pour chaque créneau (CPU à 300 %, puis erreur) — photos réduites
à la vignette une fois par PDF ; badges : photo recadrée une fois par
génération et décodée à taille réduite.

Étape 61 : sans photo, le cadre de la photo reste vide sur le badge.

Étape 60 : vue par bénévole en tranches de 30 min, affectations étirées
sur toute leur durée, postes simultanés empilés, ligne de l'heure actuelle.

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

177 lines
5.7 KiB
Python
Raw 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.
"""Génération des feuilles de poste (module pur, sans base)."""
import base64
import datetime as dt
import io
from app.sheets.pdf import (
PositionDaySheet,
SheetArtist,
SheetShift,
SheetVolunteer,
generate_position_sheets_pdf,
)
# PNG 1×1 valide (transparent), le plus petit possible.
_PNG = base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY"
"42YAAAAASUVORK5CYII="
)
def _sheet(volunteers=(), min_staff=1, ideal_staff=2) -> PositionDaySheet:
return PositionDaySheet(
position_name="Bar central",
position_location="Chapiteau",
date=dt.date(2026, 7, 10),
shifts=(
SheetShift(
starts_at=dt.datetime(2026, 7, 10, 19, 0),
ends_at=dt.datetime(2026, 7, 10, 22, 0),
min_staff=min_staff,
ideal_staff=ideal_staff,
volunteers=volunteers,
),
),
)
def test_shift_is_understaffed_when_below_min_staff():
fully_staffed = _sheet(
volunteers=(SheetVolunteer(name="Alex Martin", phone="0600000000"),),
min_staff=1,
)
assert fully_staffed.shifts[0].understaffed is False
understaffed = _sheet(volunteers=(), min_staff=1)
assert understaffed.shifts[0].understaffed is True
def test_generates_a_valid_pdf_with_volunteers():
sheet = _sheet(
volunteers=(
SheetVolunteer(name="Alex Martin", phone="0600000000"),
SheetVolunteer(name="Sam Nuit", phone=None),
)
)
pdf = generate_position_sheets_pdf([sheet])
assert pdf.startswith(b"%PDF")
assert pdf.rstrip().endswith(b"%%EOF")
def test_generates_a_valid_pdf_with_no_volunteers_assigned():
pdf = generate_position_sheets_pdf([_sheet(volunteers=())])
assert pdf.startswith(b"%PDF")
def test_generates_a_valid_pdf_with_no_shifts_that_day():
sheet = PositionDaySheet(
position_name="Bar central", position_location=None,
date=dt.date(2026, 7, 10), shifts=(),
)
pdf = generate_position_sheets_pdf([sheet])
assert pdf.startswith(b"%PDF")
def test_multiple_sheets_produce_a_larger_pdf():
one = generate_position_sheets_pdf([_sheet()])
two = generate_position_sheets_pdf([_sheet(), _sheet()])
assert len(two) > len(one)
def test_generates_a_valid_pdf_with_a_referent():
sheet = PositionDaySheet(
position_name="Bar central", position_location="Chapiteau",
date=dt.date(2026, 7, 10), shifts=(),
referent_name="Sam Nuit", referent_phone="0600000000",
)
pdf = generate_position_sheets_pdf([sheet])
assert pdf.startswith(b"%PDF")
def test_generates_a_valid_pdf_without_a_referent():
sheet = PositionDaySheet(
position_name="Bar central", position_location="Chapiteau",
date=dt.date(2026, 7, 10), shifts=(),
)
assert sheet.referent_name is None
pdf = generate_position_sheets_pdf([sheet])
assert pdf.startswith(b"%PDF")
def test_generates_a_valid_pdf_with_volunteer_photos():
sheet = _sheet(
volunteers=(
SheetVolunteer(name="Alex Martin", phone="0600000000", photo=_PNG),
SheetVolunteer(name="Sam Nuit", phone=None, photo=None),
)
)
assert sheet.shifts[0].volunteers[0].photo == _PNG
assert sheet.shifts[0].volunteers[1].photo is None
pdf = generate_position_sheets_pdf([sheet])
assert pdf.startswith(b"%PDF")
def test_a_volunteer_photo_makes_the_pdf_larger():
without_photo = generate_position_sheets_pdf(
[_sheet(volunteers=(SheetVolunteer(name="Alex Martin", phone=None, photo=None),))]
)
with_photo = generate_position_sheets_pdf(
[_sheet(volunteers=(SheetVolunteer(name="Alex Martin", phone=None, photo=_PNG),))]
)
assert len(with_photo) > len(without_photo)
def test_generates_a_valid_pdf_with_the_day_lineup():
sheet = PositionDaySheet(
position_name="Bar central", position_location="Chapiteau",
date=dt.date(2026, 7, 10), shifts=(),
artists=(
SheetArtist(name="Chilla", start_time=dt.time(20, 0), end_time=dt.time(21, 0)),
SheetArtist(name="XYZ", start_time=dt.time(21, 30), end_time=dt.time(23, 0)),
),
)
pdf = generate_position_sheets_pdf([sheet])
assert pdf.startswith(b"%PDF")
def test_generates_a_valid_pdf_without_a_lineup():
sheet = PositionDaySheet(
position_name="Bar central", position_location="Chapiteau",
date=dt.date(2026, 7, 10), shifts=(),
)
assert sheet.artists == ()
pdf = generate_position_sheets_pdf([sheet])
assert pdf.startswith(b"%PDF")
def _phone_photo(seed: int) -> bytes:
"""Photo de téléphone : 3000 × 4000 px, plusieurs Mo."""
from PIL import Image as PILImage
img = PILImage.new("RGB", (3000, 4000), (seed * 40 % 255, 120, 60))
img.paste(PILImage.effect_noise((800, 800), 80).convert("RGB"), (seed * 100, 200))
out = io.BytesIO()
img.save(out, format="JPEG", quality=92)
return out.getvalue()
def test_large_photos_are_shrunk_to_the_thumbnail():
# Régression : la photo d'origine était intégrée telle quelle pour
# chaque bénévole de chaque créneau — génération interminable, PDF
# énorme, puis erreur.
photos = [_phone_photo(i) for i in range(5)]
volunteers = tuple(
SheetVolunteer(name=f"Bénévole {i}", phone=None, photo=p) for i, p in enumerate(photos)
)
pdf = generate_position_sheets_pdf([_sheet(volunteers=volunteers)])
assert sum(len(p) for p in photos) > 2_000_000
assert len(pdf) < 200_000
def test_unreadable_photo_does_not_break_the_sheet():
pdf = generate_position_sheets_pdf(
[_sheet(volunteers=(SheetVolunteer(name="Alex", phone=None, photo=b"pas une image"),))]
)
assert pdf.startswith(b"%PDF")