"""Réinitialisation de mot de passe par email (auth/password_reset.py, auth/send_email.py) — l'envoi SMTP réel est remplacé (monkeypatch) par une capture de l'URL qui aurait été envoyée : ces tests ne doivent jamais essayer de contacter un vrai serveur SMTP.""" import auth from tests.test_auth import _register, _confirm_2fa, _cleanup_project, anon_client # noqa: F401 def _capture_reset_url(monkeypatch): sent = {} def fake_send(to_email, reset_url): sent["to_email"] = to_email sent["reset_url"] = reset_url monkeypatch.setattr(auth, "send_password_reset_email", fake_send) return sent def test_forgot_password_always_shows_the_same_generic_message(anon_client, monkeypatch): _capture_reset_url(monkeypatch) resp_known = anon_client.post("/forgot-password", data={"email": "unknown@example.com"}) resp_unknown_body = resp_known.get_data(as_text=True) assert "un email vient de t'être envoyé" in resp_unknown_body def test_full_password_reset_flow(anon_client, monkeypatch): sent = _capture_reset_url(monkeypatch) _register(anon_client, "resetflow@example.com") _confirm_2fa(anon_client) try: anon_client.post("/logout") resp = anon_client.post("/forgot-password", data={"email": "resetflow@example.com"}) assert "un email vient de t'être envoyé" in resp.get_data(as_text=True) assert sent["to_email"] == "resetflow@example.com" token = sent["reset_url"].rsplit("/", 1)[-1] # Le lien affiche bien le formulaire de nouveau mot de passe. resp = anon_client.get(f"/reset-password/{token}") assert resp.status_code == 200 assert "invalide" not in resp.get_data(as_text=True) # Mots de passe qui ne correspondent pas -> refusé. resp = anon_client.post(f"/reset-password/{token}", data={ "password": "Nouveau$ecret1", "password_confirm": "Autre$ecret2", }) assert "ne correspondent pas" in resp.get_data(as_text=True) # Mot de passe trop faible -> refusé. resp = anon_client.post(f"/reset-password/{token}", data={ "password": "faible", "password_confirm": "faible", }) assert "faible" in resp.get_data(as_text=True) # Nouveau mot de passe valide -> accepté, redirige vers /login. resp = anon_client.post(f"/reset-password/{token}", data={ "password": "Nouveau$ecret1", "password_confirm": "Nouveau$ecret1", }, follow_redirects=False) assert resp.status_code == 302 assert resp.headers["Location"] == "/login" # L'ancien mot de passe ne fonctionne plus, le nouveau fonctionne. resp = anon_client.post("/login", data={"email": "resetflow@example.com", "password": "Sup3r$ecret!"}) assert "incorrect" in resp.get_data(as_text=True) resp = anon_client.post("/login", data={"email": "resetflow@example.com", "password": "Nouveau$ecret1"}, follow_redirects=False) assert resp.status_code == 302 assert resp.headers["Location"] == "/login/2fa" # Le jeton est à usage unique : le réutiliser échoue désormais. resp = anon_client.get(f"/reset-password/{token}") assert "invalide" in resp.get_data(as_text=True) finally: _cleanup_project("resetflow@example.com") def test_reset_password_with_invalid_or_unknown_token_shows_invalid(anon_client): resp = anon_client.get("/reset-password/not-a-real-token") assert "invalide" in resp.get_data(as_text=True)