from unittest.mock import MagicMock, patch import pytest from app.core.config import settings from app.integrations.smtp import send_email @pytest.fixture(autouse=True) def smtp_settings(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(settings, "SMTP_HOST", "smtp.example.com") monkeypatch.setattr(settings, "SMTP_PORT", 587) monkeypatch.setattr(settings, "SMTP_USERNAME", "automation") monkeypatch.setattr(settings, "SMTP_PASSWORD", "secret") monkeypatch.setattr(settings, "SMTP_FROM_EMAIL", "noreply@example.com") monkeypatch.setattr(settings, "SMTP_FROM_NAME", "Home Automation") monkeypatch.setattr(settings, "SMTP_ENCRYPTION", "starttls") monkeypatch.setattr(settings, "SMTP_TIMEOUT", 10.0) def test_send_email_uses_starttls_and_login() -> None: smtp_client = MagicMock() with patch("app.integrations.smtp.smtplib.SMTP", return_value=smtp_client) as smtp_class: send_email( subject="Door opened", recipients=["user@example.com"], text_body="The front door opened.", html_body="

The front door opened.

", reply_to="support@example.com", ) smtp_class.assert_called_once_with("smtp.example.com", 587, timeout=10.0) assert smtp_client.ehlo.call_count == 2 smtp_client.starttls.assert_called_once_with() smtp_client.login.assert_called_once_with("automation", "secret") smtp_client.send_message.assert_called_once() smtp_client.quit.assert_called_once_with() message = smtp_client.send_message.call_args.args[0] assert message["Subject"] == "Door opened" assert message["From"] == "Home Automation " assert message["To"] == "user@example.com" assert message["Reply-To"] == "support@example.com" assert message.get_body(preferencelist=("plain",)).get_content().strip() == "The front door opened." assert message.get_body(preferencelist=("html",)).get_content().strip() == "

The front door opened.

" def test_send_email_uses_ssl_without_starttls(monkeypatch: pytest.MonkeyPatch) -> None: smtp_client = MagicMock() monkeypatch.setattr(settings, "SMTP_PORT", 465) monkeypatch.setattr(settings, "SMTP_ENCRYPTION", "ssl_tls") monkeypatch.setattr(settings, "SMTP_USERNAME", None) monkeypatch.setattr(settings, "SMTP_PASSWORD", None) with patch("app.integrations.smtp.smtplib.SMTP_SSL", return_value=smtp_client) as smtp_ssl_class: send_email( subject="Alarm armed", recipients=["user1@example.com", "user2@example.com"], text_body="The alarm is armed.", ) smtp_ssl_class.assert_called_once_with("smtp.example.com", 465, timeout=10.0) smtp_client.starttls.assert_not_called() smtp_client.login.assert_not_called() smtp_client.send_message.assert_called_once() message = smtp_client.send_message.call_args.args[0] assert message["To"] == "user1@example.com, user2@example.com" assert message.get_body(preferencelist=("plain",)).get_content().strip() == "The alarm is armed." def test_send_email_requires_host(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(settings, "SMTP_HOST", None) with pytest.raises(ValueError, match="SMTP_HOST"): send_email(subject="test", recipients=["user@example.com"], text_body="body") def test_send_email_requires_recipient() -> None: with pytest.raises(ValueError, match="At least one recipient"): send_email(subject="test", recipients=[], text_body="body") def test_send_email_rejects_unencrypted_mode(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(settings, "SMTP_ENCRYPTION", "none") with pytest.raises(ValueError, match="SMTP_ENCRYPTION"): send_email(subject="test", recipients=["user@example.com"], text_body="body")