Files

170 lines
6.4 KiB
Python

"""Tests for email service (T05)."""
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from email.mime.multipart import MIMEMultipart
from app.services.email_service import EmailService
@pytest.mark.asyncio
async def test_send_contact_email_success():
"""send_contact_email sends correct email via aiosmtplib."""
service = EmailService()
contact_data = {
"name": "Test User",
"email": "test@example.com",
"phone": "+49 123 456",
"message": "Hallo Welt",
}
with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock) as mock_send:
result = await service.send_contact_email(contact_data)
assert result is True
mock_send.assert_awaited_once()
sent_msg = mock_send.call_args[0][0]
assert isinstance(sent_msg, MIMEMultipart)
assert "Hallo Welt" in str(sent_msg)
assert "Test User" in str(sent_msg)
@pytest.mark.asyncio
async def test_send_rental_confirmation_success():
"""send_rental_confirmation sends email with reference_number and items."""
service = EmailService()
items = [
{"equipment_name": "L-Acoustics K2", "quantity": 2},
{"equipment_name": "d&b T10", "quantity": 4},
]
with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock) as mock_send:
result = await service.send_rental_confirmation(
to_addr="customer@example.com",
reference_number="HMS-2026-00042",
event_name="Rock Festival",
items=items,
)
assert result is True
mock_send.assert_awaited_once()
sent_msg = mock_send.call_args[0][0]
msg_str = str(sent_msg)
assert "HMS-2026-00042" in msg_str
assert "Rock Festival" in msg_str
assert "L-Acoustics K2" in msg_str
assert "d&b T10" in msg_str
@pytest.mark.asyncio
async def test_send_email_smtp_failure_returns_false():
"""SMTP failure returns False, no crash, graceful handling."""
service = EmailService()
with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock, side_effect=Exception("SMTP refused")):
result = await service.send_email(
to_addr="fail@example.com",
subject="Test Subject",
html_body="<p>Test</p>",
text_body="Test",
)
assert result is False
@pytest.mark.asyncio
async def test_send_contact_email_smtp_failure_returns_false():
"""send_contact_email returns False on SMTP failure without crashing."""
service = EmailService()
with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock, side_effect=Exception("Connection refused")):
result = await service.send_contact_email({
"name": "Fail User",
"email": "fail@example.com",
"phone": "+49 000",
"message": "Soll fehlschlagen",
})
assert result is False
@pytest.mark.asyncio
async def test_send_rental_confirmation_smtp_failure_returns_false():
"""send_rental_confirmation returns False on SMTP failure without crashing."""
service = EmailService()
with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock, side_effect=Exception("Timeout")):
result = await service.send_rental_confirmation(
to_addr="fail@example.com",
reference_number="HMS-2026-99999",
event_name="Fail Event",
items=[{"equipment_name": "Speaker", "quantity": 1}],
)
assert result is False
@pytest.mark.asyncio
async def test_send_email_failure_saves_to_queue():
"""send_email saves failed email to email_queue when db is provided."""
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from app.database import Base
from app.models.email_queue import EmailQueue
from sqlalchemy import select
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
service = EmailService()
with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock, side_effect=Exception("SMTP down")):
async with session_maker() as db:
result = await service.send_email(
to_addr="queue@example.com",
subject="Queued Email",
html_body="<p>Body</p>",
text_body="Body",
reply_to="reply@example.com",
db=db,
)
assert result is False
async with session_maker() as db:
queued = await db.execute(select(EmailQueue).where(EmailQueue.to_addr == "queue@example.com"))
item = queued.scalar_one_or_none()
assert item is not None
assert item.subject == "Queued Email"
assert item.status == "pending"
assert item.attempts == 0
assert item.reply_to == "reply@example.com"
await engine.dispose()
@pytest.mark.asyncio
async def test_retry_failed_emails_resends_pending():
"""retry_failed_emails retries pending queue items and marks sent on success."""
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from app.database import Base
from app.models.email_queue import EmailQueue
from sqlalchemy import select
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with session_maker() as db:
db.add(EmailQueue(
to_addr="retry@example.com",
subject="Retry Test",
html_body="<p>Retry</p>",
text_body="Retry",
status="pending",
attempts=0,
))
await db.commit()
with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock):
async with session_maker() as db:
sent_count = await EmailService.retry_failed_emails(db)
assert sent_count == 1
async with session_maker() as db:
result = await db.execute(select(EmailQueue).where(EmailQueue.to_addr == "retry@example.com"))
item = result.scalar_one_or_none()
assert item.status == "sent"
assert item.attempts == 1
await engine.dispose()