Beispiel #1
0
    def test_link_preview_non_html_data(self) -> None:
        user = self.example_user('hamlet')
        self.login_user(user)
        url = 'http://test.org/audio.mp3'
        with mock_queue_publish(
                'zerver.lib.actions.queue_json_publish') as patched:
            msg_id = self.send_stream_message(user,
                                              "Scotland",
                                              topic_name="foo",
                                              content=url)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        headers = {'content-type': 'application/octet-stream'}
        mocked_response = mock.Mock(
            side_effect=self.create_mock_response(url, headers=headers))

        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with mock.patch('requests.get', mocked_response), self.assertLogs(
                    level='INFO') as info_logs:
                FetchLinksEmbedData().consume(event)
                cached_data = link_embed_data_from_cache(url)
            self.assertTrue(
                'INFO:root:Time spent on get_link_embed_data for http://test.org/audio.mp3: '
                in info_logs.output[0])

        self.assertIsNone(cached_data)
        msg = Message.objects.select_related("sender").get(id=msg_id)
        self.assertEqual(('<p><a href="http://test.org/audio.mp3">'
                          'http://test.org/audio.mp3</a></p>'),
                         msg.rendered_content)
Beispiel #2
0
    def check_will_notify(self, **kwargs: Any) -> Tuple[str, str]:
        hamlet = self.example_user("hamlet")
        kwargs["user_profile_id"] = hamlet.id
        kwargs["message_id"] = 32

        # Fill up the parameters which weren't sent with defaults.
        kwargs = self.get_maybe_enqueue_notifications_parameters(**kwargs)

        email_notice = None
        mobile_notice = None
        with mock_queue_publish("zerver.tornado.event_queue.queue_json_publish"
                                ) as mock_queue_json_publish:
            notified = maybe_enqueue_notifications(**kwargs)
            for entry in mock_queue_json_publish.call_args_list:
                args = entry[0]
                if args[0] == "missedmessage_mobile_notifications":
                    mobile_notice = args[1]
                if args[0] == "missedmessage_emails":
                    email_notice = args[1]

            # Now verify the return value matches the queue actions
            if email_notice:
                self.assertTrue(notified["email_notified"])
            else:
                self.assertFalse(notified.get("email_notified", False))
            if mobile_notice:
                self.assertTrue(notified["push_notified"])
            else:
                self.assertFalse(notified.get("push_notified", False))
        return email_notice, mobile_notice
Beispiel #3
0
    def test_youtube_url_title_replaces_url(self) -> None:
        url = "https://www.youtube.com/watch?v=eSJTXC7Ixgg"
        with mock_queue_publish("zerver.lib.actions.queue_json_publish"):
            msg_id = self.send_personal_message(
                self.example_user("hamlet"),
                self.example_user("cordelia"),
                content=url,
            )
        msg = Message.objects.select_related("sender").get(id=msg_id)
        event = {
            "message_id": msg_id,
            "urls": [url],
            "message_realm_id": msg.sender.realm_id,
            "message_content": url,
        }

        mocked_data = {"title": "Clearer Code at Scale - Static Types at Zulip and Dropbox"}
        self.create_mock_response(url)
        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with self.assertLogs(level="INFO") as info_logs:
                with mock.patch(
                    "zerver.lib.markdown.link_preview.link_embed_data_from_cache",
                    lambda *args, **kwargs: mocked_data,
                ):
                    FetchLinksEmbedData().consume(event)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for https://www.youtube.com/watch?v=eSJTXC7Ixgg:"
                in info_logs.output[0]
            )

        msg.refresh_from_db()
        expected_content = f"""<p><a href="https://www.youtube.com/watch?v=eSJTXC7Ixgg">YouTube - Clearer Code at Scale - Static Types at Zulip and Dropbox</a></p>\n<div class="youtube-video message_inline_image"><a data-id="eSJTXC7Ixgg" href="https://www.youtube.com/watch?v=eSJTXC7Ixgg"><img src="{get_camo_url("https://i.ytimg.com/vi/eSJTXC7Ixgg/default.jpg")}"></a></div>"""
        self.assertEqual(expected_content, msg.rendered_content)
Beispiel #4
0
    def test_edit_message_history(self) -> None:
        user = self.example_user('hamlet')
        self.login_user(user)
        msg_id = self.send_stream_message(user,
                                          "Scotland",
                                          topic_name="editing",
                                          content="original")

        url = 'http://test.org/'
        mocked_response = mock.Mock(side_effect=self.create_mock_response(url))

        with mock_queue_publish(
                'zerver.views.message_edit.queue_json_publish') as patched:
            result = self.client_patch("/json/messages/" + str(msg_id), {
                'message_id': msg_id,
                'content': url,
            })
            self.assert_json_success(result)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with mock.patch('requests.get', mocked_response), self.assertLogs(
                    level='INFO') as info_logs:
                FetchLinksEmbedData().consume(event)
            self.assertTrue(
                'INFO:root:Time spent on get_link_embed_data for http://test.org/: '
                in info_logs.output[0])

        embedded_link = f'<a href="{url}" title="The Rock">The Rock</a>'
        msg = Message.objects.select_related("sender").get(id=msg_id)
        self.assertIn(embedded_link, msg.rendered_content)
Beispiel #5
0
    def test_link_preview_non_html_data(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)
        url = "http://test.org/audio.mp3"
        with mock_queue_publish("zerver.lib.actions.queue_json_publish") as patched:
            msg_id = self.send_stream_message(user, "Denmark", topic_name="foo", content=url)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        content_type = "application/octet-stream"
        self.create_mock_response(url, content_type=content_type)

        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with self.assertLogs(level="INFO") as info_logs:
                FetchLinksEmbedData().consume(event)
                cached_data = link_embed_data_from_cache(url)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/audio.mp3: "
                in info_logs.output[0]
            )

        self.assertIsNone(cached_data)
        msg = Message.objects.select_related("sender").get(id=msg_id)
        self.assertEqual(
            ('<p><a href="http://test.org/audio.mp3">' "http://test.org/audio.mp3</a></p>"),
            msg.rendered_content,
        )
Beispiel #6
0
    def test_invalid_url(self) -> None:
        url = "http://test.org/"
        error_url = "http://test.org/x"
        with mock_queue_publish("zerver.lib.actions.queue_json_publish"):
            msg_id = self.send_personal_message(
                self.example_user("hamlet"),
                self.example_user("cordelia"),
                content=error_url,
            )
        msg = Message.objects.select_related("sender").get(id=msg_id)
        event = {
            "message_id": msg_id,
            "urls": [error_url],
            "message_realm_id": msg.sender.realm_id,
            "message_content": error_url,
        }

        self.create_mock_response(error_url, status=404)
        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with self.assertLogs(level="INFO") as info_logs:
                FetchLinksEmbedData().consume(event)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/x: "
                in info_logs.output[0]
            )
            cached_data = link_embed_data_from_cache(error_url)

        # FIXME: Should we really cache this, especially without cache invalidation?
        self.assertIsNone(cached_data)
        msg.refresh_from_db()
        self.assertEqual(
            '<p><a href="http://test.org/x">http://test.org/x</a></p>', msg.rendered_content
        )
        self.assertTrue(responses.assert_call_count(url, 0))
Beispiel #7
0
    def test_link_preview_open_graph_image_missing_content(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)
        url = "http://test.org/foo.html"
        with mock_queue_publish("zerver.lib.actions.queue_json_publish") as patched:
            msg_id = self.send_stream_message(user, "Denmark", topic_name="foo", content=url)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        # HTML without the og:image metadata
        html = "\n".join(
            line if "og:image" not in line else '<meta property="og:image"/>'
            for line in self.open_graph_html.splitlines()
        )
        self.create_mock_response(url, body=html)
        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with self.assertLogs(level="INFO") as info_logs:
                FetchLinksEmbedData().consume(event)
                cached_data = link_embed_data_from_cache(url)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/foo.html: "
                in info_logs.output[0]
            )

        self.assertIn("title", cached_data)
        self.assertNotIn("image", cached_data)
        msg = Message.objects.select_related("sender").get(id=msg_id)
        self.assertEqual(
            ('<p><a href="http://test.org/foo.html">' "http://test.org/foo.html</a></p>"),
            msg.rendered_content,
        )
Beispiel #8
0
    def test_edit_message_history(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)
        msg_id = self.send_stream_message(user, "Denmark", topic_name="editing", content="original")

        url = "http://test.org/"
        self.create_mock_response(url)

        with mock_queue_publish("zerver.lib.actions.queue_json_publish") as patched:
            result = self.client_patch(
                "/json/messages/" + str(msg_id),
                {
                    "content": url,
                },
            )
            self.assert_json_success(result)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with self.assertLogs(level="INFO") as info_logs:
                FetchLinksEmbedData().consume(event)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
                in info_logs.output[0]
            )

        embedded_link = f'<a href="{url}" title="The Rock">The Rock</a>'
        msg = Message.objects.select_related("sender").get(id=msg_id)
        self.assertIn(embedded_link, msg.rendered_content)
Beispiel #9
0
    def test_link_preview_css_escaping_image(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)
        url = "http://test.org/"
        with mock_queue_publish("zerver.lib.actions.queue_json_publish") as patched:
            msg_id = self.send_stream_message(user, "Denmark", topic_name="foo", content=url)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        # Swap the URL out for one with characters that need CSS escaping
        html = re.sub(r"rock\.jpg", "rock).jpg", self.open_graph_html)
        self.create_mock_response(url, body=html)
        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with self.assertLogs(level="INFO") as info_logs:
                FetchLinksEmbedData().consume(event)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
                in info_logs.output[0]
            )

        msg = Message.objects.select_related("sender").get(id=msg_id)
        with_preview = (
            '<p><a href="http://test.org/">http://test.org/</a></p>\n<div class="message_embed"><a class="message_embed_image" href="http://test.org/" style="background-image: url('
            + "http\\:\\/\\/ia\\.media-imdb\\.com\\/images\\/rock\\)\\.jpg"
            + ')"></a><div class="data-container"><div class="message_embed_title"><a href="http://test.org/" title="The Rock">The Rock</a></div><div class="message_embed_description">Description text</div></div></div>'
        )
        self.assertEqual(
            with_preview,
            msg.rendered_content,
        )
Beispiel #10
0
    def test_message_deleted(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)
        url = "http://test.org/"
        with mock_queue_publish("zerver.actions.message_send.queue_json_publish") as patched:
            msg_id = self.send_stream_message(user, "Denmark", topic_name="foo", content=url)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        msg = Message.objects.select_related("sender").get(id=msg_id)
        do_delete_messages(msg.sender.realm, [msg])

        # We do still fetch the URL, as we don't want to incur the
        # cost of locking the row while we do the HTTP fetches.
        self.create_mock_response(url)
        with self.settings(TEST_SUITE=False):
            with self.assertLogs(level="INFO") as info_logs:
                # Run the queue processor. This will simulate the event for original_url being
                # processed after the message has been deleted.
                FetchLinksEmbedData().consume(event)
        self.assertTrue(
            "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
            in info_logs.output[0]
        )
Beispiel #11
0
    def test_email_sending_worker_retries(self) -> None:
        """Tests the retry_send_email_failures decorator to make sure it
        retries sending the email 3 times and then gives up."""
        fake_client = self.FakeClient()

        data = {
            "template_prefix": "zerver/emails/confirm_new_email",
            "to_emails": [self.example_email("hamlet")],
            "from_name": "Zulip Account Security",
            "from_address": FromAddress.NOREPLY,
            "context": {},
        }
        fake_client.enqueue("email_senders", data)

        def fake_publish(queue_name: str, event: Dict[str, Any],
                         processor: Optional[Callable[[Any], None]]) -> None:
            fake_client.enqueue(queue_name, event)

        with simulated_queue_client(lambda: fake_client):
            worker = queue_processors.EmailSendingWorker()
            worker.setup()
            with patch("zerver.lib.send_email.build_email",
                       side_effect=EmailNotDeliveredException
                       ), mock_queue_publish(
                           "zerver.lib.queue.queue_json_publish",
                           side_effect=fake_publish), self.assertLogs(
                               level="ERROR") as m:
                worker.start()
                self.assertIn(
                    "failed due to exception EmailNotDeliveredException",
                    m.output[0])

        self.assertEqual(data["failed_tries"], 1 + MAX_REQUEST_RETRIES)
Beispiel #12
0
    def test_signups_worker_retries(self) -> None:
        """Tests the retry logic of signups queue."""
        fake_client = self.FakeClient()

        user_id = self.example_user('hamlet').id
        data = {'user_id': user_id, 'id': 'test_missed'}
        fake_client.queue.append(('signups', data))

        def fake_publish(queue_name: str, event: Dict[str, Any], processor: Optional[Callable[[Any], None]]) -> None:
            fake_client.queue.append((queue_name, event))

        fake_response = MagicMock()
        fake_response.status_code = 400
        fake_response.content = orjson.dumps({'title': ''})
        with simulated_queue_client(lambda: fake_client):
            worker = queue_processors.SignupWorker()
            worker.setup()
            with patch('zerver.worker.queue_processors.requests.post',
                       return_value=fake_response), \
                    mock_queue_publish('zerver.lib.queue.queue_json_publish',
                                       side_effect=fake_publish), \
                    patch('logging.info'), \
                    self.settings(MAILCHIMP_API_KEY='one-two',
                                  PRODUCTION=True,
                                  ZULIP_FRIENDS_LIST_ID='id'):
                worker.start()

        self.assertEqual(data['failed_tries'], 1 + MAX_REQUEST_RETRIES)
Beispiel #13
0
    def test_email_sending_worker_retries(self) -> None:
        """Tests the retry_send_email_failures decorator to make sure it
        retries sending the email 3 times and then gives up."""
        fake_client = self.FakeClient()

        data = {
            'template_prefix': 'zerver/emails/confirm_new_email',
            'to_emails': [self.example_email("hamlet")],
            'from_name': 'Zulip Account Security',
            'from_address': FromAddress.NOREPLY,
            'context': {},
        }
        fake_client.queue.append(('email_senders', data))

        def fake_publish(queue_name: str,
                         event: Dict[str, Any],
                         processor: Optional[Callable[[Any], None]]) -> None:
            fake_client.queue.append((queue_name, event))

        with simulated_queue_client(lambda: fake_client):
            worker = queue_processors.EmailSendingWorker()
            worker.setup()
            with patch('zerver.lib.send_email.build_email',
                       side_effect=smtplib.SMTPServerDisconnected), \
                    mock_queue_publish('zerver.lib.queue.queue_json_publish',
                                       side_effect=fake_publish), \
                    patch('logging.exception'):
                worker.start()

        self.assertEqual(data['failed_tries'], 1 + MAX_REQUEST_RETRIES)
Beispiel #14
0
    def test_link_preview_no_content_type_header(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)
        url = "http://test.org/"
        with mock_queue_publish("zerver.lib.actions.queue_json_publish") as patched:
            msg_id = self.send_stream_message(user, "Denmark", topic_name="foo", content=url)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        self.create_mock_response(url)
        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with self.assertLogs(level="INFO") as info_logs:
                FetchLinksEmbedData().consume(event)
                data = link_embed_data_from_cache(url)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
                in info_logs.output[0]
            )

        self.assertIn("title", data)
        self.assertIn("image", data)

        msg = Message.objects.select_related("sender").get(id=msg_id)
        self.assertIn(data["title"], msg.rendered_content)
        self.assertIn(re.sub(r"([^\w-])", r"\\\1", data["image"]), msg.rendered_content)
Beispiel #15
0
    def test_maybe_enqueue_notifications(self) -> None:
        # We've already tested the "when to send notifications" logic as part of the
        # notification_data module.
        # This test is for verifying whether `maybe_enqueue_notifications` returns the
        # `already_notified` data correctly.
        params = self.get_maybe_enqueue_notifications_parameters(
            message_id=1, user_id=1, acting_user_id=2)

        with mock_queue_publish("zerver.tornado.event_queue.queue_json_publish"
                                ) as mock_queue_json_publish:
            notified = maybe_enqueue_notifications(**params)
            mock_queue_json_publish.assert_not_called()

        with mock_queue_publish("zerver.tornado.event_queue.queue_json_publish"
                                ) as mock_queue_json_publish:
            params["private_message"] = True
            notified = maybe_enqueue_notifications(**params)
            self.assertTrue(mock_queue_json_publish.call_count, 2)

            queues_pushed = [
                entry[0][0] for entry in mock_queue_json_publish.call_args_list
            ]
            self.assertIn("missedmessage_mobile_notifications", queues_pushed)
            self.assertIn("missedmessage_emails", queues_pushed)

            self.assertTrue(notified["email_notified"])
            self.assertTrue(notified["push_notified"])

        with mock_queue_publish("zerver.tornado.event_queue.queue_json_publish"
                                ) as mock_queue_json_publish:
            params = self.get_maybe_enqueue_notifications_parameters(
                message_id=1,
                acting_user_id=2,
                user_id=3,
                private_message=False,
                flags=["mentioned"],
                mentioned=True,
                mentioned_user_group_id=33,
            )
            notified = maybe_enqueue_notifications(**params)
            self.assertTrue(mock_queue_json_publish.call_count, 2)

            push_notice = mock_queue_json_publish.call_args_list[0][0][1]
            self.assertEqual(push_notice["mentioned_user_group_id"], 33)

            email_notice = mock_queue_json_publish.call_args_list[1][0][1]
            self.assertEqual(email_notice["mentioned_user_group_id"], 33)
Beispiel #16
0
    def test_push_notifications_worker(self) -> None:
        """
        The push notifications system has its own comprehensive test suite,
        so we can limit ourselves to simple unit testing the queue processor,
        without going deeper into the system - by mocking the handle_push_notification
        functions to immediately produce the effect we want, to test its handling by the queue
        processor.
        """
        fake_client = self.FakeClient()

        def fake_publish(queue_name: str,
                         event: Dict[str, Any],
                         processor: Callable[[Any], None]) -> None:
            fake_client.queue.append((queue_name, event))

        def generate_new_message_notification() -> Dict[str, Any]:
            return build_offline_notification(1, 1)

        def generate_remove_notification() -> Dict[str, Any]:
            return {
                "type": "remove",
                "user_profile_id": 1,
                "message_ids": [1],
            }

        with simulated_queue_client(lambda: fake_client):
            worker = queue_processors.PushNotificationsWorker()
            worker.setup()
            with patch('zerver.worker.queue_processors.handle_push_notification') as mock_handle_new, \
                    patch('zerver.worker.queue_processors.handle_remove_push_notification') as mock_handle_remove, \
                    patch('zerver.worker.queue_processors.initialize_push_notifications'):
                event_new = generate_new_message_notification()
                event_remove = generate_remove_notification()
                fake_client.queue.append(('missedmessage_mobile_notifications', event_new))
                fake_client.queue.append(('missedmessage_mobile_notifications', event_remove))

                worker.start()
                mock_handle_new.assert_called_once_with(event_new['user_profile_id'], event_new)
                mock_handle_remove.assert_called_once_with(event_remove['user_profile_id'],
                                                           event_remove['message_ids'])

            with patch('zerver.worker.queue_processors.handle_push_notification',
                       side_effect=PushNotificationBouncerRetryLaterError("test")) as mock_handle_new, \
                    patch('zerver.worker.queue_processors.handle_remove_push_notification',
                          side_effect=PushNotificationBouncerRetryLaterError("test")) as mock_handle_remove, \
                    patch('zerver.worker.queue_processors.initialize_push_notifications'):
                event_new = generate_new_message_notification()
                event_remove = generate_remove_notification()
                fake_client.queue.append(('missedmessage_mobile_notifications', event_new))
                fake_client.queue.append(('missedmessage_mobile_notifications', event_remove))

                with mock_queue_publish('zerver.lib.queue.queue_json_publish', side_effect=fake_publish), \
                        self.assertLogs('zerver.worker.queue_processors', 'WARNING') as warn_logs:
                    worker.start()
                    self.assertEqual(mock_handle_new.call_count, 1 + MAX_REQUEST_RETRIES)
                    self.assertEqual(mock_handle_remove.call_count, 1 + MAX_REQUEST_RETRIES)
                self.assertEqual(warn_logs.output, [
                    'WARNING:zerver.worker.queue_processors:Maximum retries exceeded for trigger:1 event:push_notification',
                ] * 2)
Beispiel #17
0
 def test_inline_relative_url_embed_preview(self) -> None:
     # Relative URLs should not be sent for URL preview.
     with mock_queue_publish("zerver.lib.actions.queue_json_publish") as patched:
         self.send_personal_message(
             self.example_user("prospero"),
             self.example_user("cordelia"),
             content="http://zulip.testserver/api/",
         )
         patched.assert_not_called()
Beispiel #18
0
    def _send_message_with_test_org_url(self,
                                        sender: UserProfile,
                                        queue_should_run: bool = True,
                                        relative_url: bool = False) -> Message:
        url = "http://test.org/"
        with mock_queue_publish(
                "zerver.lib.actions.queue_json_publish") as patched:
            msg_id = self.send_personal_message(
                sender,
                self.example_user("cordelia"),
                content=url,
            )
            if queue_should_run:
                patched.assert_called_once()
                queue = patched.call_args[0][0]
                self.assertEqual(queue, "embed_links")
                event = patched.call_args[0][1]
            else:
                patched.assert_not_called()
                # If we nothing was put in the queue, we don't need to
                # run the queue processor or any of the following code
                return Message.objects.select_related("sender").get(id=msg_id)

        # Verify the initial message doesn't have the embedded links rendered
        msg = Message.objects.select_related("sender").get(id=msg_id)
        self.assertNotIn(f'<a href="{url}" title="The Rock">The Rock</a>',
                         msg.rendered_content)

        # Mock the network request result so the test can be fast without Internet
        mocked_response = mock.Mock(side_effect=self.create_mock_response(
            url, relative_url=relative_url))

        # Run the queue processor to potentially rerender things
        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with mock.patch("requests.get", mocked_response), self.assertLogs(
                    level="INFO") as info_logs:
                FetchLinksEmbedData().consume(event)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
                in info_logs.output[0])

        msg = Message.objects.select_related("sender").get(id=msg_id)
        return msg
Beispiel #19
0
    def test_valid_content_type_error_get_data(self) -> None:
        url = "http://test.org/"
        with mock_queue_publish(
                "zerver.actions.message_send.queue_json_publish"):
            msg_id = self.send_personal_message(
                self.example_user("hamlet"),
                self.example_user("cordelia"),
                content=url,
            )
        msg = Message.objects.select_related("sender").get(id=msg_id)
        event = {
            "message_id": msg_id,
            "urls": [url],
            "message_realm_id": msg.sender.realm_id,
            "message_content": url,
        }

        self.create_mock_response(url, body=ConnectionError())

        with mock.patch(
                "zerver.lib.url_preview.preview.get_oembed_data",
                side_effect=lambda *args, **kwargs: None,
        ):
            with mock.patch(
                    "zerver.lib.url_preview.preview.valid_content_type",
                    side_effect=lambda k: True):
                with self.settings(TEST_SUITE=False):
                    with self.assertLogs(level="INFO") as info_logs:
                        FetchLinksEmbedData().consume(event)
                    self.assertTrue(
                        "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
                        in info_logs.output[0])

                    # This did not get cached -- hence the lack of [0] on the cache_get
                    cached_data = cache_get(preview_url_cache_key(url))
                    self.assertIsNone(cached_data)

        msg.refresh_from_db()
        self.assertEqual(
            '<p><a href="http://test.org/">http://test.org/</a></p>',
            msg.rendered_content)
Beispiel #20
0
    def test_safe_oembed_html_url(self) -> None:
        url = "http://test.org/"
        with mock_queue_publish("zerver.lib.actions.queue_json_publish"):
            msg_id = self.send_personal_message(
                self.example_user("hamlet"),
                self.example_user("cordelia"),
                content=url,
            )
        msg = Message.objects.select_related("sender").get(id=msg_id)
        event = {
            "message_id": msg_id,
            "urls": [url],
            "message_realm_id": msg.sender.realm_id,
            "message_content": url,
        }

        mocked_data = {
            "html": f'<iframe src="{url}"></iframe>',
            "oembed": True,
            "type": "video",
            "image": f"{url}/image.png",
        }
        mocked_response = mock.Mock(side_effect=self.create_mock_response(url))
        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with mock.patch("requests.get", mocked_response), self.assertLogs(
                level="INFO"
            ) as info_logs:
                with mock.patch(
                    "zerver.lib.url_preview.preview.get_oembed_data",
                    lambda *args, **kwargs: mocked_data,
                ):
                    FetchLinksEmbedData().consume(event)
                    data = link_embed_data_from_cache(url)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
                in info_logs.output[0]
            )

        self.assertEqual(data, mocked_data)
        msg.refresh_from_db()
        self.assertIn('a data-id="{}"'.format(escape(mocked_data["html"])), msg.rendered_content)
Beispiel #21
0
    def test_valid_content_type_error_get_data(self) -> None:
        url = "http://test.org/"
        with mock_queue_publish("zerver.lib.actions.queue_json_publish"):
            msg_id = self.send_personal_message(
                self.example_user("hamlet"),
                self.example_user("cordelia"),
                content=url,
            )
        msg = Message.objects.select_related("sender").get(id=msg_id)
        event = {
            "message_id": msg_id,
            "urls": [url],
            "message_realm_id": msg.sender.realm_id,
            "message_content": url,
        }

        with mock.patch(
                "zerver.lib.url_preview.preview.get_oembed_data",
                side_effect=lambda *args, **kwargs: None,
        ):
            with mock.patch(
                    "zerver.lib.url_preview.preview.valid_content_type",
                    side_effect=lambda k: True):
                with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
                    with mock.patch(
                            "requests.get",
                            mock.Mock(side_effect=ConnectionError(
                            ))), self.assertLogs(level="INFO") as info_logs:
                        FetchLinksEmbedData().consume(event)
                    self.assertTrue(
                        "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
                        in info_logs.output[0])

                    with self.assertRaises(NotFoundInCache):
                        link_embed_data_from_cache(url)

        msg.refresh_from_db()
        self.assertEqual(
            '<p><a href="http://test.org/">http://test.org/</a></p>',
            msg.rendered_content)
Beispiel #22
0
    def test_safe_oembed_html_url(self) -> None:
        url = 'http://test.org/'
        with mock_queue_publish('zerver.lib.actions.queue_json_publish'):
            msg_id = self.send_personal_message(
                self.example_user('hamlet'),
                self.example_user('cordelia'),
                content=url,
            )
        msg = Message.objects.select_related("sender").get(id=msg_id)
        event = {
            'message_id': msg_id,
            'urls': [url],
            'message_realm_id': msg.sender.realm_id,
            'message_content': url
        }

        mocked_data = {
            'html': f'<iframe src="{url}"></iframe>',
            'oembed': True,
            'type': 'video',
            'image': f'{url}/image.png'
        }
        mocked_response = mock.Mock(side_effect=self.create_mock_response(url))
        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with mock.patch('requests.get', mocked_response), self.assertLogs(
                    level='INFO') as info_logs:
                with mock.patch(
                        'zerver.lib.url_preview.preview.get_oembed_data',
                        lambda *args, **kwargs: mocked_data):
                    FetchLinksEmbedData().consume(event)
                    data = link_embed_data_from_cache(url)
            self.assertTrue(
                'INFO:root:Time spent on get_link_embed_data for http://test.org/: '
                in info_logs.output[0])

        self.assertEqual(data, mocked_data)
        msg.refresh_from_db()
        self.assertIn('a data-id="{}"'.format(escape(mocked_data['html'])),
                      msg.rendered_content)
Beispiel #23
0
    def check_will_notify(self, *args: Any, **kwargs: Any) -> Tuple[str, str]:
        email_notice = None
        mobile_notice = None
        with mock_queue_publish("zerver.tornado.event_queue.queue_json_publish") as mock_queue_json_publish:
            notified = maybe_enqueue_notifications(*args, **kwargs)
            for entry in mock_queue_json_publish.call_args_list:
                args = entry[0]
                if args[0] == "missedmessage_mobile_notifications":
                    mobile_notice = args[1]
                if args[0] == "missedmessage_emails":
                    email_notice = args[1]

            # Now verify the return value matches the queue actions
            if email_notice:
                self.assertTrue(notified['email_notified'])
            else:
                self.assertFalse(notified.get('email_notified', False))
            if mobile_notice:
                self.assertTrue(notified['push_notified'])
            else:
                self.assertFalse(notified.get('push_notified', False))
        return email_notice, mobile_notice
Beispiel #24
0
    def test_safe_oembed_html_url(self) -> None:
        url = "http://test.org/"
        with mock_queue_publish(
                "zerver.actions.message_send.queue_json_publish"):
            msg_id = self.send_personal_message(
                self.example_user("hamlet"),
                self.example_user("cordelia"),
                content=url,
            )
        msg = Message.objects.select_related("sender").get(id=msg_id)
        event = {
            "message_id": msg_id,
            "urls": [url],
            "message_realm_id": msg.sender.realm_id,
            "message_content": url,
        }

        mocked_data = UrlOEmbedData(
            html=f'<iframe src="{url}"></iframe>',
            type="video",
            image=f"{url}/image.png",
        )
        self.create_mock_response(url)
        with self.settings(TEST_SUITE=False):
            with self.assertLogs(level="INFO") as info_logs:
                with mock.patch(
                        "zerver.lib.url_preview.preview.get_oembed_data",
                        lambda *args, **kwargs: mocked_data,
                ):
                    FetchLinksEmbedData().consume(event)
                    cached_data = cache_get(preview_url_cache_key(url))[0]
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
                in info_logs.output[0])

        self.assertEqual(cached_data, mocked_data)
        msg.refresh_from_db()
        self.assertIn(f'a data-id="{escape(mocked_data.html)}"',
                      msg.rendered_content)
Beispiel #25
0
    def test_link_preview_open_graph_image_missing_content(self) -> None:
        user = self.example_user('hamlet')
        self.login_user(user)
        url = 'http://test.org/foo.html'
        with mock_queue_publish(
                'zerver.lib.actions.queue_json_publish') as patched:
            msg_id = self.send_stream_message(user,
                                              "Scotland",
                                              topic_name="foo",
                                              content=url)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        # HTML without the og:image metadata
        html = '\n'.join(
            line if 'og:image' not in line else '<meta property="og:image"/>'
            for line in self.open_graph_html.splitlines())
        mocked_response = mock.Mock(
            side_effect=self.create_mock_response(url, html=html))
        with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
            with mock.patch('requests.get', mocked_response), self.assertLogs(
                    level='INFO') as info_logs:
                FetchLinksEmbedData().consume(event)
                cached_data = link_embed_data_from_cache(url)
            self.assertTrue(
                'INFO:root:Time spent on get_link_embed_data for http://test.org/foo.html: '
                in info_logs.output[0])

        self.assertIn('title', cached_data)
        self.assertNotIn('image', cached_data)
        msg = Message.objects.select_related("sender").get(id=msg_id)
        self.assertEqual(('<p><a href="http://test.org/foo.html">'
                          'http://test.org/foo.html</a></p>'),
                         msg.rendered_content)
Beispiel #26
0
    def test_message_update_race_condition(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)
        original_url = "http://test.org/"
        edited_url = "http://edited.org/"
        with mock_queue_publish(
                "zerver.lib.actions.queue_json_publish") as patched:
            msg_id = self.send_stream_message(user,
                                              "Scotland",
                                              topic_name="foo",
                                              content=original_url)
            patched.assert_called_once()
            queue = patched.call_args[0][0]
            self.assertEqual(queue, "embed_links")
            event = patched.call_args[0][1]

        def wrapped_queue_json_publish(*args: Any, **kwargs: Any) -> None:
            # Mock the network request result so the test can be fast without Internet
            mocked_response_original = mock.Mock(
                side_effect=self.create_mock_response(original_url))
            mocked_response_edited = mock.Mock(
                side_effect=self.create_mock_response(edited_url))

            with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
                with mock.patch("requests.get",
                                mocked_response_original), self.assertLogs(
                                    level="INFO") as info_logs:
                    # Run the queue processor. This will simulate the event for original_url being
                    # processed after the message has been edited.
                    FetchLinksEmbedData().consume(event)
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://test.org/: "
                in info_logs.output[0])
            msg = Message.objects.select_related("sender").get(id=msg_id)
            # The content of the message has changed since the event for original_url has been created,
            # it should not be rendered. Another, up-to-date event will have been sent (edited_url).
            self.assertNotIn(
                f'<a href="{original_url}" title="The Rock">The Rock</a>',
                msg.rendered_content)
            mocked_response_edited.assert_not_called()

            with self.settings(TEST_SUITE=False, CACHES=TEST_CACHES):
                with mock.patch("requests.get",
                                mocked_response_edited), self.assertLogs(
                                    level="INFO") as info_logs:
                    # Now proceed with the original queue_json_publish and call the
                    # up-to-date event for edited_url.
                    queue_json_publish(*args, **kwargs)
                    msg = Message.objects.select_related("sender").get(
                        id=msg_id)
                    self.assertIn(
                        f'<a href="{edited_url}" title="The Rock">The Rock</a>',
                        msg.rendered_content,
                    )
            self.assertTrue(
                "INFO:root:Time spent on get_link_embed_data for http://edited.org/: "
                in info_logs.output[0])

        with mock_queue_publish("zerver.lib.actions.queue_json_publish",
                                wraps=wrapped_queue_json_publish):
            result = self.client_patch(
                "/json/messages/" + str(msg_id),
                {
                    "message_id": msg_id,
                    "content": edited_url,
                },
            )
            self.assert_json_success(result)
    def _get_queued_data_for_message_update(
        self, message_id: int, content: str, expect_short_circuit: bool = False
    ) -> Dict[str, Any]:
        """
        This function updates a message with a post to
        /json/messages/(message_id).

        By using mocks, we are able to capture two pieces of data:

            enqueue_kwargs: These are the arguments passed in to
                            maybe_enqueue_notifications.

            queue_messages: These are the messages that
                            maybe_enqueue_notifications actually
                            puts on the queue.

        Using this helper allows you to construct a test that goes
        pretty deep into the missed-messages codepath, without actually
        queuing the final messages.
        """
        url = "/json/messages/" + str(message_id)

        request = dict(
            message_id=message_id,
            content=content,
        )

        with mock.patch("zerver.tornado.event_queue.maybe_enqueue_notifications") as m:
            result = self.client_patch(url, request)

        cordelia = self.example_user("cordelia")
        cordelia_calls = [
            call_args
            for call_args in m.call_args_list
            if call_args[1]["user_notifications_data"].user_id == cordelia.id
        ]

        if expect_short_circuit:
            self.assert_length(cordelia_calls, 0)
            return {}

        # Normally we expect maybe_enqueue_notifications to be
        # called for Cordelia, so continue on.
        self.assert_length(cordelia_calls, 1)
        enqueue_kwargs = cordelia_calls[0][1]

        queue_messages = []

        def fake_publish(queue_name: str, event: Union[Mapping[str, Any], str], *args: Any) -> None:
            queue_messages.append(
                dict(
                    queue_name=queue_name,
                    event=event,
                )
            )

        with mock_queue_publish(
            "zerver.tornado.event_queue.queue_json_publish", side_effect=fake_publish
        ) as m:
            maybe_enqueue_notifications(**enqueue_kwargs)

        self.assert_json_success(result)

        return dict(
            enqueue_kwargs=enqueue_kwargs,
            queue_messages=queue_messages,
        )
Beispiel #28
0
    def test_report_error(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)
        self.make_stream("errors", user.realm)

        params = fix_params(
            dict(
                message="hello",
                stacktrace="trace",
                ui_message=True,
                user_agent="agent",
                href="href",
                log="log",
                more_info=dict(foo="bar", draft_content="**draft**"),
            ))

        subprocess_mock = mock.patch(
            "zerver.views.report.subprocess.check_output",
            side_effect=subprocess.CalledProcessError(1, []),
        )
        with mock_queue_publish("zerver.views.report.queue_json_publish"
                                ) as m, subprocess_mock:
            result = self.client_post("/json/report/error", params)
        self.assert_json_success(result)

        report = m.call_args[0][1]["report"]
        for k in set(params) - {"ui_message", "more_info"}:
            self.assertEqual(report[k], params[k])

        self.assertEqual(report["more_info"],
                         dict(foo="bar", draft_content="'**xxxxx**'"))
        self.assertEqual(report["user_email"], user.delivery_email)

        # Teset with no more_info
        del params["more_info"]
        with mock_queue_publish("zerver.views.report.queue_json_publish"
                                ) as m, subprocess_mock:
            result = self.client_post("/json/report/error", params)
        self.assert_json_success(result)

        with self.settings(BROWSER_ERROR_REPORTING=False):
            result = self.client_post("/json/report/error", params)
        self.assert_json_success(result)

        # If js_source_map is present, then the stack trace should be annotated.
        # DEVELOPMENT=False and TEST_SUITE=False are necessary to ensure that
        # js_source_map actually gets instantiated.
        with self.settings(DEVELOPMENT=False, TEST_SUITE=False), mock.patch(
                "zerver.lib.unminify.SourceMap.annotate_stacktrace"
        ) as annotate, self.assertLogs(level="INFO") as info_logs:
            result = self.client_post("/json/report/error", params)
        self.assert_json_success(result)
        # fix_params (see above) adds quotes when JSON encoding.
        annotate.assert_called_once_with('"trace"')
        self.assertEqual(
            info_logs.output,
            ["INFO:root:Processing traceback with type browser for None"])

        # Now test without authentication.
        self.logout()
        with self.settings(DEVELOPMENT=False, TEST_SUITE=False), mock.patch(
                "zerver.lib.unminify.SourceMap.annotate_stacktrace"
        ) as annotate, self.assertLogs(level="INFO") as info_logs:
            result = self.client_post("/json/report/error", params)
        self.assert_json_success(result)
        self.assertEqual(
            info_logs.output,
            ["INFO:root:Processing traceback with type browser for None"])
Beispiel #29
0
    def test_request(self, mock_function: MagicMock) -> None:
        mock_function.return_value = None
        """A normal request is handled properly"""
        record = self.simulate_error()
        assert isinstance(record, HasRequest)

        report = self.run_handler(record)
        self.assertIn("user", report)
        self.assertIn("user_email", report["user"])
        self.assertIn("user_role", report["user"])
        self.assertIn("message", report)
        self.assertIn("stack_trace", report)

        # Test that `add_request_metadata` throwing an exception is fine
        with patch("zerver.logging_handlers.traceback.print_exc"):
            with patch(
                "zerver.logging_handlers.add_request_metadata",
                side_effect=Exception("Unexpected exception!"),
            ):
                report = self.run_handler(record)
        self.assertNotIn("user", report)
        self.assertIn("message", report)
        self.assertEqual(report["stack_trace"], "See /var/log/zulip/errors.log")

        # Check anonymous user is handled correctly
        record.request.user = AnonymousUser()
        report = self.run_handler(record)
        self.assertIn("host", report)
        self.assertIn("user", report)
        self.assertIn("user_email", report["user"])
        self.assertIn("user_role", report["user"])
        self.assertIn("message", report)
        self.assertIn("stack_trace", report)

        # Put it back so we continue to test the non-anonymous case
        record.request.user = self.example_user("hamlet")

        # Now simulate a DisallowedHost exception
        def get_host_error() -> None:
            raise Exception("Get host failure!")

        orig_get_host = record.request.get_host
        record.request.get_host = get_host_error
        report = self.run_handler(record)
        record.request.get_host = orig_get_host
        self.assertIn("host", report)
        self.assertIn("user", report)
        self.assertIn("user_email", report["user"])
        self.assertIn("user_role", report["user"])
        self.assertIn("message", report)
        self.assertIn("stack_trace", report)

        # Test an exception_filter exception
        with patch("zerver.logging_handlers.get_exception_reporter_filter", return_value=15):
            record.request.method = "POST"
            report = self.run_handler(record)
            record.request.method = "GET"
        self.assertIn("host", report)
        self.assertIn("user", report)
        self.assertIn("user_email", report["user"])
        self.assertIn("user_role", report["user"])
        self.assertIn("message", report)
        self.assertIn("stack_trace", report)

        # Test the catch-all exception handler doesn't throw
        with patch(
            "zerver.lib.error_notify.notify_server_error", side_effect=Exception("queue error")
        ):
            self.handler.emit(record)
        with self.settings(STAGING_ERROR_NOTIFICATIONS=False):
            with mock_queue_publish(
                "zerver.logging_handlers.queue_json_publish", side_effect=Exception("queue error")
            ) as m:
                with patch("logging.warning") as log_mock:
                    self.handler.emit(record)
                    m.assert_called_once()
                    log_mock.assert_called_once_with(
                        "Reporting an exception triggered an exception!", exc_info=True
                    )
            with mock_queue_publish("zerver.logging_handlers.queue_json_publish") as m:
                with patch("logging.warning") as log_mock:
                    self.handler.emit(record)
                    m.assert_called_once()
                    log_mock.assert_not_called()

        # Test no exc_info
        record.exc_info = None
        report = self.run_handler(record)
        self.assertIn("host", report)
        self.assertIn("user", report)
        self.assertIn("user_email", report["user"])
        self.assertIn("user_role", report["user"])
        self.assertIn("message", report)
        self.assertEqual(report["stack_trace"], "No stack trace available")

        # Test arbitrary exceptions from request.user
        record.request.user = None
        with patch("zerver.logging_handlers.traceback.print_exc"):
            report = self.run_handler(record)
        self.assertIn("host", report)
        self.assertIn("user", report)
        self.assertIn("user_email", report["user"])
        self.assertIn("user_role", report["user"])
        self.assertIn("message", report)
        self.assertIn("stack_trace", report)
Beispiel #30
0
    def test_push_notifications_worker(self) -> None:
        """
        The push notifications system has its own comprehensive test suite,
        so we can limit ourselves to simple unit testing the queue processor,
        without going deeper into the system - by mocking the handle_push_notification
        functions to immediately produce the effect we want, to test its handling by the queue
        processor.
        """
        fake_client = FakeClient()

        def fake_publish(
            queue_name: str, event: Dict[str, Any], processor: Callable[[Any], None]
        ) -> None:
            fake_client.enqueue(queue_name, event)

        def generate_new_message_notification() -> Dict[str, Any]:
            return build_offline_notification(1, 1)

        def generate_remove_notification() -> Dict[str, Any]:
            return {
                "type": "remove",
                "user_profile_id": 1,
                "message_ids": [1],
            }

        with simulated_queue_client(fake_client):
            worker = queue_processors.PushNotificationsWorker()
            worker.setup()
            with patch(
                "zerver.worker.queue_processors.handle_push_notification"
            ) as mock_handle_new, patch(
                "zerver.worker.queue_processors.handle_remove_push_notification"
            ) as mock_handle_remove, patch(
                "zerver.worker.queue_processors.initialize_push_notifications"
            ):
                event_new = generate_new_message_notification()
                event_remove = generate_remove_notification()
                fake_client.enqueue("missedmessage_mobile_notifications", event_new)
                fake_client.enqueue("missedmessage_mobile_notifications", event_remove)

                worker.start()
                mock_handle_new.assert_called_once_with(event_new["user_profile_id"], event_new)
                mock_handle_remove.assert_called_once_with(
                    event_remove["user_profile_id"], event_remove["message_ids"]
                )

            with patch(
                "zerver.worker.queue_processors.handle_push_notification",
                side_effect=PushNotificationBouncerRetryLaterError("test"),
            ) as mock_handle_new, patch(
                "zerver.worker.queue_processors.handle_remove_push_notification",
                side_effect=PushNotificationBouncerRetryLaterError("test"),
            ) as mock_handle_remove, patch(
                "zerver.worker.queue_processors.initialize_push_notifications"
            ):
                event_new = generate_new_message_notification()
                event_remove = generate_remove_notification()
                fake_client.enqueue("missedmessage_mobile_notifications", event_new)
                fake_client.enqueue("missedmessage_mobile_notifications", event_remove)

                with mock_queue_publish(
                    "zerver.lib.queue.queue_json_publish", side_effect=fake_publish
                ), self.assertLogs("zerver.worker.queue_processors", "WARNING") as warn_logs:
                    worker.start()
                    self.assertEqual(mock_handle_new.call_count, 1 + MAX_REQUEST_RETRIES)
                    self.assertEqual(mock_handle_remove.call_count, 1 + MAX_REQUEST_RETRIES)
                self.assertEqual(
                    warn_logs.output,
                    [
                        "WARNING:zerver.worker.queue_processors:Maximum retries exceeded for trigger:1 event:push_notification",
                    ]
                    * 2,
                )

            # This verifies the compatibility code for the `message_id` -> `message_ids`
            # conversion for "remove" events.
            with patch(
                "zerver.worker.queue_processors.handle_remove_push_notification"
            ) as mock_handle_remove, patch(
                "zerver.worker.queue_processors.initialize_push_notifications"
            ):
                event_new = dict(
                    user_profile_id=10,
                    message_id=33,
                    type="remove",
                )
                fake_client.enqueue("missedmessage_mobile_notifications", event_new)
                worker.start()
                # The `message_id` field should have been converted to a list with a single element.
                mock_handle_remove.assert_called_once_with(10, [33])