"""Tests for email service.""" import logging from unittest.mock import patch import pytest from src.services.email import EmailService class TestEmailService: """Test suite for EmailService.""" def test_init_with_api_key(self, app): """Test EmailService initialization with API key.""" with app.app_context(): app.config["RESEND_API_KEY"] = "test-api-key" service = EmailService() assert service.api_key == "test-api-key" assert service.dev_mode is False def test_init_in_dev_mode(self, app): """Test EmailService initialization in development mode.""" with app.app_context(): app.config["FLASK_ENV"] = "development" service = EmailService() assert service.dev_mode is True def test_init_without_api_key_in_production(self, app): """Test EmailService raises error without API key in production.""" with app.app_context(): app.config["RESEND_API_KEY"] = None app.config["FLASK_ENV"] = "production" with pytest.raises(ValueError, match="RESEND_API_KEY"): EmailService() def test_init_without_api_key_in_dev_mode(self, app): """Test EmailService allows missing API key in dev mode.""" with app.app_context(): app.config["RESEND_API_KEY"] = None app.config["FLASK_ENV"] = "development" service = EmailService() assert service.api_key is None assert service.dev_mode is True @patch("resend.Emails.send") def test_send_email_success(self, mock_send, app): """Test successful email sending.""" with app.app_context(): app.config["RESEND_API_KEY"] = "test-api-key" app.config["FLASK_ENV"] = "production" service = EmailService() mock_send.return_value = {"id": "email-123"} result = service.send_email( to="test@example.com", subject="Test Subject", html_body="
Test body
", ) assert result == {"id": "email-123"} mock_send.assert_called_once() # Check the first positional argument (the dict) call_args = mock_send.call_args[0][0] assert call_args["to"] == ["test@example.com"] assert call_args["subject"] == "Test Subject" assert call_args["html"] == "Test body
" @patch("resend.Emails.send") def test_send_email_with_from_address(self, mock_send, app): """Test email sending with custom from address.""" with app.app_context(): app.config["RESEND_API_KEY"] = "test-api-key" app.config["EMAIL_FROM"] = "custom@example.com" app.config["FLASK_ENV"] = "production" service = EmailService() mock_send.return_value = {"id": "email-123"} service.send_email( to="test@example.com", subject="Test Subject", html_body="Test body
", ) call_args = mock_send.call_args[0][0] assert call_args["from"] == "custom@example.com" @patch("resend.Emails.send") def test_send_email_in_dev_mode_logs_instead(self, mock_send, app, caplog): """Test that email sending logs in dev mode instead of actually sending.""" with app.app_context(): app.config["FLASK_ENV"] = "development" service = EmailService() with caplog.at_level(logging.INFO): result = service.send_email( to="test@example.com", subject="Test Subject", html_body="Test body
", ) # Should not actually send email mock_send.assert_not_called() # Should log the email details assert "DEV MODE: Email not sent" in caplog.text assert "test@example.com" in caplog.text assert "Test Subject" in caplog.text # Should return a mock success response assert result["id"].startswith("dev-mode-") @patch("resend.Emails.send") def test_send_email_handles_resend_error(self, mock_send, app): """Test email sending handles Resend API errors.""" with app.app_context(): app.config["RESEND_API_KEY"] = "test-api-key" app.config["FLASK_ENV"] = "production" service = EmailService() mock_send.side_effect = Exception("API Error") with pytest.raises(Exception, match="API Error"): service.send_email( to="test@example.com", subject="Test Subject", html_body="Test body
", ) def test_send_magic_link_email(self, app): """Test sending magic link email.""" with app.app_context(): app.config["FLASK_ENV"] = "development" service = EmailService() with patch.object(service, "send_email") as mock_send: mock_send.return_value = {"id": "email-123"} result = service.send_magic_link( to="test@example.com", magic_link_url="https://example.com/magic/abc123", exchange_name="Secret Santa 2025", ) assert result == {"id": "email-123"} mock_send.assert_called_once() call_args = mock_send.call_args[1] assert call_args["to"] == "test@example.com" assert "Secret Santa 2025" in call_args["subject"] assert "https://example.com/magic/abc123" in call_args["html_body"] def test_send_registration_confirmation_email(self, app): """Test sending registration confirmation email.""" with app.app_context(): app.config["FLASK_ENV"] = "development" service = EmailService() with patch.object(service, "send_email") as mock_send: mock_send.return_value = {"id": "email-123"} result = service.send_registration_confirmation( to="test@example.com", participant_name="John Doe", magic_link_url="https://example.com/magic/abc123", exchange_name="Secret Santa 2025", exchange_description="Annual office Secret Santa", budget_amount=50, gift_exchange_date="2025-12-20", ) assert result == {"id": "email-123"} mock_send.assert_called_once() call_args = mock_send.call_args[1] assert call_args["to"] == "test@example.com" assert "Secret Santa 2025" in call_args["subject"] assert "John Doe" in call_args["html_body"] assert "https://example.com/magic/abc123" in call_args["html_body"] assert "Annual office Secret Santa" in call_args["html_body"] assert "$50" in call_args["html_body"] assert "2025-12-20" in call_args["html_body"] def test_dev_mode_logs_magic_link_url(self, app, caplog): """Test that magic link URLs are logged in dev mode.""" with app.app_context(): app.config["FLASK_ENV"] = "development" service = EmailService() with caplog.at_level(logging.INFO): service.send_magic_link( to="test@example.com", magic_link_url="https://example.com/magic/abc123", exchange_name="Secret Santa 2025", ) assert ( "DEV MODE: Magic link URL: https://example.com/magic/abc123" in caplog.text )