"""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)