diff --git a/tests/test_mail.py b/tests/test_mail.py index 9a4b62b..e8c6e18 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -352,3 +352,63 @@ class TestRunDailyDigest: assert result["failed"] == 1 assert result["sent"] == 0 + + +# ─── SMTP-Send-Path Coverage (#134 Backfill) ───────────────────────────────── + + +class TestSendSync: + def test_raises_when_smtp_not_configured(self, monkeypatch): + """Wenn settings.smtp_host oder smtp_user leer ist, RuntimeError.""" + from app import mail as mail_mod + from app.config import settings + monkeypatch.setattr(settings, "smtp_host", "") + monkeypatch.setattr(settings, "smtp_user", "user@example.com") + with pytest.raises(RuntimeError, match="SMTP nicht konfiguriert"): + mail_mod._send_sync("to@example.com", "subj", "text", "
html
") + + def test_calls_smtp_ssl_with_settings(self, monkeypatch): + """Bei vollstaendiger Konfig wird smtplib.SMTP_SSL aufgerufen, login + und send_message getriggert.""" + from unittest.mock import MagicMock + from app import mail as mail_mod + from app.config import settings + monkeypatch.setattr(settings, "smtp_host", "smtp.test") + monkeypatch.setattr(settings, "smtp_port", 465) + monkeypatch.setattr(settings, "smtp_user", "user@test") + monkeypatch.setattr(settings, "smtp_password", "pw") + monkeypatch.setattr(settings, "smtp_from_email", "noreply@test") + monkeypatch.setattr(settings, "smtp_from_name", "Test") + + ssl_mock = MagicMock() + server_mock = MagicMock() + ssl_mock.return_value.__enter__.return_value = server_mock + ssl_mock.return_value.__exit__.return_value = False + monkeypatch.setattr(mail_mod.smtplib, "SMTP_SSL", ssl_mock) + + mail_mod._send_sync("to@test", "subj", "Plain", "HTML
") + + # SMTP_SSL wurde aufgerufen mit host + port + ssl_mock.assert_called_once() + args, kwargs = ssl_mock.call_args + assert args[0] == "smtp.test" + assert args[1] == 465 + # Login + send wurden aufgerufen + server_mock.login.assert_called_once_with("user@test", "pw") + server_mock.send_message.assert_called_once() + + +class TestSendMailAsync: + def test_runs_send_sync_in_executor(self, monkeypatch): + """send_mail (async) delegiert an _send_sync via Thread-Executor.""" + import asyncio + from unittest.mock import MagicMock + from app import mail as mail_mod + + called: list[tuple] = [] + def fake_sync(to, subj, text, html): + called.append((to, subj, text, html)) + + monkeypatch.setattr(mail_mod, "_send_sync", fake_sync) + asyncio.run(mail_mod.send_mail("to@test", "subj", "text", "html
")) + assert called == [("to@test", "subj", "text", "html
")]