Add tests for EmailTemplate and NotificationLog models

Introduced comprehensive unit tests for EmailTemplate and NotificationLog, including creation, field validation, and custom methods. Added relevant test fixtures to support these models and expanded `conftest.py` for reusability. Validates functionality and maintains model integrity.
This commit is contained in:
2025-02-28 15:16:23 +01:00
parent b8c7d63d91
commit c15c55d691
3 changed files with 272 additions and 1 deletions

View File

@@ -5,7 +5,8 @@ from datetime import datetime, timezone
import pytest
from app.models import GiftItem, GiftStatus, GiftPriority, RSVP, RSVPStatus, EventMedia, MediaType, MediaPurpose, \
EventTheme, Guest, GuestStatus, ActivityType, ActivityLog
EventTheme, Guest, GuestStatus, ActivityType, ActivityLog, EmailTemplate, TemplateType, NotificationLog, \
NotificationType, NotificationStatus
from app.models.user import User
from app.utils.test_utils import setup_test_db, teardown_test_db
@@ -193,3 +194,48 @@ def activity_log_fixture(db_session, mock_user, guest_fixture):
db_session.add(activity_log_entry)
db_session.commit()
return activity_log_entry
@pytest.fixture
def email_template_fixture(db_session, mock_user):
"""
Fixture to create and return a default EmailTemplate instance.
"""
email_template = EmailTemplate(
id=uuid.uuid4(),
name="Invitation Template",
description="A template for event invitations.",
template_type=TemplateType.INVITATION,
subject="You're invited to {{event_name}}!",
html_content="<h1>Welcome to {{event_name}}</h1><p>We look forward to seeing you!</p>",
text_content="Welcome to {{event_name}}. We look forward to seeing you!",
variables={"event_name": "Birthday Party"},
event_id=None, # Global template
created_by=mock_user.id,
is_active=True,
is_system=False,
)
db_session.add(email_template)
db_session.commit()
return email_template
@pytest.fixture
def notification_log_fixture(db_session, email_template_fixture, guest_fixture):
"""
Fixture to create and return a default NotificationLog entry.
"""
notification_log = NotificationLog(
id=uuid.uuid4(),
notification_type=NotificationType.EMAIL,
status=NotificationStatus.QUEUED,
subject="You're invited!",
content_preview="Join us for an unforgettable event.",
template_id=email_template_fixture.id,
event_id=guest_fixture.event_id,
guest_id=guest_fixture.id,
recipient="example@example.com",
notification_metadata={"extra_info": "Email sent via SendGrid"},
)
db_session.add(notification_log)
db_session.commit()
return notification_log

View File

@@ -0,0 +1,120 @@
# tests/models/test_email_template.py
import uuid
import sqlalchemy
from app.models.email_template import EmailTemplate, TemplateType
def test_create_email_template(db_session, mock_user):
# Arrange
email_template = EmailTemplate(
id=uuid.uuid4(),
name="Reminder Template",
description="A template for sending reminders.",
template_type=TemplateType.REMINDER,
subject="Reminder: {{event_name}} is happening soon!",
html_content="<h1>Reminder: {{event_name}}</h1><p>Don't miss it!</p>",
text_content="Reminder: {{event_name}}. Don't miss it!",
variables={"event_name": "Birthday Party"},
event_id=None, # Global template
created_by=mock_user.id,
is_active=True,
is_system=False,
)
db_session.add(email_template)
# Act
db_session.commit()
created_template = db_session.query(EmailTemplate).filter_by(id=email_template.id).first()
# Assert
assert created_template is not None
assert created_template.name == "Reminder Template"
assert created_template.description == "A template for sending reminders."
assert created_template.template_type == TemplateType.REMINDER
assert created_template.subject == "Reminder: {{event_name}} is happening soon!"
assert created_template.html_content == "<h1>Reminder: {{event_name}}</h1><p>Don't miss it!</p>"
assert created_template.text_content == "Reminder: {{event_name}}. Don't miss it!"
assert created_template.variables == {"event_name": "Birthday Party"}
assert created_template.created_by == mock_user.id
assert created_template.is_active is True
assert created_template.is_system is False
def test_render_email_template(email_template_fixture):
# Arrange
context = {"event_name": "Emma's 1st Birthday"}
# Act
rendered_subject, rendered_html, rendered_text = email_template_fixture.render(context)
# Assert
assert rendered_subject == "You're invited to Emma's 1st Birthday!"
assert rendered_html == "<h1>Welcome to Emma's 1st Birthday</h1><p>We look forward to seeing you!</p>"
assert rendered_text == "Welcome to Emma's 1st Birthday. We look forward to seeing you!"
def test_unique_event_template_name_constraint(db_session, email_template_fixture, mock_user):
# Arrange: Ensure both templates share the same event_id
event_id = uuid.uuid4()
email_template_fixture.event_id = event_id # Set the event_id for the existing fixture
db_session.commit()
# Attempt to create another template with the same name for the same event
duplicate_template = EmailTemplate(
id=uuid.uuid4(),
name=email_template_fixture.name,
description="Another template with the same name.",
template_type=TemplateType.INVITATION,
subject="Duplicate Subject",
html_content="<p>Duplicate Content</p>",
text_content="Duplicate Content",
variables={},
event_id=event_id, # Use the same event_id
created_by=mock_user.id,
is_active=True,
is_system=False,
)
db_session.add(duplicate_template)
# Act & Assert
try:
db_session.commit()
assert False, "Expected IntegrityError due to unique constraint on event-specific template names."
except sqlalchemy.exc.IntegrityError as e:
# Validate that the constraint error relates to `uq_event_template_name`
assert "uq_event_template_name" in str(e.orig) or "email_templates.event_id, email_templates.name" in str(
e.orig)
def test_template_is_active_field(email_template_fixture):
# Assert
assert email_template_fixture.is_active is True
# Deactivate the template
email_template_fixture.is_active = False
# Assert
assert email_template_fixture.is_active is False
def test_template_is_system_field(email_template_fixture):
# Assert
assert email_template_fixture.is_system is False
# Promote to system template
email_template_fixture.is_system = True
# Assert
assert email_template_fixture.is_system is True
def test_repr_method(email_template_fixture):
# Act
repr_output = repr(email_template_fixture)
# Assert
assert repr_output == f"<EmailTemplate {email_template_fixture.name} ({email_template_fixture.template_type.value})>"

View File

@@ -0,0 +1,105 @@
# tests/models/test_notification_log.py
import uuid
from datetime import datetime, timezone
from app.models.notification_log import NotificationLog, NotificationType, NotificationStatus
def test_create_notification_log(db_session, email_template_fixture, guest_fixture):
# Arrange
notification_log = NotificationLog(
id=uuid.uuid4(),
notification_type=NotificationType.SMS,
status=NotificationStatus.QUEUED,
subject="Event Reminder",
content_preview="Don't forget the event!",
template_id=email_template_fixture.id,
event_id=guest_fixture.event_id,
guest_id=guest_fixture.id,
recipient="+1234567890",
notification_metadata={"provider": "Twilio", "priority": "High"},
)
db_session.add(notification_log)
# Act
db_session.commit()
created_log = db_session.query(NotificationLog).filter_by(id=notification_log.id).first()
# Assert
assert created_log is not None
assert created_log.notification_type == NotificationType.SMS
assert created_log.status == NotificationStatus.QUEUED
assert created_log.subject == "Event Reminder"
assert created_log.content_preview == "Don't forget the event!"
assert created_log.template_id == email_template_fixture.id
assert created_log.event_id == guest_fixture.event_id
assert created_log.guest_id == guest_fixture.id
assert created_log.recipient == "+1234567890"
assert created_log.notification_metadata == {"provider": "Twilio", "priority": "High"}
def test_mark_sent(notification_log_fixture):
# Act
notification_log_fixture.mark_sent()
# Assert
assert notification_log_fixture.status == NotificationStatus.SENT
assert notification_log_fixture.sent_at is not None
assert notification_log_fixture.sent_at < datetime.now(timezone.utc)
def test_mark_delivered(notification_log_fixture):
# Act
notification_log_fixture.mark_delivered()
# Assert
assert notification_log_fixture.status == NotificationStatus.DELIVERED
assert notification_log_fixture.delivered_at is not None
assert notification_log_fixture.delivered_at < datetime.now(timezone.utc)
def test_mark_opened(notification_log_fixture):
# Act
notification_log_fixture.mark_opened()
# Assert
assert notification_log_fixture.status == NotificationStatus.OPENED
assert notification_log_fixture.opened_at is not None
assert notification_log_fixture.opened_at < datetime.now(timezone.utc)
def test_mark_failed(notification_log_fixture):
# Act
notification_log_fixture.mark_failed("Undeliverable address")
# Assert
assert notification_log_fixture.status == NotificationStatus.FAILED
assert notification_log_fixture.error_message == "Undeliverable address"
assert notification_log_fixture.retry_count == 1
def test_can_retry_property(notification_log_fixture):
# Arrange
notification_log_fixture.retry_count = 2
notification_log_fixture.status = NotificationStatus.FAILED # Ensure initial FAILED status
# Act: Call mark_failed and inspect retry_count/state
notification_log_fixture.mark_failed("Temporary issue")
# Assert: Still retryable since retry_count < 3
assert notification_log_fixture.retry_count == 3 # Ensure retry_count incremented
assert notification_log_fixture.status == NotificationStatus.FAILED # Ensure status is 'FAILED'
assert notification_log_fixture.can_retry is False # Now retries are exhausted
# Reset retry count and revalidate
notification_log_fixture.retry_count = 1
can_retry = notification_log_fixture.can_retry
assert can_retry is True # Should be retryable with retry_count = 1
def test_repr_method(notification_log_fixture):
# Act
repr_output = repr(notification_log_fixture)
# Assert
assert repr_output == f"<NotificationLog {notification_log_fixture.notification_type.value} to {notification_log_fixture.recipient} status={notification_log_fixture.status.value}>"