From e69ca1c29db54ac8111c829473e283c11338fd11 Mon Sep 17 00:00:00 2001 From: Dotty Dotter Date: Tue, 28 Apr 2026 10:58:03 +0200 Subject: [PATCH] =?UTF-8?q?test(#134):=20mail.py=20Coverage=2088.2%=20?= =?UTF-8?q?=E2=86=92=20100%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TestSendSync.test_raises_when_smtp_not_configured: leerer host/user fuehrt zu RuntimeError - TestSendSync.test_calls_smtp_ssl_with_settings: smtplib.SMTP_SSL wird mit host/port instanziiert, login + send_message aufgerufen - TestSendMailAsync.test_runs_send_sync_in_executor: send_mail() delegiert per loop.run_in_executor an _send_sync --- tests/test_mail.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) 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

")]