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
Add tests for paid plans email feature
  • Loading branch information
Zahed-Riyaz committed Jun 26, 2025
commit 850c516891cd374eeae0b04d3e5d43335e5dba9c
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
db:
image: postgres:16.8
image: postgres:10.4
ports:
- "5432:5432"
env_file:
Expand Down
315 changes: 315 additions & 0 deletions tests/unit/challenges/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
get_file_content,
parse_submission_meta_attributes,
send_emails,
send_subscription_plans_email,
)
from django.conf import settings
from django.contrib.auth.models import User
Expand Down Expand Up @@ -378,3 +379,317 @@ def test_send_emails_to_multiple_recipients(
template_id=template_id,
template_data=template_data,
)


class SendSubscriptionPlansEmailTests(unittest.TestCase):
"""Test cases for send_subscription_plans_email function"""

def setUp(self):
"""Set up test fixtures"""
self.mock_challenge = MagicMock()
self.mock_challenge.pk = 123
self.mock_challenge.title = "Test Challenge"
self.mock_challenge.creator.team_name = "Test Host Team"
self.mock_challenge.creator.get_all_challenge_host_email.return_value = [
"host1@example.com",
"host2@example.com"
]
self.mock_challenge.image = None # No image by default

@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
):
"""Test successful email sending in production mode"""
# 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()
mock_email_class.return_value = mock_email_instance

# Call the function
send_subscription_plans_email(self.mock_challenge)

# 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_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
)

# Verify emails were created and sent
self.assertEqual(mock_email_class.call_count, 2)
self.assertEqual(mock_email_instance.attach_alternative.call_count, 2)
self.assertEqual(mock_email_instance.send.call_count, 2)

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

# Verify HTML content was attached
mock_email_instance.attach_alternative.assert_called_with(
"<html>Test Email Content</html>", "text/html"
)

# Verify logging
self.assertEqual(mock_logger.info.call_count, 3) # 2 individual + 1 summary
mock_logger.info.assert_any_call(
"Subscription plans email sent to host1@example.com for challenge 123"
)
mock_logger.info.assert_any_call(
"Subscription plans email sent to host2@example.com for challenge 123"
)
mock_logger.info.assert_any_call(
"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"""
# Setup mock challenge with no host emails
self.mock_challenge.creator.get_all_challenge_host_email.return_value = []

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify warning was logged and function returned early
mock_logger.warning.assert_called_once_with(
"No challenge host emails found for challenge 123"
)

@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.settings")
def test_send_subscription_plans_email_with_challenge_image(
self, mock_settings, 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>"

# Add image to challenge
mock_image = MagicMock()
mock_image.url = "https://example.com/challenge-image.jpg"
self.mock_challenge.image = mock_image

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify template rendering was called with image URL in context
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",
"challenge_image_url": "https://example.com/challenge-image.jpg",
}
mock_render_to_string.assert_called_once_with(
'challenges/subscription_plans_email.html', expected_context
)

@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
):
"""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
mock_email_instance1 = MagicMock()
mock_email_instance1.send.side_effect = Exception("SMTP Error")
mock_email_instance2 = MagicMock()

mock_email_class.side_effect = [mock_email_instance1, mock_email_instance2]

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify error was logged for failed email
mock_logger.error.assert_called_once_with(
"Failed to send subscription plans email to host1@example.com for challenge 123: SMTP Error"
)

# Verify success was logged for successful email
mock_logger.info.assert_any_call(
"Subscription plans email sent to host2@example.com for challenge 123"
)

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

@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.settings")
def test_send_subscription_plans_email_default_settings(
self, mock_settings, 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

# 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
)

@mockpatch("challenges.utils.logger")
def test_send_subscription_plans_email_general_exception(self, mock_logger):
"""Test handling of general exceptions during email sending process"""
# Make get_all_challenge_host_email raise an exception
self.mock_challenge.creator.get_all_challenge_host_email.side_effect = Exception("Database Error")

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify error was logged
mock_logger.error.assert_called_once_with(
"Error sending subscription plans email for challenge 123: Database Error"
)

@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
):
"""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
send_subscription_plans_email(self.mock_challenge)

# Verify error was logged
mock_logger.error.assert_called_once_with(
"Error sending subscription plans email for challenge 123: Template Error"
)

@mockpatch("challenges.utils.logger")
@mockpatch("challenges.utils.render_to_string")
@mockpatch("challenges.utils.settings")
def test_send_subscription_plans_email_single_host_email(
self, mock_settings, 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>"

# Set single host email
self.mock_challenge.creator.get_all_challenge_host_email.return_value = [
"singlehost@example.com"
]

# Call the function
send_subscription_plans_email(self.mock_challenge)

# Verify single email was simulated
mock_logger.info.assert_any_call(
"DEBUG MODE: Would send subscription plans email to singlehost@example.com for challenge 123"
)
mock_logger.info.assert_any_call(
"Simulated subscription plans email to 1/1 hosts for challenge 123"
)
Loading