Example #1
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_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?'})
Example #2
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_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?'})
Example #3
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?",
        )

        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?"},
        )
Example #4
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_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?'}
        )
Example #5
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 = 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)
Example #6
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?"},
        )
Example #7
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_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?'}
        )