def test_multiple_retries(self, mock_get_current_time, mock_time): mock_get_current_time.side_effect = [ self.current_time, # First call to figure out the expiration time self.current_time, # Check to see if the message has expired self.current_time, # Time after attempting the send # This attempt fails and the app is instructed to wait for a second self.current_time + datetime.timedelta( seconds=1), # Check again to see if it's expired self.current_time + datetime.timedelta(seconds=1), # Time after second send attempt # This attempt fails and the app is instructed to wait for another second self.current_time + datetime.timedelta(seconds=2), # Final expiration check ] self.mock_channel.deliver.side_effect = [ RecoverableChannelDeliveryError( u'Try again later', self.current_time + datetime.timedelta(seconds=1)), RecoverableChannelDeliveryError( u'Try again later', self.current_time + datetime.timedelta(seconds=2)), True ] deliver(self.mock_channel, sentinel.rendered_email, self.message) assert mock_time.sleep.call_args_list == [call(1), call(1)] assert self.mock_channel.deliver.call_count == 3
def _handle_error_response(self, response): u""" Handle an error response from SailThru, either by retrying or failing with an appropriate exception. Arguments: response: The HTTP response recieved from SailThru. """ error = response.get_error() error_code = error.get_error_code() error_message = error.get_message() http_status_code = response.get_status_code() if error_code in RecoverableErrorCodes: next_attempt_time = None if error_code == RecoverableErrorCodes.RATE_LIMIT: next_attempt_time = self._get_rate_limit_reset_time( sailthru_response=response) if next_attempt_time is None: # Sailthru advises waiting "a moment" and then trying again. next_attempt_time = get_current_time() + timedelta( seconds=NEXT_ATTEMPT_DELAY_SECONDS + random.uniform(-2, 2)) raise RecoverableChannelDeliveryError( u'Recoverable Sailthru error (error_code={error_code} status_code={http_status_code}): ' u'{message}'.format(error_code=error_code, http_status_code=http_status_code, message=error_message), next_attempt_time) else: raise FatalChannelDeliveryError( u'Fatal Sailthru error (error_code={error_code} status_code={http_status_code}): ' u'{message}'.format(error_code=error_code, http_status_code=http_status_code, message=error_message))
def test_next_attempt_time_after_expiration(self, mock_get_current_time, mock_time): self.message.expiration_time = self.current_time + datetime.timedelta( seconds=10) mock_get_current_time.return_value = self.current_time self.mock_channel.deliver.side_effect = [ RecoverableChannelDeliveryError( u'Try again later', self.current_time + datetime.timedelta(seconds=11)), ] deliver(self.mock_channel, sentinel.rendered_email, self.message) assert not mock_time.sleep.called assert self.mock_channel.deliver.call_count == 1
def test_single_retry(self, mock_get_current_time, mock_time): mock_get_current_time.side_effect = [ self.current_time, # First call to figure out the expiration time self.current_time, # Check to see if the message has expired self.current_time, # Time after attempting the send self.current_time + datetime.timedelta(seconds=1.1), ] self.mock_channel.deliver.side_effect = [ RecoverableChannelDeliveryError( u'Try again later', self.current_time + datetime.timedelta(seconds=1)), True ] deliver(self.mock_channel, sentinel.rendered_email, self.message) assert self.mock_channel.deliver.call_count == 2 mock_time.sleep.assert_called_once_with(1)
def _handle_error_response(self, response, message, exception): """ Handle an error response from Braze, either by retrying or failing with an appropriate exception. Arguments: response: The HTTP response received from Braze. message: An error message from Braze. exception: The exception that triggered this error. """ if response.status_code == 429 or 500 <= response.status_code < 600: next_attempt_time = get_current_time() + timedelta( seconds=NEXT_ATTEMPT_DELAY_SECONDS + random.uniform(-2, 2)) raise RecoverableChannelDeliveryError( 'Recoverable Braze error (status_code={http_status_code}): {message}' .format(http_status_code=response.status_code, message=message), next_attempt_time) from exception raise FatalChannelDeliveryError( 'Fatal Braze error (status_code={http_status_code}): {message}'. format(http_status_code=response.status_code, message=message)) from exception
class SendActivationEmailTestCase(TestCase): """ Test for send activation email to user """ def setUp(self): """ Setup components used by each test.""" super().setUp() self.student = UserFactory() registration = Registration() registration.register(self.student) self.msg = compose_activation_email("http://www.example.com", self.student, registration) def test_ComposeEmail(self): """ Tests that attributes of the message are being filled correctly in compose_activation_email """ # Check that variables used by the base template are present in generated context assert 'platform_name' in self.msg.context assert 'contact_mailing_address' in self.msg.context # Verify the presence of the activation-email specific attributes assert self.msg.recipient.lms_user_id == self.student.id assert self.msg.recipient.email_address == self.student.email assert self.msg.context['routed_user'] == self.student.username assert self.msg.context['routed_user_email'] == self.student.email assert self.msg.context['routed_profile_name'] == '' @mock.patch('time.sleep', mock.Mock(return_value=None)) @mock.patch('common.djangoapps.student.tasks.log') @mock.patch('common.djangoapps.student.tasks.ace.send', mock.Mock(side_effect=RecoverableChannelDeliveryError(None, None))) # lint-amnesty, pylint: disable=line-too-long def test_RetrySendUntilFail(self, mock_log): """ Tests retries when the activation email doesn't send """ from_address = '*****@*****.**' email_max_attempts = settings.RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS send_activation_email.delay(str(self.msg), from_address=from_address) # Asserts sending email retry logging. for attempt in range(email_max_attempts): mock_log.info.assert_any_call( 'Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}'.format( dest_addr=self.student.email, attempt=attempt, max_attempts=email_max_attempts )) assert mock_log.info.call_count == 6 # Asserts that the error was logged on crossing max retry attempts. mock_log.error.assert_called_with( 'Unable to send activation email to user from "%s" to "%s"', from_address, self.student.email, exc_info=True ) assert mock_log.error.call_count == 1 @mock.patch('common.djangoapps.student.tasks.log') @mock.patch('common.djangoapps.student.tasks.ace.send', mock.Mock(side_effect=ChannelError)) def test_UnrecoverableSendError(self, mock_log): """ Tests that a major failure of the send is logged """ from_address = '*****@*****.**' send_activation_email.delay(str(self.msg), from_address=from_address) # Asserts that the error was logged mock_log.exception.assert_called_with( 'Unable to send activation email to user from "%s" to "%s"', from_address, self.student.email, ) # Assert that nothing else was logged assert mock_log.info.call_count == 0 assert mock_log.error.call_count == 0 assert mock_log.exception.call_count == 1
class SendActivationEmailTestCase(TestCase): """ Test for send activation email to user """ def setUp(self): """ Setup components used by each test.""" super(SendActivationEmailTestCase, self).setUp() self.student = UserFactory() registration = Registration() registration.register(self.student) self.msg = compose_activation_email("http://www.example.com", self.student, registration) def test_ComposeEmail(self): """ Tests that attributes of the message are being filled correctly in compose_activation_email """ self.assertEqual(self.msg.recipient.username, self.student.username) self.assertEqual(self.msg.recipient.email_address, self.student.email) self.assertEqual(self.msg.context['routed_user'], self.student.username) self.assertEqual(self.msg.context['routed_user_email'], self.student.email) self.assertEqual(self.msg.context['routed_profile_name'], '') @mock.patch('time.sleep', mock.Mock(return_value=None)) @mock.patch('student.tasks.log') @mock.patch( 'student.tasks.ace.send', mock.Mock(side_effect=RecoverableChannelDeliveryError(None, None))) def test_RetrySendUntilFail(self, mock_log): """ Tests retries when the activation email doesn't send """ from_address = '*****@*****.**' email_max_attempts = settings.RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS send_activation_email.delay(str(self.msg), from_address=from_address) # Asserts sending email retry logging. for attempt in range(email_max_attempts): mock_log.info.assert_any_call( 'Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}' .format(dest_addr=self.student.email, attempt=attempt, max_attempts=email_max_attempts)) self.assertEqual(mock_log.info.call_count, 6) # Asserts that the error was logged on crossing max retry attempts. mock_log.error.assert_called_with( 'Unable to send activation email to user from "%s" to "%s"', from_address, self.student.email, exc_info=True) self.assertEqual(mock_log.error.call_count, 1) @mock.patch('student.tasks.log') @mock.patch('student.tasks.ace.send', mock.Mock(side_effect=ChannelError)) def test_UnrecoverableSendError(self, mock_log): """ Tests that a major failure of the send is logged """ from_address = '*****@*****.**' send_activation_email.delay(str(self.msg), from_address=from_address) # Asserts that the error was logged mock_log.exception.assert_called_with( 'Unable to send activation email to user from "%s" to "%s"', from_address, self.student.email, ) # Assert that nothing else was logged self.assertEqual(mock_log.info.call_count, 0) self.assertEqual(mock_log.error.call_count, 0) self.assertEqual(mock_log.exception.call_count, 1)
class SendCalendarSyncEmailTestCase(TestCase): """ Test for send activation email to user """ def setUp(self): """ Setup components used by each test.""" super(SendCalendarSyncEmailTestCase, self).setUp() self.user = UserFactory() self.course_overview = CourseOverviewFactory() self.msg = compose_calendar_sync_email(self.user, self.course_overview) @mock.patch('time.sleep', mock.Mock(return_value=None)) @mock.patch('openedx.features.calendar_sync.tasks.log') @mock.patch( 'openedx.features.calendar_sync.tasks.ace.send', mock.Mock(side_effect=RecoverableChannelDeliveryError(None, None))) def test_RetrySendUntilFail(self, mock_log): """ Tests retries when the activation email doesn't send """ from_address = '*****@*****.**' email_max_attempts = settings.RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS send_calendar_sync_email.delay(str(self.msg), from_address=from_address) # Asserts sending email retry logging. for attempt in range(email_max_attempts): mock_log.info.assert_any_call( 'Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}' .format(dest_addr=self.user.email, attempt=attempt, max_attempts=email_max_attempts)) self.assertEqual(mock_log.info.call_count, 6) # Asserts that the error was logged on crossing max retry attempts. mock_log.error.assert_called_with( 'Unable to send calendar sync email to user from "%s" to "%s"', from_address, self.user.email, exc_info=True) self.assertEqual(mock_log.error.call_count, 1) @mock.patch('openedx.features.calendar_sync.tasks.log') @mock.patch('openedx.features.calendar_sync.tasks.ace.send', mock.Mock(side_effect=ChannelError)) def test_UnrecoverableSendError(self, mock_log): """ Tests that a major failure of the send is logged """ from_address = '*****@*****.**' send_calendar_sync_email.delay(str(self.msg), from_address=from_address) # Asserts that the error was logged mock_log.exception.assert_called_with( 'Unable to send calendar sync email to user from "%s" to "%s"', from_address, self.user.email) # Assert that nothing else was logged self.assertEqual(mock_log.info.call_count, 0) self.assertEqual(mock_log.error.call_count, 0) self.assertEqual(mock_log.exception.call_count, 1)