""" Unit tests for email service. Tests email sending with SMTP, TLS configuration, and error handling. Uses mocking to avoid actual SMTP connections. """ from unittest.mock import MagicMock, patch import pytest import smtplib from gondulf.email import EmailError, EmailService class TestEmailServiceInit: """Tests for EmailService initialization.""" def test_init_with_all_parameters(self): """Test EmailService initializes with all parameters.""" service = EmailService( smtp_host="smtp.gmail.com", smtp_port=587, smtp_from="sender@example.com", smtp_username="user@example.com", smtp_password="password", smtp_use_tls=True, ) assert service.smtp_host == "smtp.gmail.com" assert service.smtp_port == 587 assert service.smtp_from == "sender@example.com" assert service.smtp_username == "user@example.com" assert service.smtp_password == "password" assert service.smtp_use_tls is True def test_init_without_credentials(self): """Test EmailService initializes without username/password.""" service = EmailService( smtp_host="localhost", smtp_port=25, smtp_from="sender@example.com", ) assert service.smtp_username is None assert service.smtp_password is None class TestEmailServiceSendVerificationCode: """Tests for send_verification_code method.""" @patch("gondulf.email.smtplib.SMTP") def test_send_verification_code_success_starttls(self, mock_smtp): """Test sending verification code with STARTTLS.""" mock_server = MagicMock() mock_smtp.return_value = mock_server service = EmailService( smtp_host="smtp.example.com", smtp_port=587, smtp_from="sender@example.com", smtp_username="user", smtp_password="pass", smtp_use_tls=True, ) service.send_verification_code("recipient@example.com", "123456", "example.com") # Verify SMTP was called correctly mock_smtp.assert_called_once_with("smtp.example.com", 587, timeout=10) mock_server.starttls.assert_called_once() mock_server.login.assert_called_once_with("user", "pass") mock_server.send_message.assert_called_once() mock_server.quit.assert_called_once() @patch("gondulf.email.smtplib.SMTP_SSL") def test_send_verification_code_success_implicit_tls(self, mock_smtp_ssl): """Test sending verification code with implicit TLS (port 465).""" mock_server = MagicMock() mock_smtp_ssl.return_value = mock_server service = EmailService( smtp_host="smtp.example.com", smtp_port=465, smtp_from="sender@example.com", smtp_username="user", smtp_password="pass", ) service.send_verification_code("recipient@example.com", "123456", "example.com") # Verify SMTP_SSL was called mock_smtp_ssl.assert_called_once_with("smtp.example.com", 465, timeout=10) # starttls should NOT be called for implicit TLS assert not mock_server.starttls.called mock_server.login.assert_called_once() mock_server.send_message.assert_called_once() @patch("gondulf.email.smtplib.SMTP") def test_send_verification_code_without_auth(self, mock_smtp): """Test sending without authentication.""" mock_server = MagicMock() mock_smtp.return_value = mock_server service = EmailService( smtp_host="localhost", smtp_port=25, smtp_from="sender@example.com", smtp_use_tls=False, ) service.send_verification_code("recipient@example.com", "123456", "example.com") # Verify login was not called assert not mock_server.login.called mock_server.send_message.assert_called_once() @patch("gondulf.email.smtplib.SMTP") def test_send_verification_code_smtp_error(self, mock_smtp): """Test EmailError raised on SMTP failure.""" mock_server = MagicMock() mock_server.send_message.side_effect = smtplib.SMTPException("SMTP error") mock_smtp.return_value = mock_server service = EmailService( smtp_host="smtp.example.com", smtp_port=587, smtp_from="sender@example.com", ) with pytest.raises(EmailError, match="SMTP error"): service.send_verification_code( "recipient@example.com", "123456", "example.com" ) @patch("gondulf.email.smtplib.SMTP") def test_send_verification_code_auth_error(self, mock_smtp): """Test EmailError raised on authentication failure.""" mock_server = MagicMock() mock_server.login.side_effect = smtplib.SMTPAuthenticationError( 535, "Authentication failed" ) mock_smtp.return_value = mock_server service = EmailService( smtp_host="smtp.example.com", smtp_port=587, smtp_from="sender@example.com", smtp_username="user", smtp_password="wrong", smtp_use_tls=True, ) with pytest.raises(EmailError, match="authentication failed"): service.send_verification_code( "recipient@example.com", "123456", "example.com" ) class TestEmailServiceConnection: """Tests for test_connection method.""" @patch("gondulf.email.smtplib.SMTP") def test_connection_success_starttls(self, mock_smtp): """Test connection test succeeds with STARTTLS.""" mock_server = MagicMock() mock_smtp.return_value = mock_server service = EmailService( smtp_host="smtp.example.com", smtp_port=587, smtp_from="sender@example.com", smtp_username="user", smtp_password="pass", smtp_use_tls=True, ) assert service.test_connection() is True mock_smtp.assert_called_once() mock_server.starttls.assert_called_once() mock_server.login.assert_called_once() mock_server.quit.assert_called_once() @patch("gondulf.email.smtplib.SMTP_SSL") def test_connection_success_implicit_tls(self, mock_smtp_ssl): """Test connection test succeeds with implicit TLS.""" mock_server = MagicMock() mock_smtp_ssl.return_value = mock_server service = EmailService( smtp_host="smtp.example.com", smtp_port=465, smtp_from="sender@example.com", smtp_username="user", smtp_password="pass", ) assert service.test_connection() is True mock_smtp_ssl.assert_called_once() mock_server.login.assert_called_once() @patch("gondulf.email.smtplib.SMTP") def test_connection_failure(self, mock_smtp): """Test connection test fails gracefully.""" mock_smtp.side_effect = smtplib.SMTPException("Connection failed") service = EmailService( smtp_host="smtp.example.com", smtp_port=587, smtp_from="sender@example.com", ) assert service.test_connection() is False @patch("gondulf.email.smtplib.SMTP") def test_connection_without_credentials(self, mock_smtp): """Test connection test works without credentials.""" mock_server = MagicMock() mock_smtp.return_value = mock_server service = EmailService( smtp_host="localhost", smtp_port=25, smtp_from="sender@example.com", smtp_use_tls=False, ) assert service.test_connection() is True # Login should not be called without credentials assert not mock_server.login.called class TestEmailMessageContent: """Tests for email message content.""" @patch("gondulf.email.smtplib.SMTP") def test_message_contains_code(self, mock_smtp): """Test email message contains the verification code.""" mock_server = MagicMock() mock_smtp.return_value = mock_server service = EmailService( smtp_host="localhost", smtp_port=25, smtp_from="sender@example.com", ) service.send_verification_code("recipient@example.com", "ABC123", "example.com") # Get the message that was sent call_args = mock_server.send_message.call_args sent_message = call_args[0][0] # Verify message contains code message_body = sent_message.as_string() assert "ABC123" in message_body @patch("gondulf.email.smtplib.SMTP") def test_message_contains_domain(self, mock_smtp): """Test email message contains the domain being verified.""" mock_server = MagicMock() mock_smtp.return_value = mock_server service = EmailService( smtp_host="localhost", smtp_port=25, smtp_from="sender@example.com", ) service.send_verification_code( "recipient@example.com", "123456", "mydomain.com" ) # Get the message that was sent call_args = mock_server.send_message.call_args sent_message = call_args[0][0] message_body = sent_message.as_string() assert "mydomain.com" in message_body @patch("gondulf.email.smtplib.SMTP") def test_message_has_correct_headers(self, mock_smtp): """Test email message has correct From/To/Subject headers.""" mock_server = MagicMock() mock_smtp.return_value = mock_server service = EmailService( smtp_host="localhost", smtp_port=25, smtp_from="noreply@gondulf.example", ) service.send_verification_code("user@example.com", "123456", "example.com") # Get the message that was sent call_args = mock_server.send_message.call_args sent_message = call_args[0][0] assert sent_message["From"] == "noreply@gondulf.example" assert sent_message["To"] == "user@example.com" assert "example.com" in sent_message["Subject"]