from datetime import timedelta import jsonpickle from django.core import mail from django.core.files import File from django.core.mail import EmailMultiAlternatives from django.template import TemplateDoesNotExist from django.test import TestCase, override_settings from django.utils import timezone from herald.base import (EmailNotification, NotificationBase, TwilioTextNotification) from herald.models import SentNotification from mock import patch from .notifications import MyNotification, MyNotificationAttachmentOpen try: # twilio version 6 from twilio.rest.api.v2010.account import MessageList except ImportError: # twillio version < 6 from twilio.rest.resources import Messages as MessageList class BaseNotificationTests(TestCase): def test_get_context_data(self): self.assertDictEqual( MyNotification().get_context_data(), {'hello': 'world', 'base_url': 'http://example.com', 'subject': None} ) def test_get_recipients(self): self.assertRaises(NotImplementedError, NotificationBase().get_recipients) def test_get_extra_data(self): self.assertDictEqual(NotificationBase().get_extra_data(), {}) def test_get_sent_from(self): self.assertRaises(NotImplementedError, NotificationBase().get_sent_from) def test_get_subject(self): self.assertIsNone(NotificationBase().get_subject()) def test_get_demo_args(self): self.assertListEqual(NotificationBase.get_demo_args(), []) def test_private_send(self): self.assertRaises(NotImplementedError, NotificationBase()._send, []) def test_get_attachments(self): self.assertIsNone(NotificationBase().get_attachments()) def test_send(self): with patch.object(MyNotification, 'resend') as mocked_resend: MyNotification().send() mocked_resend.assert_called_once() obj = mocked_resend.call_args[0][0] self.assertEqual(obj.recipients, 'test@test.com') def test_send_no_text(self): class DummyNotification(EmailNotification): render_types = ['html'] to_emails = ['test@test.com'] with patch.object(DummyNotification, 'resend') as mocked_resend: DummyNotification().send() mocked_resend.assert_called_once() obj = mocked_resend.call_args[0][0] self.assertEqual(obj.recipients, 'test@test.com') self.assertIsNone(obj.text_content) def test_real_send(self): MyNotification().send() self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].to, ['test@test.com']) def test_real_send_attachments(self): MyNotification().send() self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].attachments[0][1], 'Some Report Data') def test_real_send_attachments_open(self): MyNotificationAttachmentOpen().send() self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].attachments[0][0], 'tests/python.jpeg') self.assertEqual(mail.outbox[0].attachments[1][0], 'tests/python.jpeg') def test_render_no_type(self): class DummyNotification(NotificationBase): pass with self.assertRaises(AssertionError): DummyNotification().render('text', {}) def test_render_invalid_template(self): class DummyNotification(NotificationBase): render_types = ['text'] template_name = 'does_not_exist' self.assertIsNone(DummyNotification().render('text', {})) @override_settings(DEBUG=True) def test_render_invalid_template_debug(self): class DummyNotification(NotificationBase): render_types = ['text'] template_name = 'does_not_exist' with self.assertRaises(TemplateDoesNotExist): DummyNotification().render('text', {}) def test_render_invalid(self): class DummyNotification(NotificationBase): render_types = ['text'] template_name = 'hello_world' self.assertEqual(DummyNotification().render('text', {}), 'Hello World') def test_resend_error(self): notification = SentNotification() with patch.object(NotificationBase, '_send') as mocked__send: mocked__send.side_effect = Exception result = NotificationBase.resend(notification) self.assertFalse(result) def test_resend_error_raise(self): notification = SentNotification() with patch.object(NotificationBase, '_send') as mocked__send: mocked__send.side_effect = Exception self.assertRaises(Exception, NotificationBase.resend, notification, raise_exception=True) def test_resend(self): notification = SentNotification() with patch.object(NotificationBase, '_send') as mocked__send: result = NotificationBase.resend(notification) self.assertTrue(result) def test_get_verbose_name(self): class TestNotification(EmailNotification): pass self.assertEqual(TestNotification.get_verbose_name(), 'Test Notification') class TestNotification2(EmailNotification): verbose_name = 'A verbose name' self.assertEqual(TestNotification2.get_verbose_name(), 'A verbose name') def test_get_encoded_attachments_none(self): class TestNotification(EmailNotification): attachments = [] self.assertJSONEqual(TestNotification()._get_encoded_attachments(), []) def test_get_encoded_attachments_basic(self): class TestNotification(EmailNotification): attachments = [('Report.txt', 'raw_data', 'text/plain')] self.assertJSONEqual( TestNotification()._get_encoded_attachments(), [{'py/tuple': ['Report.txt', 'raw_data', 'text/plain']}] ) def test_get_encoded_attachments_file(self): class TestNotification(EmailNotification): attachments = [File(open('tests/python.jpeg', 'rb'))] attachments = jsonpickle.loads(TestNotification()._get_encoded_attachments()) self.assertEqual(attachments[0][0], 'tests/python.jpeg') self.assertEqual(attachments[0][2], 'image/jpeg') def test_delete_notifications_no_setting(self): # create a test notification from a long time ago SentNotification.objects.create( recipients='test@test.com', date_sent=timezone.now() - timedelta(weeks=52), notification_class='MyNotification' ) # create a test notification from recently SentNotification.objects.create( recipients='test@test.com', date_sent=timezone.now() - timedelta(weeks=10), notification_class='MyNotification' ) MyNotification().send() # all three were not deleted because we didn't have a setting self.assertEqual(SentNotification.objects.count(), 3) @override_settings(HERALD_NOTIFICATION_RETENTION_TIME=timedelta(weeks=26)) def test_delete_notifications(self): # create a test notification from a long time ago n1 = SentNotification.objects.create( recipients='test@test.com', date_sent=timezone.now() - timedelta(weeks=52), notification_class='MyNotification' ) # create a test notification from recently n2 = SentNotification.objects.create( recipients='test@test.com', date_sent=timezone.now() - timedelta(weeks=10), notification_class='MyNotification' ) MyNotification().send() # the one from a year ago was deleted, but not the one from 10 weeks ago. self.assertEqual(SentNotification.objects.count(), 2) ids = SentNotification.objects.values_list('id', flat=True) self.assertTrue(n2.id in ids) self.assertFalse(n1.id in ids) class EmailNotificationTests(TestCase): def test_get_recipients(self): self.assertListEqual(MyNotification().get_recipients(), ['test@test.com']) def test_get_sent_from(self): class TestNotification(EmailNotification): from_email = 'bob@example.com' self.assertEqual(TestNotification().get_sent_from(), 'bob@example.com') def test_get_sent_from_default(self): class TestNotification(EmailNotification): from_email = None with override_settings(DEFAULT_FROM_EMAIL='default@example.com'): self.assertEqual(TestNotification().get_sent_from(), 'default@example.com') def test_get_subject(self): class TestNotification(EmailNotification): subject = 'test subject' self.assertEqual(TestNotification().get_subject(), 'test subject') def test_get_extra_data_none(self): self.assertDictEqual(EmailNotification().get_extra_data(), {}) def test_get_extra_data(self): class TestNotification(EmailNotification): bcc = 'bcc@test.com' cc = 'cc@test.com' headers = {'HEADER': 'test'} reply_to = 'reply_to@test.com' self.assertDictEqual(TestNotification().get_extra_data(), { 'bcc': 'bcc@test.com', 'cc': 'cc@test.com', 'headers': {'HEADER': 'test'}, 'reply_to': 'reply_to@test.com', }) def test_send_html_content(self): class TestNotification(EmailNotification): subject = 'test subject' with patch.object(EmailMultiAlternatives, 'attach_alternative') as mocked_attach_alternative: TestNotification._send([], text_content='Text') mocked_attach_alternative.assert_not_called() with patch.object(EmailMultiAlternatives, 'attach_alternative') as mocked_attach_alternative: TestNotification._send([], html_content='Text') mocked_attach_alternative.assert_called_once_with('Text', 'text/html') class TwilioNotificationTests(TestCase): def test_get_recipients(self): class TestNotification(TwilioTextNotification): to_number = '1231231234' self.assertListEqual(TestNotification().get_recipients(), ['1231231234']) def test_get_sent_from(self): class TestNotification(TwilioTextNotification): from_number = '1231231234' self.assertEqual(TestNotification().get_sent_from(), '1231231234') def test_get_sent_from_default(self): class TestNotification(TwilioTextNotification): from_number = None with override_settings(TWILIO_DEFAULT_FROM_NUMBER='1231231234'): self.assertEqual(TestNotification().get_sent_from(), '1231231234') def test_get_sent_from_default_error(self): class TestNotification(TwilioTextNotification): from_number = None self.assertRaisesMessage( Exception, 'TWILIO_DEFAULT_FROM_NUMBER setting is required for sending a TwilioTextNotification', TestNotification().get_sent_from ) @override_settings( TWILIO_ACCOUNT_SID='sid', TWILIO_AUTH_TOKEN='token' ) def test_send(self): class TestNotification(TwilioTextNotification): from_number = '1231231234' to_number = '1231231234' template_name = 'hello_world' with patch.object(MessageList, 'create') as mocked_create: TestNotification().send() mocked_create.assert_called_once_with( body='Hello World', to='1231231234', from_='1231231234' ) @override_settings( TWILIO_ACCOUNT_SID='sid', TWILIO_AUTH_TOKEN='token' ) def test_sending_to_multiple_numbers(self): class TestNotification(TwilioTextNotification): from_number = '1231231234' template_name = 'hello_world' def get_recipients(self): return ['1234567890', '0987654321'] with patch.object(MessageList, 'create') as mocked_create: notification = TestNotification() notification.send() self.assertEqual(mocked_create.call_count, 2) for recipient in notification.get_recipients(): mocked_create.assert_any_call( body='Hello World', to=recipient, from_=notification.get_sent_from() ) def test_send_no_settings(self): class TestNotification(TwilioTextNotification): from_number = '1231231234' to_number = '1231231234' template_name = 'hello_world' with self.assertRaisesMessage(Exception, 'TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN settings are required for ' 'sending a TwilioTextNotification'): TestNotification().send(raise_exception=True)