Ajoute 4 mécanismes moteur manquants pour le thème sécurité incendie : étiquette, carte, image SVG inline, bouton avec pièce jointe

Contenu et mécanisme uniquement, aucun style ajouté (voir consigne du
24/09/2026) : deux nouveaux kinds de contenu (badge/carte, rendu en
div brutes sans CSS), un mode SVG inline pour l'image (svg_markup,
nettoyé par un nouveau sanitizer allow-list avant chaque rendu) et un
fichier téléchargeable joignable à un bouton (upload/download routes,
stockage sous db.support_dir). Le futur système de templates portera
l'habillage visuel de ces éléments.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-24 08:12:29 +02:00
co-authored by Claude Sonnet 5
parent e7c6ed7159
commit a34bcf4159
15 changed files with 668 additions and 18 deletions
+56
View File
@@ -8,6 +8,7 @@ pages elles-mêmes) — _page_id() renvoie l'id de la page par défaut
("Page 1") créée avec chaque support de test, réutilisé par toutes les
routes d'élément ci-dessous qui exigent désormais un page_id explicite."""
import io
from typing import Any
from flask.testing import FlaskClient
@@ -344,3 +345,58 @@ def test_cannot_open_another_owners_support(client: FlaskClient, user_client: Fl
assert client.post(f"/document/{victim_slug}/delete").status_code == 403
finally:
db.delete_support(victim_slug)
def _add_bouton(client: FlaskClient, support: str) -> int:
resp = client.post(f"/document/{support}/elements/add", data={"kind": "bouton", "page_id": _page_id(support)})
return int(resp.get_json()["id"])
def test_upload_attachment_stores_file_and_updates_button_attributes(client: FlaskClient, support: str) -> None:
element_id = _add_bouton(client, support)
resp = client.post(
f"/document/{support}/elements/{element_id}/upload-attachment",
data={"file": (io.BytesIO(b"%PDF-1.4 fake pdf content"), "fiche-consignes.pdf")},
content_type="multipart/form-data",
)
assert resp.status_code == 200
payload = resp.get_json()
assert payload["attributes"]["attachment_filename"] == "fiche-consignes.pdf"
assert payload["attributes"]["attachment_stored_name"]
assert 'data-attachment-filename="fiche-consignes.pdf"' in payload["rendered_html"]
def test_upload_attachment_rejects_a_non_bouton_element(client: FlaskClient, support: str) -> None:
resp = client.post(f"/document/{support}/elements/add", data={"kind": "titre", "page_id": _page_id(support)})
element_id = resp.get_json()["id"]
resp = client.post(
f"/document/{support}/elements/{element_id}/upload-attachment",
data={"file": (io.BytesIO(b"peu importe"), "x.pdf")},
content_type="multipart/form-data",
)
assert resp.status_code == 400
def test_upload_attachment_rejects_a_missing_file(client: FlaskClient, support: str) -> None:
element_id = _add_bouton(client, support)
resp = client.post(f"/document/{support}/elements/{element_id}/upload-attachment", data={})
assert resp.status_code == 400
def test_download_attachment_serves_the_file_under_its_original_name(client: FlaskClient, support: str) -> None:
element_id = _add_bouton(client, support)
client.post(
f"/document/{support}/elements/{element_id}/upload-attachment",
data={"file": (io.BytesIO(b"%PDF-1.4 fake pdf content"), "fiche-consignes.pdf")},
content_type="multipart/form-data",
)
resp = client.get(f"/document/{support}/elements/{element_id}/download-attachment")
assert resp.status_code == 200
assert resp.data == b"%PDF-1.4 fake pdf content"
assert "fiche-consignes.pdf" in resp.headers["Content-Disposition"]
def test_download_attachment_404s_when_nothing_was_uploaded(client: FlaskClient, support: str) -> None:
element_id = _add_bouton(client, support)
resp = client.get(f"/document/{support}/elements/{element_id}/download-attachment")
assert resp.status_code == 404