"""Unit tests for email service.
These tests verify email template rendering and HTML formatting.
"""
from unittest.mock import AsyncMock, patch
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.models.notification_template import NotificationTemplate
from backend.app.services.email_service import (
create_password_reset_email_from_template,
create_welcome_email_from_template,
)
class TestEmailTemplateFormatting:
"""Tests for email template formatting."""
@pytest.mark.asyncio
async def test_welcome_email_newlines_converted_to_br(self):
"""Verify that newlines in welcome email body are converted to
tags."""
# Mock database session
db = AsyncMock(spec=AsyncSession)
# Mock template with newlines
template = NotificationTemplate(
event_type="user_created",
name="Welcome Email",
title_template="Welcome to {app_name}",
body_template="Hello {username}!\n\nYour password is: {password}\n\nPlease login at: {login_url}",
is_default=True,
)
# Patch get_notification_template to return our template
with patch("backend.app.services.email_service.get_notification_template", return_value=template):
# Generate email
subject, text_body, html_body = await create_welcome_email_from_template(
db=db,
username="testuser",
password="testpass123",
login_url="http://example.com/login",
app_name="TestApp",
)
# Verify subject
assert subject == "Welcome to TestApp"
# Verify text body has newlines
assert "\n\n" in text_body
assert "Hello testuser!" in text_body
assert "Your password is: testpass123" in text_body
# Verify HTML body has
tags instead of relying on CSS
assert "
" in html_body
# Should not use white-space: pre-wrap
assert "white-space: pre-wrap" not in html_body
# Should have proper structure
assert "" in html_body
assert '
' in html_body
# Verify that escaped content is present (XSS protection)
assert "Hello testuser!
" in html_body
assert "Your password is: testpass123
" in html_body
@pytest.mark.asyncio
async def test_password_reset_email_newlines_converted_to_br(self):
"""Verify that newlines in password reset email body are converted to
tags."""
# Mock database session
db = AsyncMock(spec=AsyncSession)
# Mock template with newlines
template = NotificationTemplate(
event_type="password_reset",
name="Password Reset",
title_template="{app_name} - Password Reset",
body_template="Hello {username},\n\nYour password has been reset.\nNew password: {password}\n\nLogin at: {login_url}",
is_default=True,
)
# Patch get_notification_template to return our template
with patch("backend.app.services.email_service.get_notification_template", return_value=template):
# Generate email
subject, text_body, html_body = await create_password_reset_email_from_template(
db=db,
username="testuser",
password="newpass456",
login_url="http://example.com/login",
app_name="TestApp",
)
# Verify subject
assert subject == "TestApp - Password Reset"
# Verify text body has newlines
assert "\n\n" in text_body
assert "Hello testuser," in text_body
# Verify HTML body has
tags
assert "
" in html_body
# Should not use white-space: pre-wrap
assert "white-space: pre-wrap" not in html_body
# Should have security alert
assert "Security Alert" in html_body
@pytest.mark.asyncio
async def test_email_header_padding(self):
"""Verify that email header has proper padding to prevent cutoff."""
# Mock database session
db = AsyncMock(spec=AsyncSession)
# Mock template
template = NotificationTemplate(
event_type="user_created",
name="Welcome Email",
title_template="Welcome",
body_template="Test body",
is_default=True,
)
# Patch get_notification_template to return our template
with patch("backend.app.services.email_service.get_notification_template", return_value=template):
# Generate email
subject, text_body, html_body = await create_welcome_email_from_template(
db=db,
username="testuser",
password="testpass123",
login_url="http://example.com/login",
)
# Verify header has 30px padding (not 20px which was cutting off)
assert "padding: 30px; border-radius: 8px 8px 0 0;" in html_body
@pytest.mark.asyncio
async def test_email_xss_protection(self):
"""Verify that HTML escaping is applied to prevent XSS attacks."""
# Mock database session
db = AsyncMock(spec=AsyncSession)
# Mock template with potential XSS content
template = NotificationTemplate(
event_type="user_created",
name="Welcome Email",
title_template="Welcome ",
body_template="Hello \nTest",
is_default=True,
)
# Patch get_notification_template to return our template
with patch("backend.app.services.email_service.get_notification_template", return_value=template):
# Generate email
subject, text_body, html_body = await create_welcome_email_from_template(
db=db,
username="testuser",
password="testpass123",
login_url="http://example.com/login",
)
# Verify that script tags are escaped
assert "<script>" in html_body
# Verify no unescaped script tags
assert "