-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update FailedRunsNotificationCronJob to report more clearly
- Loading branch information
1 parent
518965a
commit c1e3614
Showing
6 changed files
with
266 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
django_cron/migrations/0003_cronjoblog_failure_reported.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.10.3 on 2016-11-29 14:35 | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
def set_old_logs_as_reported(apps, *args, **kwargs): | ||
CronJobLog = apps.get_model('django_cron', 'CronJobLog') | ||
CronJobLog.objects.update(failure_reported=True) | ||
|
||
|
||
def noop(*args, **kwargs): | ||
pass | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('django_cron', '0002_remove_max_length_from_CronJobLog_message'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='cronjoblog', | ||
name='failure_reported', | ||
field=models.BooleanField(default=False), | ||
), | ||
migrations.RunPython(set_old_logs_as_reported, reverse_code=noop) | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,19 +2,21 @@ | |
from time import sleep | ||
from datetime import timedelta | ||
|
||
from django import db | ||
from mock import patch | ||
from freezegun import freeze_time | ||
|
||
from django import db | ||
from django.test import TransactionTestCase | ||
from django.core.management import call_command | ||
from django.test.utils import override_settings | ||
from django.test.client import Client | ||
from django.core.urlresolvers import reverse | ||
from django.contrib.auth.models import User | ||
|
||
from freezegun import freeze_time | ||
|
||
from django_cron.cron import FailedRunsNotificationCronJob | ||
from django_cron.helpers import humanize_duration | ||
from django_cron.models import CronJobLog | ||
from test_crons import TestErrorCronJob | ||
|
||
|
||
class OutBuffer(object): | ||
|
@@ -34,7 +36,9 @@ def str_content(self): | |
return self._str_cache | ||
|
||
|
||
class TestCase(TransactionTestCase): | ||
class DjangoCronTestCase(TransactionTestCase): | ||
def setUp(self): | ||
CronJobLog.objects.all().delete() | ||
|
||
success_cron = 'test_crons.TestSucessCronJob' | ||
error_cron = 'test_crons.TestErrorCronJob' | ||
|
@@ -44,9 +48,8 @@ class TestCase(TransactionTestCase): | |
does_not_exist_cron = 'ThisCronObviouslyDoesntExist' | ||
test_failed_runs_notification_cron = 'django_cron.cron.FailedRunsNotificationCronJob' | ||
|
||
def setUp(self): | ||
CronJobLog.objects.all().delete() | ||
|
||
class BaseTests(DjangoCronTestCase): | ||
def test_success_cron(self): | ||
logs_count = CronJobLog.objects.all().count() | ||
call_command('runcrons', self.success_cron, force=True) | ||
|
@@ -157,16 +160,6 @@ def test_cache_locking_backend(self): | |
# t.join(10) | ||
# self.assertEqual(CronJobLog.objects.all().count(), logs_count + 1) | ||
|
||
def test_failed_runs_notification(self): | ||
CronJobLog.objects.all().delete() | ||
logs_count = CronJobLog.objects.all().count() | ||
|
||
for i in range(10): | ||
call_command('runcrons', self.error_cron, force=True) | ||
call_command('runcrons', self.test_failed_runs_notification_cron) | ||
|
||
self.assertEqual(CronJobLog.objects.all().count(), logs_count + 11) | ||
|
||
def test_humanize_duration(self): | ||
test_subjects = ( | ||
(timedelta(days=1, hours=1, minutes=1, seconds=1), '1 day, 1 hour, 1 minute, 1 second'), | ||
|
@@ -180,3 +173,113 @@ def test_humanize_duration(self): | |
humanize_duration(duration), | ||
humanized | ||
) | ||
|
||
|
||
class FailureReportTests(DjangoCronTestCase): | ||
""" | ||
Unit tests for the FailedRunsNotificationCronJob. | ||
""" | ||
def _error_cron(self): | ||
call_command('runcrons', self.error_cron, force=True) | ||
|
||
def _report_cron(self): | ||
call_command( | ||
'runcrons', self.test_failed_runs_notification_cron, | ||
force=True | ||
) | ||
|
||
def _error_and_report(self): | ||
self._error_cron() | ||
self._report_cron() | ||
|
||
def _resolve_reported_failures(self, cron_cls, failed_jobs): | ||
""" | ||
Resolve the failed jobs passed to the notifier's report_failure(). | ||
This allows us to assert the jobs passed given that failed jobs is a | ||
queryset which shouldn't match any instances after the notifier runs | ||
as it should make all log entries as having been reported. | ||
""" | ||
self.reported_cls = cron_cls | ||
self.reported_jobs = set(failed_jobs) | ||
|
||
@patch.object(FailedRunsNotificationCronJob, 'report_failure') | ||
def test_failed_notifications(self, mock_report): | ||
""" | ||
By default, the user should be notified after 10 job failures. | ||
""" | ||
mock_report.side_effect = self._resolve_reported_failures | ||
|
||
for _ in range(9): | ||
self._error_and_report() | ||
self.assertEquals(0, mock_report.call_count) | ||
|
||
# The tenth error triggers the report | ||
self._error_and_report() | ||
self.assertEqual(1, mock_report.call_count) | ||
|
||
# The correct job class and entries should be included | ||
self.assertEquals(TestErrorCronJob, self.reported_cls) | ||
self.assertEquals( | ||
set(CronJobLog.objects.filter(code=TestErrorCronJob.code)), | ||
self.reported_jobs | ||
) | ||
|
||
@patch.object(FailedRunsNotificationCronJob, 'report_failure') | ||
@override_settings(CRON_MIN_NUM_FAILURES=1) | ||
def test_settings_can_override_number_of_failures(self, mock_report): | ||
mock_report.side_effect = self._resolve_reported_failures | ||
self._error_and_report() | ||
self.assertEqual(1, mock_report.call_count) | ||
|
||
@patch.object(FailedRunsNotificationCronJob, 'report_failure') | ||
@override_settings(CRON_MIN_NUM_FAILURES=1) | ||
def test_logs_all_unreported(self, mock_report): | ||
mock_report.side_effect = self._resolve_reported_failures | ||
self._error_cron() | ||
self._error_and_report() | ||
self.assertEqual(1, mock_report.call_count) | ||
self.assertEqual(2, len(self.reported_jobs)) | ||
|
||
@patch.object(FailedRunsNotificationCronJob, 'report_failure') | ||
@override_settings(CRON_MIN_NUM_FAILURES=1) | ||
def test_only_logs_failures(self, mock_report): | ||
mock_report.side_effect = self._resolve_reported_failures | ||
call_command('runcrons', self.success_cron, force=True) | ||
self._error_and_report() | ||
self.assertEqual( | ||
self.reported_jobs, | ||
{CronJobLog.objects.get(code=TestErrorCronJob.code)} | ||
) | ||
|
||
@patch.object(FailedRunsNotificationCronJob, 'report_failure') | ||
@override_settings(CRON_MIN_NUM_FAILURES=1) | ||
def test_only_reported_once(self, mock_report): | ||
mock_report.side_effect = self._resolve_reported_failures | ||
self._error_and_report() | ||
self.assertEqual(1, mock_report.call_count) | ||
|
||
# Calling the notifier for a second time doesn't report a second time | ||
self._report_cron() | ||
self.assertEqual(1, mock_report.call_count) | ||
|
||
@patch('django_cron.cron.send_mail') | ||
@override_settings( | ||
CRON_MIN_NUM_FAILURES=1, | ||
CRON_FAILURE_FROM_EMAIL='[email protected]', | ||
CRON_FAILURE_EMAIL_RECIPIENTS=['[email protected]', '[email protected]'], | ||
FAILED_RUNS_CRONJOB_EMAIL_PREFIX='ERROR!!!' | ||
) | ||
def test_uses_send_mail(self, mock_send_mail): | ||
""" | ||
Test that django_common is used to send the email notifications. | ||
""" | ||
self._error_and_report() | ||
self.assertEquals(1, mock_send_mail.call_count) | ||
kwargs = mock_send_mail.call_args[1] | ||
|
||
self.assertIn('ERROR!!!', kwargs['subject']) | ||
self.assertEquals('[email protected]', kwargs['from_email']) | ||
self.assertEquals( | ||
['[email protected]', '[email protected]'], kwargs['recipient_emails'] | ||
) |
Oops, something went wrong.