Files
benevoles/backend/tests/test_badges_api.py
T
AdamThomasandClaude Opus 5.5 54cba467fc Badges : photo du bénévole et texte libre
Champ « Photo du bénévole » (PHOTO) : photo du formulaire recadrée au
cadre, orientation EXIF appliquée, réduite à 300 dpi ; cadre vide sans
photo. Champ « Texte libre » (FREE_TEXT) alimenté par le nouveau « Texte
du badge » de la fiche bénévole (Volunteer.badge_text, migration 0024),
repris dans l'export Excel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:12:09 +01:00

247 lines
8.6 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.
"""Configuration et génération des planches de badges (API)."""
import base64
import pytest
from app.models import Role
from tests.factories import dt_at, make_position, make_shift, make_volunteer
# PNG 1×1 valide (transparent), le plus petit possible.
_PNG = base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY"
"42YAAAAASUVORK5CYII="
)
def _badges_url(edition):
return f"/editions/{edition.id}/badges"
def _set_full_name_field(client, edition):
return client.put(
f"{_badges_url(edition)}/template/fields",
json={
"fields": [
{
"field_type": "FULL_NAME",
"x_mm": 5,
"y_mm": 5,
"width_mm": 60,
"height_mm": 10,
"font_size": 12,
}
]
},
)
def test_template_is_coordinator_only(edition, make_account, as_account):
lead = make_account(Role.LEAD, email="lead@e.fr")
client = as_account(lead)
assert client.get(f"{_badges_url(edition)}/template").status_code == 403
resp = client.put(f"{_badges_url(edition)}/template/fields", json={"fields": []})
assert resp.status_code == 403
def test_template_starts_empty(client, edition):
resp = client.get(f"{_badges_url(edition)}/template")
assert resp.status_code == 200
body = resp.json()
assert body["has_front_image"] is False
assert body["has_back_image"] is False
assert body["fields"] == []
# Format CR80 par défaut tant qu'aucune image n'a été importée.
assert body["badge_width_mm"] == 85.6
assert body["badge_height_mm"] == 54.0
def test_uploading_front_image_derives_the_badge_format_from_its_pixels(client, edition):
resp = client.put(
f"{_badges_url(edition)}/template/front-image",
files={"file": ("front.png", _PNG, "image/png")},
)
body = resp.json()
# PNG 1×1 px, résolution supposée 300 dpi ⇒ 25,4/300 mm de côté.
assert body["badge_width_mm"] == pytest.approx(25.4 / 300)
assert body["badge_height_mm"] == pytest.approx(25.4 / 300)
def test_uploading_front_and_back_images(client, edition):
front = client.put(
f"{_badges_url(edition)}/template/front-image",
files={"file": ("front.png", _PNG, "image/png")},
)
assert front.status_code == 200
assert front.json()["has_front_image"] is True
back = client.put(
f"{_badges_url(edition)}/template/back-image",
files={"file": ("back.png", _PNG, "image/png")},
)
assert back.status_code == 200
assert back.json()["has_back_image"] is True
served = client.get(f"{_badges_url(edition)}/template/front-image")
assert served.status_code == 200
assert served.headers["content-type"] == "image/png"
assert served.content == _PNG
def test_uploading_an_unsupported_image_type_is_rejected(client, edition):
resp = client.put(
f"{_badges_url(edition)}/template/front-image",
files={"file": ("front.gif", b"not-a-real-gif", "image/gif")},
)
assert resp.status_code == 422
assert resp.json()["detail"]["code"] == "badge.image_type_not_supported"
def test_resizing_the_badge_scales_it_independently_of_the_image(client, edition):
client.put(
f"{_badges_url(edition)}/template/front-image",
files={"file": ("front.png", _PNG, "image/png")},
)
# Image 1×1 px à 300 dpi supposés ⇒ badge minuscule (~0,08 mm) : on
# l'agrandit à un format exploitable, en conservant le ratio (ici 1:1).
resp = client.put(
f"{_badges_url(edition)}/template/size",
json={"badge_width_mm": 90, "badge_height_mm": 90},
)
assert resp.status_code == 200
body = resp.json()
assert body["badge_width_mm"] == 90
assert body["badge_height_mm"] == 90
# Persisté : une relecture du gabarit renvoie toujours la taille agrandie.
again = client.get(f"{_badges_url(edition)}/template").json()
assert again["badge_width_mm"] == 90
assert again["badge_height_mm"] == 90
def test_resizing_the_badge_to_a_non_positive_size_is_rejected(client, edition):
resp = client.put(
f"{_badges_url(edition)}/template/size",
json={"badge_width_mm": 0, "badge_height_mm": 54},
)
assert resp.status_code == 422
assert resp.json()["detail"]["code"] == "badge.invalid_size"
def test_setting_the_same_field_type_twice_is_rejected(client, edition):
resp = client.put(
f"{_badges_url(edition)}/template/fields",
json={
"fields": [
{
"field_type": "FULL_NAME", "x_mm": 0, "y_mm": 0,
"width_mm": 10, "height_mm": 10,
},
{
"field_type": "FULL_NAME", "x_mm": 5, "y_mm": 5,
"width_mm": 10, "height_mm": 10,
},
]
},
)
assert resp.status_code == 422
assert resp.json()["detail"]["code"] == "badge.duplicate_field_type"
def test_generating_without_any_field_configured_is_rejected(client, edition, session):
make_volunteer(session, edition, first_name="Alex", last_name="Martin")
resp = client.post(f"{_badges_url(edition)}/generate", json={})
assert resp.status_code == 422
assert resp.json()["detail"]["code"] == "badge.no_fields"
def test_generating_with_no_matching_volunteers_is_rejected(client, edition, session):
_set_full_name_field(client, edition)
make_volunteer(session, edition, first_name="Alex", last_name="Martin")
resp = client.post(
f"{_badges_url(edition)}/generate",
json={"volunteer_ids": ["00000000-0000-0000-0000-000000000000"]},
)
assert resp.status_code == 422
assert resp.json()["detail"]["code"] == "badge.no_volunteers"
def test_generating_produces_a_pdf(client, edition, session):
from app.models import Assignment
resp = client.put(
f"{_badges_url(edition)}/template/fields",
json={
"fields": [
{
"field_type": "FULL_NAME", "x_mm": 5, "y_mm": 5,
"width_mm": 60, "height_mm": 10, "font_size": 12,
},
{
"field_type": "POSITIONS", "x_mm": 5, "y_mm": 20,
"width_mm": 60, "height_mm": 10, "font_size": 9,
},
]
},
)
assert resp.status_code == 200
client.put(
f"{_badges_url(edition)}/template/front-image",
files={"file": ("front.png", _PNG, "image/png")},
)
client.put(
f"{_badges_url(edition)}/template/back-image",
files={"file": ("back.png", _PNG, "image/png")},
)
volunteer = make_volunteer(session, edition, first_name="Alex", last_name="Martin")
position = make_position(session, edition, name="Bar central")
shift = make_shift(session, edition, position, start=dt_at(9, 18), end=dt_at(9, 22))
session.add(Assignment(edition_id=edition.id, volunteer_id=volunteer.id, shift_id=shift.id))
session.commit()
resp = client.post(
f"{_badges_url(edition)}/generate",
json={"volunteer_ids": [str(volunteer.id)]},
)
assert resp.status_code == 200
assert resp.headers["content-type"] == "application/pdf"
assert resp.content.startswith(b"%PDF")
def test_generating_with_photo_and_free_text_fields(client, edition, session):
resp = client.put(
f"{_badges_url(edition)}/template/fields",
json={
"fields": [
{
"field_type": "PHOTO", "x_mm": 60, "y_mm": 5,
"width_mm": 20, "height_mm": 25, "font_size": 8,
},
{
"field_type": "FREE_TEXT", "x_mm": 5, "y_mm": 40,
"width_mm": 50, "height_mm": 8, "font_size": 9,
},
]
},
)
assert resp.status_code == 200
assert {f["field_type"] for f in resp.json()} == {"PHOTO", "FREE_TEXT"}
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(with_photo)
session.commit()
resp = client.patch(
f"/editions/{edition.id}/volunteers/{without_photo.id}",
json={"badge_text": "Accès backstage"},
)
assert resp.status_code == 200
assert resp.json()["badge_text"] == "Accès backstage"
resp = client.post(f"{_badges_url(edition)}/generate", json={})
assert resp.status_code == 200
assert resp.content.startswith(b"%PDF")