test(#134): mail.py Coverage 88.2% → 100%

- 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
This commit is contained in:
Dotty Dotter 2026-04-28 10:58:03 +02:00
parent 9af74b1a05
commit e69ca1c29d

View File

@ -352,3 +352,63 @@ class TestRunDailyDigest:
assert result["failed"] == 1 assert result["failed"] == 1
assert result["sent"] == 0 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", "<p>html</p>")
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", "<p>HTML</p>")
# 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", "<p>html</p>"))
assert called == [("to@test", "subj", "text", "<p>html</p>")]