241 lines
No EOL
9.4 KiB
Python
241 lines
No EOL
9.4 KiB
Python
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from app.services.emailer import EmailService
|
|
|
|
|
|
class TestEmailService:
|
|
"""Test email service functionality"""
|
|
|
|
@pytest.fixture
|
|
def email_service(self):
|
|
"""Create email service with mocked SendGrid client"""
|
|
with patch('app.services.emailer.settings') as mock_settings:
|
|
mock_settings.sendgrid_api_key = "test_api_key"
|
|
mock_settings.email_from = "support@example.com"
|
|
|
|
with patch('app.services.emailer.SendGridAPIClient') as mock_client:
|
|
service = EmailService()
|
|
service.client = MagicMock()
|
|
return service
|
|
|
|
@pytest.fixture
|
|
def sample_download_links(self):
|
|
"""Sample download links for testing"""
|
|
return {
|
|
"en": {
|
|
"captions_vtt": "https://signed-url.example.com/en/captions.vtt",
|
|
"audio_description_vtt": "https://signed-url.example.com/en/ad.vtt",
|
|
"audio_description_mp3": "https://signed-url.example.com/en/ad.mp3"
|
|
},
|
|
"es": {
|
|
"captions_vtt": "https://signed-url.example.com/es/captions.vtt",
|
|
"audio_description_vtt": "https://signed-url.example.com/es/ad.vtt",
|
|
"audio_description_mp3": "https://signed-url.example.com/es/ad.mp3"
|
|
}
|
|
}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_completion_email_success(self, email_service, sample_download_links):
|
|
"""Test successful completion email sending"""
|
|
# Mock successful SendGrid response
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 202
|
|
email_service.client.send.return_value = mock_response
|
|
|
|
result = await email_service.send_completion_email(
|
|
recipient_email="client@example.com",
|
|
job_title="Test Video Project",
|
|
download_links=sample_download_links
|
|
)
|
|
|
|
assert result is True
|
|
email_service.client.send.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_completion_email_no_client(self):
|
|
"""Test email sending when client is not configured"""
|
|
service = EmailService()
|
|
service.client = None
|
|
|
|
result = await service.send_completion_email(
|
|
recipient_email="client@example.com",
|
|
job_title="Test Video",
|
|
download_links={}
|
|
)
|
|
|
|
assert result is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_completion_email_api_failure(self, email_service, sample_download_links):
|
|
"""Test email sending with SendGrid API failure"""
|
|
# Mock failed SendGrid response
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 400
|
|
email_service.client.send.return_value = mock_response
|
|
|
|
result = await email_service.send_completion_email(
|
|
recipient_email="client@example.com",
|
|
job_title="Test Video",
|
|
download_links=sample_download_links
|
|
)
|
|
|
|
assert result is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_completion_email_exception(self, email_service, sample_download_links):
|
|
"""Test email sending with exception"""
|
|
# Mock SendGrid client raising exception
|
|
email_service.client.send.side_effect = Exception("SendGrid error")
|
|
|
|
result = await email_service.send_completion_email(
|
|
recipient_email="client@example.com",
|
|
job_title="Test Video",
|
|
download_links=sample_download_links
|
|
)
|
|
|
|
assert result is False
|
|
|
|
def test_render_completion_template_basic(self, email_service, sample_download_links):
|
|
"""Test rendering completion email template"""
|
|
html_content = email_service._render_completion_template(
|
|
job_title="Test Video Project",
|
|
download_links=sample_download_links
|
|
)
|
|
|
|
# Check that key elements are present
|
|
assert "Test Video Project" in html_content
|
|
assert "EN Assets" in html_content
|
|
assert "ES Assets" in html_content
|
|
assert "captions.vtt" in html_content
|
|
assert "audio_description_vtt" in html_content
|
|
assert "audio_description_mp3" in html_content
|
|
assert "24 hours" in html_content # Expiry warning
|
|
|
|
def test_render_completion_template_single_language(self, email_service):
|
|
"""Test rendering template with single language"""
|
|
download_links = {
|
|
"en": {
|
|
"captions_vtt": "https://example.com/captions.vtt"
|
|
}
|
|
}
|
|
|
|
html_content = email_service._render_completion_template(
|
|
job_title="English Only Video",
|
|
download_links=download_links
|
|
)
|
|
|
|
assert "English Only Video" in html_content
|
|
assert "EN Assets" in html_content
|
|
assert "ES Assets" not in html_content
|
|
|
|
def test_render_completion_template_no_downloads(self, email_service):
|
|
"""Test rendering template with no download links"""
|
|
html_content = email_service._render_completion_template(
|
|
job_title="Empty Job",
|
|
download_links={}
|
|
)
|
|
|
|
assert "Empty Job" in html_content
|
|
assert "<!DOCTYPE html>" in html_content
|
|
assert "24 hours" in html_content
|
|
|
|
def test_render_completion_template_html_structure(self, email_service, sample_download_links):
|
|
"""Test that rendered template has proper HTML structure"""
|
|
html_content = email_service._render_completion_template(
|
|
job_title="Test Video",
|
|
download_links=sample_download_links
|
|
)
|
|
|
|
# Check HTML structure
|
|
assert html_content.startswith("<!DOCTYPE html>")
|
|
assert "<html>" in html_content
|
|
assert "</html>" in html_content
|
|
assert "<head>" in html_content
|
|
assert "<body>" in html_content
|
|
assert "font-family: Arial" in html_content # CSS present
|
|
|
|
def test_render_completion_template_download_link_formatting(self, email_service):
|
|
"""Test that download links are properly formatted in template"""
|
|
download_links = {
|
|
"en": {
|
|
"captions_vtt": "https://example.com/captions.vtt",
|
|
"audio_description_mp3": "https://example.com/ad.mp3"
|
|
}
|
|
}
|
|
|
|
html_content = email_service._render_completion_template(
|
|
job_title="Test Video",
|
|
download_links=download_links
|
|
)
|
|
|
|
# Check that file types are properly formatted
|
|
assert "Download Captions Vtt" in html_content
|
|
assert "Download Audio Description Mp3" in html_content
|
|
assert 'href="https://example.com/captions.vtt"' in html_content
|
|
assert 'href="https://example.com/ad.mp3"' in html_content
|
|
|
|
def test_service_initialization_with_api_key(self):
|
|
"""Test service initialization with SendGrid API key"""
|
|
with patch('app.services.emailer.settings') as mock_settings:
|
|
mock_settings.sendgrid_api_key = "test_api_key"
|
|
|
|
with patch('app.services.emailer.SendGridAPIClient') as mock_client:
|
|
service = EmailService()
|
|
|
|
mock_client.assert_called_once_with(api_key="test_api_key")
|
|
assert service.client is not None
|
|
|
|
def test_service_initialization_without_api_key(self):
|
|
"""Test service initialization without SendGrid API key"""
|
|
with patch('app.services.emailer.settings') as mock_settings:
|
|
mock_settings.sendgrid_api_key = ""
|
|
|
|
service = EmailService()
|
|
|
|
assert service.client is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_completion_email_mail_object_creation(self, email_service, sample_download_links):
|
|
"""Test that Mail object is created correctly"""
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 202
|
|
email_service.client.send.return_value = mock_response
|
|
|
|
with patch('app.services.emailer.Mail') as mock_mail:
|
|
mock_mail_instance = MagicMock()
|
|
mock_mail.return_value = mock_mail_instance
|
|
|
|
await email_service.send_completion_email(
|
|
recipient_email="client@example.com",
|
|
job_title="Test Video",
|
|
download_links=sample_download_links
|
|
)
|
|
|
|
# Verify Mail object was created with correct parameters
|
|
mock_mail.assert_called_once()
|
|
call_args = mock_mail.call_args
|
|
|
|
# Check that from_email, to_emails, subject, and html_content are set
|
|
assert call_args is not None
|
|
email_service.client.send.assert_called_once_with(mock_mail_instance)
|
|
|
|
def test_template_injection_safety(self, email_service):
|
|
"""Test that template is safe from injection attacks"""
|
|
malicious_title = "<script>alert('xss')</script>Malicious Title"
|
|
malicious_links = {
|
|
"en": {
|
|
"captions_vtt": "javascript:alert('xss')"
|
|
}
|
|
}
|
|
|
|
html_content = email_service._render_completion_template(
|
|
job_title=malicious_title,
|
|
download_links=malicious_links
|
|
)
|
|
|
|
# Jinja2 should escape HTML by default
|
|
assert "<script>" not in html_content
|
|
assert "javascript:" in html_content # URL would still be there but not executed
|
|
assert "Malicious Title" in html_content |