Emails : robustesse SMTP + diagnostic
- config.py : retrait des identifiants OVH réels remis en dur ; défauts neutres (email_enabled=false). Les vraies valeurs vont dans .env. - open_smtp : le port 465 force le SSL implicite (SMTPS) même si SMTP_SSL=false — c'était la cause des envois qui ne partaient pas (connexion en clair sur un port TLS -> timeout). - deliver_many : les erreurs de connexion / auth / rejet sont journalisées avec le message du serveur et ne remontent plus (l'appel HTTP n'échoue pas). - Lien magique : envoi en tâche de fond (BackgroundTasks) comme les notifications ; ne bloque plus la réponse. - app/email/check.py : `python -m app.email.check <destinataire>` — affiche la config effective et envoie un email de test, avec la cause exacte en cas d'échec. - Tests : fixture autouse forçant EMAIL_ENABLED=false (aucun test ne touche un vrai SMTP, quel que soit le .env local) ; test_full_flow lit l'email capturé au lieu des logs. 137 tests OK. - .env.example : notes port 465 / From = boîte authentifiée / commande check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6b08dcc953
commit
b84f62dfab
@@ -13,6 +13,16 @@ from app.main import app
|
||||
from app.models import Account, DayPart, Edition, Role
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _never_use_real_smtp(monkeypatch):
|
||||
"""Quel que soit le `.env` local, les tests n'ouvrent jamais de connexion
|
||||
SMTP : `EMAIL_ENABLED` est forcé à false."""
|
||||
monkeypatch.setenv("EMAIL_ENABLED", "false")
|
||||
get_settings.cache_clear()
|
||||
yield
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def engine():
|
||||
"""Vraie base PostgreSQL de test (voir docker/postgres-init)."""
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import datetime as dt
|
||||
import logging
|
||||
|
||||
from sqlmodel import select
|
||||
|
||||
@@ -22,17 +21,17 @@ def test_magic_link_unknown_email_is_accepted_without_token(
|
||||
|
||||
|
||||
def test_full_flow_magic_link_then_jwt_then_me(
|
||||
raw_client, session, edition, make_account, caplog
|
||||
raw_client, session, edition, make_account, outbox
|
||||
):
|
||||
make_account(Role.LEAD, email="ref@example.fr")
|
||||
|
||||
with caplog.at_level(logging.INFO, logger="app.auth"):
|
||||
resp = raw_client.post(
|
||||
_url(edition, "magic-link"), json={"email": "REF@example.fr"}
|
||||
)
|
||||
resp = raw_client.post(
|
||||
_url(edition, "magic-link"), json={"email": "REF@example.fr"}
|
||||
)
|
||||
assert resp.status_code == 202
|
||||
log_line = next(m for m in caplog.messages if "Lien magique" in m)
|
||||
raw_token = log_line.split("jeton=")[-1].strip()
|
||||
assert len(outbox) == 1
|
||||
assert outbox[0]["To"] == "ref@example.fr"
|
||||
raw_token = outbox[0].get_content().split("?token=")[1].split()[0].strip()
|
||||
|
||||
verified = raw_client.post(
|
||||
_url(edition, "verify"), json={"token": raw_token}
|
||||
|
||||
Reference in New Issue
Block a user