Files
benevoles/backend/tests/test_api_test_email.py
AdamThomasandClaude Opus 5.5 440466ccf9
ci-cd / build-backend (push) Successful in 46s
ci-cd / deploy (push) Successful in 12s
ci-cd / build-frontend (push) Successful in 21s
Email de test : récapitulatif de planning compilé
L'email de test reprend le modèle en cours d'édition, balises remplies
avec le planning d'un bénévole au choix (premier avec un créneau par
défaut), option lien de connexion sans vrai jeton, sujet « [Test] … » —
même fonction de rendu que l'envoi réel.

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

145 lines
5.2 KiB
Python

"""Email de test depuis l'écran « Messages » (`POST .../notifications/test`) :
envoi synchrone, résultat réel du serveur SMTP renvoyé à la coordination."""
import smtplib
import pytest
from app.config import get_settings
from app.models import Role
def _url(edition):
return f"/editions/{edition.id}/notifications/test"
class _FakeSmtp:
def __init__(self, error=None):
self.sent = []
self.error = error
def __enter__(self):
return self
def __exit__(self, *exc):
return False
def send_message(self, message):
if self.error:
raise self.error
self.sent.append(message)
@pytest.fixture
def smtp_enabled(monkeypatch):
monkeypatch.setenv("EMAIL_ENABLED", "true")
get_settings.cache_clear()
def test_disabled_email_is_reported(client, edition):
resp = client.post(_url(edition), json={"email": "moi@exemple.fr"})
assert resp.status_code == 200
assert resp.json()["status"] == "disabled"
def test_sends_the_test_email(client, edition, smtp_enabled, monkeypatch):
fake = _FakeSmtp()
monkeypatch.setattr("app.email.sender.open_smtp", lambda settings: fake)
resp = client.post(_url(edition), json={"email": " moi@exemple.fr "})
assert resp.json()["status"] == "sent"
(message,) = fake.sent
assert message["To"] == "moi@exemple.fr"
def test_connection_failure_is_reported_with_the_server_message(
client, edition, smtp_enabled, monkeypatch
):
def refuse(settings):
raise smtplib.SMTPAuthenticationError(535, b"authentication failed")
monkeypatch.setattr("app.email.sender.open_smtp", refuse)
body = client.post(_url(edition), json={"email": "moi@exemple.fr"}).json()
assert body["status"] == "connection_failed"
assert "535" in body["detail"]
def test_rejected_message_never_echoes_the_address(client, edition, smtp_enabled, monkeypatch):
refused = smtplib.SMTPRecipientsRefused({"moi@exemple.fr": (550, b"no such user")})
monkeypatch.setattr("app.email.sender.open_smtp", lambda settings: _FakeSmtp(refused))
body = client.post(_url(edition), json={"email": "moi@exemple.fr"}).json()
assert body["status"] == "rejected"
assert "550" in body["detail"]
assert "moi@exemple.fr" not in body["detail"]
@pytest.mark.parametrize("email", ["", "pas-un-email", "@exemple.fr", "moi@", "a b@c.fr"])
def test_invalid_address_is_rejected(client, edition, email):
resp = client.post(_url(edition), json={"email": email})
assert resp.status_code == 422
def test_is_coordinator_only(edition, make_account, as_account):
lead = as_account(make_account(Role.LEAD))
assert lead.post(_url(edition), json={"email": "moi@exemple.fr"}).status_code == 403
def _send(client, edition, monkeypatch, **payload):
captured = []
monkeypatch.setattr(
"app.api.notifications.deliver_test",
lambda message: captured.append(message) or ("sent", None),
)
body = client.post(_url(edition), json={"email": "moi@exemple.fr", **payload}).json()
return body, captured[0]
def test_test_email_is_the_compiled_planning_email(client, edition, session, monkeypatch):
from app.models import Assignment
from tests.factories import dt_at, make_position, make_shift, make_volunteer
from tests.mail import plain_text
make_volunteer(session, edition, first_name="Zoé", last_name="Zébu") # sans créneau
alex = make_volunteer(session, edition, first_name="Alex", last_name="Martin")
shift = make_shift(session, edition, make_position(session, edition, name="Bar"),
start=dt_at(10, 18), end=dt_at(10, 22))
session.add(Assignment(edition_id=edition.id, volunteer_id=alex.id, shift_id=shift.id))
session.commit()
body, message = _send(
client, edition, monkeypatch,
subject="Planning de {prenom}",
body="Salut {prenom} {nom} : {nb_creneaux} créneau(x), {nb_heures} h\n{creneaux}",
)
# Premier bénévole (par nom) qui a un créneau, pas Zoé.
assert body["example_volunteer_name"] == "Alex Martin"
assert message["Subject"] == "[Test] Planning de Alex"
text = plain_text(message)
assert "Salut Alex Martin : 1 créneau(x), 4.0 h" in text
assert "Bar" in text
assert "{" not in text
def test_login_link_in_test_email_carries_no_token(client, edition, session, monkeypatch):
from sqlmodel import select
from app.models import MagicLinkToken
from tests.factories import make_volunteer
from tests.mail import plain_text
make_volunteer(session, edition)
_, message = _send(client, edition, monkeypatch, body="Connexion : {lien_connexion}")
assert "token=" not in plain_text(message)
assert session.exec(select(MagicLinkToken)).all() == []
def test_chosen_volunteer_and_fictitious_example(client, edition, session, monkeypatch):
from tests.factories import make_volunteer
body, message = _send(client, edition, monkeypatch, subject="Pour {prenom}")
assert body["example_volunteer_id"] is None
assert message["Subject"] == "[Test] Pour Camille"
sam = make_volunteer(session, edition, first_name="Sam", last_name="Nuit")
body, _ = _send(client, edition, monkeypatch, volunteer_id=str(sam.id))
assert body["example_volunteer_id"] == str(sam.id)