""" Unit tests for bulk-email-related models. """ import datetime from unittest.mock import Mock, patch import pytest import ddt from django.core.management import call_command from django.test import TestCase from opaque_keys.edx.keys import CourseKey from pytz import UTC from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.bulk_email.api import is_bulk_email_feature_enabled from lms.djangoapps.bulk_email.models import ( SEND_TO_COHORT, SEND_TO_STAFF, SEND_TO_TRACK, BulkEmailFlag, CourseAuthorization, CourseEmail, CourseEmailTemplate, Optout ) from openedx.core.djangoapps.course_groups.models import CourseCohort from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @ddt.ddt @patch('lms.djangoapps.bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True)) # lint-amnesty, pylint: disable=line-too-long class CourseEmailTest(ModuleStoreTestCase): """Test the CourseEmail model.""" def test_creation(self): course_id = CourseKey.from_string('abc/123/doremi') sender = UserFactory.create() to_option = SEND_TO_STAFF subject = "dummy subject" html_message = "dummy message" email = CourseEmail.create(course_id, sender, [to_option], subject, html_message) assert email.course_id == course_id assert SEND_TO_STAFF in [target.target_type for target in email.targets.all()] assert email.subject == subject assert email.html_message == html_message assert email.sender == sender def test_creation_with_optional_attributes(self): course_id = CourseKey.from_string('abc/123/doremi') sender = UserFactory.create() to_option = SEND_TO_STAFF subject = "dummy subject" html_message = "dummy message" template_name = "branded_template" from_addr = "branded@branding.com" email = CourseEmail.create( course_id, sender, [to_option], subject, html_message, template_name=template_name, from_addr=from_addr ) assert email.course_id == course_id assert email.targets.all()[0].target_type == SEND_TO_STAFF assert email.subject == subject assert email.html_message == html_message assert email.sender == sender assert email.template_name == template_name assert email.from_addr == from_addr def test_bad_to_option(self): course_id = CourseKey.from_string('abc/123/doremi') sender = UserFactory.create() to_option = "fake" subject = "dummy subject" html_message = "dummy message" with pytest.raises(ValueError): CourseEmail.create(course_id, sender, to_option, subject, html_message) @ddt.data( datetime.datetime(1999, 1, 1, tzinfo=UTC), datetime.datetime(datetime.MAXYEAR, 1, 1, tzinfo=UTC), ) def test_track_target(self, expiration_datetime): """ Tests that emails can be sent to a specific track. Also checks that emails can be sent to an expired track (EDUCATOR-364) """ course = CourseFactory.create() course_id = course.id sender = UserFactory.create() to_option = 'track:test' subject = "dummy subject" html_message = "dummy message" CourseMode.objects.create( mode_slug='test', mode_display_name='Test', course_id=course_id, expiration_datetime=expiration_datetime, ) email = CourseEmail.create(course_id, sender, [to_option], subject, html_message) assert len(email.targets.all()) == 1 target = email.targets.all()[0] assert target.target_type == SEND_TO_TRACK assert target.short_display() == 'track-test' assert target.long_display() == 'Course mode: Test, Currency: usd' @ddt.data( CourseMode.AUDIT, CourseMode.HONOR, ) def test_track_target_with_free_mode(self, free_mode): """ Tests that when emails are sent to a free track the track display should not contain currency. """ course = CourseFactory.create() mode_display_name = free_mode.capitalize course_id = course.id sender = UserFactory.create() to_option = f'track:{free_mode}' subject = "dummy subject" html_message = "dummy message" CourseMode.objects.create( mode_slug=free_mode, mode_display_name=mode_display_name, course_id=course_id, ) email = CourseEmail.create(course_id, sender, [to_option], subject, html_message) assert len(email.targets.all()) == 1 target = email.targets.all()[0] assert target.target_type == SEND_TO_TRACK assert target.short_display() == f'track-{free_mode}' assert target.long_display() == f'Course mode: {mode_display_name}' def test_cohort_target(self): course_id = CourseKey.from_string('abc/123/doremi') sender = UserFactory.create() to_option = 'cohort:test cohort' subject = "dummy subject" html_message = "dummy message" CourseCohort.create(cohort_name='test cohort', course_id=course_id) email = CourseEmail.create(course_id, sender, [to_option], subject, html_message) assert len(email.targets.all()) == 1 target = email.targets.all()[0] assert target.target_type == SEND_TO_COHORT assert target.short_display() == 'cohort-test cohort' assert target.long_display() == 'Cohort: test cohort' class OptoutTest(TestCase): # lint-amnesty, pylint: disable=missing-class-docstring def test_is_user_opted_out_for_course(self): user = UserFactory.create() course_id = CourseKey.from_string('abc/123/doremi') assert not Optout.is_user_opted_out_for_course(user, course_id) Optout.objects.create( user=user, course_id=course_id, ) assert Optout.is_user_opted_out_for_course(user, course_id) class NoCourseEmailTemplateTest(TestCase): """Test the CourseEmailTemplate model without loading the template data.""" def test_get_missing_template(self): with pytest.raises(CourseEmailTemplate.DoesNotExist): CourseEmailTemplate.get_template() class CourseEmailTemplateTest(TestCase): """Test the CourseEmailTemplate model.""" def setUp(self): super().setUp() # load initial content (since we don't run migrations as part of tests): call_command("loaddata", "course_email_template.json") def _get_sample_plain_context(self): """Provide sample context sufficient for rendering plaintext template""" context = { 'course_title': "Bogus Course Title", 'course_url': "/location/of/course/url", 'email_settings_url': "/location/of/email/settings/url", 'platform_name': 'edX', 'email': 'your-email@test.com', 'unsubscribe_link': '/bulk_email/email/optout/dummy' } return context def _get_sample_html_context(self): """Provide sample context sufficient for rendering HTML template""" context = self._get_sample_plain_context() context['course_image_url'] = "/location/of/course/image/url" return context def _add_xss_fields(self, context): """ Add fields to the context for XSS testing. """ context['course_title'] = "