def test_basic(self): transactional_message = Message(options={u'transactional': True}, **self.msg_kwargs) for key in self.msg_kwargs: self.assertEqual(getattr(transactional_message, key), self.msg_kwargs.get(key)) self.assertIsNotNone(transactional_message.uuid) assert transactional_message.options.get(u'transactional') # pylint: disable=no-member normal_message = Message(**self.msg_kwargs) assert not dict(normal_message.options)
def test_log_level(self, log_level, expect_log_warn, expect_log_debug): logging.getLogger().setLevel(logging.INFO) self.msg_kwargs[u'log_level'] = log_level message = Message(**self.msg_kwargs) logger = message.get_message_specific_logger(LOG) with patch(u'logging.Logger.callHandlers') as mock_log: logger.warning(u'Test warning statement') self.assertEqual(mock_log.called, expect_log_warn) with patch(u'logging.Logger.callHandlers') as mock_log: logger.debug(u'Test debug statement') self.assertEqual(mock_log.called, expect_log_debug)
def test_ace_send_happy_path(self): patch_policies(self, [StubPolicy([ChannelType.PUSH])]) mock_channel = Mock( channel_type=ChannelType.EMAIL, action_links=[], tracker_image_sources=[], ) recipient = Recipient(username=u'testuser') msg = Message( app_label=u'testapp', name=u'testmessage', recipient=recipient, ) channel_map = ChannelMap([ [u'sailthru_email', mock_channel], ]) with patch(u'edx_ace.channel.channels', return_value=channel_map): ace.send(msg) mock_channel.deliver.assert_called_once_with( msg, RenderedEmail( from_name=u'template from_name.txt', subject=u'template subject.txt', # The new lines are needed because the template has some tags, which means leftover newlines body_html=u'template body.html\n\n\n\n\n', head_html=u'template head.html\n', body=u'template body.txt', ), )
def _course_update_schedule_send(site_id, msg_str): site = Site.objects.get(pk=site_id) if not ScheduleConfig.current(site).deliver_course_update: return msg = Message.from_string(msg_str) ace.send(msg)
def test_happy_path(self): channel = FileEmailChannel() rendered_message = RenderedEmail( from_name=u'Robot', head_html=u'<img src="https://tracker/.img" />', subject=u'\n Hello from \r\nRobot ! \n', body=u'Just trying to see what is like to talk to a human!', body_html=u""" <p>Just trying to see what is like to talk to a human!</p> <hr /> """, ) with NamedTemporaryFile('r+') as f: message = Message( app_label=u'testapp', name=u'testmessage', options={ u'from_address': u'*****@*****.**', PATH_OVERRIDE_KEY: f.name, }, recipient=Recipient(username=u'Robot', email_address=u'*****@*****.**'), ) channel.deliver(message, rendered_message) contents = f.read() assert u'subject: Hello from' in contents assert u'to: [email protected]' in contents assert u'body: Just trying to see what' in contents assert u'talk to a human!</p>' in contents
def _deserialize_field(cls, field_name, field_value): u""" Deserialize a single encoded field value for this message. Arguments: field_name (str): The name of the field being decoded. field_value: The value of field being deserialized. """ # We have to import these here to avoid a circular dependency since these classes use this mixin. from edx_ace.recipient import Recipient from edx_ace.message import Message if field_value is None: return None if field_name == u'expiration_time': return date.deserialize(field_value) elif field_name in (u'uuid', u'send_uuid'): return UUID(field_value) # TODO(later): should this be more dynamic? elif field_name == u'message': return Message(**field_value) elif field_name == u'recipient': return Recipient(**field_value) else: return field_value
def test_ace_send_happy_path(self, _mock_get_template): patch_policies(self, [StubPolicy([ChannelType.PUSH])]) mock_channel = Mock( name=u'test_channel', channel_type=ChannelType.EMAIL ) patch_channels(self, [mock_channel]) recipient = Recipient(username=u'testuser') msg = Message( app_label=u'testapp', name=u'testmessage', recipient=recipient, ) ace.send(msg) mock_channel.deliver.assert_called_once_with( msg, RenderedEmail( from_name=TEMPLATES[u'testapp/edx_ace/testmessage/email/from_name.txt'].render(), subject=TEMPLATES[u'testapp/edx_ace/testmessage/email/subject.txt'].render(), body_html=TEMPLATES[u'testapp/edx_ace/testmessage/email/body.html'].render(), head_html=TEMPLATES[u'testapp/edx_ace/testmessage/email/head.html'].render(), body=TEMPLATES[u'testapp/edx_ace/testmessage/email/body.txt'].render(), ), )
def _upgrade_reminder_schedule_send(site_id, msg_str): site = Site.objects.get(pk=site_id) if not ScheduleConfig.current(site).deliver_upgrade_reminder: return msg = Message.from_string(msg_str) ace.send(msg)
def _upgrade_reminder_schedule_send(site_id, msg_str): site = Site.objects.get(pk=site_id) if not ScheduleConfig.current(site).deliver_upgrade_reminder: return msg = Message.from_string(msg_str) ace.send(msg)
def _course_update_schedule_send(site_id, msg_str): site = Site.objects.get(pk=site_id) if not ScheduleConfig.current(site).deliver_course_update: return msg = Message.from_string(msg_str) ace.send(msg)
def _recurring_nudge_schedule_send(site_id, msg_str): site = Site.objects.get(pk=site_id) if not ScheduleConfig.current(site).deliver_recurring_nudge: LOG.debug('Recurring Nudge: Message delivery disabled for site %s', site.domain) return msg = Message.from_string(msg_str) LOG.debug('Recurring Nudge: Sending message = %s', msg_str) ace.send(msg)
def _recurring_nudge_schedule_send(site_id, msg_str): site = Site.objects.get(pk=site_id) if not ScheduleConfig.current(site).deliver_recurring_nudge: LOG.debug('Recurring Nudge: Message delivery disabled for site %s', site.domain) return msg = Message.from_string(msg_str) LOG.debug('Recurring Nudge: Sending message = %s', msg_str) ace.send(msg)
def test_ace_send_unsupported_channel(self, *_args): recipient = Recipient(username=u'testuser') msg = Message( app_label=u'testapp', name=u'testmessage', recipient=recipient, ) ace.send(msg) # UnsupportedChannelError shouldn't throw UnsupportedChannelError
def test_default_channel(self): channel_map = ChannelMap([ ['sailthru', SailthruEmailChannel], ]) message = Message(options={u'transactional': True}, **self.msg_kwargs) with patch(u'edx_ace.channel.channels', return_value=channel_map): channel = get_channel_for_message(ChannelType.EMAIL, message) assert channel is SailthruEmailChannel
def test_get_channel_for_message(self): channel_map = ChannelMap([ [u'file_email', FileEmailChannel], [u'sailthru_email', SailthruEmailChannel], ]) transactional_msg = Message(options={u'transactional': True}, **self.msg_kwargs) info_msg = Message(options={}, **self.msg_kwargs) with patch(u'edx_ace.channel.channels', return_value=channel_map): assert get_channel_for_message( ChannelType.EMAIL, transactional_msg) is FileEmailChannel assert get_channel_for_message(ChannelType.EMAIL, info_msg) is SailthruEmailChannel with self.assertRaises(UnsupportedChannelError): assert get_channel_for_message(ChannelType.PUSH, transactional_msg)
def create_test_message(self): return Message( app_label='foo', name='bar', recipient=Recipient( username=self.student.username, email_address=self.student.email, ), context={'course_ids': [str(self.course.id)]}, )
def test_with_no_from_address_with_default(self): message = Message( app_label='testapp', name='testmessage', options={}, recipient=Recipient(lms_user_id=123, email_address='*****@*****.**'), ) self.channel.deliver(message, self.mock_rendered_message) assert len(mail.outbox) == 1, 'Should have one email'
def _get_rendered_message(self): channel = DjangoEmailChannel() message = Message( app_label='testapp', name='testmessage', options={}, recipient=Recipient(lms_user_id=123, email_address='*****@*****.**'), ) return render(channel, message)
def test_with_no_from_address_without_default(self): message = Message( app_label='testapp', name='testmessage', options={}, recipient=Recipient(lms_user_id=123, email_address='*****@*****.**'), ) with self.assertRaises(FatalChannelDeliveryError): self.channel.deliver(message, self.mock_rendered_message)
def _schedule_send(msg_str, site_id, delivery_config_var, log_prefix): # lint-amnesty, pylint: disable=missing-function-docstring site = Site.objects.select_related('configuration').get(pk=site_id) if _is_delivery_enabled(site, delivery_config_var, log_prefix): msg = Message.from_string(msg_str) user = User.objects.get(id=msg.recipient.lms_user_id) with emulate_http_request(site=site, user=user): _annonate_send_task_for_monitoring(msg) LOG.debug('%s: Sending message = %s', log_prefix, msg_str) ace.send(msg) _track_message_sent(site, user, msg)
def _schedule_send(msg_str, site_id, delivery_config_var, log_prefix): site = Site.objects.select_related('configuration').get(pk=site_id) if _is_delivery_enabled(site, delivery_config_var, log_prefix): msg = Message.from_string(msg_str) user = User.objects.get(username=msg.recipient.username) with emulate_http_request(site=site, user=user): _annonate_send_task_for_monitoring(msg) LOG.debug('%s: Sending message = %s', log_prefix, msg_str) ace.send(msg) _track_message_sent(site, user, msg)
def _schedule_send(msg_str, site_id, delivery_config_var, log_prefix): site = Site.objects.select_related('configuration').get(pk=site_id) if _is_delivery_enabled(site, delivery_config_var, log_prefix): msg = Message.from_string(msg_str) user = User.objects.get(username=msg.recipient.username) with emulate_http_request(site=site, user=user): _annonate_send_task_for_monitoring(msg) LOG.debug(u'%s: Sending message = %s', log_prefix, msg_str) ace.send(msg) _track_message_sent(site, user, msg)
def test_no_recipient_email_address(self): message = Message( app_label='testapp', name='testmessage', options={}, recipient=Recipient(lms_user_id=123), ) rendered_email = render(self.channel, message) with self.assertRaisesRegex(InvalidMessageError, 'No email address'): deliver(self.channel, rendered_email, message)
def send_ace_message(goal): """ Send an email reminding users to stay on track for their learning goal in this course Arguments: goal (CourseGoal): Goal object """ user = goal.user try: course = CourseOverview.get_from_id(goal.course_key) except CourseOverview.DoesNotExist: log.error("Goal Reminder course not found.") course_name = course.display_name site = Site.objects.get_current() message_context = get_base_template_context(site) course_home_url = reverse(course_home_url_name(course.id), args=[str(course.id)]) course_home_absolute_url_parts = ("https", site.name, course_home_url, '', '', '') course_home_absolute_url = six.moves.urllib.parse.urlunparse( course_home_absolute_url_parts) goals_unsubscribe_url = reverse('course-home:unsubscribe-from-course-goal', kwargs={'token': goal.unsubscribe_token}) message_context.update({ 'email': user.email, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'course_name': course_name, 'days_per_week': goal.days_per_week, 'course_url': course_home_absolute_url, 'goals_unsubscribe_url': goals_unsubscribe_url, 'unsubscribe_url': None, # We don't want to include the default unsubscribe link }) msg = Message(name="goalreminder", app_label="course_goals", recipient=Recipient(user.id, user.email), language=get_user_preference(user, LANGUAGE_KEY), context=message_context) with emulate_http_request(site, user): ace.send(msg)
def _recurring_nudge_schedule_send(site_id, msg_str): site = Site.objects.get(pk=site_id) if not ScheduleConfig.current(site).deliver_recurring_nudge: LOG.debug('Recurring Nudge: Message delivery disabled for site %s', site.domain) return msg = Message.from_string(msg_str) # A unique identifier for this batch of messages being sent. set_custom_metric('send_uuid', msg.send_uuid) # A unique identifier for this particular message. set_custom_metric('uuid', msg.uuid) LOG.debug('Recurring Nudge: Sending message = %s', msg_str) ace.send(msg)
def setUp(self): super(TestDelivery, self).setUp() self.mock_channel = Mock(name=u'test_channel', channel_type=ChannelType.EMAIL) patch_channels(self, [self.mock_channel]) self.recipient = Recipient(username=str(sentinel.username)) self.message = Message( app_label=str(sentinel.app_label), name=str(sentinel.name), recipient=self.recipient, ) self.current_time = datetime.datetime.utcnow().replace(tzinfo=tzutc())
def test_render_email_with_sailthru(self): message = Message( app_label=u'testapp', name=u'testmessage', options={}, recipient=Recipient(username=u'Robot', email_address=u'*****@*****.**'), ) rendered_email = render(self.channel, message) assert u'{beacon_src}' in rendered_email.body_html assert u'{view_url}' in rendered_email.body_html assert u'{optout_confirm_url}' in rendered_email.body_html
def _send_message_task(self, schedule, offset, target_day): """ Calls the task for sending a message to the given schedule and for the given offset and target_day. Returns the message that would have been sent. """ sent_messages = [] with patch.object(self.task, 'async_send_task') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args[1]) self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(schedule.enrollment.user), )) self.assertEqual(len(sent_messages), 1) return Message.from_string(sent_messages[0])
def _send_message_task(self, schedule, offset, target_day): """ Calls the task for sending a message to the given schedule and for the given offset and target_day. Returns the message that would have been sent. """ sent_messages = [] with patch.object(self.task, 'async_send_task') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args[1]) self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(schedule.enrollment.user), )) self.assertEqual(len(sent_messages), 1) return Message.from_string(sent_messages[0])
def send_calendar_sync_email(self, msg_string, from_address=None): """ Sending calendar sync email to the user. """ msg = Message.from_string(msg_string) max_retries = settings.RETRY_CALENDAR_SYNC_EMAIL_MAX_ATTEMPTS retries = self.request.retries if from_address is None: from_address = configuration_helpers.get_value( 'ACTIVATION_EMAIL_FROM_ADDRESS') or ( configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)) msg.options['from_address'] = from_address dest_addr = msg.recipient.email_address site = Site.objects.get_current() user = User.objects.get(username=msg.recipient.username) try: with emulate_http_request(site=site, user=user): ace.send(msg) # Log that the Activation Email has been sent to user without an exception log.info( "Calendar Sync Email has been sent to User {user_email}".format( user_email=dest_addr)) except RecoverableChannelDeliveryError: log.info( 'Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}' .format(dest_addr=dest_addr, attempt=retries, max_attempts=max_retries)) try: self.retry(countdown=settings.RETRY_ACTIVATION_EMAIL_TIMEOUT, max_retries=max_retries) except MaxRetriesExceededError: log.error( 'Unable to send calendar sync email to user from "%s" to "%s"', from_address, dest_addr, exc_info=True) except Exception: log.exception( 'Unable to send calendar sync email to user from "%s" to "%s"', from_address, dest_addr, ) raise Exception
def _recurring_nudge_schedule_send(site_id, msg_str): site = Site.objects.get(pk=site_id) if not ScheduleConfig.current(site).deliver_recurring_nudge: LOG.debug('Recurring Nudge: Message delivery disabled for site %s', site.domain) return msg = Message.from_string(msg_str) # A unique identifier for this batch of messages being sent. set_custom_metric('send_uuid', msg.send_uuid) # A unique identifier for this particular message. set_custom_metric('uuid', msg.uuid) LOG.debug('Recurring Nudge: Sending message = %s', msg_str) ace.send(msg)
def test_on_behalf_option_with_sailthru(self, message_options, expected_options): """ Tests sailthru send API is called with on_behalf option """ message = Message( app_label=u'testapp', name=u'testmessage', options=message_options, recipient=Recipient(username=u'Robot', email_address=u'*****@*****.**'), ) rendered_email = render(self.channel, message) with patch('edx_ace.channel.sailthru.SailthruClient.send') as mock_send: deliver(self.channel, rendered_email, message) self.assertEqual(mock_send.call_args_list[0][1]['options'], expected_options)
def setUp(self): super(TestDelivery, self).setUp() self.mock_channel = Mock(name=u'test_channel', channel_type=ChannelType.EMAIL) self.recipient = Recipient(username=str(sentinel.username)) self.message = Message( app_label=str(sentinel.app_label), name=str(sentinel.name), options={ u'from_address': u'*****@*****.**', }, recipient=self.recipient, ) self.current_time = datetime.datetime.utcnow().replace(tzinfo=tzutc())
def _schedule_send(msg_str, site_id, delivery_config_var, log_prefix): site = Site.objects.get(pk=site_id) if _is_delivery_enabled(site, delivery_config_var, log_prefix): msg = Message.from_string(msg_str) user = User.objects.get(username=msg.recipient.username) middleware_classes = [ CurrentRequestUserMiddleware, CurrentSiteThemeMiddleware, ] with emulate_http_request(site=site, user=user, middleware_classes=middleware_classes): _annonate_send_task_for_monitoring(msg) LOG.debug('%s: Sending message = %s', log_prefix, msg_str) ace.send(msg)
def send_activation_email(self, msg_string, from_address=None): """ Sending an activation email to the user. """ msg = Message.from_string(msg_string) max_retries = settings.RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS retries = self.request.retries if from_address is None: from_address = configuration_helpers.get_value('ACTIVATION_EMAIL_FROM_ADDRESS') or ( configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) ) msg.options['from_address'] = from_address dest_addr = msg.recipient.email_address try: ace.send(msg) # Log that the Activation Email has been sent to user without an exception log.info("Activation Email has been sent to User {user_email}".format( user_email=dest_addr )) except RecoverableChannelDeliveryError: log.info('Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}'.format( dest_addr=dest_addr, attempt=retries, max_attempts=max_retries )) try: self.retry(countdown=settings.RETRY_ACTIVATION_EMAIL_TIMEOUT, max_retries=max_retries) except MaxRetriesExceededError: log.error( 'Unable to send activation email to user from "%s" to "%s"', from_address, dest_addr, exc_info=True ) except Exception: # pylint: disable=bare-except log.exception( 'Unable to send activation email to user from "%s" to "%s"', from_address, dest_addr, exc_info=True ) raise Exception
def send_activation_email(self, msg_string, from_address=None): """ Sending an activation email to the user. """ msg = Message.from_string(msg_string) max_retries = settings.RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS retries = self.request.retries if from_address is None: from_address = configuration_helpers.get_value( 'ACTIVATION_EMAIL_FROM_ADDRESS') or ( configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)) msg.options['from_address'] = from_address dest_addr = msg.recipient.email_address site = Site.objects.get_current() user = User.objects.get(id=msg.recipient.lms_user_id) try: with emulate_http_request(site=site, user=user): ace.send(msg) except RecoverableChannelDeliveryError: log.info( 'Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}' .format(dest_addr=dest_addr, attempt=retries, max_attempts=max_retries)) try: self.retry(countdown=settings.RETRY_ACTIVATION_EMAIL_TIMEOUT, max_retries=max_retries) except MaxRetriesExceededError: log.error( 'Unable to send activation email to user from "%s" to "%s"', from_address, dest_addr, exc_info=True) except Exception: log.exception( 'Unable to send activation email to user from "%s" to "%s"', from_address, dest_addr, ) raise Exception # lint-amnesty, pylint: disable=raise-missing-from
def _contains_upsell_attribute(self, msg_attr): msg = Message.from_string(msg_attr) tmp = msg.context["show_upsell"] return msg.context["show_upsell"]
def _contains_upsell(self, message_str): message = Message.from_string(message_str) return message.context["show_upsell"]
def _schedule_send(msg_str, site_id, delivery_config_var, log_prefix): if _is_delivery_enabled(site_id, delivery_config_var, log_prefix): msg = Message.from_string(msg_str) _annonate_send_task_for_monitoring(msg) LOG.debug('%s: Sending message = %s', log_prefix, msg_str) ace.send(msg)