Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4cbe80f
implement email logic
Zahed-Riyaz Jun 24, 2025
9ef7fcc
Add templates + refine email logic
Zahed-Riyaz Jun 25, 2025
850c516
Add tests for paid plans email feature
Zahed-Riyaz Jun 26, 2025
20e8cf6
Omit debugs
Zahed-Riyaz Jul 2, 2025
41f0b3e
Update utils.ppy
Zahed-Riyaz Jul 2, 2025
c057444
implement email logic
Zahed-Riyaz Jun 24, 2025
fb91c17
Add templates + refine email logic
Zahed-Riyaz Jun 25, 2025
c694e42
Add tests for paid plans email feature
Zahed-Riyaz Jun 26, 2025
66933ac
Omit debugs
Zahed-Riyaz Jul 2, 2025
210d8d1
Update utils.ppy
Zahed-Riyaz Jul 2, 2025
e77ef40
Refine tests
Zahed-Riyaz Jul 3, 2025
a17f453
Merge branch 'email-plan' of https://github.com/Zahed-Riyaz/EvalAI in…
Zahed-Riyaz Jul 3, 2025
952cb64
Modify tests
Zahed-Riyaz Jul 3, 2025
c360077
remove unnecessary imports
Zahed-Riyaz Jul 3, 2025
9c6cd0d
Update docker-compose.yml
Zahed-Riyaz Jul 3, 2025
6d8f6dc
Handle undefined variables
Zahed-Riyaz Jul 3, 2025
d61cecf
Update test_views.py
Zahed-Riyaz Jul 3, 2025
963ccea
Update env variables and tests
Zahed-Riyaz Jul 5, 2025
42db7bc
Update env variables and tests
Zahed-Riyaz Jul 5, 2025
8a91943
Update email
Zahed-Riyaz Jul 5, 2025
e9802a6
Merge branch 'master' into email-plan
Zahed-Riyaz Jul 5, 2025
0fadd8d
fix isort checks
Zahed-Riyaz Jul 5, 2025
5784799
Resolve Flake8 checks
Zahed-Riyaz Jul 5, 2025
bc60d74
Merge branch 'master' into email-plan
Zahed-Riyaz Jul 10, 2025
98c06bc
Merge branch 'master' into email-plan
Zahed-Riyaz Jul 15, 2025
065cdb3
Merge branch 'master' into email-plan
Zahed-Riyaz Jul 21, 2025
b0d9085
Merge branch 'Cloud-CV:master' into email-plan
Zahed-Riyaz Jul 30, 2025
8effb2d
Update email template
Zahed-Riyaz Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refine tests
  • Loading branch information
Zahed-Riyaz committed Jul 3, 2025
commit e77ef40569be0236eff5616b836e47bd8cb938d7
249 changes: 145 additions & 104 deletions tests/unit/challenges/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from django.conf import settings
from django.contrib.auth.models import User
from django.test import override_settings
from hosts.models import ChallengeHostTeam


Expand Down Expand Up @@ -396,18 +397,15 @@ def setUp(self):
]
self.mock_challenge.image = None # No image by default

@override_settings(CLOUDCV_TEAM_EMAIL="team@cloudcv.org")
@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.EmailMultiAlternatives")
@mockpatch("challenges.utils.settings")
def test_send_subscription_plans_email_success(
self, mock_settings, mock_email_class, mock_render_to_string, mock_logger
self, mock_email_class, mock_render_to_string, mock_logger
):
"""Test successful email sending in production mode"""
"""Test successful email sending with default localhost setting"""
# Setup mocks
mock_settings.DEBUG = False
mock_settings.EVALAI_API_SERVER = "https://evalai.cloudcv.org"
mock_settings.CLOUDCV_TEAM_EMAIL = "team@cloudcv.org"
mock_render_to_string.return_value = "<html>Test Email Content</html>"

mock_email_instance = MagicMock()
Expand All @@ -419,8 +417,8 @@ def test_send_subscription_plans_email_success(
# Verify template rendering was called with correct context
expected_context = {
"challenge_name": "Test Challenge",
"challenge_url": "https://evalai.cloudcv.org/web/challenges/challenge-page/123",
"challenge_manage_url": "https://evalai.cloudcv.org/web/challenges/challenge-page/123/manage",
"challenge_url": "http://localhost:8000/web/challenges/challenge-page/123",
"challenge_manage_url": "http://localhost:8000/web/challenges/challenge-page/123/manage",
"challenge_id": 123,
"host_team_name": "Test Host Team",
"support_email": "team@cloudcv.org",
Expand Down Expand Up @@ -465,49 +463,6 @@ def test_send_subscription_plans_email_success(
"Sent subscription plans email to 2/2 hosts for challenge 123"
)

@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.settings")
def test_send_subscription_plans_email_debug_mode(
self, mock_settings, mock_render_to_string, mock_logger
):
"""Test email sending in DEBUG mode (should only log, not send)"""
# Setup mocks
mock_settings.DEBUG = True
mock_settings.EVALAI_API_SERVER = "http://localhost:8000"
mock_settings.CLOUDCV_TEAM_EMAIL = "team@cloudcv.org"
mock_render_to_string.return_value = "<html>Test Email Content</html>"

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify template rendering was called
mock_render_to_string.assert_called_once()

# Verify debug logging
expected_context = {
"challenge_name": "Test Challenge",
"challenge_url": "http://localhost:8000/web/challenges/challenge-page/123",
"challenge_manage_url": "http://localhost:8000/web/challenges/challenge-page/123/manage",
"challenge_id": 123,
"host_team_name": "Test Host Team",
"support_email": "team@cloudcv.org",
}

mock_logger.info.assert_any_call(
"DEBUG MODE: Would send subscription plans email to host1@example.com for challenge 123"
)
mock_logger.info.assert_any_call(
"Email subject: EvalAI Subscription Plans - Challenge: Test Challenge"
)
mock_logger.info.assert_any_call(f"Email context: {expected_context}")
mock_logger.info.assert_any_call(
"Subscription plans email simulated to host1@example.com for challenge 123"
)
mock_logger.info.assert_any_call(
"Simulated subscription plans email to 2/2 hosts for challenge 123"
)

@mockpatch("challenges.utils.logger")
def test_send_subscription_plans_email_no_host_emails(self, mock_logger):
"""Test behavior when no challenge host emails are found"""
Expand All @@ -522,19 +477,20 @@ def test_send_subscription_plans_email_no_host_emails(self, mock_logger):
"No challenge host emails found for challenge 123"
)

@override_settings(CLOUDCV_TEAM_EMAIL="team@cloudcv.org")
@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.settings")
@mockpatch("challenges.utils.EmailMultiAlternatives")
def test_send_subscription_plans_email_with_challenge_image(
self, mock_settings, mock_render_to_string, mock_logger
self, mock_email_class, mock_render_to_string, mock_logger
):
"""Test email sending when challenge has an image"""
# Setup mocks
mock_settings.DEBUG = True
mock_settings.EVALAI_API_SERVER = "http://localhost:8000"
mock_settings.CLOUDCV_TEAM_EMAIL = "team@cloudcv.org"
mock_render_to_string.return_value = "<html>Test Email Content</html>"

mock_email_instance = MagicMock()
mock_email_class.return_value = mock_email_instance

# Add image to challenge
mock_image = MagicMock()
mock_image.url = "https://example.com/challenge-image.jpg"
Expand All @@ -557,18 +513,15 @@ def test_send_subscription_plans_email_with_challenge_image(
'challenges/subscription_plans_email.html', expected_context
)

@override_settings(CLOUDCV_TEAM_EMAIL="team@cloudcv.org")
@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.EmailMultiAlternatives")
@mockpatch("challenges.utils.settings")
def test_send_subscription_plans_email_individual_email_failure(
self, mock_settings, mock_email_class, mock_render_to_string, mock_logger
self, mock_email_class, mock_render_to_string, mock_logger
):
"""Test handling of individual email sending failures"""
# Setup mocks
mock_settings.DEBUG = False
mock_settings.EVALAI_API_SERVER = "https://evalai.cloudcv.org"
mock_settings.CLOUDCV_TEAM_EMAIL = "team@cloudcv.org"
mock_render_to_string.return_value = "<html>Test Email Content</html>"

# Make the first email fail, second succeed
Expand Down Expand Up @@ -597,38 +550,33 @@ def test_send_subscription_plans_email_individual_email_failure(
)

@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.settings")
def test_send_subscription_plans_email_default_settings(
self, mock_settings, mock_logger
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.EmailMultiAlternatives")
def test_send_subscription_plans_email_default_settings_fallback(
self, mock_email_class, mock_render_to_string, mock_logger
):
"""Test email sending with default settings fallback"""
# Setup mocks with no custom settings
mock_settings.DEBUG = True
del mock_settings.EVALAI_API_SERVER # Remove attribute to test getattr fallback
del mock_settings.CLOUDCV_TEAM_EMAIL # Remove attribute to test getattr fallback
"""Test email sending with default settings fallback when settings are not configured"""
# Setup mocks
mock_render_to_string.return_value = "<html>Test Email Content</html>"

# Mock getattr to return defaults
with mockpatch("challenges.utils.getattr") as mock_getattr:
mock_getattr.side_effect = lambda obj, attr, default: default

with mockpatch("challenges.utils.render_to_string") as mock_render_to_string:
mock_render_to_string.return_value = "<html>Test Email Content</html>"

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify template rendering was called with default values
expected_context = {
"challenge_name": "Test Challenge",
"challenge_url": "http://localhost:8000/web/challenges/challenge-page/123",
"challenge_manage_url": "http://localhost:8000/web/challenges/challenge-page/123/manage",
"challenge_id": 123,
"host_team_name": "Test Host Team",
"support_email": "team@cloudcv.org",
}
mock_render_to_string.assert_called_once_with(
'challenges/subscription_plans_email.html', expected_context
)
mock_email_instance = MagicMock()
mock_email_class.return_value = mock_email_instance

# Call the function without @override_settings to test default fallback
send_subscription_plans_email(self.mock_challenge)

# Verify template rendering was called with default fallback values
expected_context = {
"challenge_name": "Test Challenge",
"challenge_url": "http://localhost:8000/web/challenges/challenge-page/123",
"challenge_manage_url": "http://localhost:8000/web/challenges/challenge-page/123/manage",
"challenge_id": 123,
"host_team_name": "Test Host Team",
"support_email": "team@cloudcv.org",
}
mock_render_to_string.assert_called_once_with(
'challenges/subscription_plans_email.html', expected_context
)

@mockpatch("challenges.utils.logger")
def test_send_subscription_plans_email_general_exception(self, mock_logger):
Expand All @@ -644,17 +592,14 @@ def test_send_subscription_plans_email_general_exception(self, mock_logger):
"Error sending subscription plans email for challenge 123: Database Error"
)

@override_settings(CLOUDCV_TEAM_EMAIL="team@cloudcv.org")
@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.settings")
def test_send_subscription_plans_email_template_rendering_exception(
self, mock_settings, mock_render_to_string, mock_logger
self, mock_render_to_string, mock_logger
):
"""Test handling of template rendering exceptions"""
# Setup mocks
mock_settings.DEBUG = False
mock_settings.EVALAI_API_SERVER = "https://evalai.cloudcv.org"
mock_settings.CLOUDCV_TEAM_EMAIL = "team@cloudcv.org"
mock_render_to_string.side_effect = Exception("Template Error")

# Call the function
Expand All @@ -665,19 +610,20 @@ def test_send_subscription_plans_email_template_rendering_exception(
"Error sending subscription plans email for challenge 123: Template Error"
)

@override_settings(CLOUDCV_TEAM_EMAIL="team@cloudcv.org")
@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.settings")
@mockpatch("challenges.utils.EmailMultiAlternatives")
def test_send_subscription_plans_email_single_host_email(
self, mock_settings, mock_render_to_string, mock_logger
self, mock_email_class, mock_render_to_string, mock_logger
):
"""Test email sending with single host email"""
# Setup mocks
mock_settings.DEBUG = True
mock_settings.EVALAI_API_SERVER = "http://localhost:8000"
mock_settings.CLOUDCV_TEAM_EMAIL = "team@cloudcv.org"
mock_render_to_string.return_value = "<html>Test Email Content</html>"

mock_email_instance = MagicMock()
mock_email_class.return_value = mock_email_instance

# Set single host email
self.mock_challenge.creator.get_all_challenge_host_email.return_value = [
"singlehost@example.com"
Expand All @@ -686,10 +632,105 @@ def test_send_subscription_plans_email_single_host_email(
# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify single email was simulated
# Verify single email was sent
mock_email_class.assert_called_once_with(
subject="EvalAI Subscription Plans - Challenge: Test Challenge",
body="Please view this email in HTML format.",
from_email="team@cloudcv.org",
to=["singlehost@example.com"],
)
mock_logger.info.assert_any_call(
"DEBUG MODE: Would send subscription plans email to singlehost@example.com for challenge 123"
"Subscription plans email sent to singlehost@example.com for challenge 123"
)
mock_logger.info.assert_any_call(
"Sent subscription plans email to 1/1 hosts for challenge 123"
)

@override_settings(CLOUDCV_TEAM_EMAIL="team@cloudcv.org")
@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.EmailMultiAlternatives")
def test_send_subscription_plans_email_smtp_specific_exception(
self, mock_email_class, mock_render_to_string, mock_logger
):
"""Test handling of SMTP-specific exceptions"""
from smtplib import SMTPException

# Setup mocks
mock_render_to_string.return_value = "<html>Test Email Content</html>"

mock_email_instance = MagicMock()
mock_email_instance.send.side_effect = SMTPException("SMTP server unavailable")
mock_email_class.return_value = mock_email_instance

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify SMTP error was logged for each host
mock_logger.error.assert_any_call(
"Failed to send subscription plans email to host1@example.com for challenge 123: SMTP server unavailable"
)
mock_logger.error.assert_any_call(
"Failed to send subscription plans email to host2@example.com for challenge 123: SMTP server unavailable"
)

# Verify summary shows 0 success out of 2 attempts
mock_logger.info.assert_any_call(
"Simulated subscription plans email to 1/1 hosts for challenge 123"
"Sent subscription plans email to 0/2 hosts for challenge 123"
)

@override_settings(CLOUDCV_TEAM_EMAIL="team@cloudcv.org")
@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.EmailMultiAlternatives")
def test_send_subscription_plans_email_empty_challenge_title(
self, mock_email_class, mock_render_to_string, mock_logger
):
"""Test email sending when challenge has empty title"""
# Setup mocks
mock_render_to_string.return_value = "<html>Test Email Content</html>"

mock_email_instance = MagicMock()
mock_email_class.return_value = mock_email_instance

# Set empty challenge title
self.mock_challenge.title = ""

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify email was created with empty title
mock_email_class.assert_any_call(
subject="EvalAI Subscription Plans - Challenge: ",
body="Please view this email in HTML format.",
from_email="team@cloudcv.org",
to=["host1@example.com"],
)

@override_settings(CLOUDCV_TEAM_EMAIL="team@cloudcv.org")
@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.EmailMultiAlternatives")
def test_send_subscription_plans_email_unicode_challenge_title(
self, mock_email_class, mock_render_to_string, mock_logger
):
"""Test email sending when challenge has unicode characters in title"""
# Setup mocks
mock_render_to_string.return_value = "<html>Test Email Content</html>"

mock_email_instance = MagicMock()
mock_email_class.return_value = mock_email_instance

# Set unicode challenge title
self.mock_challenge.title = "测试挑战 🚀 Challenge"

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify email was created with unicode title
mock_email_class.assert_any_call(
subject="EvalAI Subscription Plans - Challenge: 测试挑战 🚀 Challenge",
body="Please view this email in HTML format.",
from_email="team@cloudcv.org",
to=["host1@example.com"],
)
Loading