55from django .core import mail
66from django .core .mail import EmailMultiAlternatives
77from django .core .management import call_command
8+ from django .db import utils
89from django .test import TestCase , SimpleTestCase
910from django .test .utils import override_settings
1011from django_dynamic_fixture import G
@@ -378,41 +379,13 @@ def test_multiple_events_only_following_true(self):
378379 self .assertEquals (email .subject , '' )
379380 self .assertEquals (email .scheduled , datetime (2013 , 1 , 2 ))
380381
381- @freeze_time ('2013-1-2' )
382- def test_bulk_multiple_events_only_following_true (self ):
383- """
384- Handles bulk creating events and tests the unique constraint of the duplicated subscription which would cause
385- a bulk create error if it wasn't handled
386- """
387- source = G (Source )
388- e = G (Entity )
389- other_e = G (Entity )
390-
391- G (Subscription , entity = e , source = source , medium = self .email_medium , only_following = True )
392- G (Subscription , entity = e , source = source , medium = self .email_medium , only_following = True )
393- G (Subscription , entity = other_e , source = source , medium = self .email_medium , only_following = True )
394- email_context = {
395- 'entity_emailer_template' : 'template' ,
396- 'entity_emailer_subject' : 'hi' ,
397- }
398- G (Event , source = source , context = email_context )
399- event = G (Event , source = source , context = email_context )
400- G (EventActor , event = event , entity = e )
401-
402- EntityEmailerInterface .bulk_convert_events_to_emails ()
403-
404- email = Email .objects .get ()
405- self .assertEquals (set (email .recipients .all ()), set ([e ]))
406- self .assertEquals (email .event .context , email_context )
407- self .assertEquals (email .subject , '' )
408- self .assertEquals (email .scheduled , datetime (2013 , 1 , 2 ))
409-
410382
411383@freeze_time ('2014-01-05' )
412384class SendUnsentScheduledEmailsTest (TestCase ):
413385 def setUp (self ):
414386 G (Medium , name = 'email' )
415387
388+ @override_settings (DISABLE_DURABILITY_CHECKING = True )
416389 @patch ('entity_emailer.interface.get_subscribed_email_addresses' )
417390 @patch .object (Event , 'render' , spec_set = True )
418391 def test_sends_all_scheduled_emails_no_email_addresses (self , render_mock , address_mock ):
@@ -423,6 +396,7 @@ def test_sends_all_scheduled_emails_no_email_addresses(self, render_mock, addres
423396 EntityEmailerInterface .send_unsent_scheduled_emails ()
424397 self .assertEqual (len (mail .outbox ), 0 )
425398
399+ @override_settings (DISABLE_DURABILITY_CHECKING = True )
426400 @patch ('entity_emailer.interface.get_subscribed_email_addresses' )
427401 @patch .object (Event , 'render' , spec_set = True )
428402 def test_sends_all_scheduled_emails (self , render_mock , address_mock ):
@@ -436,6 +410,7 @@ def test_sends_all_scheduled_emails(self, render_mock, address_mock):
436410
437411 self .assertEqual (2 , mock_connection .return_value .__enter__ .return_value .send_messages .call_count )
438412
413+ @override_settings (DISABLE_DURABILITY_CHECKING = True )
439414 @patch ('entity_emailer.interface.pre_send' )
440415 @patch ('entity_emailer.interface.get_subscribed_email_addresses' )
441416 @patch .object (Event , 'render' , spec_set = True )
@@ -468,6 +443,7 @@ def test_send_signals(self, render_mock, address_mock, mock_pre_send):
468443 })
469444 self .assertIsInstance (kwargs ['message' ], EmailMultiAlternatives )
470445
446+ @override_settings (DISABLE_DURABILITY_CHECKING = True )
471447 @patch ('entity_emailer.interface.get_subscribed_email_addresses' )
472448 @patch .object (Event , 'render' , spec_set = True )
473449 def test_sends_email_with_specified_from_address (self , render_mock , address_mock ):
@@ -482,6 +458,7 @@ def test_sends_email_with_specified_from_address(self, render_mock, address_mock
482458 args = mock_connection .return_value .__enter__ .return_value .send_messages .call_args
483459 self .assertEqual (args [0 ][0 ][0 ].from_email , from_address )
484460
461+ @override_settings (DISABLE_DURABILITY_CHECKING = True )
485462 @patch ('entity_emailer.interface.get_subscribed_email_addresses' )
486463 @patch .object (Event , 'render' , spec_set = True )
487464 def test_sends_no_future_emails (self , render_mock , address_mock ):
@@ -491,6 +468,7 @@ def test_sends_no_future_emails(self, render_mock, address_mock):
491468 EntityEmailerInterface .send_unsent_scheduled_emails ()
492469 self .assertEqual (len (mail .outbox ), 0 )
493470
471+ @override_settings (DISABLE_DURABILITY_CHECKING = True )
494472 @patch ('entity_emailer.interface.get_subscribed_email_addresses' )
495473 @patch .object (Event , 'render' , spec_set = True )
496474 def test_sends_no_sent_emails (self , render_mock , address_mock ):
@@ -500,6 +478,7 @@ def test_sends_no_sent_emails(self, render_mock, address_mock):
500478 EntityEmailerInterface .send_unsent_scheduled_emails ()
501479 self .assertEqual (len (mail .outbox ), 0 )
502480
481+ @override_settings (DISABLE_DURABILITY_CHECKING = True )
503482 @patch ('entity_emailer.interface.get_subscribed_email_addresses' )
504483 @patch .object (Event , 'render' , spec_set = True )
505484 def test_updates_times (self , render_mock , address_mock ):
@@ -510,6 +489,7 @@ def test_updates_times(self, render_mock, address_mock):
510489 sent_email = Email .objects .filter (sent__isnull = False )
511490 self .assertEqual (sent_email .count (), 1 )
512491
492+ @override_settings (DISABLE_DURABILITY_CHECKING = True )
513493 @patch ('entity_emailer.interface.email_exception' )
514494 @patch ('entity_emailer.interface.get_subscribed_email_addresses' )
515495 @patch .object (Event , 'render' , spec_set = True )
@@ -535,19 +515,26 @@ def test_exceptions(self, render_mock, address_mock, mock_email_exception):
535515 with patch (settings .EMAIL_BACKEND ) as mock_connection :
536516 EntityEmailerInterface .send_unsent_scheduled_emails ()
537517
538- # Assert that both emails were marked as sent
539- self .assertEqual (Email .objects .filter (sent__isnull = False ).count (), 2 )
518+ # Assert that only one email was marked as sent
519+ self .assertEqual (Email .objects .filter (sent__isnull = False ).count (), 1 )
540520
541521 # Assert that only one email is actually sent through backend
542522 self .assertEquals (1 , mock_connection .call_count )
543- # Assert that one email raised an exception
544- exception_email = Email .objects .get (sent__isnull = False , exception__isnull = False )
523+ # Assert that one email raised an exception and that the tries count was updated
524+ exception_email = Email .objects .get (exception__isnull = False , num_tries = 1 )
545525 self .assertIsNotNone (exception_email )
546526 self .assertTrue ('Exception: test' in exception_email .exception )
547527
528+ def test_send_unsent_emails_is_durable (self ):
529+ """
530+ Verify that the process to send unsent emails is durable and will not be rolled-back on exit from any caller
531+ """
532+ self .assertRaises (utils .ProgrammingError , EntityEmailerInterface .send_unsent_scheduled_emails )
533+
534+ @override_settings (DISABLE_DURABILITY_CHECKING = True , ENTITY_EMAILER_MAX_SEND_MESSAGE_TRIES = 2 )
548535 @patch .object (Event , 'render' , spec_set = True )
549536 @patch ('entity_emailer.interface.get_subscribed_email_addresses' )
550- def test_send_exceptions (self , mock_get_subscribed_addresses , mock_render ):
537+ def test_send_exceptions_and_retry (self , mock_get_subscribed_addresses , mock_render ):
551538 """
552539 Verifies that when a single email raises an exception from within the backend, the batch is still
553540 updated as sent and the failed email is saved with the exception
@@ -574,16 +561,44 @@ def to_dict(self):
574561
575562 EntityEmailerInterface .send_unsent_scheduled_emails ()
576563
577- # Verify that both emails are marked as sent
578- self .assertEquals (2 , Email .objects .filter (sent__isnull = False ).count ())
564+ # Verify that only one email is marked as sent
565+ self .assertEquals (1 , Email .objects .filter (sent__isnull = False ).count ())
579566 # Verify that the failed email was saved with the exception
580- actual_failed_email = Email .objects .get (sent__isnull = False , exception__isnull = False )
567+ actual_failed_email = Email .objects .get (exception__isnull = False , num_tries = 1 )
581568 self .assertEquals (failed_email .id , actual_failed_email .id )
582569 self .assertEquals (
583570 'test: {}' .format (json .dumps ({'message' : 'test' })),
584571 actual_failed_email .exception
585572 )
586573
574+ # Verify that a subsequent attempt to send unscheduled emails will retry the failed email
575+ with patch (settings .EMAIL_BACKEND ) as mock_connection :
576+ # Mock side effect for sending email
577+ mock_connection .return_value .__enter__ .return_value .send_messages .side_effect = (
578+ TestEmailSendMessageException ('test' )
579+ )
580+
581+ EntityEmailerInterface .send_unsent_scheduled_emails ()
582+
583+ # Verify only called once, with the failed email
584+ self .assertEquals (1 , mock_connection .return_value .__enter__ .return_value .send_messages .call_count )
585+
586+ # Verify num tries updated
587+ actual_failed_email = Email .objects .get (exception__isnull = False , num_tries = 2 )
588+ self .assertEquals (failed_email .id , actual_failed_email .id )
589+
590+ # Verify that a subsequent attempt to send unscheduled emails will find no emails to send
591+ with patch (settings .EMAIL_BACKEND ) as mock_connection :
592+
593+ EntityEmailerInterface .send_unsent_scheduled_emails ()
594+
595+ # Verify only called once, with the failed email
596+ self .assertEquals (0 , mock_connection .return_value .__enter__ .return_value .send_messages .call_count )
597+
598+ # Verify num tries not updated
599+ actual_failed_email = Email .objects .get (exception__isnull = False , num_tries = 2 )
600+ self .assertEquals (failed_email .id , actual_failed_email .id )
601+
587602
588603class CreateEmailObjectTest (TestCase ):
589604 def test_no_html (self ):
0 commit comments