Skip to content

Commit

Permalink
even stronger error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
brad committed May 9, 2016
1 parent 2f2395d commit 13dc2d3
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 27 deletions.
55 changes: 32 additions & 23 deletions misfitapp/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def process_notification(content):
# Try to get the appropriate Misfit model based on message type
misfit_class = getattr(models, message.type.capitalize()[0:-1])
# Run the class's processing on the message
mf_obj, _ = misfit_class.process_message(message, misfit, uid)
obj, _ = misfit_class.process_message(message, misfit, uid)
except AttributeError:
logger.exception('Received unknown misfit notification type' +
message.type)
Expand All @@ -91,31 +91,40 @@ def process_notification(content):
'Error while processing {0} message with id {1}'.format(
message.type, message.id)
)
if message.type == 'goals' and mf_obj:
# Adjust date range for later summary retrieval
# For whatever reason, the end_date is not inclusive, so
# we add a day
next_day = mf_obj.date + arrow.util.timedelta(days=1)
if ownerId not in summaries:
summaries[ownerId] = {
'misfit': misfit,
'mfuser_id': mfuser.user_id,
'date_range': {'start': mf_obj.date, 'end': next_day}
}
elif mf_obj.date < summaries[ownerId]['date_range']['start']:
summaries[ownerId]['date_range']['start'] = mf_obj.date
elif mf_obj.date > summaries[ownerId]['date_range']['end']:
summaries[ownerId]['date_range']['end'] = next_day
except MisfitRateLimitError:
raise misfit_retry_exc(process_notification, sys.exc_info()[1])
except Exception:
logger.exception(
'Generic exception while processing {0} data: {1}'.format(
message.type, sys.exc_info()[1])
)
else:
if message.type == 'goals' and obj:
# Adjust date range for later summary retrieval
# For whatever reason, the end_date is not inclusive, so
# we add a day
goal = obj
next_day = goal.date + arrow.util.timedelta(days=1)
if ownerId not in summaries:
summaries[ownerId] = {
'misfit': misfit,
'mfuser_id': mfuser.user_id,
'date_range': {'start': goal.date, 'end': next_day}
}
elif goal.date < summaries[ownerId]['date_range']['start']:
summaries[ownerId]['date_range']['start'] = goal.date
elif goal.date > summaries[ownerId]['date_range']['end']:
summaries[ownerId]['date_range']['end'] = next_day

# Use the date ranges we built to get updated summary data
for ownerId, summary in summaries.items():
models.Summary.import_from_misfit(
summary['misfit'], summary['mfuser_id'], update=True,
start_date=summary['date_range']['start'],
end_date=summary['date_range']['end'])

except MisfitRateLimitError:
raise misfit_retry_exc(process_notification, sys.exc_info()[1])
try:
models.Summary.import_from_misfit(
summary['misfit'], summary['mfuser_id'], update=True,
start_date=summary['date_range']['start'],
end_date=summary['date_range']['end'])
except MisfitRateLimitError:
raise misfit_retry_exc(process_notification, sys.exc_info()[1])
except Exception:
exc = sys.exc_info()[1]
logger.exception("Unknown exception processing notification: %s" % exc)
Expand Down
70 changes: 66 additions & 4 deletions misfitapp/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,8 @@ def test_notification(self, mock_delay, verify_signature_mock):
@patch('celery.app.task.Task.delay')
@patch('misfit.Misfit.goal')
@patch('celery.app.task.Task.retry')
def test_notification_rate_limit(self, mock_retry, mock_goal, mock_delay,
verify_signature_mock, debug_mock):
def test_notification_rate_limit1(self, mock_retry, mock_goal, mock_delay,
verify_signature_mock, debug_mock):
""" Test that the notification task rate limit errors ok """
# Check that we fail gracefully when we hit the rate limit
mock_delay.side_effect = lambda arg: process_notification(arg)
Expand All @@ -362,6 +362,42 @@ def test_notification_rate_limit(self, mock_retry, mock_goal, mock_delay,
eq_(Profile.objects.filter(user=self.user).count(), 1)
eq_(Summary.objects.filter(user=self.user).count(), 0)

@freeze_time("2014-07-02 10:52:00", tz_offset=0)
@patch('logging.Logger.debug')
@patch('misfit.notification.MisfitNotification.verify_signature')
@patch('celery.app.task.Task.delay')
@patch('misfit.Misfit.summary')
@patch('celery.app.task.Task.retry')
def test_notification_rate_limit2(self, mock_retry, mock_summ, mock_delay,
verify_signature_mock, debug_mock):
""" Test that the notification task rate limit errors ok """
# Check that we fail gracefully when we hit the rate limit
mock_delay.side_effect = lambda arg: process_notification(arg)
resp = MagicMock()
resp.headers = {'x-ratelimit-reset': 1404298869}
exc = misfit_exceptions.MisfitRateLimitError(429, '', resp)
mock_summ.side_effect = exc
mock_retry.side_effect = Exception
with HTTMock(JsonMock().goal_http,
JsonMock().profile_http,
JsonMock('summary_detail').summary_http):
try:
content = json.dumps(self.notification_content).encode('utf8')
self.client.post(reverse('misfit-notification'), data=content,
content_type='application/json')
assert False, 'We should have raised an exception'
except Exception:
assert True
mock_delay.assert_called_once_with(content)
mock_summ.assert_called_once_with(
detail=True,
end_date=datetime.date(2014, 10, 8),
start_date=datetime.date(2014, 10, 5))
mock_retry.assert_called_once_with(countdown=549)
eq_(Goal.objects.filter(user=self.user).count(), 2)
eq_(Profile.objects.filter(user=self.user).count(), 1)
eq_(Summary.objects.filter(user=self.user).count(), 0)

@patch('logging.Logger.exception')
@patch('celery.app.task.Task.delay')
@patch('misfit.notification.MisfitNotification.verify_signature')
Expand All @@ -381,8 +417,6 @@ def test_notification_badrequest(self, mock_process, mock_verify,
self.client.post(reverse('misfit-notification'), data=content,
content_type='application/json')
except Exception:
exc = sys.exc_info()[1]
print(exc)
assert False, 'We should not have raised an exception'
mock_exc.assert_called_once_with(
'Error while processing profiles message with id 1234')
Expand All @@ -392,6 +426,33 @@ def test_notification_badrequest(self, mock_process, mock_verify,
eq_(Profile.objects.filter(user=self.user).count(), 0)
eq_(Summary.objects.filter(user=self.user).count(), 3)

@patch('logging.Logger.exception')
@patch('celery.app.task.Task.delay')
@patch('misfit.notification.MisfitNotification.verify_signature')
@patch('misfitapp.models.Profile.process_message')
def test_notification_process_error(self, mock_process, mock_verify,
mock_delay, mock_exc):
""" Test that the notification task handles errors while processing """
# Check that we continue to get other data types after generic error
mock_delay.side_effect = lambda arg: process_notification(arg)
mock_process.side_effect = Exception('WHA HAPPENED?')
try:
with HTTMock(JsonMock().goal_http,
JsonMock().profile_http,
JsonMock('summary_detail').summary_http):
content = json.dumps(self.notification_content).encode('utf8')
self.client.post(reverse('misfit-notification'), data=content,
content_type='application/json')
except Exception:
assert False, 'We should not have raised an exception'
mock_exc.assert_called_once_with(
'Generic exception while processing profiles data: WHA HAPPENED?')
# We retrieve data for other types in the notification, even though
# there was an error processing the profile update
eq_(Goal.objects.filter(user=self.user).count(), 2)
eq_(Profile.objects.filter(user=self.user).count(), 0)
eq_(Summary.objects.filter(user=self.user).count(), 3)

@patch('logging.Logger.exception')
@patch('celery.app.task.Task.delay')
@patch('misfit.notification.MisfitNotification.verify_signature')
Expand All @@ -415,6 +476,7 @@ def test_notification_unknown_error(self, mock_create, mock_sig,
eq_(Profile.objects.filter(user=self.user).count(), 0)
eq_(Summary.objects.filter(user=self.user).count(), 0)


@patch('misfit.notification.MisfitNotification.verify_signature')
@patch('celery.app.task.Task.delay')
@patch('logging.Logger.warning')
Expand Down

0 comments on commit 13dc2d3

Please sign in to comment.