def test_missed_message_worker(self) -> None: cordelia = self.example_user('cordelia') hamlet = self.example_user('hamlet') othello = self.example_user('othello') hamlet1_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='hi hamlet', ) hamlet2_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='goodbye hamlet', ) othello_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=othello.email, content='where art thou, othello?', ) events = [ dict(user_profile_id=hamlet.id, message_id=hamlet1_msg_id), dict(user_profile_id=hamlet.id, message_id=hamlet2_msg_id), dict(user_profile_id=othello.id, message_id=othello_msg_id), ] fake_client = self.FakeClient() for event in events: fake_client.queue.append(('missedmessage_emails', event)) mmw = MissedMessageWorker() time_mock = patch( 'zerver.worker.queue_processors.time.sleep', side_effect=AbortLoop, ) send_mock = patch( 'zerver.lib.notifications.do_send_missedmessage_events_reply_in_zulip' ) with send_mock as sm, time_mock as tm: with simulated_queue_client(lambda: fake_client): try: mmw.setup() mmw.start() except AbortLoop: pass self.assertEqual(tm.call_args[0][0], 120) # should sleep two minutes args = [c[0] for c in sm.call_args_list] arg_dict = { arg[0].id: dict( missed_messages=arg[1], count=arg[2], ) for arg in args } hamlet_info = arg_dict[hamlet.id] self.assertEqual(hamlet_info['count'], 2) self.assertEqual( {m.content for m in hamlet_info['missed_messages']}, {'hi hamlet', 'goodbye hamlet'}, ) othello_info = arg_dict[othello.id] self.assertEqual(othello_info['count'], 1) self.assertEqual({m.content for m in othello_info['missed_messages']}, {'where art thou, othello?'})
def test_missed_message_worker(self) -> None: cordelia = self.example_user('cordelia') hamlet = self.example_user('hamlet') othello = self.example_user('othello') hamlet1_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='hi hamlet', ) hamlet2_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='goodbye hamlet', ) hamlet3_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='hello again hamlet', ) othello_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=othello.email, content='where art thou, othello?', ) events = [ dict(user_profile_id=hamlet.id, message_id=hamlet1_msg_id), dict(user_profile_id=hamlet.id, message_id=hamlet2_msg_id), dict(user_profile_id=othello.id, message_id=othello_msg_id), ] fake_client = self.FakeClient() for event in events: fake_client.queue.append(('missedmessage_emails', event)) mmw = MissedMessageWorker() class MockTimer(): is_running = False def is_alive(self) -> bool: return self.is_running def start(self) -> None: self.is_running = True def cancel(self) -> None: self.is_running = False timer = MockTimer() time_mock = patch( 'zerver.worker.queue_processors.Timer', return_value=timer, ) send_mock = patch( 'zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip' ) mmw.BATCH_DURATION = 0 bonus_event = dict(user_profile_id=hamlet.id, message_id=hamlet3_msg_id) with send_mock as sm, time_mock as tm: with simulated_queue_client(lambda: fake_client): self.assertFalse(timer.is_alive()) mmw.setup() mmw.start() self.assertTrue(timer.is_alive()) fake_client.queue.append(('missedmessage_emails', bonus_event)) # Double-calling start is our way to get it to run again self.assertTrue(timer.is_alive()) mmw.start() # Now, we actually send the emails. mmw.maybe_send_batched_emails() self.assertFalse(timer.is_alive()) self.assertEqual(tm.call_args[0][0], 5) # should sleep 5 seconds args = [c[0] for c in sm.call_args_list] arg_dict = { arg[0].id: dict( missed_messages=arg[1], count=arg[2], ) for arg in args } hamlet_info = arg_dict[hamlet.id] self.assertEqual(hamlet_info['count'], 3) self.assertEqual( {m['message'].content for m in hamlet_info['missed_messages']}, {'hi hamlet', 'goodbye hamlet', 'hello again hamlet'}, ) othello_info = arg_dict[othello.id] self.assertEqual(othello_info['count'], 1) self.assertEqual( {m['message'].content for m in othello_info['missed_messages']}, {'where art thou, othello?'})
def test_missed_message_worker(self) -> None: cordelia = self.example_user("cordelia") hamlet = self.example_user("hamlet") othello = self.example_user("othello") hamlet1_msg_id = self.send_personal_message( from_user=cordelia, to_user=hamlet, content="hi hamlet", ) hamlet2_msg_id = self.send_personal_message( from_user=cordelia, to_user=hamlet, content="goodbye hamlet", ) hamlet3_msg_id = self.send_personal_message( from_user=cordelia, to_user=hamlet, content="hello again hamlet", ) othello_msg_id = self.send_personal_message( from_user=cordelia, to_user=othello, content="where art thou, othello?", ) events = [ dict(user_profile_id=hamlet.id, message_id=hamlet1_msg_id), dict(user_profile_id=hamlet.id, message_id=hamlet2_msg_id), dict(user_profile_id=othello.id, message_id=othello_msg_id), ] fake_client = self.FakeClient() for event in events: fake_client.enqueue("missedmessage_emails", event) mmw = MissedMessageWorker() class MockTimer: is_running = False def is_alive(self) -> bool: return self.is_running def start(self) -> None: self.is_running = True timer = MockTimer() timer_mock = patch( "zerver.worker.queue_processors.Timer", return_value=timer, ) send_mock = patch( "zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip", ) mmw.BATCH_DURATION = 0 bonus_event = dict(user_profile_id=hamlet.id, message_id=hamlet3_msg_id) with send_mock as sm, timer_mock as tm: with simulated_queue_client(lambda: fake_client): self.assertFalse(timer.is_alive()) mmw.setup() mmw.start() self.assertTrue(timer.is_alive()) fake_client.enqueue("missedmessage_emails", bonus_event) # Double-calling start is our way to get it to run again self.assertTrue(timer.is_alive()) mmw.start() with self.assertLogs(level="INFO") as info_logs: # Now, we actually send the emails. mmw.maybe_send_batched_emails() self.assertEqual( info_logs.output, [ "INFO:root:Batch-processing 3 missedmessage_emails events for user 10", "INFO:root:Batch-processing 1 missedmessage_emails events for user 12", ], ) self.assertEqual(mmw.timer_event, None) self.assertEqual(tm.call_args[0][0], 5) # should sleep 5 seconds args = [c[0] for c in sm.call_args_list] arg_dict = { arg[0].id: dict( missed_messages=arg[1], count=arg[2], ) for arg in args } hamlet_info = arg_dict[hamlet.id] self.assertEqual(hamlet_info["count"], 3) self.assertEqual( {m["message"].content for m in hamlet_info["missed_messages"]}, {"hi hamlet", "goodbye hamlet", "hello again hamlet"}, ) othello_info = arg_dict[othello.id] self.assertEqual(othello_info["count"], 1) self.assertEqual( {m["message"].content for m in othello_info["missed_messages"]}, {"where art thou, othello?"}, )
def test_missed_message_worker(self) -> None: cordelia = self.example_user('cordelia') hamlet = self.example_user('hamlet') othello = self.example_user('othello') hamlet1_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='hi hamlet', ) hamlet2_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='goodbye hamlet', ) othello_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=othello.email, content='where art thou, othello?', ) events = [ dict(user_profile_id=hamlet.id, message_id=hamlet1_msg_id), dict(user_profile_id=hamlet.id, message_id=hamlet2_msg_id), dict(user_profile_id=othello.id, message_id=othello_msg_id), ] fake_client = self.FakeClient() for event in events: fake_client.queue.append(('missedmessage_emails', event)) mmw = MissedMessageWorker() time_mock = patch( 'zerver.worker.queue_processors.time.sleep', side_effect=AbortLoop, ) send_mock = patch( 'zerver.lib.notifications.do_send_missedmessage_events_reply_in_zulip' ) with send_mock as sm, time_mock as tm: with simulated_queue_client(lambda: fake_client): try: mmw.setup() mmw.start() except AbortLoop: pass self.assertEqual(tm.call_args[0][0], 120) # should sleep two minutes args = [c[0] for c in sm.call_args_list] arg_dict = { arg[0].id: dict( missed_messages=arg[1], count=arg[2], ) for arg in args } hamlet_info = arg_dict[hamlet.id] self.assertEqual(hamlet_info['count'], 2) self.assertEqual( {m.content for m in hamlet_info['missed_messages']}, {'hi hamlet', 'goodbye hamlet'}, ) othello_info = arg_dict[othello.id] self.assertEqual(othello_info['count'], 1) self.assertEqual( {m.content for m in othello_info['missed_messages']}, {'where art thou, othello?'} )
def test_missed_message_worker(self) -> None: cordelia = self.example_user("cordelia") hamlet = self.example_user("hamlet") othello = self.example_user("othello") hamlet1_msg_id = self.send_personal_message( from_user=cordelia, to_user=hamlet, content="hi hamlet", ) hamlet2_msg_id = self.send_personal_message( from_user=cordelia, to_user=hamlet, content="goodbye hamlet", ) hamlet3_msg_id = self.send_personal_message( from_user=cordelia, to_user=hamlet, content="hello again hamlet", ) othello_msg_id = self.send_personal_message( from_user=cordelia, to_user=othello, content="where art thou, othello?", ) hamlet_event1 = dict( user_profile_id=hamlet.id, message_id=hamlet1_msg_id, trigger=NotificationTriggers.PRIVATE_MESSAGE, ) hamlet_event2 = dict( user_profile_id=hamlet.id, message_id=hamlet2_msg_id, trigger=NotificationTriggers.PRIVATE_MESSAGE, mentioned_user_group_id=4, ) othello_event = dict( user_profile_id=othello.id, message_id=othello_msg_id, trigger=NotificationTriggers.PRIVATE_MESSAGE, ) events = [hamlet_event1, hamlet_event2, othello_event] fake_client = FakeClient() for event in events: fake_client.enqueue("missedmessage_emails", event) mmw = MissedMessageWorker() batch_duration = datetime.timedelta( seconds=hamlet.email_notifications_batching_period_seconds ) assert ( hamlet.email_notifications_batching_period_seconds == othello.email_notifications_batching_period_seconds ) class MockTimer: is_running = False def is_alive(self) -> bool: return self.is_running def start(self) -> None: self.is_running = True timer = MockTimer() timer_mock = patch( "zerver.worker.queue_processors.Timer", return_value=timer, ) send_mock = patch( "zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip", ) bonus_event_hamlet = dict( user_profile_id=hamlet.id, message_id=hamlet3_msg_id, trigger=NotificationTriggers.PRIVATE_MESSAGE, ) def check_row( row: ScheduledMessageNotificationEmail, scheduled_timestamp: datetime.datetime, mentioned_user_group_id: Optional[int], ) -> None: self.assertEqual(row.trigger, NotificationTriggers.PRIVATE_MESSAGE) self.assertEqual(row.scheduled_timestamp, scheduled_timestamp) self.assertEqual(row.mentioned_user_group_id, mentioned_user_group_id) with send_mock as sm, timer_mock as tm: with simulated_queue_client(fake_client): self.assertFalse(timer.is_alive()) time_zero = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc) expected_scheduled_timestamp = time_zero + batch_duration with patch("zerver.worker.queue_processors.timezone_now", return_value=time_zero): mmw.setup() mmw.start() # The events should be saved in the database hamlet_row1 = ScheduledMessageNotificationEmail.objects.get( user_profile_id=hamlet.id, message_id=hamlet1_msg_id ) check_row(hamlet_row1, expected_scheduled_timestamp, None) hamlet_row2 = ScheduledMessageNotificationEmail.objects.get( user_profile_id=hamlet.id, message_id=hamlet2_msg_id ) check_row(hamlet_row2, expected_scheduled_timestamp, 4) othello_row1 = ScheduledMessageNotificationEmail.objects.get( user_profile_id=othello.id, message_id=othello_msg_id ) check_row(othello_row1, expected_scheduled_timestamp, None) # Additionally, the timer should have be started self.assertTrue(timer.is_alive()) # If another event is received, test that it gets saved with the same # `expected_scheduled_timestamp` as the earlier events. fake_client.enqueue("missedmessage_emails", bonus_event_hamlet) self.assertTrue(timer.is_alive()) few_moments_later = time_zero + datetime.timedelta(seconds=3) with patch( "zerver.worker.queue_processors.timezone_now", return_value=few_moments_later ): # Double-calling start is our way to get it to run again mmw.start() hamlet_row3 = ScheduledMessageNotificationEmail.objects.get( user_profile_id=hamlet.id, message_id=hamlet3_msg_id ) check_row(hamlet_row3, expected_scheduled_timestamp, None) # Now let us test `maybe_send_batched_emails` # If called too early, it shouldn't process the emails. one_minute_premature = expected_scheduled_timestamp - datetime.timedelta(seconds=60) with patch( "zerver.worker.queue_processors.timezone_now", return_value=one_minute_premature ): mmw.maybe_send_batched_emails() self.assertEqual(ScheduledMessageNotificationEmail.objects.count(), 4) # If called after `expected_scheduled_timestamp`, it should process all emails. one_minute_overdue = expected_scheduled_timestamp + datetime.timedelta(seconds=60) with self.assertLogs(level="INFO") as info_logs, patch( "zerver.worker.queue_processors.timezone_now", return_value=one_minute_overdue ): mmw.maybe_send_batched_emails() self.assertEqual(ScheduledMessageNotificationEmail.objects.count(), 0) self.assert_length(info_logs.output, 2) self.assertIn( f"INFO:root:Batch-processing 3 missedmessage_emails events for user {hamlet.id}", info_logs.output, ) self.assertIn( f"INFO:root:Batch-processing 1 missedmessage_emails events for user {othello.id}", info_logs.output, ) # All batches got processed. Verify that the timer isn't running. self.assertEqual(mmw.timer_event, None) # Hacky test coming up! We want to test the try-except block in the consumer which handles # IntegrityErrors raised when the message was deleted before it processed the notification # event. # However, Postgres defers checking ForeignKey constraints to when the current transaction # commits. This poses some difficulties in testing because of Django running tests inside a # transaction which never commits. See https://code.djangoproject.com/ticket/22431 for more # details, but the summary is that IntegrityErrors due to database constraints are raised at # the end of the test, not inside the `try` block. So, we have the code inside the `try` block # raise `IntegrityError` by mocking. def raise_error(**kwargs: Any) -> None: raise IntegrityError fake_client.enqueue("missedmessage_emails", hamlet_event1) with patch( "zerver.models.ScheduledMessageNotificationEmail.objects.create", side_effect=raise_error, ), self.assertLogs(level="DEBUG") as debug_logs: mmw.start() self.assertIn( "DEBUG:root:ScheduledMessageNotificationEmail row could not be created. The message may have been deleted. Skipping event.", debug_logs.output, ) # Check that the frequency of calling maybe_send_batched_emails is correct (5 seconds) self.assertEqual(tm.call_args[0][0], 5) # Verify the payloads now args = [c[0] for c in sm.call_args_list] arg_dict = { arg[0].id: dict( missed_messages=arg[1], count=arg[2], ) for arg in args } hamlet_info = arg_dict[hamlet.id] self.assertEqual(hamlet_info["count"], 3) self.assertEqual( {m["message"].content for m in hamlet_info["missed_messages"]}, {"hi hamlet", "goodbye hamlet", "hello again hamlet"}, ) othello_info = arg_dict[othello.id] self.assertEqual(othello_info["count"], 1) self.assertEqual( {m["message"].content for m in othello_info["missed_messages"]}, {"where art thou, othello?"}, ) with send_mock as sm, timer_mock as tm: with simulated_queue_client(fake_client): time_zero = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc) # Verify that we make forward progress if one of the messages throws an exception fake_client.enqueue("missedmessage_emails", hamlet_event1) fake_client.enqueue("missedmessage_emails", hamlet_event2) fake_client.enqueue("missedmessage_emails", othello_event) with patch("zerver.worker.queue_processors.timezone_now", return_value=time_zero): mmw.setup() mmw.start() def fail_some(user: UserProfile, *args: Any) -> None: if user.id == hamlet.id: raise RuntimeError sm.side_effect = fail_some one_minute_overdue = expected_scheduled_timestamp + datetime.timedelta(seconds=60) with patch( "zerver.worker.queue_processors.timezone_now", return_value=one_minute_overdue ), self.assertLogs(level="ERROR") as error_logs: mmw.maybe_send_batched_emails() self.assertIn( "ERROR:root:Failed to process 2 missedmessage_emails for user 10", error_logs.output[0], ) self.assertEqual(ScheduledMessageNotificationEmail.objects.count(), 0)
def test_missed_message_worker(self) -> None: cordelia = self.example_user("cordelia") hamlet = self.example_user("hamlet") othello = self.example_user("othello") hamlet1_msg_id = self.send_personal_message( from_user=cordelia, to_user=hamlet, content="hi hamlet", ) hamlet2_msg_id = self.send_personal_message( from_user=cordelia, to_user=hamlet, content="goodbye hamlet", ) hamlet3_msg_id = self.send_personal_message( from_user=cordelia, to_user=hamlet, content="hello again hamlet", ) othello_msg_id = self.send_personal_message( from_user=cordelia, to_user=othello, content="where art thou, othello?", ) hamlet_event1 = dict( user_profile_id=hamlet.id, message_id=hamlet1_msg_id, trigger=NotificationTriggers.PRIVATE_MESSAGE, ) hamlet_event2 = dict( user_profile_id=hamlet.id, message_id=hamlet2_msg_id, trigger=NotificationTriggers.PRIVATE_MESSAGE, mentioned_user_group_id=4, ) othello_event = dict( user_profile_id=othello.id, message_id=othello_msg_id, trigger=NotificationTriggers.PRIVATE_MESSAGE, ) events = [hamlet_event1, hamlet_event2, othello_event] fake_client = self.FakeClient() for event in events: fake_client.enqueue("missedmessage_emails", event) mmw = MissedMessageWorker() batch_duration = datetime.timedelta(seconds=mmw.BATCH_DURATION) class MockTimer: is_running = False def is_alive(self) -> bool: return self.is_running def start(self) -> None: self.is_running = True timer = MockTimer() timer_mock = patch( "zerver.worker.queue_processors.Timer", return_value=timer, ) send_mock = patch( "zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip", ) bonus_event_hamlet = dict( user_profile_id=hamlet.id, message_id=hamlet3_msg_id, trigger=NotificationTriggers.PRIVATE_MESSAGE, ) def check_row( row: ScheduledMessageNotificationEmail, scheduled_timestamp: datetime.datetime, mentioned_user_group_id: Optional[int], ) -> None: self.assertEqual(row.trigger, NotificationTriggers.PRIVATE_MESSAGE) self.assertEqual(row.scheduled_timestamp, scheduled_timestamp) self.assertEqual(row.mentioned_user_group_id, mentioned_user_group_id) with send_mock as sm, timer_mock as tm: with simulated_queue_client(lambda: fake_client): self.assertFalse(timer.is_alive()) time_zero = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc) expected_scheduled_timestamp = time_zero + batch_duration with patch("zerver.worker.queue_processors.timezone_now", return_value=time_zero): mmw.setup() mmw.start() # The events should be saved in the database hamlet_row1 = ScheduledMessageNotificationEmail.objects.get( user_profile_id=hamlet.id, message_id=hamlet1_msg_id) check_row(hamlet_row1, expected_scheduled_timestamp, None) hamlet_row2 = ScheduledMessageNotificationEmail.objects.get( user_profile_id=hamlet.id, message_id=hamlet2_msg_id) check_row(hamlet_row2, expected_scheduled_timestamp, 4) othello_row1 = ScheduledMessageNotificationEmail.objects.get( user_profile_id=othello.id, message_id=othello_msg_id) check_row(othello_row1, expected_scheduled_timestamp, None) # Additionally, the timer should have be started self.assertTrue(timer.is_alive()) # If another event is received, test that it gets saved with the same # `expected_scheduled_timestamp` as the earlier events. fake_client.enqueue("missedmessage_emails", bonus_event_hamlet) self.assertTrue(timer.is_alive()) few_moments_later = time_zero + datetime.timedelta(seconds=3) with patch("zerver.worker.queue_processors.timezone_now", return_value=few_moments_later): # Double-calling start is our way to get it to run again mmw.start() hamlet_row3 = ScheduledMessageNotificationEmail.objects.get( user_profile_id=hamlet.id, message_id=hamlet3_msg_id) check_row(hamlet_row3, expected_scheduled_timestamp, None) # Now let us test `maybe_send_batched_emails` # If called too early, it shouldn't process the emails. one_minute_premature = expected_scheduled_timestamp - datetime.timedelta( seconds=60) with patch("zerver.worker.queue_processors.timezone_now", return_value=one_minute_premature): mmw.maybe_send_batched_emails() self.assertEqual( ScheduledMessageNotificationEmail.objects.count(), 4) # If called after `expected_scheduled_timestamp`, it should process all emails. one_minute_overdue = expected_scheduled_timestamp + datetime.timedelta( seconds=60) with self.assertLogs(level="INFO") as info_logs, patch( "zerver.worker.queue_processors.timezone_now", return_value=one_minute_overdue): mmw.maybe_send_batched_emails() self.assertEqual( ScheduledMessageNotificationEmail.objects.count(), 0) self.assert_length(info_logs.output, 2) self.assertIn( f"INFO:root:Batch-processing 3 missedmessage_emails events for user {hamlet.id}", info_logs.output, ) self.assertIn( f"INFO:root:Batch-processing 1 missedmessage_emails events for user {othello.id}", info_logs.output, ) # All batches got processed. Verify that the timer isn't running. self.assertEqual(mmw.timer_event, None) # Check that the frequency of calling maybe_send_batched_emails is correct (5 seconds) self.assertEqual(tm.call_args[0][0], 5) # Verify the payloads now args = [c[0] for c in sm.call_args_list] arg_dict = { arg[0].id: dict( missed_messages=arg[1], count=arg[2], ) for arg in args } hamlet_info = arg_dict[hamlet.id] self.assertEqual(hamlet_info["count"], 3) self.assertEqual( {m["message"].content for m in hamlet_info["missed_messages"]}, {"hi hamlet", "goodbye hamlet", "hello again hamlet"}, ) othello_info = arg_dict[othello.id] self.assertEqual(othello_info["count"], 1) self.assertEqual( {m["message"].content for m in othello_info["missed_messages"]}, {"where art thou, othello?"}, )
def test_missed_message_worker(self) -> None: cordelia = self.example_user('cordelia') hamlet = self.example_user('hamlet') othello = self.example_user('othello') hamlet1_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='hi hamlet', ) hamlet2_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='goodbye hamlet', ) hamlet3_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=hamlet.email, content='hello again hamlet', ) othello_msg_id = self.send_personal_message( from_email=cordelia.email, to_email=othello.email, content='where art thou, othello?', ) events = [ dict(user_profile_id=hamlet.id, message_id=hamlet1_msg_id), dict(user_profile_id=hamlet.id, message_id=hamlet2_msg_id), dict(user_profile_id=othello.id, message_id=othello_msg_id), ] fake_client = self.FakeClient() for event in events: fake_client.queue.append(('missedmessage_emails', event)) mmw = MissedMessageWorker() class MockTimer(): is_running = False def is_alive(self) -> bool: return self.is_running def start(self) -> None: self.is_running = True def cancel(self) -> None: self.is_running = False timer = MockTimer() time_mock = patch( 'zerver.worker.queue_processors.Timer', return_value=timer, ) send_mock = patch( 'zerver.lib.notifications.do_send_missedmessage_events_reply_in_zulip' ) mmw.BATCH_DURATION = 0 bonus_event = dict(user_profile_id=hamlet.id, message_id=hamlet3_msg_id) with send_mock as sm, time_mock as tm: with simulated_queue_client(lambda: fake_client): self.assertFalse(timer.is_alive()) mmw.setup() mmw.start() self.assertTrue(timer.is_alive()) fake_client.queue.append(('missedmessage_emails', bonus_event)) # Double-calling start is our way to get it to run again self.assertTrue(timer.is_alive()) mmw.start() # Now, we actually send the emails. mmw.maybe_send_batched_emails() self.assertFalse(timer.is_alive()) self.assertEqual(tm.call_args[0][0], 5) # should sleep 5 seconds args = [c[0] for c in sm.call_args_list] arg_dict = { arg[0].id: dict( missed_messages=arg[1], count=arg[2], ) for arg in args } hamlet_info = arg_dict[hamlet.id] self.assertEqual(hamlet_info['count'], 3) self.assertEqual( {m['message'].content for m in hamlet_info['missed_messages']}, {'hi hamlet', 'goodbye hamlet', 'hello again hamlet'}, ) othello_info = arg_dict[othello.id] self.assertEqual(othello_info['count'], 1) self.assertEqual( {m['message'].content for m in othello_info['missed_messages']}, {'where art thou, othello?'} )