import pytest from ccma.services import mail_delivery from ccma.storage.repository import RepositoryError class _FakeSmtpClient: instances: list["_FakeSmtpClient"] = [] def __init__(self, host, port, **kwargs): self.host = host self.port = port self.kwargs = kwargs self.calls: list[str] = [] self.login_args: tuple | None = None self.sent_messages: list = [] _FakeSmtpClient.instances.append(self) def starttls(self, **kwargs): self.calls.append("starttls") def login(self, username, password): self.login_args = (username, password) self.calls.append("login") def send_message(self, message): self.sent_messages.append(message) self.calls.append("send_message") def quit(self): self.calls.append("quit") class _FakeImapClient: instances: list["_FakeImapClient"] = [] error = Exception def __init__(self, host, port, **kwargs): self.host = host self.port = port self.kwargs = kwargs self.calls: list[str] = [] self.login_args: tuple | None = None self.appended: list[tuple] = [] self.append_result = ("OK", [b"APPEND completed"]) _FakeImapClient.instances.append(self) def starttls(self, *args, **kwargs): self.calls.append("starttls") def login(self, username, password): self.login_args = (username, password) self.calls.append("login") def append(self, folder, flags, date_time, message_bytes): self.appended.append((folder, flags, date_time, message_bytes)) self.calls.append("append") return self.append_result def logout(self): self.calls.append("logout") @pytest.fixture(autouse=True) def _reset_fakes(): _FakeSmtpClient.instances.clear() _FakeImapClient.instances.clear() yield def _patch_smtp(monkeypatch): monkeypatch.setattr(mail_delivery.smtplib, "SMTP", _FakeSmtpClient) monkeypatch.setattr(mail_delivery.smtplib, "SMTP_SSL", _FakeSmtpClient) def _patch_imap(monkeypatch): monkeypatch.setattr(mail_delivery.imaplib, "IMAP4", _FakeImapClient) monkeypatch.setattr(mail_delivery.imaplib, "IMAP4_SSL", _FakeImapClient) SMTP_SETTINGS = { "smtp_host": "mail.example.org", "smtp_port": 587, "smtp_security": "starttls", "smtp_username": "board@example.org", "smtp_password": "secret", } IMAP_SETTINGS = { "imap_host": "mail.example.org", "imap_port": 993, "imap_security": "ssl", "imap_username": "board@example.org", "imap_password": "secret", } MESSAGE = ( b"From: Board \r\n" b"To: member@example.org\r\n" b"Subject: Test\r\n" b"\r\n" b"Hello\r\n" ) def test_send_via_smtp_logs_in_and_sends_reusing_one_connection(monkeypatch) -> None: _patch_smtp(monkeypatch) with mail_delivery.smtp_session(SMTP_SETTINGS) as client: mail_delivery.send_via_smtp(client, MESSAGE) mail_delivery.send_via_smtp(client, MESSAGE) assert len(_FakeSmtpClient.instances) == 1 fake = _FakeSmtpClient.instances[0] assert fake.host == "mail.example.org" assert fake.port == 587 assert fake.login_args == ("board@example.org", "secret") assert fake.calls == ["starttls", "login", "send_message", "send_message", "quit"] assert len(fake.sent_messages) == 2 def test_append_to_imap_drafts_uses_configured_folder(monkeypatch) -> None: _patch_imap(monkeypatch) with mail_delivery.imap_session(IMAP_SETTINGS) as client: mail_delivery.append_to_imap_drafts(client, MESSAGE, folder="INBOX.Entwürfe") fake = _FakeImapClient.instances[0] assert fake.login_args == ("board@example.org", "secret") assert fake.appended == [("INBOX.Entwürfe", r"(\Draft)", None, MESSAGE)] assert fake.calls == ["login", "append", "logout"] def test_append_to_imap_drafts_raises_on_rejected_status(monkeypatch) -> None: _patch_imap(monkeypatch) with mail_delivery.imap_session(IMAP_SETTINGS) as client: client.append_result = ("NO", [b"Mailbox does not exist"]) with pytest.raises(RepositoryError, match="Mailbox does not exist"): mail_delivery.append_to_imap_drafts(client, MESSAGE, folder="Missing") def test_smtp_session_wraps_connection_errors(monkeypatch) -> None: def _boom(*args, **kwargs): raise OSError("connection refused") monkeypatch.setattr(mail_delivery.smtplib, "SMTP", _boom) with pytest.raises(RepositoryError, match="SMTP-Verbindung fehlgeschlagen"): with mail_delivery.smtp_session({**SMTP_SETTINGS, "smtp_security": "none"}): pass def test_smtp_session_requires_host() -> None: with pytest.raises(RepositoryError, match="Kein SMTP-Server konfiguriert"): with mail_delivery.smtp_session({}): pass def test_imap_session_requires_host() -> None: with pytest.raises(RepositoryError, match="Kein IMAP-Server konfiguriert"): with mail_delivery.imap_session({}): pass def test_test_smtp_connection_succeeds_and_closes(monkeypatch) -> None: _patch_smtp(monkeypatch) mail_delivery.test_smtp_connection(SMTP_SETTINGS) assert _FakeSmtpClient.instances[0].calls == ["starttls", "login", "quit"] def test_test_imap_connection_succeeds_and_closes(monkeypatch) -> None: _patch_imap(monkeypatch) mail_delivery.test_imap_connection(IMAP_SETTINGS) assert _FakeImapClient.instances[0].calls == ["login", "logout"]