def test_financial_aid_email_with_blank_subject_and_body(self, mock_post): """ Test that MailgunClient.send_financial_aid_email() sends an individual message with blank subject and blank email, and that the audit record saves correctly """ assert FinancialAidEmailAudit.objects.count() == 0 MailgunClient.send_financial_aid_email(self.staff_user_profile.user, self.financial_aid, '', '') # Check method call assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'] == '' assert called_kwargs['data']['subject'] == '' assert called_kwargs['data']['to'] == [self.financial_aid.user.email] assert called_kwargs['data']['from'] == settings.EMAIL_SUPPORT # Check audit creation assert FinancialAidEmailAudit.objects.count() == 1 audit = FinancialAidEmailAudit.objects.first() assert audit.acting_user == self.staff_user_profile.user assert audit.financial_aid == self.financial_aid assert audit.to_email == self.financial_aid.user.email assert audit.from_email == settings.EMAIL_SUPPORT assert audit.email_subject == '' assert audit.email_body == ''
def test_financial_aid_email_with_blank_subject_and_body(self, mock_post): """ Test that MailgunClient.send_financial_aid_email() sends an individual message with blank subject and blank email, and that the audit record saves correctly """ assert FinancialAidEmailAudit.objects.count() == 0 MailgunClient.send_financial_aid_email( self.staff_user_profile.user, self.financial_aid, '', '' ) # Check method call assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'] == '' assert called_kwargs['data']['subject'] == '' assert called_kwargs['data']['to'] == [self.financial_aid.user.email] assert called_kwargs['data']['from'] == settings.EMAIL_SUPPORT # Check audit creation assert FinancialAidEmailAudit.objects.count() == 1 audit = FinancialAidEmailAudit.objects.first() assert audit.acting_user == self.staff_user_profile.user assert audit.financial_aid == self.financial_aid assert audit.to_email == self.financial_aid.user.email assert audit.from_email == settings.EMAIL_SUPPORT assert audit.email_subject == '' assert audit.email_body == ''
def save(self, **kwargs): """ Save method for this serializer """ self.instance.status = self.validated_data["action"] if self.instance.status == FinancialAidStatus.APPROVED: self.instance.tier_program = self.validated_data["tier_program"] self.instance.justification = self.validated_data["justification"] elif self.instance.status == FinancialAidStatus.PENDING_MANUAL_APPROVAL: # This is intentionally left blank for clarity that this is a valid status for .save() pass elif self.instance.status == FinancialAidStatus.RESET: self.instance.justification = "Reset via the financial aid review form" # also saves history of this change in FinancialAidAudit. self.instance.save_and_log(self.context["request"].user) # Send email notification MailgunClient.send_financial_aid_email( acting_user=self.context["request"].user, financial_aid=self.instance, **generate_financial_aid_email(self.instance) ) return self.instance
def test_send_batch(self, sender_name, mock_post): """ Test that MailgunClient.send_batch sends expected parameters to the Mailgun API Base case with only one batch call to the Mailgun API. """ email_body = '<h1>A title</h1><p> and some text <a href="www.google.com">google</a></p>' MailgunClient.send_batch('email subject', email_body, self.batch_recipient_arg, sender_name=sender_name) assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data'][ 'text'] == "A title and some text www.google.com" assert called_kwargs['data']['html'] == email_body assert called_kwargs['data']['subject'] == 'email subject' assert sorted(called_kwargs['data']['to']) == sorted( [email for email, _ in self.batch_recipient_arg]) assert called_kwargs['data']['recipient-variables'] == json.dumps({ '*****@*****.**': {}, '*****@*****.**': { 'name': 'B' }, }) if sender_name is not None: self.assertEqual( called_kwargs['data']['from'], "{sender_name} <{email}>".format(sender_name=sender_name, email=settings.EMAIL_SUPPORT)) else: self.assertEqual(called_kwargs['data']['from'], settings.EMAIL_SUPPORT)
def test_send_batch_chunk(self, mock_post): """ Test that MailgunClient.send_batch chunks recipients """ chunk_size = 10 emails_to = [ "{0}@example.com".format(letter) for letter in string.ascii_letters ] chuncked_emails_to = [ emails_to[i:i + chunk_size] for i in range(0, len(emails_to), chunk_size) ] assert len(emails_to) == 52 MailgunClient.send_batch('email subject', 'email body', emails_to, chunk_size=chunk_size) assert mock_post.called assert mock_post.call_count == 6 for call_num, args in enumerate(mock_post.call_args_list): called_args, called_kwargs = args assert list(called_args)[0] == '{}/{}'.format( settings.MAILGUN_URL, 'messages') assert called_kwargs['data']['text'].startswith('email body') assert called_kwargs['data']['subject'] == 'email subject' assert called_kwargs['data']['to'] == chuncked_emails_to[call_num] assert called_kwargs['data']['recipient-variables'] == json.dumps( {email: {} for email in chuncked_emails_to[call_num]})
def test_send_batch_exception(self, mock_post): """ Test that MailgunClient.send_batch returns a non-zero error code where the mailgun API returns a non-zero code """ mock_post.side_effect = KeyError chunk_size = 10 recipient_tuples = [("{0}@example.com".format(letter), None) for letter in string.ascii_letters] chunked_emails_to = [recipient_tuples[i:i + chunk_size] for i in range(0, len(recipient_tuples), chunk_size)] assert len(recipient_tuples) == 52 with self.assertRaises(SendBatchException) as send_batch_exception: MailgunClient.send_batch('email subject', 'email body', recipient_tuples, chunk_size=chunk_size) assert mock_post.called assert mock_post.call_count == 6 for call_num, args in enumerate(mock_post.call_args_list): called_args, called_kwargs = args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['data']['text'].startswith('email body') assert called_kwargs['data']['subject'] == 'email subject' assert sorted(called_kwargs['data']['to']) == sorted([email for email, _ in chunked_emails_to[call_num]]) assert called_kwargs['data']['recipient-variables'] == json.dumps( {email: context or {} for email, context in chunked_emails_to[call_num]} ) exception_pairs = send_batch_exception.exception.exception_pairs assert len(exception_pairs) == 6 for call_num, (recipients, exception) in enumerate(exception_pairs): assert sorted(recipients) == sorted([email for email, _ in chunked_emails_to[call_num]]) assert isinstance(exception, KeyError)
def test_send_batch(self, sender_name, mock_post): """ Test that MailgunClient.send_batch sends expected parameters to the Mailgun API Base case with only one batch call to the Mailgun API. """ email_body = '<h1>A title</h1><p> and some text <a href="www.google.com">google</a></p>' MailgunClient.send_batch('email subject', email_body, self.batch_recipient_arg, sender_name=sender_name) assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'] == "A title and some text www.google.com" assert called_kwargs['data']['html'] == email_body assert called_kwargs['data']['subject'] == 'email subject' assert sorted(called_kwargs['data']['to']) == sorted([email for email, _ in self.batch_recipient_arg]) assert called_kwargs['data']['recipient-variables'] == json.dumps( { '*****@*****.**': {}, '*****@*****.**': {'name': 'B'}, } ) if sender_name is not None: self.assertEqual( called_kwargs['data']['from'], "{sender_name} <{email}>".format(sender_name=sender_name, email=settings.EMAIL_SUPPORT) ) else: self.assertEqual(called_kwargs['data']['from'], settings.EMAIL_SUPPORT)
def test_send_batch_recipient_override(self, mock_post): """ Test that MailgunClient.send_batch works properly with recipient override enabled """ MailgunClient.send_batch('subject', 'body', self.batch_recipient_arg, sender_name='sender') assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'] == """body [overridden recipient] [email protected]: {} [email protected]: {"name": "B"}""" assert called_kwargs['data']['subject'] == 'subject' assert called_kwargs['data']['to'] == ['*****@*****.**'] assert called_kwargs['data']['recipient-variables'] == json.dumps({ '*****@*****.**': {}, }) self.assertEqual(called_kwargs['data']['from'], "sender <*****@*****.**>")
def test_send_batch_error(self, recipient_override, mock_post): """ Test that MailgunClient.send_batch returns a non-zero error code where the mailgun API returns a non-zero code """ mock_post.return_value = Response() mock_post.return_value.status_code = HTTP_400_BAD_REQUEST chunk_size = 10 recipient_tuples = [ ("{0}@example.com".format(letter), {"letter": letter}) for letter in string.ascii_letters ] chunked_emails_to = [ recipient_tuples[i : i + chunk_size] for i in range(0, len(recipient_tuples), chunk_size) ] assert len(recipient_tuples) == 52 with override_settings( MAILGUN_RECIPIENT_OVERRIDE=recipient_override, ), self.assertRaises(SendBatchException) as send_batch_exception: MailgunClient.send_batch( "email subject", "html", "text", recipient_tuples, chunk_size=chunk_size ) if recipient_override is None: assert mock_post.call_count == 6 else: assert mock_post.call_count == 1 chunked_emails_to = [[(recipient_override, None)]] for call_num, args in enumerate(mock_post.call_args_list): called_args, called_kwargs = args assert list(called_args)[0] == "{}/{}".format( settings.MAILGUN_URL, "messages" ) assert called_kwargs["data"]["html"].startswith("html") assert called_kwargs["data"]["text"].startswith("text") assert called_kwargs["data"]["subject"] == "email subject" assert sorted(called_kwargs["data"]["to"]) == sorted( [email for email, _ in chunked_emails_to[call_num]] ) assert called_kwargs["data"]["recipient-variables"] == json.dumps( {email: context or {} for email, context in chunked_emails_to[call_num]} ) exception_pairs = send_batch_exception.exception.exception_pairs if recipient_override is None: assert len(exception_pairs) == 6 for call_num, (recipients, exception) in enumerate(exception_pairs): assert sorted(recipients) == sorted( [email for email, _ in chunked_emails_to[call_num]] ) assert isinstance(exception, HTTPError) else: assert len(exception_pairs) == 1 assert exception_pairs[0][0] == [recipient_override] assert isinstance(exception_pairs[0][1], HTTPError)
def test_email_override(self, mock_post): """ Test that an email override value will be used over recipients specified in MailgunClient.send_bcc """ MailgunClient.send_bcc('email subject', 'email body', ['*****@*****.**', '*****@*****.**']) _, called_kwargs = mock_post.call_args assert called_kwargs['data']['bcc'] == '*****@*****.**'
def test_no_email_override(self, mock_post): """ Test that recipients passed to MailgunClient.send_bcc will be used when no email override exists """ MailgunClient.send_bcc('email subject', 'email body', ['*****@*****.**', '*****@*****.**']) _, called_kwargs = mock_post.call_args assert called_kwargs['data']['bcc'] == '[email protected],[email protected]'
def post(self, request, *args, **kargs): # pylint: disable=unused-argument """ POST method handler """ email_subject = request.data['email_subject'] email_body = request.data['email_body'] + get_email_footer( request.build_absolute_uri('/settings')) sender_name = full_name(request.user) search_obj = create_search_obj( request.user, search_param_dict=request.data.get('search_request'), filter_on_email_optin=True) emails = get_all_query_matching_emails(search_obj) if request.data.get('send_automatic_emails'): automatic_email = add_automatic_email( search_obj, email_subject=request.data['email_subject'], email_body=email_body, sender_name=sender_name, staff_user=request.user, ) try: with mark_emails_as_sent(automatic_email, emails) as user_ids: # user_ids should be all users with the matching email in emails # except some who were already sent email in the meantime recipient_emails = list( User.objects.filter(id__in=user_ids).values_list( 'email', flat=True)) MailgunClient.send_batch( subject=email_subject, body=email_body, recipients=( (context['email'], context) for context in get_mail_vars(recipient_emails)), sender_name=sender_name, ) except SendBatchException as send_batch_exception: success_emails = set(emails).difference( send_batch_exception.failed_recipient_emails) with mark_emails_as_sent(automatic_email, success_emails): pass raise else: MailgunClient.send_batch( subject=email_subject, body=email_body, recipients=((context['email'], context) for context in get_mail_vars(emails)), sender_name=sender_name, ) return Response(status=status.HTTP_200_OK, data={})
def test_from_address(self, mock_post): """ Test that the 'from' address for our emails is set correctly """ # NOTE: Using patch.multiple to override settings values because Django's # override_settings decorator fails to work for mysterious reasons MailgunClient.send_bcc('email subject', 'email body', ['*****@*****.**']) _, called_kwargs = mock_post.call_args assert called_kwargs['data'][ 'from'] == '*****@*****.**'
def test_send_to_course_team_without_email(self, mock_post): """ Tests that an attempt to send an email to a course with no contact email will fail """ course_no_email = CourseFactory.create(title='course no email', contact_email=None) with self.assertRaises(ImproperlyConfigured) as ex: MailgunClient.send_course_team_email(self.user, course_no_email, 'email subject', 'email body') assert ex.exception.args[0].startswith( 'Course team contact email attempted') assert not mock_post.called
def test_send_to_course_team_without_email(self, mock_post): """ Tests that an attempt to send an email to a course with no contact email will fail """ course_no_email = CourseFactory.create(title='course no email', contact_email=None) with self.assertRaises(ImproperlyConfigured) as ex: MailgunClient.send_course_team_email( self.user, course_no_email, 'email subject', 'email body' ) assert ex.exception.args[0].startswith('Course team contact email attempted') assert not mock_post.called
def test_send_bcc(self, mock_post): """ Test that MailgunClient.send_bcc sends expected parameters to the Mailgun API """ MailgunClient.send_bcc('email subject', 'email body', ['*****@*****.**', '*****@*****.**']) assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'].startswith('email body') assert called_kwargs['data']['subject'] == 'email subject' assert called_kwargs['data']['to'] == settings.MAILGUN_BCC_TO_EMAIL
def test_send_batch_improperly_configured(self, mock_post): """ If MailgunClient.send_batch returns a 401, it should raise a ImproperlyConfigured exception """ mock_post.return_value = Mock( spec=Response, status_code=HTTP_401_UNAUTHORIZED, ) chunk_size = 10 recipient_pairs = [("{0}@example.com".format(letter), None) for letter in string.ascii_letters] with self.assertRaises(ImproperlyConfigured) as ex: MailgunClient.send_batch('email subject', 'email body', recipient_pairs, chunk_size=chunk_size) assert ex.exception.args[0] == "Mailgun API keys not properly configured."
def post(self, request, *args, **kargs): # pylint: disable=unused-argument """ POST method handler """ email_subject = request.data['email_subject'] email_body = request.data['email_body'] + get_email_footer(request.build_absolute_uri('/settings')) sender_name = full_name(request.user) search_obj = create_search_obj( request.user, search_param_dict=request.data.get('search_request'), filter_on_email_optin=True ) emails = get_all_query_matching_emails(search_obj) if request.data.get('send_automatic_emails'): automatic_email = add_automatic_email( search_obj, email_subject=request.data['email_subject'], email_body=email_body, sender_name=sender_name, staff_user=request.user, ) try: with mark_emails_as_sent(automatic_email, emails) as user_ids: # user_ids should be all users with the matching email in emails # except some who were already sent email in the meantime recipient_emails = list(User.objects.filter(id__in=user_ids).values_list('email', flat=True)) MailgunClient.send_batch( subject=email_subject, body=email_body, recipients=((context['email'], context) for context in get_mail_vars(recipient_emails)), sender_name=sender_name, ) except SendBatchException as send_batch_exception: success_emails = set(emails).difference(send_batch_exception.failed_recipient_emails) with mark_emails_as_sent(automatic_email, success_emails): pass raise else: MailgunClient.send_batch( subject=email_subject, body=email_body, recipients=((context['email'], context) for context in get_mail_vars(emails)), sender_name=sender_name, ) return Response(status=status.HTTP_200_OK, data={})
def test_send_with_sender_address(self, mock_post): """ Test that specifying a sender address in our mail API functions will result in an email with the sender address in the 'from' field """ sender_address = '*****@*****.**' MailgunClient.send_batch( 'email subject', 'email body', self.batch_recipient_arg, sender_address=sender_address ) MailgunClient.send_individual_email( 'email subject', 'email body', self.individual_recipient_arg, sender_address=sender_address ) for args in mock_post.call_args_list: _, called_kwargs = args assert called_kwargs['data']['from'] == sender_address
def test_send_batch_400_no_raise(self, mock_post): """ Test that if raise_for_status is False we don't raise an exception for a 400 response """ mock_post.return_value = Mock( spec=Response, status_code=HTTP_400_BAD_REQUEST, json=mocked_json() ) chunk_size = 10 recipient_tuples = [("{0}@example.com".format(letter), None) for letter in string.ascii_letters] assert len(recipient_tuples) == 52 with override_settings( MAILGUN_RECIPIENT_OVERRIDE=None, ): resp_list = MailgunClient.send_batch( 'email subject', 'email body', recipient_tuples, chunk_size=chunk_size, raise_for_status=False ) assert len(resp_list) == 6 for resp in resp_list: assert resp.status_code == HTTP_400_BAD_REQUEST assert mock_post.call_count == 6 assert mock_post.return_value.raise_for_status.called is False
def post(self, request, *args, **kargs): # pylint: disable=unused-argument """ POST method handler """ sender_user = request.user school = self.get_object() enrollment = get_object_or_404(ProgramEnrollment, hash=request.data['enrollment_hash']) mailgun_response = MailgunClient.send_individual_email( subject="MicroMasters Program Record", body=render_to_string( 'grades_record_email.html', { 'user_full_name': sender_user.profile.full_name, 'pathway_name': school.name, 'program_name': enrollment.program.title, 'record_link': request.build_absolute_uri( reverse('grade_records', args=[request.data['enrollment_hash']])), }), recipient=school.email, sender_address=sender_user.email, sender_name=sender_user.profile.display_name, ) return Response(status=mailgun_response.status_code, data=generate_mailgun_response_json(mailgun_response))
def test_financial_aid_email(self, mock_post): """ Test that MailgunClient.send_financial_aid_email() sends an individual message """ assert FinancialAidEmailAudit.objects.count() == 0 response = MailgunClient.send_financial_aid_email( self.staff_user_profile.user, self.financial_aid, 'email subject', 'email body') assert response.status_code == HTTP_200_OK # Check method call assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'] == 'email body' assert called_kwargs['data']['subject'] == 'email subject' assert called_kwargs['data']['to'] == [self.financial_aid.user.email] assert called_kwargs['data']['from'] == settings.EMAIL_SUPPORT # Check audit creation assert FinancialAidEmailAudit.objects.count() == 1 audit = FinancialAidEmailAudit.objects.first() assert audit.acting_user == self.staff_user_profile.user assert audit.financial_aid == self.financial_aid assert audit.to_email == self.financial_aid.user.email assert audit.from_email == settings.EMAIL_SUPPORT assert audit.email_subject == 'email subject' assert audit.email_body == 'email body'
def test_send_batch_400_no_raise(self, mock_post): """ Test that if raise_for_status is False we don't raise an exception for a 400 response """ mock_post.return_value = Mock( spec=Response, status_code=HTTP_400_BAD_REQUEST, json=mocked_json() ) chunk_size = 10 recipient_tuples = [ ("{0}@example.com".format(letter), None) for letter in string.ascii_letters ] assert len(recipient_tuples) == 52 with override_settings( MAILGUN_RECIPIENT_OVERRIDE=None, ): resp_list = MailgunClient.send_batch( "email subject", "html", "text", recipient_tuples, chunk_size=chunk_size, raise_for_status=False, ) assert len(resp_list) == 6 for resp in resp_list: assert resp.status_code == HTTP_400_BAD_REQUEST assert mock_post.call_count == 6 assert mock_post.return_value.raise_for_status.called is False
def test_send_individual_email(self, sender_name, mock_post): """ Test that MailgunClient.send_individual_email() sends an individual message """ context = {'abc': {'def': 'xyz'}} response = MailgunClient.send_individual_email( subject='email subject', body='email body', recipient='*****@*****.**', recipient_variables=context, sender_name=sender_name, ) assert response.status_code == HTTP_200_OK assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'].startswith('email body') assert called_kwargs['data']['subject'] == 'email subject' assert called_kwargs['data']['to'] == ['*****@*****.**'] assert called_kwargs['data']['recipient-variables'] == json.dumps( {'*****@*****.**': context}) if sender_name is not None: self.assertEqual( called_kwargs['data']['from'], "{sender_name} <{email}>".format(sender_name=sender_name, email=settings.EMAIL_SUPPORT)) else: self.assertEqual(called_kwargs['data']['from'], settings.EMAIL_SUPPORT)
def test_send_individual_email(self, sender_name, mock_post): """ Test that MailgunClient.send_individual_email() sends an individual message """ context = {"abc": {"def": "xyz"}} response = MailgunClient.send_individual_email( subject="email subject", html_body="html", text_body="text", recipient="*****@*****.**", recipient_variables=context, sender_name=sender_name, ) assert response.status_code == HTTP_200_OK assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == "{}/{}".format(settings.MAILGUN_URL, "messages") assert called_kwargs["auth"] == ("api", settings.MAILGUN_KEY) assert called_kwargs["data"]["html"].startswith("html") assert called_kwargs["data"]["text"].startswith("text") assert called_kwargs["data"]["subject"] == "email subject" assert called_kwargs["data"]["to"] == ["*****@*****.**"] assert called_kwargs["data"]["recipient-variables"] == json.dumps( {"*****@*****.**": context} ) if sender_name is not None: self.assertEqual( called_kwargs["data"]["from"], "{sender_name} <{email}>".format( sender_name=sender_name, email=settings.EMAIL_SUPPORT ), ) else: self.assertEqual(called_kwargs["data"]["from"], settings.EMAIL_SUPPORT)
def test_send_batch_chunk(self, mock_post): """ Test that MailgunClient.send_batch chunks recipients """ chunk_size = 10 recipient_tuples = [ ("{0}@example.com".format(letter), None) for letter in string.ascii_letters ] chunked_emails_to = [ recipient_tuples[i : i + chunk_size] for i in range(0, len(recipient_tuples), chunk_size) ] assert len(recipient_tuples) == 52 responses = MailgunClient.send_batch( "email subject", "html", "text", recipient_tuples, chunk_size=chunk_size ) assert mock_post.called assert mock_post.call_count == 6 for call_num, args in enumerate(mock_post.call_args_list): called_args, called_kwargs = args assert list(called_args)[0] == "{}/{}".format( settings.MAILGUN_URL, "messages" ) assert called_kwargs["data"]["html"].startswith("html") assert called_kwargs["data"]["text"].startswith("text") assert called_kwargs["data"]["subject"] == "email subject" assert sorted(called_kwargs["data"]["to"]) == sorted( [email for email, _ in chunked_emails_to[call_num]] ) assert called_kwargs["data"]["recipient-variables"] == json.dumps( {email: context or {} for email, context in chunked_emails_to[call_num]} ) response = responses[call_num] assert response.status_code == HTTP_200_OK
def test_financial_aid_email(self, mock_post): """ Test that MailgunClient.send_financial_aid_email() sends an individual message """ assert FinancialAidEmailAudit.objects.count() == 0 response = MailgunClient.send_financial_aid_email( self.staff_user_profile.user, self.financial_aid, 'email subject', 'email body' ) assert response.status_code == HTTP_200_OK # Check method call assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'] == 'email body' assert called_kwargs['data']['subject'] == 'email subject' assert called_kwargs['data']['to'] == [self.financial_aid.user.email] assert called_kwargs['data']['from'] == settings.EMAIL_SUPPORT # Check audit creation assert FinancialAidEmailAudit.objects.count() == 1 audit = FinancialAidEmailAudit.objects.first() assert audit.acting_user == self.staff_user_profile.user assert audit.financial_aid == self.financial_aid assert audit.to_email == self.financial_aid.user.email assert audit.from_email == settings.EMAIL_SUPPORT assert audit.email_subject == 'email subject' assert audit.email_body == 'email body'
def test_send_individual_email(self, sender_name, mock_post): """ Test that MailgunClient.send_individual_email() sends an individual message """ context = {'abc': {'def': 'xyz'}} response = MailgunClient.send_individual_email( subject='email subject', body='email body', recipient='*****@*****.**', recipient_variables=context, sender_name=sender_name, ) assert response.status_code == HTTP_200_OK assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'].startswith('email body') assert called_kwargs['data']['subject'] == 'email subject' assert called_kwargs['data']['to'] == ['*****@*****.**'] assert called_kwargs['data']['recipient-variables'] == json.dumps({'*****@*****.**': context}) if sender_name is not None: self.assertEqual( called_kwargs['data']['from'], "{sender_name} <{email}>".format(sender_name=sender_name, email=settings.EMAIL_SUPPORT) ) else: self.assertEqual(called_kwargs['data']['from'], settings.EMAIL_SUPPORT)
def post(self, request, *args, **kargs): # pylint: disable=unused-argument """ POST method handler """ sender_user = request.user school = self.get_object() enrollment = get_object_or_404(ProgramEnrollment, hash=request.data['enrollment_hash']) mailgun_response = MailgunClient.send_individual_email( subject="MicroMasters Program Record", body=render_to_string( 'grades_record_email.html', { 'user_full_name': sender_user.profile.full_name, 'pathway_name': school.name, 'program_name': enrollment.program.title, 'record_link': request.build_absolute_uri( reverse('grade_records', args=[request.data['enrollment_hash']]) ), }), recipient=school.email, sender_address=sender_user.email, sender_name=sender_user.profile.display_name, ) return Response( status=mailgun_response.status_code, data=generate_mailgun_response_json(mailgun_response) )
def test_send_with_sender_address(self, mock_post): """ Test that specifying a sender address in our mail API functions will result in an email with the sender address in the 'from' field """ sender_address = '*****@*****.**' MailgunClient.send_batch('email subject', 'email body', self.batch_recipient_arg, sender_address=sender_address) MailgunClient.send_individual_email('email subject', 'email body', self.individual_recipient_arg, sender_address=sender_address) for args in mock_post.call_args_list: _, called_kwargs = args assert called_kwargs['data']['from'] == sender_address
def test_send_batch_improperly_configured(self, mock_post): """ If MailgunClient.send_batch returns a 401, it should raise a ImproperlyConfigured exception """ mock_post.return_value = Mock( spec=Response, status_code=HTTP_401_UNAUTHORIZED, ) chunk_size = 10 recipient_pairs = [ ("{0}@example.com".format(letter), None) for letter in string.ascii_letters ] with self.assertRaises(ImproperlyConfigured) as ex: MailgunClient.send_batch( "email subject", "html", "text", recipient_pairs, chunk_size=chunk_size ) assert ex.exception.args[0] == "Mailgun API keys not properly configured."
def test_send_batch(self, mock_post): """ Test that MailgunClient.send_batch sends expected parameters to the Mailgun API Base case with only one batch call to the Mailgun API. """ emails_to = ['*****@*****.**', '*****@*****.**'] MailgunClient.send_batch('email subject', 'email body', emails_to) assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'].startswith('email body') assert called_kwargs['data']['subject'] == 'email subject' assert called_kwargs['data']['to'] == emails_to assert called_kwargs['data']['recipient-variables'] == json.dumps( {email: {} for email in emails_to})
def test_send_batch_exception(self, mock_post): """ Test that MailgunClient.send_batch returns a non-zero error code where the mailgun API returns a non-zero code """ mock_post.side_effect = KeyError chunk_size = 10 recipient_tuples = [ ("{0}@example.com".format(letter), None) for letter in string.ascii_letters ] chunked_emails_to = [ recipient_tuples[i : i + chunk_size] for i in range(0, len(recipient_tuples), chunk_size) ] assert len(recipient_tuples) == 52 with self.assertRaises(SendBatchException) as send_batch_exception: MailgunClient.send_batch( "email subject", "html", "text", recipient_tuples, chunk_size=chunk_size ) assert mock_post.called assert mock_post.call_count == 6 for call_num, args in enumerate(mock_post.call_args_list): called_args, called_kwargs = args assert list(called_args)[0] == "{}/{}".format( settings.MAILGUN_URL, "messages" ) assert called_kwargs["data"]["html"].startswith("html") assert called_kwargs["data"]["text"].startswith("text") assert called_kwargs["data"]["subject"] == "email subject" assert sorted(called_kwargs["data"]["to"]) == sorted( [email for email, _ in chunked_emails_to[call_num]] ) assert called_kwargs["data"]["recipient-variables"] == json.dumps( {email: context or {} for email, context in chunked_emails_to[call_num]} ) exception_pairs = send_batch_exception.exception.exception_pairs assert len(exception_pairs) == 6 for call_num, (recipients, exception) in enumerate(exception_pairs): assert sorted(recipients) == sorted( [email for email, _ in chunked_emails_to[call_num]] ) assert isinstance(exception, KeyError)
def test_send_batch_error(self, recipient_override, mock_post): """ Test that MailgunClient.send_batch returns a non-zero error code where the mailgun API returns a non-zero code """ mock_post.return_value = Response() mock_post.return_value.status_code = HTTP_400_BAD_REQUEST chunk_size = 10 recipient_tuples = [("{0}@example.com".format(letter), {"letter": letter}) for letter in string.ascii_letters] chunked_emails_to = [recipient_tuples[i:i + chunk_size] for i in range(0, len(recipient_tuples), chunk_size)] assert len(recipient_tuples) == 52 with override_settings( MAILGUN_RECIPIENT_OVERRIDE=recipient_override, ), self.assertRaises(SendBatchException) as send_batch_exception: MailgunClient.send_batch('email subject', 'email body', recipient_tuples, chunk_size=chunk_size) if recipient_override is None: assert mock_post.call_count == 6 else: assert mock_post.call_count == 1 chunked_emails_to = [[(recipient_override, None)]] for call_num, args in enumerate(mock_post.call_args_list): called_args, called_kwargs = args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['data']['text'].startswith('email body') assert called_kwargs['data']['subject'] == 'email subject' assert sorted(called_kwargs['data']['to']) == sorted([email for email, _ in chunked_emails_to[call_num]]) assert called_kwargs['data']['recipient-variables'] == json.dumps( {email: context or {} for email, context in chunked_emails_to[call_num]} ) exception_pairs = send_batch_exception.exception.exception_pairs if recipient_override is None: assert len(exception_pairs) == 6 for call_num, (recipients, exception) in enumerate(exception_pairs): assert sorted(recipients) == sorted([email for email, _ in chunked_emails_to[call_num]]) assert isinstance(exception, HTTPError) else: assert len(exception_pairs) == 1 assert exception_pairs[0][0] == [recipient_override] assert isinstance(exception_pairs[0][1], HTTPError)
def test_send_batch_recipient_override(self, mock_post): """ Test that MailgunClient.send_batch works properly with recipient override enabled """ MailgunClient.send_batch( "subject", "html", "text", self.batch_recipient_arg, sender_name="sender" ) assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == "{}/{}".format(settings.MAILGUN_URL, "messages") assert called_kwargs["auth"] == ("api", settings.MAILGUN_KEY) assert called_kwargs["data"]["html"] == "html" assert called_kwargs["data"]["subject"] == "subject" assert called_kwargs["data"]["to"] == ["*****@*****.**"] assert called_kwargs["data"]["recipient-variables"] == json.dumps( { "*****@*****.**": {}, } ) self.assertEqual(called_kwargs["data"]["from"], "sender <*****@*****.**>")
def test_send_batch_recipient_override(self, mock_post): """ Test that MailgunClient.send_batch works properly with recipient override enabled """ MailgunClient.send_batch('subject', 'body', self.batch_recipient_arg, sender_name='sender') assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['auth'] == ('api', settings.MAILGUN_KEY) assert called_kwargs['data']['text'] == """body [overridden recipient] [email protected]: {} [email protected]: {"name": "B"}""" assert called_kwargs['data']['subject'] == 'subject' assert called_kwargs['data']['to'] == ['*****@*****.**'] assert called_kwargs['data']['recipient-variables'] == json.dumps( { '*****@*****.**': {}, } ) self.assertEqual(called_kwargs['data']['from'], "sender <*****@*****.**>")
def post(self, request, *args, **kwargs): # pylint: disable=unused-argument """ Post request to send emails to an individual learner """ financial_aid = self.get_object() serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) mailgun_response = MailgunClient.send_financial_aid_email( body=serializer.data['email_body'], acting_user=request.user, subject=serializer.data['email_subject'], financial_aid=financial_aid) return Response(status=mailgun_response.status_code, data=generate_mailgun_response_json(mailgun_response))
def test_send_with_sender_address(self, mock_post): """ Test that specifying a sender address in our mail API functions will result in an email with the sender address in the 'from' field """ sender_address = "*****@*****.**" MailgunClient.send_batch( "email subject", "html", "text", self.batch_recipient_arg, sender_address=sender_address, ) MailgunClient.send_individual_email( "email subject", "html", "text", self.individual_recipient_arg, sender_address=sender_address, ) for args in mock_post.call_args_list: _, called_kwargs = args assert called_kwargs["data"]["from"] == sender_address
def test_send_batch(self, sender_name, mock_post): """ Test that MailgunClient.send_batch sends expected parameters to the Mailgun API Base case with only one batch call to the Mailgun API. """ MailgunClient.send_batch( "email subject", "html", "text", self.batch_recipient_arg, sender_name=sender_name, ) assert mock_post.called called_args, called_kwargs = mock_post.call_args assert list(called_args)[0] == "{}/{}".format(settings.MAILGUN_URL, "messages") assert called_kwargs["auth"] == ("api", settings.MAILGUN_KEY) assert called_kwargs["data"]["html"].startswith("html") assert called_kwargs["data"]["text"].startswith("text") assert called_kwargs["data"]["subject"] == "email subject" assert sorted(called_kwargs["data"]["to"]) == sorted( [email for email, _ in self.batch_recipient_arg] ) assert called_kwargs["data"]["recipient-variables"] == json.dumps( { "*****@*****.**": {}, "*****@*****.**": {"name": "B"}, } ) if sender_name is not None: self.assertEqual( called_kwargs["data"]["from"], "{sender_name} <{email}>".format( sender_name=sender_name, email=settings.EMAIL_SUPPORT ), ) else: self.assertEqual(called_kwargs["data"]["from"], settings.EMAIL_SUPPORT)
def post(self, request, *args, **kargs): # pylint: disable=unused-argument """ POST method handler """ user = request.user course = self.get_object() serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) mailgun_response = MailgunClient.send_course_team_email( user=user, course=course, subject=serializer.data['email_subject'], body=serializer.data['email_body']) return Response(status=mailgun_response.status_code, data=generate_mailgun_response_json(mailgun_response))
def save(self, **kwargs): """ Save method for this serializer """ self.instance.status = self.validated_data["action"] if self.instance.status == FinancialAidStatus.APPROVED: self.instance.tier_program = self.validated_data["tier_program"] self.instance.justification = self.validated_data["justification"] elif self.instance.status == FinancialAidStatus.PENDING_MANUAL_APPROVAL: # This is intentionally left blank for clarity that this is a valid status for .save() pass elif self.instance.status == FinancialAidStatus.RESET: self.instance.justification = "Reset via the financial aid review form" # also saves history of this change in FinancialAidAudit. self.instance.save_and_log(self.context["request"].user) # Send email notification MailgunClient.send_financial_aid_email( acting_user=self.context["request"].user, financial_aid=self.instance, **generate_financial_aid_email(self.instance)) return self.instance
def post(self, request, *args, **kargs): # pylint: disable=unused-argument """ POST method handler """ sender_user = request.user recipient_user = self.get_object().user serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) mailgun_response = MailgunClient.send_individual_email( subject=request.data['email_subject'], body=request.data['email_body'], recipient=recipient_user.email, sender_address=sender_user.email, sender_name=sender_user.profile.display_name, ) return Response(status=mailgun_response.status_code, data=generate_mailgun_response_json(mailgun_response))
def test_send_course_team_email(self, mock_post): """ Tests that a course team contact email is sent correctly """ course_with_email = CourseFactory.create( title='course with email', contact_email='*****@*****.**') response = MailgunClient.send_course_team_email( self.user, course_with_email, 'email subject', 'email body') assert response.status_code == HTTP_200_OK assert mock_post.called _, called_kwargs = mock_post.call_args assert called_kwargs['data']['text'] == 'email body' assert called_kwargs['data']['subject'] == 'email subject' assert called_kwargs['data']['to'] == [course_with_email.contact_email] self.assertEqual( called_kwargs['data']['from'], '{} <{}>'.format(self.user_profile.display_name, self.user.email))
def post(self, request, *args, **kwargs): """ Post request to send emails to an individual learner """ financial_aid = self.get_object() serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) mailgun_response = MailgunClient.send_financial_aid_email( body=serializer.data['email_body'], acting_user=request.user, subject=serializer.data['email_subject'], financial_aid=financial_aid ) return Response( status=mailgun_response.status_code, data=generate_mailgun_response_json(mailgun_response) )
def test_send_individual_email_error(self, raise_for_status, mock_post): """ Test handling of errors for send_individual_email """ mock_post.return_value = Mock(spec=Response, status_code=HTTP_400_BAD_REQUEST, json=mocked_json()) response = MailgunClient.send_individual_email( subject='email subject', body='email body', recipient='*****@*****.**', raise_for_status=raise_for_status, ) assert response.raise_for_status.called is raise_for_status assert response.status_code == HTTP_400_BAD_REQUEST assert response.json() == {}
def post(self, request, *args, **kargs): # pylint: disable=unused-argument """ POST method handler """ user = request.user course = self.get_object() serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) mailgun_response = MailgunClient.send_course_team_email( user=user, course=course, subject=serializer.data['email_subject'], body=serializer.data['email_body'] ) return Response( status=mailgun_response.status_code, data=generate_mailgun_response_json(mailgun_response) )
def post(self, request, *args, **kargs): # pylint: disable=unused-argument """ POST method handler """ sender_user = request.user recipient_user = self.get_object().user serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) mailgun_response = MailgunClient.send_individual_email( subject=request.data['email_subject'], body=request.data['email_body'], recipient=recipient_user.email, sender_address=sender_user.email, sender_name=sender_user.profile.display_name, ) return Response( status=mailgun_response.status_code, data=generate_mailgun_response_json(mailgun_response) )
def test_send_individual_email_error(self, raise_for_status, mock_post): """ Test handling of errors for send_individual_email """ mock_post.return_value = Mock( spec=Response, status_code=HTTP_400_BAD_REQUEST, json=mocked_json() ) response = MailgunClient.send_individual_email( subject='email subject', body='email body', recipient='*****@*****.**', raise_for_status=raise_for_status, ) assert response.raise_for_status.called is raise_for_status assert response.status_code == HTTP_400_BAD_REQUEST assert response.json() == {}
def test_send_to_course_team_error(self, raise_for_status, mock_post): """ Test that send_course_team_email handles errors correctly """ mock_post.return_value = Mock( spec=Response, status_code=HTTP_400_BAD_REQUEST, json=mocked_json(), ) course_with_email = CourseFactory.create(title='course with email', contact_email='*****@*****.**') response = MailgunClient.send_course_team_email( self.user, course_with_email, 'email subject', 'email body', raise_for_status=raise_for_status, ) assert response.raise_for_status.called is raise_for_status assert response.status_code == HTTP_400_BAD_REQUEST assert response.json() == {}
def test_financial_aid_email_error(self, raise_for_status, mock_post): """ Test that send_financial_aid_email handles errors correctly """ mock_post.return_value = Mock( spec=Response, status_code=HTTP_400_BAD_REQUEST, json=mocked_json(), ) response = MailgunClient.send_financial_aid_email( self.staff_user_profile.user, self.financial_aid, 'email subject', 'email body', raise_for_status=raise_for_status, ) assert response.raise_for_status.called is raise_for_status assert response.status_code == HTTP_400_BAD_REQUEST assert response.json() == {}
def test_send_course_team_email(self, mock_post): """ Tests that a course team contact email is sent correctly """ course_with_email = CourseFactory.create(title='course with email', contact_email='*****@*****.**') response = MailgunClient.send_course_team_email( self.user, course_with_email, 'email subject', 'email body' ) assert response.status_code == HTTP_200_OK assert mock_post.called _, called_kwargs = mock_post.call_args assert called_kwargs['data']['text'] == 'email body' assert called_kwargs['data']['subject'] == 'email subject' assert called_kwargs['data']['to'] == [course_with_email.contact_email] self.assertEqual( called_kwargs['data']['from'], '{} <{}>'.format(self.user_profile.display_name, self.user.email) )
def test_send_batch_chunk(self, mock_post): """ Test that MailgunClient.send_batch chunks recipients """ chunk_size = 10 recipient_tuples = [("{0}@example.com".format(letter), None) for letter in string.ascii_letters] chunked_emails_to = [recipient_tuples[i:i + chunk_size] for i in range(0, len(recipient_tuples), chunk_size)] assert len(recipient_tuples) == 52 responses = MailgunClient.send_batch('email subject', 'email body', recipient_tuples, chunk_size=chunk_size) assert mock_post.called assert mock_post.call_count == 6 for call_num, args in enumerate(mock_post.call_args_list): called_args, called_kwargs = args assert list(called_args)[0] == '{}/{}'.format(settings.MAILGUN_URL, 'messages') assert called_kwargs['data']['text'].startswith('email body') assert called_kwargs['data']['subject'] == 'email subject' assert sorted(called_kwargs['data']['to']) == sorted([email for email, _ in chunked_emails_to[call_num]]) assert called_kwargs['data']['recipient-variables'] == json.dumps( {email: context or {} for email, context in chunked_emails_to[call_num]} ) response = responses[call_num] assert response.status_code == HTTP_200_OK
def test_send_batch_empty(self, mock_post): """If the recipient list is empty there should be no attempt to mail users""" assert MailgunClient.send_batch('subject', 'body', []) == [] assert mock_post.called is False