mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	For get and filter queries of NamedUserGroup, realm_for_sharding field is used instead of realm field, as directly using realm_for_sharding field on NamedUserGroup makes the query faster than using realm present on the base UserGroup table.
		
			
				
	
	
		
			3733 lines
		
	
	
		
			162 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			3733 lines
		
	
	
		
			162 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import os
 | 
						|
import re
 | 
						|
from html import escape
 | 
						|
from textwrap import dedent
 | 
						|
from typing import Any
 | 
						|
from unittest import mock
 | 
						|
 | 
						|
import orjson
 | 
						|
import requests
 | 
						|
import responses
 | 
						|
from bs4 import BeautifulSoup
 | 
						|
from django.conf import settings
 | 
						|
from django.test import override_settings
 | 
						|
from markdown import Markdown
 | 
						|
from responses import matchers
 | 
						|
from typing_extensions import override
 | 
						|
 | 
						|
from zerver.actions.alert_words import do_add_alert_words
 | 
						|
from zerver.actions.create_realm import do_create_realm
 | 
						|
from zerver.actions.realm_emoji import do_remove_realm_emoji
 | 
						|
from zerver.actions.realm_settings import do_set_realm_property
 | 
						|
from zerver.actions.streams import do_change_stream_group_based_setting
 | 
						|
from zerver.actions.user_groups import (
 | 
						|
    add_subgroups_to_user_group,
 | 
						|
    check_add_user_group,
 | 
						|
    do_deactivate_user_group,
 | 
						|
)
 | 
						|
from zerver.actions.user_settings import do_change_user_setting
 | 
						|
from zerver.actions.users import change_user_is_active
 | 
						|
from zerver.lib.alert_words import get_alert_word_automaton
 | 
						|
from zerver.lib.camo import get_camo_url
 | 
						|
from zerver.lib.create_user import create_user
 | 
						|
from zerver.lib.emoji import codepoint_to_name, get_emoji_url
 | 
						|
from zerver.lib.emoji_utils import hex_codepoint_to_emoji
 | 
						|
from zerver.lib.exceptions import JsonableError, MarkdownRenderingError
 | 
						|
from zerver.lib.markdown import (
 | 
						|
    POSSIBLE_EMOJI_RE,
 | 
						|
    InlineInterestingLinkProcessor,
 | 
						|
    MarkdownListPreprocessor,
 | 
						|
    MessageRenderingResult,
 | 
						|
    clear_web_link_regex_for_testing,
 | 
						|
    content_has_emoji_syntax,
 | 
						|
    image_preview_enabled,
 | 
						|
    markdown_convert,
 | 
						|
    maybe_update_markdown_engines,
 | 
						|
    possible_linked_stream_names,
 | 
						|
    render_message_markdown,
 | 
						|
    topic_links,
 | 
						|
    url_embed_preview_enabled,
 | 
						|
    url_to_a,
 | 
						|
)
 | 
						|
from zerver.lib.markdown.fenced_code import FencedBlockPreprocessor
 | 
						|
from zerver.lib.mdiff import diff_strings
 | 
						|
from zerver.lib.mention import (
 | 
						|
    FullNameInfo,
 | 
						|
    MentionBackend,
 | 
						|
    MentionData,
 | 
						|
    PossibleMentions,
 | 
						|
    get_possible_mentions_info,
 | 
						|
    possible_mentions,
 | 
						|
    possible_user_group_mentions,
 | 
						|
    stream_wildcards,
 | 
						|
    topic_wildcards,
 | 
						|
)
 | 
						|
from zerver.lib.per_request_cache import flush_per_request_caches
 | 
						|
from zerver.lib.streams import user_has_content_access, user_has_metadata_access
 | 
						|
from zerver.lib.test_classes import ZulipTestCase
 | 
						|
from zerver.lib.tex import render_tex
 | 
						|
from zerver.lib.types import UserGroupMembersData
 | 
						|
from zerver.lib.upload import upload_message_attachment
 | 
						|
from zerver.lib.user_groups import UserGroupMembershipDetails
 | 
						|
from zerver.models import Message, NamedUserGroup, RealmEmoji, RealmFilter, UserMessage, UserProfile
 | 
						|
from zerver.models.clients import get_client
 | 
						|
from zerver.models.groups import SystemGroups
 | 
						|
from zerver.models.linkifiers import linkifiers_for_realm
 | 
						|
from zerver.models.realms import get_realm
 | 
						|
from zerver.models.streams import get_stream
 | 
						|
from zerver.models.users import get_system_bot
 | 
						|
 | 
						|
 | 
						|
class SimulatedFencedBlockPreprocessor(FencedBlockPreprocessor):
 | 
						|
    # Simulate code formatting.
 | 
						|
 | 
						|
    @override
 | 
						|
    def format_code(self, lang: str | None, code: str) -> str:
 | 
						|
        return (lang or "") + ":" + code
 | 
						|
 | 
						|
    @override
 | 
						|
    def placeholder(self, s: str) -> str:
 | 
						|
        return "**" + s.strip("\n") + "**"
 | 
						|
 | 
						|
 | 
						|
class FencedBlockPreprocessorTest(ZulipTestCase):
 | 
						|
    def test_simple_quoting(self) -> None:
 | 
						|
        processor = FencedBlockPreprocessor(Markdown())
 | 
						|
        markdown_input = [
 | 
						|
            "~~~ quote",
 | 
						|
            "hi",
 | 
						|
            "bye",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
        ]
 | 
						|
        expected = [
 | 
						|
            "",
 | 
						|
            "> hi",
 | 
						|
            "> bye",
 | 
						|
            "> ",
 | 
						|
            "> ",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
        ]
 | 
						|
        lines = processor.run(markdown_input)
 | 
						|
        self.assertEqual(lines, expected)
 | 
						|
 | 
						|
    def test_serial_quoting(self) -> None:
 | 
						|
        processor = FencedBlockPreprocessor(Markdown())
 | 
						|
        markdown_input = [
 | 
						|
            "~~~ quote",
 | 
						|
            "hi",
 | 
						|
            "~~~",
 | 
						|
            "",
 | 
						|
            "~~~ quote",
 | 
						|
            "bye",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
        ]
 | 
						|
        expected = [
 | 
						|
            "",
 | 
						|
            "> hi",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
            "> bye",
 | 
						|
            "> ",
 | 
						|
            "> ",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
        ]
 | 
						|
        lines = processor.run(markdown_input)
 | 
						|
        self.assertEqual(lines, expected)
 | 
						|
 | 
						|
    def test_serial_code(self) -> None:
 | 
						|
        processor = SimulatedFencedBlockPreprocessor(Markdown())
 | 
						|
 | 
						|
        markdown_input = [
 | 
						|
            "``` .py",
 | 
						|
            "hello()",
 | 
						|
            "```",
 | 
						|
            "",
 | 
						|
            "```vb.net",
 | 
						|
            "goodbye()",
 | 
						|
            "```",
 | 
						|
            "",
 | 
						|
            "```c#",
 | 
						|
            "weirdchar()",
 | 
						|
            "```",
 | 
						|
            "",
 | 
						|
            "```",
 | 
						|
            "no-highlight()",
 | 
						|
            "```",
 | 
						|
            "",
 | 
						|
        ]
 | 
						|
        expected = [
 | 
						|
            "",
 | 
						|
            "**py:hello()**",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
            "**vb.net:goodbye()**",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
            "**c#:weirdchar()**",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
            "**:no-highlight()**",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
        ]
 | 
						|
        lines = processor.run(markdown_input)
 | 
						|
        self.assertEqual(lines, expected)
 | 
						|
 | 
						|
    def test_nested_code(self) -> None:
 | 
						|
        processor = SimulatedFencedBlockPreprocessor(Markdown())
 | 
						|
 | 
						|
        markdown_input = [
 | 
						|
            "~~~ quote",
 | 
						|
            "hi",
 | 
						|
            "``` .py",
 | 
						|
            "hello()",
 | 
						|
            "```",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
        ]
 | 
						|
        expected = [
 | 
						|
            "",
 | 
						|
            "> hi",
 | 
						|
            "> ",
 | 
						|
            "> **py:hello()**",
 | 
						|
            "> ",
 | 
						|
            "> ",
 | 
						|
            "> ",
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
        ]
 | 
						|
        lines = processor.run(markdown_input)
 | 
						|
        self.assertEqual(lines, expected)
 | 
						|
 | 
						|
 | 
						|
def markdown_convert_wrapper(content: str) -> str:
 | 
						|
    return markdown_convert(
 | 
						|
        content=content,
 | 
						|
        message_realm=get_realm("zulip"),
 | 
						|
    ).rendered_content
 | 
						|
 | 
						|
 | 
						|
class MarkdownMiscTest(ZulipTestCase):
 | 
						|
    def test_diffs_work_as_expected(self) -> None:
 | 
						|
        str1 = "<p>The quick brown fox jumps over the lazy dog.  Animal stories are fun, yeah</p>"
 | 
						|
        str2 = "<p>The fast fox jumps over the lazy dogs and cats.  Animal stories are fun</p>"
 | 
						|
        expected_diff = "\u001b[34m-\u001b[0m <p>The \u001b[33mquick brown\u001b[0m fox jumps over the lazy dog.  Animal stories are fun\u001b[31m, yeah\u001b[0m</p>\n\u001b[34m+\u001b[0m <p>The \u001b[33mfast\u001b[0m fox jumps over the lazy dog\u001b[32ms and cats\u001b[0m.  Animal stories are fun</p>\n"
 | 
						|
        self.assertEqual(diff_strings(str1, str2), expected_diff)
 | 
						|
 | 
						|
    def test_get_possible_mentions_info(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
 | 
						|
        def make_user(email: str, full_name: str) -> UserProfile:
 | 
						|
            return create_user(
 | 
						|
                email=email,
 | 
						|
                password="whatever",
 | 
						|
                realm=realm,
 | 
						|
                full_name=full_name,
 | 
						|
            )
 | 
						|
 | 
						|
        fred1 = make_user("fred1@example.com", "Fred Flintstone")
 | 
						|
        change_user_is_active(fred1, False)
 | 
						|
 | 
						|
        fred2 = make_user("fred2@example.com", "Fred Flintstone")
 | 
						|
 | 
						|
        fred3 = make_user("fred3@example.com", "Fred Flintstone")
 | 
						|
        change_user_is_active(fred3, False)
 | 
						|
 | 
						|
        fred4 = make_user("fred4@example.com", "Fred Flintstone")
 | 
						|
 | 
						|
        mention_backend = MentionBackend(realm.id)
 | 
						|
        lst = get_possible_mentions_info(
 | 
						|
            mention_backend,
 | 
						|
            {"Fred Flintstone", "Cordelia, LEAR's daughter", "Not A User"},
 | 
						|
            message_sender=None,
 | 
						|
        )
 | 
						|
        set_of_names = {x.full_name.lower() for x in lst}
 | 
						|
        self.assertEqual(set_of_names, {"fred flintstone", "cordelia, lear's daughter"})
 | 
						|
 | 
						|
        by_id = {row.id: row for row in lst}
 | 
						|
        self.assertEqual(
 | 
						|
            by_id.get(fred2.id),
 | 
						|
            FullNameInfo(
 | 
						|
                full_name="Fred Flintstone",
 | 
						|
                id=fred2.id,
 | 
						|
                is_active=True,
 | 
						|
            ),
 | 
						|
        )
 | 
						|
        self.assertEqual(
 | 
						|
            by_id.get(fred4.id),
 | 
						|
            FullNameInfo(
 | 
						|
                full_name="Fred Flintstone",
 | 
						|
                id=fred4.id,
 | 
						|
                is_active=True,
 | 
						|
            ),
 | 
						|
        )
 | 
						|
 | 
						|
    def test_mention_data(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        content = "@**King Hamlet** @**Cordelia, lear's daughter**"
 | 
						|
        mention_backend = MentionBackend(realm.id)
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
        self.assertEqual(mention_data.get_user_ids(), {hamlet.id, cordelia.id})
 | 
						|
        self.assertEqual(
 | 
						|
            mention_data.get_user_by_id(hamlet.id),
 | 
						|
            FullNameInfo(
 | 
						|
                full_name=hamlet.full_name,
 | 
						|
                id=hamlet.id,
 | 
						|
                is_active=True,
 | 
						|
            ),
 | 
						|
        )
 | 
						|
 | 
						|
        user = mention_data.get_user_by_name("king hamLET")
 | 
						|
        assert user is not None
 | 
						|
        self.assertEqual(user.full_name, hamlet.full_name)
 | 
						|
 | 
						|
        self.assertFalse(mention_data.message_has_stream_wildcards())
 | 
						|
        content = "@**King Hamlet** @**Cordelia, lear's daughter** @**all**"
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
        self.assertTrue(mention_data.message_has_stream_wildcards())
 | 
						|
 | 
						|
        self.assertFalse(mention_data.message_has_topic_wildcards())
 | 
						|
        content = "@**King Hamlet** @**Cordelia, lear's daughter** @**topic**"
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
        self.assertTrue(mention_data.message_has_topic_wildcards())
 | 
						|
 | 
						|
        content = "@*hamletcharacters*"
 | 
						|
        group = NamedUserGroup.objects.get(realm_for_sharding=realm, name="hamletcharacters")
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
        self.assertEqual(mention_data.get_group_members(group.id), {hamlet.id, cordelia.id})
 | 
						|
 | 
						|
        change_user_is_active(cordelia, False)
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
        self.assertEqual(mention_data.get_group_members(group.id), {hamlet.id})
 | 
						|
 | 
						|
    def test_bulk_user_group_mentions(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
        mention_backend = MentionBackend(realm.id)
 | 
						|
 | 
						|
        content = ""
 | 
						|
        for i in range(5):
 | 
						|
            group_name = f"group{i}"
 | 
						|
            check_add_user_group(realm, group_name, [hamlet, cordelia], acting_user=othello)
 | 
						|
            content += f" @*{group_name}*"
 | 
						|
 | 
						|
        CONSTANT_QUERY_COUNT = 2  # even if it increases in future, make sure it's constant.
 | 
						|
        with self.assert_database_query_count(CONSTANT_QUERY_COUNT):
 | 
						|
            MentionData(mention_backend, content, message_sender=None)
 | 
						|
 | 
						|
    def test_mention_user_groups_with_common_subgroup(self) -> None:
 | 
						|
        # Mention multiple groups (class-A and class-B) with a common sub-group (good-students)
 | 
						|
        # and make sure each mentioned group has the expected members
 | 
						|
        # (i.e. direct and via sub-groups) in mention_data.
 | 
						|
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        aaron = self.example_user("aaron")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        iago = self.example_user("iago")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        good_students = check_add_user_group(
 | 
						|
            realm, "good-students", [aaron, hamlet], acting_user=othello
 | 
						|
        )
 | 
						|
        class_A = check_add_user_group(realm, "class-A", [iago], acting_user=othello)
 | 
						|
        class_B = check_add_user_group(realm, "class-B", [cordelia], acting_user=othello)
 | 
						|
 | 
						|
        add_subgroups_to_user_group(class_A, [good_students], acting_user=othello)
 | 
						|
        add_subgroups_to_user_group(class_B, [good_students], acting_user=othello)
 | 
						|
 | 
						|
        content = "@*class-A*  @*class-B*"
 | 
						|
        mention_backend = MentionBackend(realm.id)
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
 | 
						|
        # both groups should have their direct members and the sub-group's members.
 | 
						|
        self.assertEqual(mention_data.get_group_members(class_A.id), {iago.id, aaron.id, hamlet.id})
 | 
						|
        self.assertEqual(
 | 
						|
            mention_data.get_group_members(class_B.id), {cordelia.id, aaron.id, hamlet.id}
 | 
						|
        )
 | 
						|
 | 
						|
    def test_silent_mention_user_groups(self) -> None:
 | 
						|
        # silent VS non-silent group mentions, in regard to fetching group membership.
 | 
						|
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        aaron = self.example_user("aaron")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        iago = self.example_user("iago")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        hamlet_group = NamedUserGroup.objects.get(realm_for_sharding=realm, name="hamletcharacters")
 | 
						|
        zulip_group = check_add_user_group(realm, "zulip_group", [iago, aaron], acting_user=othello)
 | 
						|
        mention_backend = MentionBackend(realm.id)
 | 
						|
 | 
						|
        # mention zulip_group, but silent mention hamlet_group.
 | 
						|
        content = "@*zulip_group*, @_*hamletcharacters*"
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
 | 
						|
        # non-silent mention should fetch group membership.
 | 
						|
        self.assertEqual(mention_data.get_group_members(zulip_group.id), {iago.id, aaron.id})
 | 
						|
 | 
						|
        # silent mention should NOT fetch group membership.
 | 
						|
        self.assertEqual(mention_data.get_group_members(hamlet_group.id), set())
 | 
						|
 | 
						|
        # We should make sure non-silent mention always results in fetching group membership,
 | 
						|
        # whether it precedes or follows a silent mention for the SAME group
 | 
						|
 | 
						|
        # non-silent before silent.
 | 
						|
        content = "@*hamletcharacters*, @_*hamletcharacters*"
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
        self.assertEqual(mention_data.get_group_members(hamlet_group.id), {hamlet.id, cordelia.id})
 | 
						|
 | 
						|
        # non-silent after silent.
 | 
						|
        content = "@_*hamletcharacters*, @*hamletcharacters*"
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
        self.assertEqual(mention_data.get_group_members(hamlet_group.id), {hamlet.id, cordelia.id})
 | 
						|
 | 
						|
    def test_invalid_katex_path(self) -> None:
 | 
						|
        with self.settings(DEPLOY_ROOT="/nonexistent"):
 | 
						|
            with self.assertLogs(level="ERROR") as m:
 | 
						|
                render_tex("random text")
 | 
						|
            self.assertEqual(m.output, ["ERROR:root:Cannot find KaTeX for latex rendering!"])
 | 
						|
 | 
						|
    @responses.activate
 | 
						|
    @override_settings(KATEX_SERVER=True, SHARED_SECRET="foo")
 | 
						|
    def test_katex_server(self) -> None:
 | 
						|
        responses.post(
 | 
						|
            "http://localhost:9700/",
 | 
						|
            match=[
 | 
						|
                matchers.urlencoded_params_matcher(
 | 
						|
                    {"content": "foo", "is_display": "false", "shared_secret": "foo"}
 | 
						|
                )
 | 
						|
            ],
 | 
						|
            content_type="text/html; charset=utf-8",
 | 
						|
            body="<i>html</i>",
 | 
						|
        )
 | 
						|
        self.assertEqual(render_tex("foo"), "<i>html</i>")
 | 
						|
 | 
						|
        responses.post(
 | 
						|
            "http://localhost:9700/?",
 | 
						|
            match=[
 | 
						|
                matchers.urlencoded_params_matcher(
 | 
						|
                    {"content": "foo", "is_display": "true", "shared_secret": "foo"}
 | 
						|
                )
 | 
						|
            ],
 | 
						|
            content_type="text/html; charset=utf-8",
 | 
						|
            body="<i>other</i>",
 | 
						|
        )
 | 
						|
        self.assertEqual(render_tex("foo", is_inline=False), "<i>other</i>")
 | 
						|
 | 
						|
        responses.post(
 | 
						|
            "http://localhost:9700/",
 | 
						|
            content_type="text/html; charset=utf-8",
 | 
						|
            status=400,
 | 
						|
            body=r"KaTeX parse error: '\'",
 | 
						|
        )
 | 
						|
        self.assertEqual(render_tex("bad"), None)
 | 
						|
 | 
						|
        responses.post(
 | 
						|
            "http://localhost:9700/",
 | 
						|
            content_type="text/html; charset=utf-8",
 | 
						|
            status=400,
 | 
						|
            body=r"KaTeX parse error: '\'",
 | 
						|
        )
 | 
						|
        self.assertEqual(render_tex("bad"), None)
 | 
						|
 | 
						|
        responses.post("http://localhost:9700/", status=403, body="")
 | 
						|
        with self.assertLogs(level="WARNING") as m:
 | 
						|
            self.assertEqual(render_tex("bad"), None)
 | 
						|
        self.assertEqual(m.output, ["WARNING:root:KaTeX rendering service failed: (403) "])
 | 
						|
 | 
						|
        responses.post("http://localhost:9700/", status=500, body="")
 | 
						|
        with self.assertLogs(level="WARNING") as m:
 | 
						|
            self.assertEqual(render_tex("bad"), None)
 | 
						|
        self.assertEqual(m.output, ["WARNING:root:KaTeX rendering service failed: (500) "])
 | 
						|
 | 
						|
        responses.post("http://localhost:9700/", body=requests.exceptions.Timeout())
 | 
						|
        with self.assertLogs(level="WARNING") as m:
 | 
						|
            self.assertEqual(render_tex("bad"), None)
 | 
						|
        self.assertEqual(
 | 
						|
            m.output, ["WARNING:root:KaTeX rendering service timed out with 3 byte long input"]
 | 
						|
        )
 | 
						|
 | 
						|
        responses.post("http://localhost:9700/", body=requests.exceptions.ConnectionError())
 | 
						|
        with self.assertLogs(level="WARNING") as m:
 | 
						|
            self.assertEqual(render_tex("bad"), None)
 | 
						|
        self.assertEqual(m.output, ["WARNING:root:KaTeX rendering service failed: ConnectionError"])
 | 
						|
 | 
						|
        with override_settings(KATEX_SERVER_PORT=9701):
 | 
						|
            responses.post(
 | 
						|
                "http://localhost:9701/",
 | 
						|
                body="<i>html</i>",
 | 
						|
                content_type="text/html; charset=utf-8",
 | 
						|
            )
 | 
						|
            self.assertEqual(render_tex("foo"), "<i>html</i>")
 | 
						|
 | 
						|
 | 
						|
class MarkdownListPreprocessorTest(ZulipTestCase):
 | 
						|
    # We test that the preprocessor inserts blank lines at correct places.
 | 
						|
    # We use <> to indicate that we need to insert a blank line here.
 | 
						|
    def split_message(self, msg: str) -> tuple[list[str], list[str]]:
 | 
						|
        original = msg.replace("<>", "").split("\n")
 | 
						|
        expected = re.split(r"\n|<>", msg)
 | 
						|
        return original, expected
 | 
						|
 | 
						|
    def test_basic_list(self) -> None:
 | 
						|
        preprocessor = MarkdownListPreprocessor()
 | 
						|
        original, expected = self.split_message("List without a gap\n<>* One\n* Two")
 | 
						|
        self.assertEqual(preprocessor.run(original), expected)
 | 
						|
 | 
						|
    def test_list_after_quotes(self) -> None:
 | 
						|
        preprocessor = MarkdownListPreprocessor()
 | 
						|
        original, expected = self.split_message(
 | 
						|
            "```quote\nSomething\n```\n\nList without a gap\n<>* One\n* Two"
 | 
						|
        )
 | 
						|
        self.assertEqual(preprocessor.run(original), expected)
 | 
						|
 | 
						|
    def test_list_in_code(self) -> None:
 | 
						|
        preprocessor = MarkdownListPreprocessor()
 | 
						|
        original, expected = self.split_message("```\nList without a gap\n* One\n* Two\n```")
 | 
						|
        self.assertEqual(preprocessor.run(original), expected)
 | 
						|
 | 
						|
    def test_complex_nesting_with_different_fences(self) -> None:
 | 
						|
        preprocessor = MarkdownListPreprocessor()
 | 
						|
        msg = """```quote
 | 
						|
In quote. We should convert a list here:<>
 | 
						|
* one
 | 
						|
* two
 | 
						|
 | 
						|
~~~
 | 
						|
This is a nested code fence, do not make changes here:
 | 
						|
* one
 | 
						|
* two
 | 
						|
 | 
						|
````quote
 | 
						|
Quote in code fence. Should not convert:
 | 
						|
* one
 | 
						|
* two
 | 
						|
````
 | 
						|
 | 
						|
~~~
 | 
						|
 | 
						|
Back in the quote. We should convert:<>
 | 
						|
* one
 | 
						|
* two
 | 
						|
```
 | 
						|
 | 
						|
Outside. Should convert:<>
 | 
						|
* one
 | 
						|
* two
 | 
						|
        """
 | 
						|
        original, expected = self.split_message(msg)
 | 
						|
        self.assertEqual(preprocessor.run(original), expected)
 | 
						|
 | 
						|
    def test_complex_nesting_with_same_fence(self) -> None:
 | 
						|
        preprocessor = MarkdownListPreprocessor()
 | 
						|
        msg = """```quote
 | 
						|
In quote. We should convert a list here:<>
 | 
						|
* one
 | 
						|
* two
 | 
						|
 | 
						|
```python
 | 
						|
This is a nested code fence, do not make changes here:
 | 
						|
* one
 | 
						|
* two
 | 
						|
 | 
						|
```quote
 | 
						|
Quote in code fence. Should not convert:
 | 
						|
* one
 | 
						|
* two
 | 
						|
```
 | 
						|
 | 
						|
```
 | 
						|
 | 
						|
Back in the quote. We should convert:<>
 | 
						|
* one
 | 
						|
* two
 | 
						|
```
 | 
						|
 | 
						|
Outside. Should convert:<>
 | 
						|
* one
 | 
						|
* two
 | 
						|
        """
 | 
						|
        original, expected = self.split_message(msg)
 | 
						|
        self.assertEqual(preprocessor.run(original), expected)
 | 
						|
 | 
						|
 | 
						|
class MarkdownFixtureTest(ZulipTestCase):
 | 
						|
    def load_markdown_tests(self) -> tuple[dict[str, Any], list[list[str]]]:
 | 
						|
        test_fixtures = {}
 | 
						|
        with open(
 | 
						|
            os.path.join(os.path.dirname(__file__), "fixtures/markdown_test_cases.json"), "rb"
 | 
						|
        ) as f:
 | 
						|
            data = orjson.loads(f.read())
 | 
						|
        for test in data["regular_tests"]:
 | 
						|
            test_fixtures[test["name"]] = test
 | 
						|
 | 
						|
        return test_fixtures, data["linkify_tests"]
 | 
						|
 | 
						|
    def test_markdown_no_ignores(self) -> None:
 | 
						|
        # We do not want any ignored tests to be committed and merged.
 | 
						|
        format_tests, linkify_tests = self.load_markdown_tests()
 | 
						|
        for name, test in format_tests.items():
 | 
						|
            message = f'Test "{name}" shouldn\'t be ignored.'
 | 
						|
            is_ignored = test.get("ignore", False)
 | 
						|
            self.assertFalse(is_ignored, message)
 | 
						|
 | 
						|
    def test_markdown_fixtures_unique_names(self) -> None:
 | 
						|
        # All markdown fixtures must have unique names.
 | 
						|
        found_names: set[str] = set()
 | 
						|
        with open(
 | 
						|
            os.path.join(os.path.dirname(__file__), "fixtures/markdown_test_cases.json"), "rb"
 | 
						|
        ) as f:
 | 
						|
            data = orjson.loads(f.read())
 | 
						|
        for test in data["regular_tests"]:
 | 
						|
            test_name = test["name"]
 | 
						|
            message = f'Test name: "{test_name}" must be unique.'
 | 
						|
            is_unique = test_name not in found_names
 | 
						|
            self.assertTrue(is_unique, message)
 | 
						|
            found_names.add(test_name)
 | 
						|
 | 
						|
    def test_markdown_fixtures(self) -> None:
 | 
						|
        format_tests, linkify_tests = self.load_markdown_tests()
 | 
						|
        valid_keys = {
 | 
						|
            "name",
 | 
						|
            "input",
 | 
						|
            "expected_output",
 | 
						|
            "backend_only_rendering",
 | 
						|
            "marked_expected_output",
 | 
						|
            "text_content",
 | 
						|
            "translate_emoticons",
 | 
						|
            "ignore",
 | 
						|
        }
 | 
						|
 | 
						|
        for name, test in format_tests.items():
 | 
						|
            with self.subTest(markdown_test_case=name):
 | 
						|
                # Check that there aren't any unexpected keys as those are often typos
 | 
						|
                self.assert_length(set(test.keys()) - valid_keys, 0)
 | 
						|
                # Ignore tests if specified
 | 
						|
                if test.get("ignore", False):
 | 
						|
                    continue  # nocoverage
 | 
						|
 | 
						|
                if test.get("translate_emoticons", False):
 | 
						|
                    # Create a userprofile and send message with it.
 | 
						|
                    user_profile = self.example_user("othello")
 | 
						|
                    do_change_user_setting(
 | 
						|
                        user_profile, "translate_emoticons", True, acting_user=None
 | 
						|
                    )
 | 
						|
                    msg = Message(
 | 
						|
                        sender=user_profile,
 | 
						|
                        sending_client=get_client("test"),
 | 
						|
                        realm=user_profile.realm,
 | 
						|
                    )
 | 
						|
                    rendering_result = render_message_markdown(msg, test["input"])
 | 
						|
                    converted = rendering_result.rendered_content
 | 
						|
                else:
 | 
						|
                    converted = markdown_convert_wrapper(test["input"])
 | 
						|
 | 
						|
                self.assertEqual(converted, test["expected_output"])
 | 
						|
 | 
						|
        def replaced(payload: str, url: str, phrase: str = "") -> str:
 | 
						|
            if url[:4] == "http":
 | 
						|
                href = url
 | 
						|
            elif "@" in url:
 | 
						|
                href = "mailto:" + url
 | 
						|
            else:
 | 
						|
                href = "http://" + url
 | 
						|
            return payload % (f'<a href="{href}">{url}</a>',)
 | 
						|
 | 
						|
        for inline_url, reference, url in linkify_tests:
 | 
						|
            try:
 | 
						|
                match = replaced(reference, url, phrase=inline_url)
 | 
						|
            except TypeError:
 | 
						|
                match = reference
 | 
						|
            converted = markdown_convert_wrapper(inline_url)
 | 
						|
            self.assertEqual(match, converted)
 | 
						|
 | 
						|
 | 
						|
class MarkdownLinkTest(ZulipTestCase):
 | 
						|
    def test_url_to_a(self) -> None:
 | 
						|
        url = "javascript://example.com/invalidURL"
 | 
						|
        converted = url_to_a(db_data=None, url=url, text=url)
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            "javascript://example.com/invalidURL",
 | 
						|
        )
 | 
						|
 | 
						|
    def test_normal_link(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        message = Message(sender=sender_user_profile, sending_client=get_client("test"))
 | 
						|
        msg = "http://example.com/#settings/"
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert(msg, message_realm=realm, message=message).rendered_content,
 | 
						|
            '<p><a href="http://example.com/#settings/">http://example.com/#settings/</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_relative_link(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        message = Message(sender=sender_user_profile, sending_client=get_client("test"))
 | 
						|
 | 
						|
        msg = "http://zulip.testserver/#narrow/channel/999-hello"
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert(msg, message_realm=realm, message=message).rendered_content,
 | 
						|
            '<p><a href="#narrow/channel/999-hello">http://zulip.testserver/#narrow/channel/999-hello</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        msg = f"http://zulip.testserver/user_uploads/{realm.id}/ff/file.txt"
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert(msg, message_realm=realm, message=message).rendered_content,
 | 
						|
            f'<p><a href="user_uploads/{realm.id}/ff/file.txt">http://zulip.testserver/user_uploads/{realm.id}/ff/file.txt</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "http://zulip.testserver/not:relative"
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert(msg, message_realm=realm, message=message).rendered_content,
 | 
						|
            '<p><a href="http://zulip.testserver/not:relative">http://zulip.testserver/not:relative</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_relative_link_streams_page(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        message = Message(sender=sender_user_profile, sending_client=get_client("test"))
 | 
						|
        msg = "http://zulip.testserver/#channels/all"
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert(msg, message_realm=realm, message=message).rendered_content,
 | 
						|
            '<p><a href="#channels/all">http://zulip.testserver/#channels/all</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_md_relative_link(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        message = Message(sender=sender_user_profile, sending_client=get_client("test"))
 | 
						|
 | 
						|
        msg = "[hello](http://zulip.testserver/#narrow/channel/999-hello)"
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert(msg, message_realm=realm, message=message).rendered_content,
 | 
						|
            '<p><a href="#narrow/channel/999-hello">hello</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        msg = f"[hello](http://zulip.testserver/user_uploads/{realm.id}/ff/file.txt)"
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert(msg, message_realm=realm, message=message).rendered_content,
 | 
						|
            f'<p><a href="user_uploads/{realm.id}/ff/file.txt">hello</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "[hello](http://zulip.testserver/not:relative)"
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert(msg, message_realm=realm, message=message).rendered_content,
 | 
						|
            '<p><a href="http://zulip.testserver/not:relative">hello</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_inline_file(self) -> None:
 | 
						|
        msg = "Check out this file file:///Volumes/myserver/Users/Shared/pi.py"
 | 
						|
 | 
						|
        # Make separate realms because the markdown engines cache the
 | 
						|
        # linkifiers on them, including if ENABLE_FILE_LINKS was used
 | 
						|
        realm = do_create_realm(string_id="file_links_disabled", name="File links disabled")
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert(msg, message_realm=realm).rendered_content,
 | 
						|
            "<p>Check out this file file:///Volumes/myserver/Users/Shared/pi.py</p>",
 | 
						|
        )
 | 
						|
        clear_web_link_regex_for_testing()
 | 
						|
 | 
						|
        try:
 | 
						|
            with self.settings(ENABLE_FILE_LINKS=True):
 | 
						|
                realm = do_create_realm(string_id="file_links_enabled", name="File links enabled")
 | 
						|
                self.assertEqual(
 | 
						|
                    markdown_convert(msg, message_realm=realm).rendered_content,
 | 
						|
                    '<p>Check out this file <a href="file:///Volumes/myserver/Users/Shared/pi.py">file:///Volumes/myserver/Users/Shared/pi.py</a></p>',
 | 
						|
                )
 | 
						|
        finally:
 | 
						|
            clear_web_link_regex_for_testing()
 | 
						|
 | 
						|
    def test_inline_bitcoin(self) -> None:
 | 
						|
        msg = "To bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa or not to bitcoin"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            '<p>To <a href="bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa">bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa</a> or not to bitcoin</p>',
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class MarkdownEmbedsTest(ZulipTestCase):
 | 
						|
    def assert_message_content_is(
 | 
						|
        self, message_id: int, rendered_content: str, user_name: str = "othello"
 | 
						|
    ) -> None:
 | 
						|
        sender_user_profile = self.example_user(user_name)
 | 
						|
        result = self.assert_json_success(
 | 
						|
            self.api_get(sender_user_profile, f"/api/v1/messages/{message_id}")
 | 
						|
        )
 | 
						|
        self.assertEqual(result["message"]["content"], rendered_content)
 | 
						|
 | 
						|
    def send_message_content(self, content: str, user_name: str = "othello") -> int:
 | 
						|
        sender_user_profile = self.example_user(user_name)
 | 
						|
        return self.send_stream_message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            stream_name="Verona",
 | 
						|
            content=content,
 | 
						|
        )
 | 
						|
 | 
						|
    def test_inline_youtube(self) -> None:
 | 
						|
        msg = "Check out the debate: http://www.youtube.com/watch?v=hx1mjT73xYE"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<p>Check out the debate: <a href="http://www.youtube.com/watch?v=hx1mjT73xYE">http://www.youtube.com/watch?v=hx1mjT73xYE</a></p>\n<div class="youtube-video message_inline_image"><a data-id="hx1mjT73xYE" href="http://www.youtube.com/watch?v=hx1mjT73xYE"><img src="{get_camo_url("https://i.ytimg.com/vi/hx1mjT73xYE/mqdefault.jpg")}"></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "http://www.youtube.com/watch?v=hx1mjT73xYE"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<p><a href="http://www.youtube.com/watch?v=hx1mjT73xYE">http://www.youtube.com/watch?v=hx1mjT73xYE</a></p>\n<div class="youtube-video message_inline_image"><a data-id="hx1mjT73xYE" href="http://www.youtube.com/watch?v=hx1mjT73xYE"><img src="{get_camo_url("https://i.ytimg.com/vi/hx1mjT73xYE/mqdefault.jpg")}"></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "https://youtu.be/hx1mjT73xYE"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<p><a href="https://youtu.be/hx1mjT73xYE">https://youtu.be/hx1mjT73xYE</a></p>\n<div class="youtube-video message_inline_image"><a data-id="hx1mjT73xYE" href="https://youtu.be/hx1mjT73xYE"><img src="{get_camo_url("https://i.ytimg.com/vi/hx1mjT73xYE/mqdefault.jpg")}"></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "https://www.youtube.com/playlist?list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo"
 | 
						|
        not_converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            not_converted,
 | 
						|
            '<p><a href="https://www.youtube.com/playlist?list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo">https://www.youtube.com/playlist?list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "https://www.youtube.com/watch?v=O5nskjZ_GoI&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<p><a href="https://www.youtube.com/watch?v=O5nskjZ_GoI&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo">https://www.youtube.com/watch?v=O5nskjZ_GoI&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo</a></p>\n<div class="youtube-video message_inline_image"><a data-id="O5nskjZ_GoI" href="https://www.youtube.com/watch?v=O5nskjZ_GoI&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo"><img src="{get_camo_url("https://i.ytimg.com/vi/O5nskjZ_GoI/mqdefault.jpg")}"></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "http://www.youtube.com/watch_videos?video_ids=nOJgD4fcZhI,i96UO8-GFvw"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<p><a href="http://www.youtube.com/watch_videos?video_ids=nOJgD4fcZhI,i96UO8-GFvw">http://www.youtube.com/watch_videos?video_ids=nOJgD4fcZhI,i96UO8-GFvw</a></p>\n<div class="youtube-video message_inline_image"><a data-id="nOJgD4fcZhI" href="http://www.youtube.com/watch_videos?video_ids=nOJgD4fcZhI,i96UO8-GFvw"><img src="{get_camo_url("https://i.ytimg.com/vi/nOJgD4fcZhI/mqdefault.jpg")}"></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
    def test_inline_image_preview(self) -> None:
 | 
						|
        url = "http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg"
 | 
						|
        camo_url = get_camo_url(url)
 | 
						|
        with_preview = (
 | 
						|
            f'<div class="message_inline_image"><a href="{url}"><img src="{camo_url}"></a></div>'
 | 
						|
        )
 | 
						|
        without_preview = f'<p><a href="{url}">{url}</a></p>'
 | 
						|
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        converted = render_message_markdown(msg, url)
 | 
						|
        self.assertEqual(converted.rendered_content, with_preview)
 | 
						|
 | 
						|
        realm = msg.get_realm()
 | 
						|
        realm.inline_image_preview = False
 | 
						|
        realm.save()
 | 
						|
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        converted = render_message_markdown(msg, url)
 | 
						|
        self.assertEqual(converted.rendered_content, without_preview)
 | 
						|
 | 
						|
    @override_settings(EXTERNAL_URI_SCHEME="https://")
 | 
						|
    def test_static_image_preview_skip_camo(self) -> None:
 | 
						|
        content = f"{settings.STATIC_URL}/thing.jpeg"
 | 
						|
 | 
						|
        thumbnail_img = f"""<div class="message_inline_image"><a href="{content}"><img src="{content}"></a></div>"""
 | 
						|
        converted = markdown_convert_wrapper(content)
 | 
						|
        self.assertIn(converted, thumbnail_img)
 | 
						|
 | 
						|
    @override_settings(EXTERNAL_URI_SCHEME="https://")
 | 
						|
    def test_realm_image_preview_skip_camo(self) -> None:
 | 
						|
        content = f"https://zulip.{settings.EXTERNAL_HOST}/thing.jpeg"
 | 
						|
        converted = markdown_convert_wrapper(content)
 | 
						|
        self.assertNotIn(converted, get_camo_url(content))
 | 
						|
 | 
						|
    @override_settings(EXTERNAL_URI_SCHEME="https://")
 | 
						|
    def test_cross_realm_image_preview_use_camo(self) -> None:
 | 
						|
        content = f"https://otherrealm.{settings.EXTERNAL_HOST}/thing.jpeg"
 | 
						|
 | 
						|
        thumbnail_img = f"""<div class="message_inline_image"><a href="{content}"><img src="{get_camo_url(content)}"></a></div>"""
 | 
						|
        converted = markdown_convert_wrapper(content)
 | 
						|
        self.assertIn(converted, thumbnail_img)
 | 
						|
 | 
						|
    def test_max_inline_preview(self) -> None:
 | 
						|
        image_links = [
 | 
						|
            # Add a youtube link within a spoiler to ensure other link types are counted
 | 
						|
            """```spoiler Check out this PyCon video\nhttps://www.youtube.com/watch?v=0c46YHS3RY8\n```""",
 | 
						|
            # Add a link within blockquote to test that it does NOT get counted
 | 
						|
            "> http://cdn.wallpapersafari.com/spoiler/dont_count.jpeg\n",
 | 
						|
            # Using INLINE_PREVIEW_LIMIT_PER_MESSAGE - 1 because of the one link in a spoiler added already
 | 
						|
            *(
 | 
						|
                f"http://cdn.wallpapersafari.com/{x}/6/16eVjx.jpeg"
 | 
						|
                for x in range(InlineInterestingLinkProcessor.INLINE_PREVIEW_LIMIT_PER_MESSAGE - 1)
 | 
						|
            ),
 | 
						|
        ]
 | 
						|
        within_limit_content = "\n".join(image_links)
 | 
						|
        above_limit_content = (
 | 
						|
            within_limit_content + "\nhttp://cdn.wallpapersafari.com/above/0/6/16eVjx.jpeg"
 | 
						|
        )
 | 
						|
 | 
						|
        # When the number of image links is within the preview limit, the
 | 
						|
        # output should contain the same number of inline images.
 | 
						|
        converted = markdown_convert_wrapper(within_limit_content)
 | 
						|
        soup = BeautifulSoup(converted, "html.parser")
 | 
						|
        self.assert_length(
 | 
						|
            soup(class_="message_inline_image"),
 | 
						|
            InlineInterestingLinkProcessor.INLINE_PREVIEW_LIMIT_PER_MESSAGE,
 | 
						|
        )
 | 
						|
 | 
						|
        # When the number of image links is over the limit, then there should
 | 
						|
        # be zero inline images.
 | 
						|
        converted = markdown_convert_wrapper(above_limit_content)
 | 
						|
        soup = BeautifulSoup(converted, "html.parser")
 | 
						|
        self.assert_length(soup(class_="message_inline_image"), 0)
 | 
						|
 | 
						|
    def test_inline_image_quoted_blocks(self) -> None:
 | 
						|
        url = "http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg"
 | 
						|
        camo_url = get_camo_url(url)
 | 
						|
        content = f"{url}"
 | 
						|
        expected = (
 | 
						|
            f'<div class="message_inline_image"><a href="{url}"><img src="{camo_url}"></a></div>'
 | 
						|
        )
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        converted = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(converted.rendered_content, expected)
 | 
						|
 | 
						|
        content = f">{url}\n\nAwesome!"
 | 
						|
        expected = f'<blockquote>\n<p><a href="{url}">{url}</a></p>\n</blockquote>\n<p>Awesome!</p>'
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        converted = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(converted.rendered_content, expected)
 | 
						|
 | 
						|
        content = f">* {url}\n\nAwesome!"
 | 
						|
        expected = f'<blockquote>\n<ul>\n<li><a href="{url}">{url}</a></li>\n</ul>\n</blockquote>\n<p>Awesome!</p>'
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        converted = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(converted.rendered_content, expected)
 | 
						|
 | 
						|
    def test_inline_image_preview_order(self) -> None:
 | 
						|
        urls = [
 | 
						|
            "http://imaging.nikon.com/lineup/dslr/df/img/sample/img_01.jpg",
 | 
						|
            "http://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg",
 | 
						|
            "http://imaging.nikon.com/lineup/dslr/df/img/sample/img_03.jpg",
 | 
						|
        ]
 | 
						|
        content = "\n".join(urls)
 | 
						|
        expected = (
 | 
						|
            "<p>"
 | 
						|
            f'<a href="{urls[0]}">{urls[0]}</a><br>\n'
 | 
						|
            f'<a href="{urls[1]}">{urls[1]}</a><br>\n'
 | 
						|
            f'<a href="{urls[2]}">{urls[2]}</a>'
 | 
						|
            "</p>\n"
 | 
						|
            f'<div class="message_inline_image"><a href="{urls[0]}"><img src="{get_camo_url(urls[0])}"></a></div>'
 | 
						|
            f'<div class="message_inline_image"><a href="{urls[1]}"><img src="{get_camo_url(urls[1])}"></a></div>'
 | 
						|
            f'<div class="message_inline_image"><a href="{urls[2]}"><img src="{get_camo_url(urls[2])}"></a></div>'
 | 
						|
        )
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        converted = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(converted.rendered_content, expected)
 | 
						|
 | 
						|
        urls.append("https://www.google.com/images/srpr/logo4w.png")
 | 
						|
        content = f"{urls[0]}\n\n>{urls[1]}\n\n* {urls[2]}\n* {urls[3]}"
 | 
						|
        expected = (
 | 
						|
            f'<div class="message_inline_image"><a href="{urls[0]}"><img src="{get_camo_url(urls[0])}"></a></div>'
 | 
						|
            f'<blockquote>\n<p><a href="{urls[1]}">{urls[1]}</a></p>\n</blockquote>\n'
 | 
						|
            "<ul>\n"
 | 
						|
            f'<li><div class="message_inline_image"><a href="{urls[2]}"><img src="{get_camo_url(urls[2])}"></a></div></li>\n'
 | 
						|
            f'<li><div class="message_inline_image"><a href="{urls[3]}"><img src="{get_camo_url(urls[3])}"></a></div></li>\n'
 | 
						|
            "</ul>"
 | 
						|
        )
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        converted = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(converted.rendered_content, expected)
 | 
						|
 | 
						|
    def test_corrected_image_source(self) -> None:
 | 
						|
        # testing only Wikipedia because linx.li URLs can be expected to expire
 | 
						|
        content = "https://en.wikipedia.org/wiki/File:Wright_of_Derby,_The_Orrery.jpg"
 | 
						|
        expected_url = (
 | 
						|
            "https://en.wikipedia.org/wiki/Special:FilePath/File:Wright_of_Derby,_The_Orrery.jpg"
 | 
						|
        )
 | 
						|
        camo_url = get_camo_url(expected_url)
 | 
						|
        expected = f'<div class="message_inline_image"><a href="{expected_url}"><img src="{camo_url}"></a></div>'
 | 
						|
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        converted = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(converted.rendered_content, expected)
 | 
						|
 | 
						|
        content = "https://en.wikipedia.org/static/images/icons/wikipedia.png"
 | 
						|
        camo_url = get_camo_url(content)
 | 
						|
        expected = f'<div class="message_inline_image"><a href="https://en.wikipedia.org/static/images/icons/wikipedia.png"><img src="{camo_url}"></a></div>'
 | 
						|
        converted = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(converted.rendered_content, expected)
 | 
						|
 | 
						|
    def test_inline_audio_preview(self) -> None:
 | 
						|
        # Test audio previews with a valid audio file upload.
 | 
						|
        url = upload_message_attachment(
 | 
						|
            "filename",
 | 
						|
            "audio/mpeg",
 | 
						|
            b"",
 | 
						|
            self.example_user("othello"),
 | 
						|
        )[0]
 | 
						|
        path_id = re.sub(r"/user_uploads/", "", url)
 | 
						|
        message_id = self.send_message_content(f"")
 | 
						|
        expected = (
 | 
						|
            f'<p><audio controls preload="metadata" src="{url}" title="Audio link"></audio></p>'
 | 
						|
        )
 | 
						|
        self.assert_message_content_is(message_id, expected)
 | 
						|
 | 
						|
        # Test audio files are not previewed if the file doesn't
 | 
						|
        # exist.
 | 
						|
        url = "/user_uploads/path/to/file.mp3"
 | 
						|
        path_id = re.sub(r"/user_uploads/", "", url)
 | 
						|
 | 
						|
        with self.assertLogs(level="WARNING"):
 | 
						|
            message_id = self.send_message_content(f"")
 | 
						|
 | 
						|
        expected = f"<p></p>"
 | 
						|
        self.assert_message_content_is(message_id, expected)
 | 
						|
 | 
						|
        # Test audio files are not previewed when the content type
 | 
						|
        # is not supported one, but filename contains a supported
 | 
						|
        # audio file extension.
 | 
						|
        url = upload_message_attachment(
 | 
						|
            "filename.mp3",
 | 
						|
            "",
 | 
						|
            b"",
 | 
						|
            self.example_user("othello"),
 | 
						|
        )[0]
 | 
						|
        path_id = re.sub(r"/user_uploads/", "", url)
 | 
						|
        message_id = self.send_message_content(f"")
 | 
						|
        expected = f"<p></p>"
 | 
						|
        self.assert_message_content_is(message_id, expected)
 | 
						|
 | 
						|
    @override_settings(INLINE_IMAGE_PREVIEW=False)
 | 
						|
    def test_image_preview_enabled(self) -> None:
 | 
						|
        ret = image_preview_enabled()
 | 
						|
        self.assertFalse(ret)
 | 
						|
 | 
						|
        settings.INLINE_IMAGE_PREVIEW = True
 | 
						|
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        message = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        realm = message.get_realm()
 | 
						|
 | 
						|
        ret = image_preview_enabled()
 | 
						|
        self.assertTrue(ret)
 | 
						|
 | 
						|
        ret = image_preview_enabled(no_previews=True)
 | 
						|
        self.assertFalse(ret)
 | 
						|
 | 
						|
        ret = image_preview_enabled(message, realm)
 | 
						|
        self.assertTrue(ret)
 | 
						|
 | 
						|
        ret = image_preview_enabled(message)
 | 
						|
        self.assertTrue(ret)
 | 
						|
 | 
						|
        ret = image_preview_enabled(message, realm, no_previews=True)
 | 
						|
        self.assertFalse(ret)
 | 
						|
 | 
						|
        ret = image_preview_enabled(message, no_previews=True)
 | 
						|
        self.assertFalse(ret)
 | 
						|
 | 
						|
    def test_url_embed_preview_enabled(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        realm = sender_user_profile.realm
 | 
						|
        realm.inline_url_embed_preview = True  # off by default
 | 
						|
        realm.save(update_fields=["inline_url_embed_preview"])
 | 
						|
 | 
						|
        self.assertFalse(url_embed_preview_enabled())
 | 
						|
 | 
						|
        with override_settings(INLINE_URL_EMBED_PREVIEW=True):
 | 
						|
            self.assertTrue(url_embed_preview_enabled())
 | 
						|
 | 
						|
            self.assertFalse(image_preview_enabled(no_previews=True))
 | 
						|
 | 
						|
            message = Message(
 | 
						|
                sender=sender_user_profile,
 | 
						|
                sending_client=get_client("test"),
 | 
						|
                realm=sender_user_profile.realm,
 | 
						|
            )
 | 
						|
            self.assertTrue(url_embed_preview_enabled(message, realm))
 | 
						|
            self.assertTrue(url_embed_preview_enabled(message))
 | 
						|
 | 
						|
            self.assertFalse(url_embed_preview_enabled(message, no_previews=True))
 | 
						|
 | 
						|
    def test_inline_dropbox(self) -> None:
 | 
						|
        msg = "Look at how hilarious our old office was: https://www.dropbox.com/scl/fi/cbabl5ryv1veky9ehs603/IMG_0923.JPG?rlkey=24sfgf0k0dneebzf5tfccldg0"
 | 
						|
        image_info = {
 | 
						|
            "image": "https://www.dropbox.com/scl/fi/cbabl5ryv1veky9ehs603/IMG_0923.JPG?rlkey=24sfgf0k0dneebzf5tfccldg0&raw=1",
 | 
						|
        }
 | 
						|
        with mock.patch("zerver.lib.markdown.fetch_open_graph_image", return_value=image_info):
 | 
						|
            converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<p>Look at how hilarious our old office was: <a href="https://www.dropbox.com/scl/fi/cbabl5ryv1veky9ehs603/IMG_0923.JPG?rlkey=24sfgf0k0dneebzf5tfccldg0">https://www.dropbox.com/scl/fi/cbabl5ryv1veky9ehs603/IMG_0923.JPG?rlkey=24sfgf0k0dneebzf5tfccldg0</a></p>\n<div class="message_inline_image"><a href="https://www.dropbox.com/scl/fi/cbabl5ryv1veky9ehs603/IMG_0923.JPG?rlkey=24sfgf0k0dneebzf5tfccldg0&raw=1"><img src="{get_camo_url("https://www.dropbox.com/scl/fi/cbabl5ryv1veky9ehs603/IMG_0923.JPG?rlkey=24sfgf0k0dneebzf5tfccldg0&raw=1")}"></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "Look at my hilarious drawing folder: https://www.dropbox.com/scl/fo/ty22bx4thyhl9r89p839g/AAzfPX5IbiOb8wmxHvns2pM?rlkey=5pinfuoghias9cueq0zyhj2rp&st=dz5p1ytw"  # codespell:ignore fo
 | 
						|
        image_info = {
 | 
						|
            "image": "https://www.dropbox.com/static/metaserver/static/images/opengraph/opengraph-content-icon-folder-dropbox-landscape.png",
 | 
						|
        }
 | 
						|
        with mock.patch("zerver.lib.markdown.fetch_open_graph_image", return_value=image_info):
 | 
						|
            converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            """<p>Look at my hilarious drawing folder: <a href="https://www.dropbox.com/scl/fo/ty22bx4thyhl9r89p839g/AAzfPX5IbiOb8wmxHvns2pM?rlkey=5pinfuoghias9cueq0zyhj2rp&st=dz5p1ytw">https://www.dropbox.com/scl/fo/ty22bx4thyhl9r89p839g/AAzfPX5IbiOb8wmxHvns2pM?rlkey=5pinfuoghias9cueq0zyhj2rp&st=dz5p1ytw</a></p>\n<div class="message_embed"><a class="message_embed_image" href="https://www.dropbox.com/scl/fo/ty22bx4thyhl9r89p839g/AAzfPX5IbiOb8wmxHvns2pM?rlkey=5pinfuoghias9cueq0zyhj2rp&st=dz5p1ytw" style="background-image: url("https://external-content.zulipcdn.net/external_content/a301902b9942efb85cfe2a6f4bb07d76ba7b86de/68747470733a2f2f7777772e64726f70626f782e636f6d2f7374617469632f6d6574617365727665722f7374617469632f696d616765732f6f70656e67726170682f6f70656e67726170682d636f6e74656e742d69636f6e2d666f6c6465722d64726f70626f782d6c616e6473636170652e706e67")"></a><div class="data-container"><div class="message_embed_title"><a href="https://www.dropbox.com/scl/fo/ty22bx4thyhl9r89p839g/AAzfPX5IbiOb8wmxHvns2pM?rlkey=5pinfuoghias9cueq0zyhj2rp&st=dz5p1ytw" title="Dropbox folder">Dropbox folder</a></div><div class="message_embed_description">Click to open folder.</div></div></div>""",  # codespell:ignore fo
 | 
						|
        )
 | 
						|
 | 
						|
    def test_inline_dropbox_video(self) -> None:
 | 
						|
        msg = "Look at this video: https://www.dropbox.com/scl/fi/x8z01rodq1n6pgyznt1kh/SampleVideo_1280x720_1mb.mp4?rlkey=fiibsgnu06tms041vfzfopmos&st=kjtkea8h&dl=0"
 | 
						|
        image_info = {
 | 
						|
            "image": "https://www.dropbox.com/scl/fi/x8z01rodq1n6pgyznt1kh/SampleVideo_1280x720_1mb.mp4?rlkey=fiibsgnu06tms041vfzfopmos&st=kjtkea8h&dl=0&raw=1",
 | 
						|
        }
 | 
						|
        with mock.patch("zerver.lib.markdown.fetch_open_graph_image", return_value=image_info):
 | 
						|
            converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            """<p>Look at this video: <a href="https://www.dropbox.com/scl/fi/x8z01rodq1n6pgyznt1kh/SampleVideo_1280x720_1mb.mp4?rlkey=fiibsgnu06tms041vfzfopmos&st=kjtkea8h&dl=0">https://www.dropbox.com/scl/fi/x8z01rodq1n6pgyznt1kh/SampleVideo_1280x720_1mb.mp4?rlkey=fiibsgnu06tms041vfzfopmos&st=kjtkea8h&dl=0</a></p>
 | 
						|
<div class="message_inline_image message_inline_video"><a href="https://www.dropbox.com/scl/fi/x8z01rodq1n6pgyznt1kh/SampleVideo_1280x720_1mb.mp4?rlkey=fiibsgnu06tms041vfzfopmos&st=kjtkea8h&dl=0&raw=1"><video preload="metadata" src="https://external-content.zulipcdn.net/external_content/eca04355025c60f40334c9a03d220c3298d4df47/68747470733a2f2f7777772e64726f70626f782e636f6d2f73636c2f66692f78387a3031726f6471316e367067797a6e74316b682f53616d706c65566964656f5f31323830783732305f316d622e6d70343f726c6b65793d6669696273676e753036746d7330343176667a666f706d6f732673743d6b6a746b6561386826646c3d30267261773d31"></video></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
    def test_inline_dropbox_preview(self) -> None:
 | 
						|
        # Test photo album previews
 | 
						|
        msg = "https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5"
 | 
						|
        image_info = {
 | 
						|
            "image": "https://photos-6.dropbox.com/t/2/AAAlawaeD61TyNewO5vVi-DGf2ZeuayfyHFdNTNzpGq-QA/12/271544745/jpeg/1024x1024/2/_/0/5/baby-piglet.jpg/CKnjvYEBIAIgBygCKAc/tditp9nitko60n5/AADX03VAIrQlTl28CtujDcMla/0",
 | 
						|
        }
 | 
						|
        with mock.patch("zerver.lib.markdown.fetch_open_graph_image", return_value=image_info):
 | 
						|
            converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            """<p><a href="https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5">https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5</a></p>
 | 
						|
<div class="message_embed"><a class="message_embed_image" href="https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5" style="background-image: url("https://external-content.zulipcdn.net/external_content/e7584a56d9ba7f2a2aee96ee1427a6b746eab5ff/68747470733a2f2f70686f746f732d362e64726f70626f782e636f6d2f742f322f4141416c6177616544363154794e65774f357656692d444766325a6575617966794846644e544e7a7047712d51412f31322f3237313534343734352f6a7065672f3130323478313032342f322f5f2f302f352f626162792d7069676c65742e6a70672f434b6e6a7659454249414967427967434b41632f7464697470396e69746b6f36306e352f41414458303356414972516c546c32384374756a44634d6c612f30")"></a><div class="data-container"><div class="message_embed_title"><a href="https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5" title="Dropbox folder">Dropbox folder</a></div><div class="message_embed_description">Click to open folder.</div></div></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
    def test_inline_dropbox_negative(self) -> None:
 | 
						|
        # Make sure we're not overzealous in our conversion:
 | 
						|
        url = "https://www.dropbox.com/static/images/home_logo.png"
 | 
						|
        msg = f"Look at the new dropbox logo: {url}"
 | 
						|
        with mock.patch("zerver.lib.markdown.fetch_open_graph_image", return_value=None):
 | 
						|
            converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        camo_url = get_camo_url(url)
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            (
 | 
						|
                f'<p>Look at the new dropbox logo: <a href="{url}">{url}</a></p>'
 | 
						|
                "\n"
 | 
						|
                f'<div class="message_inline_image"><a href="{url}"><img src="{camo_url}"></a></div>'
 | 
						|
            ),
 | 
						|
        )
 | 
						|
 | 
						|
    def test_inline_dropbox_bad(self) -> None:
 | 
						|
        # Don't fail on bad dropbox links
 | 
						|
        msg = "https://zulip-test.dropbox.com/photos/cl/ROmr9K1XYtmpneM"
 | 
						|
        with mock.patch("zerver.lib.markdown.fetch_open_graph_image", return_value=None):
 | 
						|
            converted = markdown_convert_wrapper(msg)
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            '<p><a href="https://zulip-test.dropbox.com/photos/cl/ROmr9K1XYtmpneM">https://zulip-test.dropbox.com/photos/cl/ROmr9K1XYtmpneM</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_inline_github_preview(self) -> None:
 | 
						|
        # Test github URL translation
 | 
						|
        url = "https://github.com/zulip/zulip/blob/main/static/images/logo/zulip-icon-128x128.png"
 | 
						|
        camo_url = get_camo_url(
 | 
						|
            "https://raw.githubusercontent.com/zulip/zulip/main/static/images/logo/zulip-icon-128x128.png"
 | 
						|
        )
 | 
						|
        msg = f"Test: {url}"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            (
 | 
						|
                f'<p>Test: <a href="{url}">{url}</a></p>'
 | 
						|
                "\n"
 | 
						|
                f'<div class="message_inline_image"><a href="{url}"><img src="{camo_url}"></a></div>'
 | 
						|
            ),
 | 
						|
        )
 | 
						|
 | 
						|
        url = "https://developer.github.com/assets/images/hero-circuit-bg.png"
 | 
						|
        camo_url = get_camo_url(url)
 | 
						|
        msg = f"Test: {url}"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            (
 | 
						|
                f'<p>Test: <a href="{url}">{url}</a></p>'
 | 
						|
                "\n"
 | 
						|
                f'<div class="message_inline_image"><a href="{url}"><img src="{camo_url}"></a></div>'
 | 
						|
            ),
 | 
						|
        )
 | 
						|
 | 
						|
    def test_inline_youtube_preview(self) -> None:
 | 
						|
        # Test YouTube URLs in spoilers
 | 
						|
        msg = """\n```spoiler Check out this PyCon video\nhttps://www.youtube.com/watch?v=0c46YHS3RY8\n```"""
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<div class="spoiler-block"><div class="spoiler-header">\n<p>Check out this PyCon video</p>\n</div><div class="spoiler-content" aria-hidden="true">\n<p><a href="https://www.youtube.com/watch?v=0c46YHS3RY8">https://www.youtube.com/watch?v=0c46YHS3RY8</a></p>\n<div class="youtube-video message_inline_image"><a data-id="0c46YHS3RY8" href="https://www.youtube.com/watch?v=0c46YHS3RY8"><img src="{get_camo_url("https://i.ytimg.com/vi/0c46YHS3RY8/mqdefault.jpg")}"></a></div></div></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
        # Test YouTube URLs in normal messages.
 | 
						|
        msg = "[YouTube link](https://www.youtube.com/watch?v=0c46YHS3RY8)"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<p><a href="https://www.youtube.com/watch?v=0c46YHS3RY8">YouTube link</a></p>\n<div class="youtube-video message_inline_image"><a data-id="0c46YHS3RY8" href="https://www.youtube.com/watch?v=0c46YHS3RY8"><img src="{get_camo_url("https://i.ytimg.com/vi/0c46YHS3RY8/mqdefault.jpg")}"></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "https://www.youtube.com/watch?v=0c46YHS3RY8\n\nSample text\n\nhttps://www.youtube.com/watch?v=lXFO2ULktEI"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<p><a href="https://www.youtube.com/watch?v=0c46YHS3RY8">https://www.youtube.com/watch?v=0c46YHS3RY8</a></p>\n<div class="youtube-video message_inline_image"><a data-id="0c46YHS3RY8" href="https://www.youtube.com/watch?v=0c46YHS3RY8"><img src="{get_camo_url("https://i.ytimg.com/vi/0c46YHS3RY8/mqdefault.jpg")}"></a></div><p>Sample text</p>\n<p><a href="https://www.youtube.com/watch?v=lXFO2ULktEI">https://www.youtube.com/watch?v=lXFO2ULktEI</a></p>\n<div class="youtube-video message_inline_image"><a data-id="lXFO2ULktEI" href="https://www.youtube.com/watch?v=lXFO2ULktEI"><img src="{get_camo_url("https://i.ytimg.com/vi/lXFO2ULktEI/mqdefault.jpg")}"></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
        # Test order of YouTube inline previews in same paragraph.
 | 
						|
        msg = "https://www.youtube.com/watch?v=0c46YHS3RY8\nhttps://www.youtube.com/watch?v=lXFO2ULktEI"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            f"""<p><a href="https://www.youtube.com/watch?v=0c46YHS3RY8">https://www.youtube.com/watch?v=0c46YHS3RY8</a><br>\n<a href="https://www.youtube.com/watch?v=lXFO2ULktEI">https://www.youtube.com/watch?v=lXFO2ULktEI</a></p>\n<div class="youtube-video message_inline_image"><a data-id="0c46YHS3RY8" href="https://www.youtube.com/watch?v=0c46YHS3RY8"><img src="{get_camo_url("https://i.ytimg.com/vi/0c46YHS3RY8/mqdefault.jpg")}"></a></div><div class="youtube-video message_inline_image"><a data-id="lXFO2ULktEI" href="https://www.youtube.com/watch?v=lXFO2ULktEI"><img src="{get_camo_url("https://i.ytimg.com/vi/lXFO2ULktEI/mqdefault.jpg")}"></a></div>""",
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class MarkdownEmojiTest(ZulipTestCase):
 | 
						|
    def test_content_has_emoji(self) -> None:
 | 
						|
        self.assertFalse(content_has_emoji_syntax("boring"))
 | 
						|
        self.assertFalse(content_has_emoji_syntax("hello: world"))
 | 
						|
        self.assertFalse(content_has_emoji_syntax(":foobar"))
 | 
						|
        self.assertFalse(content_has_emoji_syntax("::: hello :::"))
 | 
						|
 | 
						|
        self.assertTrue(content_has_emoji_syntax("foo :whatever:"))
 | 
						|
        self.assertTrue(content_has_emoji_syntax("\n:whatever:"))
 | 
						|
        self.assertTrue(content_has_emoji_syntax(":smile: ::::::"))
 | 
						|
 | 
						|
    def test_realm_emoji(self) -> None:
 | 
						|
        def emoji_img(name: str, file_name: str, realm_id: int) -> str:
 | 
						|
            return '<img alt="{}" class="emoji" src="{}" title="{}">'.format(
 | 
						|
                name, get_emoji_url(file_name, realm_id), name[1:-1].replace("_", " ")
 | 
						|
            )
 | 
						|
 | 
						|
        realm = get_realm("zulip")
 | 
						|
 | 
						|
        # Needs to mock an actual message because that's how Markdown obtains the realm
 | 
						|
        msg = Message(sender=self.example_user("hamlet"), realm=realm)
 | 
						|
        converted = markdown_convert(":green_tick:", message_realm=realm, message=msg)
 | 
						|
        realm_emoji = RealmEmoji.objects.filter(
 | 
						|
            realm=realm, name="green_tick", deactivated=False
 | 
						|
        ).get()
 | 
						|
        assert realm_emoji.file_name is not None
 | 
						|
        self.assertEqual(
 | 
						|
            converted.rendered_content,
 | 
						|
            "<p>{}</p>".format(emoji_img(":green_tick:", realm_emoji.file_name, realm.id)),
 | 
						|
        )
 | 
						|
 | 
						|
        # Deactivate realm emoji.
 | 
						|
        do_remove_realm_emoji(realm, "green_tick", acting_user=None)
 | 
						|
        converted = markdown_convert(":green_tick:", message_realm=realm, message=msg)
 | 
						|
        self.assertEqual(converted.rendered_content, "<p>:green_tick:</p>")
 | 
						|
 | 
						|
    def test_deactivated_realm_emoji(self) -> None:
 | 
						|
        # Deactivate realm emoji.
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        do_remove_realm_emoji(realm, "green_tick", acting_user=None)
 | 
						|
 | 
						|
        msg = Message(sender=self.example_user("hamlet"), realm=realm)
 | 
						|
        converted = markdown_convert(":green_tick:", message_realm=realm, message=msg)
 | 
						|
        self.assertEqual(converted.rendered_content, "<p>:green_tick:</p>")
 | 
						|
 | 
						|
    def test_unicode_emoji(self) -> None:
 | 
						|
        msg = "\u2615"  # ☕
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            '<p><span aria-label="coffee" class="emoji emoji-2615" role="img" title="coffee">:coffee:</span></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        msg = "\u2615\u2615"  # ☕☕
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            '<p><span aria-label="coffee" class="emoji emoji-2615" role="img" title="coffee">:coffee:</span><span aria-label="coffee" class="emoji emoji-2615" role="img" title="coffee">:coffee:</span></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_no_translate_emoticons_if_off(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        do_change_user_setting(user_profile, "translate_emoticons", False, acting_user=None)
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        content = ":)"
 | 
						|
        expected = "<p>:)</p>"
 | 
						|
        converted = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(converted.rendered_content, expected)
 | 
						|
 | 
						|
    def test_same_markup(self) -> None:
 | 
						|
        msg = "\u2615"  # ☕
 | 
						|
        unicode_converted = markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
        msg = ":coffee:"  # ☕☕
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
        self.assertEqual(converted, unicode_converted)
 | 
						|
 | 
						|
    def test_all_emoji_match_regex(self) -> None:
 | 
						|
        non_matching_emoji = [
 | 
						|
            emoji
 | 
						|
            for codepoint in codepoint_to_name
 | 
						|
            if not POSSIBLE_EMOJI_RE.fullmatch(emoji := hex_codepoint_to_emoji(codepoint))
 | 
						|
        ]
 | 
						|
        self.assertEqual(
 | 
						|
            non_matching_emoji,
 | 
						|
            # unqualified numbers in boxes shouldn't be converted to emoji images, so this is fine
 | 
						|
            ["#⃣", "*⃣", "0⃣", "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣"],
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class MarkdownLinkifierTest(ZulipTestCase):
 | 
						|
    def test_links_in_topic_name(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        msg = Message(sender=self.example_user("othello"), realm=realm)
 | 
						|
 | 
						|
        msg.set_topic_name("https://google.com/hello-world")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [{"url": "https://google.com/hello-world", "text": "https://google.com/hello-world"}],
 | 
						|
        )
 | 
						|
 | 
						|
        msg.set_topic_name("http://google.com/hello-world")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [{"url": "http://google.com/hello-world", "text": "http://google.com/hello-world"}],
 | 
						|
        )
 | 
						|
 | 
						|
        msg.set_topic_name("Without scheme google.com/hello-world")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [{"url": "https://google.com/hello-world", "text": "google.com/hello-world"}],
 | 
						|
        )
 | 
						|
 | 
						|
        msg.set_topic_name("Without scheme random.words/hello-world")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(converted_topic, [])
 | 
						|
 | 
						|
        msg.set_topic_name(
 | 
						|
            "Try out http://ftp.debian.org, https://google.com/ and https://google.in/."
 | 
						|
        )
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "http://ftp.debian.org", "text": "http://ftp.debian.org"},
 | 
						|
                {"url": "https://google.com/", "text": "https://google.com/"},
 | 
						|
                {"url": "https://google.in/", "text": "https://google.in/"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        # test order for links without scheme
 | 
						|
        msg.set_topic_name("google.in google.com")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://google.in", "text": "google.in"},
 | 
						|
                {"url": "https://google.com", "text": "google.com"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        # Query strings in a URL should be included in the link.
 | 
						|
        msg.set_topic_name("https://google.com/test?foo=bar")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {
 | 
						|
                    "url": "https://google.com/test?foo=bar",
 | 
						|
                    "text": "https://google.com/test?foo=bar",
 | 
						|
                },
 | 
						|
            ],
 | 
						|
        )
 | 
						|
        # But question marks at the end of sentence are not part of the URL.
 | 
						|
        msg.set_topic_name("Have you seen github.com/zulip?")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://github.com/zulip", "text": "github.com/zulip"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
        msg.set_topic_name("Do you like https://example.com? I love it.")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://example.com", "text": "https://example.com"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
    def check_add_linkifiers(
 | 
						|
        self, linkifiers: list[RealmFilter], expected_linkifier_reprs: list[str]
 | 
						|
    ) -> None:
 | 
						|
        self.assert_length(linkifiers, len(expected_linkifier_reprs))
 | 
						|
        for linkifier, expected_linkifier_repr in zip(
 | 
						|
            linkifiers, expected_linkifier_reprs, strict=False
 | 
						|
        ):
 | 
						|
            linkifier.clean()
 | 
						|
            linkifier.save()
 | 
						|
            self.assertEqual(repr(linkifier), expected_linkifier_repr)
 | 
						|
 | 
						|
    def test_realm_patterns(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        self.check_add_linkifiers(
 | 
						|
            [
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern=r"#(?P<id>[0-9]{2,8})",
 | 
						|
                    url_template=r"https://trac.example.com/ticket/{id}",
 | 
						|
                )
 | 
						|
            ],
 | 
						|
            ["<RealmFilter: zulip: #(?P<id>[0-9]{2,8}) https://trac.example.com/ticket/{id}>"],
 | 
						|
        )
 | 
						|
 | 
						|
        msg = Message(sender=self.example_user("othello"), realm=realm)
 | 
						|
        msg.set_topic_name("#444")
 | 
						|
 | 
						|
        flush_per_request_caches()
 | 
						|
 | 
						|
        content = "We should fix #224 #336 #446 and #115, but not issue#124 or #1124z or [trac #15](https://trac.example.com/ticket/16) today."
 | 
						|
        converted = markdown_convert(content, message_realm=realm, message=msg)
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted.rendered_content,
 | 
						|
            '<p>We should fix <a href="https://trac.example.com/ticket/224">#224</a> <a href="https://trac.example.com/ticket/336">#336</a> <a href="https://trac.example.com/ticket/446">#446</a> and <a href="https://trac.example.com/ticket/115">#115</a>, but not issue#124 or #1124z or <a href="https://trac.example.com/ticket/16">trac #15</a> today.</p>',
 | 
						|
        )
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic, [{"url": "https://trac.example.com/ticket/444", "text": "#444"}]
 | 
						|
        )
 | 
						|
 | 
						|
        msg.set_topic_name("#444 https://google.com")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://trac.example.com/ticket/444", "text": "#444"},
 | 
						|
                {"url": "https://google.com", "text": "https://google.com"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        msg.set_topic_name("#111 https://google.com #111 #222 #111 https://google.com #222")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://trac.example.com/ticket/111", "text": "#111"},
 | 
						|
                {"url": "https://google.com", "text": "https://google.com"},
 | 
						|
                {"url": "https://trac.example.com/ticket/111", "text": "#111"},
 | 
						|
                {"url": "https://trac.example.com/ticket/222", "text": "#222"},
 | 
						|
                {"url": "https://trac.example.com/ticket/111", "text": "#111"},
 | 
						|
                {"url": "https://google.com", "text": "https://google.com"},
 | 
						|
                {"url": "https://trac.example.com/ticket/222", "text": "#222"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        msg.set_topic_name("#444 #555 #666")
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://trac.example.com/ticket/444", "text": "#444"},
 | 
						|
                {"url": "https://trac.example.com/ticket/555", "text": "#555"},
 | 
						|
                {"url": "https://trac.example.com/ticket/666", "text": "#666"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        RealmFilter(
 | 
						|
            realm=realm,
 | 
						|
            pattern=r"#(?P<id>[a-zA-Z]+-[0-9]+)",
 | 
						|
            url_template=r"https://trac.example.com/ticket/{id}",
 | 
						|
        ).save()
 | 
						|
        msg = Message(sender=self.example_user("hamlet"), realm=realm)
 | 
						|
 | 
						|
        content = "#ZUL-123 was fixed and code was deployed to production, also #zul-321 was deployed to staging"
 | 
						|
        converted = markdown_convert(content, message_realm=realm, message=msg)
 | 
						|
 | 
						|
        self.assertEqual(
 | 
						|
            converted.rendered_content,
 | 
						|
            '<p><a href="https://trac.example.com/ticket/ZUL-123">#ZUL-123</a> was fixed and code was deployed to production, also <a href="https://trac.example.com/ticket/zul-321">#zul-321</a> was deployed to staging</p>',
 | 
						|
        )
 | 
						|
 | 
						|
        def assert_conversion(content: str, should_have_converted: bool = True) -> None:
 | 
						|
            converted = markdown_convert(content, message_realm=realm, message=msg).rendered_content
 | 
						|
            converted_topic = topic_links(realm.id, content)
 | 
						|
            if should_have_converted:
 | 
						|
                self.assertTrue("https://trac.example.com" in converted)
 | 
						|
                self.assert_length(converted_topic, 1)
 | 
						|
                self.assertEqual(
 | 
						|
                    converted_topic[0],
 | 
						|
                    {"url": "https://trac.example.com/ticket/123", "text": "#123"},
 | 
						|
                )
 | 
						|
            else:
 | 
						|
                self.assertTrue("https://trac.example.com" not in converted)
 | 
						|
                self.assert_length(converted_topic, 0)
 | 
						|
 | 
						|
        assert_conversion("Hello #123 World")
 | 
						|
        assert_conversion("Hello #123World", False)
 | 
						|
        assert_conversion("Hello#123 World", False)
 | 
						|
        assert_conversion("Hello#123World", False)
 | 
						|
        assert_conversion("Hello\u00a0#123\u00a0World")
 | 
						|
        # Ideally, these should be converted, but Markdown doesn't
 | 
						|
        # handle word boundary detection in languages that don't use
 | 
						|
        # whitespace for that correctly yet.
 | 
						|
        assert_conversion("チケットは#123です", False)
 | 
						|
        assert_conversion("チケットは #123です", False)
 | 
						|
        assert_conversion("チケットは#123 です", False)
 | 
						|
        assert_conversion("チケットは #123 です")
 | 
						|
        assert_conversion("(#123)")
 | 
						|
        assert_conversion("#123>")
 | 
						|
        assert_conversion('"#123"')
 | 
						|
        assert_conversion("#123@")
 | 
						|
        assert_conversion(")#123(", False)
 | 
						|
        assert_conversion("##123", False)
 | 
						|
 | 
						|
        # test nested realm patterns should avoid double matching
 | 
						|
        RealmFilter(
 | 
						|
            realm=realm,
 | 
						|
            pattern=r"hello#(?P<id>[0-9]+)",
 | 
						|
            url_template=r"https://trac.example.com/hello/{id}",
 | 
						|
        ).save()
 | 
						|
        converted_topic = topic_links(realm.id, "hello#123 #234")
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://trac.example.com/hello/123", "text": "hello#123"},
 | 
						|
                {"url": "https://trac.example.com/ticket/234", "text": "#234"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        # test correct order when realm pattern and normal links are both present.
 | 
						|
        converted_topic = topic_links(realm.id, "#234 https://google.com")
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://trac.example.com/ticket/234", "text": "#234"},
 | 
						|
                {"url": "https://google.com", "text": "https://google.com"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        # Test URL escaping
 | 
						|
        RealmFilter(
 | 
						|
            realm=realm,
 | 
						|
            pattern=r"url-(?P<id>[0-9]+)",
 | 
						|
            url_template="https://example.com/A%20Test/%%%ba/{id}",
 | 
						|
        ).save()
 | 
						|
        msg = Message(sender=self.example_user("hamlet"), realm=realm)
 | 
						|
        content = "url-123 is well-escaped"
 | 
						|
        converted = markdown_convert(content, message_realm=realm, message=msg)
 | 
						|
        self.assertEqual(
 | 
						|
            converted.rendered_content,
 | 
						|
            '<p><a href="https://example.com/A%20Test/%25%25%ba/123">url-123</a> is well-escaped</p>',
 | 
						|
        )
 | 
						|
        converted_topic = topic_links(realm.id, content)
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [{"url": "https://example.com/A%20Test/%25%25%ba/123", "text": "url-123"}],
 | 
						|
        )
 | 
						|
 | 
						|
        # Test spaces in the linkifier pattern
 | 
						|
        RealmFilter(
 | 
						|
            realm=realm,
 | 
						|
            pattern=r"community guidelines",
 | 
						|
            url_template="https://zulip.com/development-community/#community-norms",
 | 
						|
        ).save()
 | 
						|
        converted = markdown_convert("community guidelines", message_realm=realm, message=msg)
 | 
						|
        self.assertEqual(
 | 
						|
            converted.rendered_content,
 | 
						|
            '<p><a href="https://zulip.com/development-community/#community-norms">community guidelines</a></p>',
 | 
						|
        )
 | 
						|
        converted = markdown_convert(
 | 
						|
            "please observe community guidelines here", message_realm=realm, message=msg
 | 
						|
        )
 | 
						|
        self.assertEqual(
 | 
						|
            converted.rendered_content,
 | 
						|
            '<p>please observe <a href="https://zulip.com/development-community/#community-norms">community guidelines</a> here</p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_multiple_matching_realm_patterns(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        self.check_add_linkifiers(
 | 
						|
            [
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern="(?P<id>ABC-[0-9]+)",
 | 
						|
                    url_template="https://trac.example.com/ticket/{id}",
 | 
						|
                ),
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern="(?P<id>[A-Z][A-Z0-9]*-[0-9]+)",
 | 
						|
                    url_template="https://other-trac.example.com/ticket/{id}",
 | 
						|
                ),
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern="(?P<id>[A-Z][A-Z0-9]+)",
 | 
						|
                    url_template="https://yet-another-trac.example.com/ticket/{id}",
 | 
						|
                ),
 | 
						|
            ],
 | 
						|
            [
 | 
						|
                "<RealmFilter: zulip: (?P<id>ABC-[0-9]+) https://trac.example.com/ticket/{id}>",
 | 
						|
                "<RealmFilter: zulip: (?P<id>[A-Z][A-Z0-9]*-[0-9]+) https://other-trac.example.com/ticket/{id}>",
 | 
						|
                "<RealmFilter: zulip: (?P<id>[A-Z][A-Z0-9]+) https://yet-another-trac.example.com/ticket/{id}>",
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        msg = Message(sender=self.example_user("othello"), realm=realm)
 | 
						|
        msg.set_topic_name("ABC-123")
 | 
						|
 | 
						|
        flush_per_request_caches()
 | 
						|
 | 
						|
        content = (
 | 
						|
            "We should fix ABC-123 or [trac ABC-123](https://trac.example.com/ticket/16) today."
 | 
						|
        )
 | 
						|
        converted = markdown_convert(content, message_realm=realm, message=msg)
 | 
						|
        converted_topic = topic_links(realm.id, msg.topic_name())
 | 
						|
 | 
						|
        # The second linkifier (which was saved later) was ignored as the content was marked AtomicString after first conversion.
 | 
						|
        # There was no easy way to support parsing both linkifiers and not run into an infinite loop, hence the second linkifier is ignored.
 | 
						|
        self.assertEqual(
 | 
						|
            converted.rendered_content,
 | 
						|
            '<p>We should fix <a href="https://trac.example.com/ticket/ABC-123">ABC-123</a> or <a href="https://trac.example.com/ticket/16">trac ABC-123</a> today.</p>',
 | 
						|
        )
 | 
						|
        # Only the older linkifier should be used in the topic, because the two patterns overlap.
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://trac.example.com/ticket/ABC-123", "text": "ABC-123"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        # linkifier 3 matches ASD, ABC and QWE, but because it has lower priority
 | 
						|
        # than linkifier 1 and linkifier 2 because it is created last, the former
 | 
						|
        # two matches will not be chosen.
 | 
						|
        # Both linkifier 1 and linkifier 2 matches ABC-123, similarly, as linkifier 2
 | 
						|
        # has a lower priority, only linkifier 1's URL will be generated.
 | 
						|
        converted_topic = topic_links(realm.id, "ASD-123 ABC-123 QWE")
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {"url": "https://other-trac.example.com/ticket/ASD-123", "text": "ASD-123"},
 | 
						|
                {"url": "https://trac.example.com/ticket/ABC-123", "text": "ABC-123"},
 | 
						|
                {"url": "https://yet-another-trac.example.com/ticket/QWE", "text": "QWE"},
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
    def test_links_and_linkifiers_in_topic_name(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        self.check_add_linkifiers(
 | 
						|
            [
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern="ABC-42",
 | 
						|
                    url_template="https://google.com",
 | 
						|
                ),
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern=r"com.+(?P<id>ABC\-[0-9]+)",
 | 
						|
                    url_template="https://trac.example.com/ticket/{id}",
 | 
						|
                ),
 | 
						|
            ],
 | 
						|
            [
 | 
						|
                "<RealmFilter: zulip: ABC-42 https://google.com>",
 | 
						|
                r"<RealmFilter: zulip: com.+(?P<id>ABC\-[0-9]+) https://trac.example.com/ticket/{id}>",
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        # This verifies that second linkifier has a lower priority than the first one.
 | 
						|
        # It helps us to later ensure that even with a low priority, the linkifier can take effect
 | 
						|
        # when it appears earlier than a raw URL.
 | 
						|
        converted_topic = topic_links(realm.id, "com ABC-42")
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [{"url": "https://google.com", "text": "ABC-42"}],
 | 
						|
        )
 | 
						|
        # The linkifier matches "com/ABC-123", which is after where the raw URL starts
 | 
						|
        converted_topic = topic_links(realm.id, "https://foo.com/ABC-123")
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [{"url": "https://foo.com/ABC-123", "text": "https://foo.com/ABC-123"}],
 | 
						|
        )
 | 
						|
 | 
						|
        # The linkifier matches "com https://foo.com/ABC-123", which is before where the raw URL starts
 | 
						|
        converted_topic = topic_links(realm.id, "com https://foo.com/ABC-123")
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {
 | 
						|
                    "url": "https://trac.example.com/ticket/ABC-123",
 | 
						|
                    "text": "com https://foo.com/ABC-123",
 | 
						|
                }
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
    def test_topic_links_ordering_by_priority(self) -> None:
 | 
						|
        # The same test case is also implemented in web/tests/markdown_parse.test.cjs
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        self.check_add_linkifiers(
 | 
						|
            [
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern="http",
 | 
						|
                    url_template="http://example.com/",
 | 
						|
                    order=1,
 | 
						|
                ),
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern="b#(?P<id>[a-z]+)",
 | 
						|
                    url_template="http://example.com/b/{id}",
 | 
						|
                    order=2,
 | 
						|
                ),
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern="a#(?P<aid>[a-z]+) b#(?P<bid>[a-z]+)",
 | 
						|
                    url_template="http://example.com/a/{aid}/b/{bid}",
 | 
						|
                    order=3,
 | 
						|
                ),
 | 
						|
                RealmFilter(
 | 
						|
                    realm=realm,
 | 
						|
                    pattern="a#(?P<id>[a-z]+)",
 | 
						|
                    url_template="http://example.com/a/{id}",
 | 
						|
                    order=4,
 | 
						|
                ),
 | 
						|
            ],
 | 
						|
            [
 | 
						|
                "<RealmFilter: zulip: http http://example.com/>",
 | 
						|
                "<RealmFilter: zulip: b#(?P<id>[a-z]+) http://example.com/b/{id}>",
 | 
						|
                "<RealmFilter: zulip: a#(?P<aid>[a-z]+) b#(?P<bid>[a-z]+) http://example.com/a/{aid}/b/{bid}>",
 | 
						|
                "<RealmFilter: zulip: a#(?P<id>[a-z]+) http://example.com/a/{id}>",
 | 
						|
            ],
 | 
						|
        )
 | 
						|
        # There should be 5 link matches in the topic, if ordered from the most prioritized to the least:
 | 
						|
        # 1. "http" (linkifier)
 | 
						|
        # 2. "b#bar" (linkifier)
 | 
						|
        # 3. "a#asd b#bar" (linkifier)
 | 
						|
        # 4. "a#asd" (linkifier)
 | 
						|
        # 5. "http://foo.com" (raw URL)
 | 
						|
        # When there are overlapping matches, the one that appears earlier in the list should
 | 
						|
        # have a topic link generated.
 | 
						|
        # For this test case, while "a#asd" and "a#asd b#bar" both match and they overlap,
 | 
						|
        # there is a match "b#bar" with a higher priority, preventing "a#asd b#bar" from being matched.
 | 
						|
        converted_topic = topic_links(realm.id, "http://foo.com a#asd b#bar")
 | 
						|
        self.assertEqual(
 | 
						|
            converted_topic,
 | 
						|
            [
 | 
						|
                {
 | 
						|
                    "text": "http",
 | 
						|
                    "url": "http://example.com/",
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    "text": "a#asd",
 | 
						|
                    "url": "http://example.com/a/asd",
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    "text": "b#bar",
 | 
						|
                    "url": "http://example.com/b/bar",
 | 
						|
                },
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
    def test_linkifier_precedence(self) -> None:
 | 
						|
        realm = self.example_user("hamlet").realm
 | 
						|
        RealmFilter.objects.filter(realm=realm).delete()
 | 
						|
        # The insertion order should not affect the fact that the linkifiers are
 | 
						|
        # ordered by the `order` field.
 | 
						|
        order_values = (10, 3, 11, 2, 4, 5, 6)
 | 
						|
        order_to_id = {}
 | 
						|
        for cur_order in order_values:
 | 
						|
            linkifier = RealmFilter(
 | 
						|
                realm=realm,
 | 
						|
                pattern=f"abc{cur_order}",
 | 
						|
                url_template="http://foo.com",
 | 
						|
                order=cur_order,
 | 
						|
            )
 | 
						|
            linkifier.save()
 | 
						|
            order_to_id[cur_order] = linkifier.id
 | 
						|
        linkifiers = linkifiers_for_realm(realm.id)
 | 
						|
        for index, cur_order in enumerate(sorted(order_values)):
 | 
						|
            self.assertEqual(linkifiers[index]["id"], order_to_id[cur_order])
 | 
						|
 | 
						|
    def test_realm_patterns_negative(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        RealmFilter(
 | 
						|
            realm=realm,
 | 
						|
            pattern=r"#(?P<id>[0-9]{2,8})",
 | 
						|
            url_template=r"https://trac.example.com/ticket/{id}",
 | 
						|
        ).save()
 | 
						|
        boring_msg = Message(sender=self.example_user("othello"), realm=realm)
 | 
						|
        boring_msg.set_topic_name("no match here")
 | 
						|
        converted_boring_topic = topic_links(realm.id, boring_msg.topic_name())
 | 
						|
        self.assertEqual(converted_boring_topic, [])
 | 
						|
 | 
						|
    def test_is_status_message(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        content = "/me makes a list\n* one\n* two"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>/me makes a list</p>\n<ul>\n<li>one</li>\n<li>two</li>\n</ul>",
 | 
						|
        )
 | 
						|
        self.assertTrue(Message.is_status_message(content, rendering_result.rendered_content))
 | 
						|
 | 
						|
        content = "/me takes a walk"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>/me takes a walk</p>",
 | 
						|
        )
 | 
						|
        self.assertTrue(Message.is_status_message(content, rendering_result.rendered_content))
 | 
						|
 | 
						|
        content = "/me writes a second line\nline"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>/me writes a second line<br>\nline</p>",
 | 
						|
        )
 | 
						|
        self.assertTrue(Message.is_status_message(content, rendering_result.rendered_content))
 | 
						|
 | 
						|
    def test_linkifier_caching(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
 | 
						|
        RealmFilter.objects.all().delete()
 | 
						|
 | 
						|
        with self.assert_database_query_count(1):
 | 
						|
            self.assertEqual(linkifiers_for_realm(realm.id), [])
 | 
						|
 | 
						|
        # Verify that our in-memory cache avoids round trips.
 | 
						|
        with (
 | 
						|
            self.assert_database_query_count(0, keep_cache_warm=True),
 | 
						|
            self.assert_memcached_count(0),
 | 
						|
        ):
 | 
						|
            self.assertEqual(linkifiers_for_realm(realm.id), [])
 | 
						|
 | 
						|
        linkifier = RealmFilter(realm=realm, pattern=r"whatever", url_template="whatever")
 | 
						|
        linkifier.save()
 | 
						|
 | 
						|
        # cache gets properly invalidated by virtue of our save
 | 
						|
        self.assertEqual(
 | 
						|
            linkifiers_for_realm(realm.id),
 | 
						|
            [{"id": linkifier.id, "pattern": "whatever", "url_template": "whatever"}],
 | 
						|
        )
 | 
						|
 | 
						|
        # And the in-process cache works again.
 | 
						|
        with (
 | 
						|
            self.assert_database_query_count(0, keep_cache_warm=True),
 | 
						|
            self.assert_memcached_count(0),
 | 
						|
        ):
 | 
						|
            self.assertEqual(
 | 
						|
                linkifiers_for_realm(realm.id),
 | 
						|
                [{"id": linkifier.id, "pattern": "whatever", "url_template": "whatever"}],
 | 
						|
            )
 | 
						|
 | 
						|
 | 
						|
class MarkdownAlertTest(ZulipTestCase):
 | 
						|
    def test_alert_words(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        do_add_alert_words(user_profile, ["ALERTWORD", "scaryword"])
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
        realm_alert_words_automaton = get_alert_word_automaton(user_profile.realm)
 | 
						|
 | 
						|
        def render(msg: Message, content: str) -> MessageRenderingResult:
 | 
						|
            return render_message_markdown(
 | 
						|
                msg, content, realm_alert_words_automaton=realm_alert_words_automaton
 | 
						|
            )
 | 
						|
 | 
						|
        content = "We have an ALERTWORD day today!"
 | 
						|
        rendering_result = render(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content, "<p>We have an ALERTWORD day today!</p>"
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.user_ids_with_alert_words, {user_profile.id})
 | 
						|
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
        content = "We have a NOTHINGWORD day today!"
 | 
						|
        rendering_result = render(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content, "<p>We have a NOTHINGWORD day today!</p>"
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.user_ids_with_alert_words, set())
 | 
						|
 | 
						|
    def test_alert_words_returns_user_ids_with_alert_words(self) -> None:
 | 
						|
        alert_words_for_users: dict[str, list[str]] = {
 | 
						|
            "hamlet": ["how"],
 | 
						|
            "cordelia": ["this possible"],
 | 
						|
            "iago": ["hello"],
 | 
						|
            "prospero": ["hello"],
 | 
						|
            "othello": ["how are you"],
 | 
						|
            "aaron": ["hey"],
 | 
						|
        }
 | 
						|
        user_profiles: dict[str, UserProfile] = {}
 | 
						|
        for username, alert_words in alert_words_for_users.items():
 | 
						|
            user_profile = self.example_user(username)
 | 
						|
            user_profiles.update({username: user_profile})
 | 
						|
            do_add_alert_words(user_profile, alert_words)
 | 
						|
        sender_user_profile = self.example_user("polonius")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        realm_alert_words_automaton = get_alert_word_automaton(sender_user_profile.realm)
 | 
						|
 | 
						|
        def render(msg: Message, content: str) -> MessageRenderingResult:
 | 
						|
            return render_message_markdown(
 | 
						|
                msg, content, realm_alert_words_automaton=realm_alert_words_automaton
 | 
						|
            )
 | 
						|
 | 
						|
        content = "hello how is this possible how are you doing today"
 | 
						|
        rendering_result = render(msg, content)
 | 
						|
        expected_user_ids: set[int] = {
 | 
						|
            user_profiles["hamlet"].id,
 | 
						|
            user_profiles["cordelia"].id,
 | 
						|
            user_profiles["iago"].id,
 | 
						|
            user_profiles["prospero"].id,
 | 
						|
            user_profiles["othello"].id,
 | 
						|
        }
 | 
						|
        # All users except aaron have their alert word appear in the message content
 | 
						|
        self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids)
 | 
						|
 | 
						|
    def test_alert_words_returns_user_ids_with_alert_words_1(self) -> None:
 | 
						|
        alert_words_for_users: dict[str, list[str]] = {
 | 
						|
            "hamlet": ["provisioning", "Prod deployment"],
 | 
						|
            "cordelia": ["test", "Prod"],
 | 
						|
            "iago": ["prod"],
 | 
						|
            "prospero": ["deployment"],
 | 
						|
            "othello": ["last"],
 | 
						|
        }
 | 
						|
        user_profiles: dict[str, UserProfile] = {}
 | 
						|
        for username, alert_words in alert_words_for_users.items():
 | 
						|
            user_profile = self.example_user(username)
 | 
						|
            user_profiles.update({username: user_profile})
 | 
						|
            do_add_alert_words(user_profile, alert_words)
 | 
						|
        sender_user_profile = self.example_user("polonius")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        realm_alert_words_automaton = get_alert_word_automaton(sender_user_profile.realm)
 | 
						|
 | 
						|
        def render(msg: Message, content: str) -> MessageRenderingResult:
 | 
						|
            return render_message_markdown(
 | 
						|
                msg, content, realm_alert_words_automaton=realm_alert_words_automaton
 | 
						|
            )
 | 
						|
 | 
						|
        content = """Hello, everyone. Prod deployment has been completed
 | 
						|
        And this is a new line
 | 
						|
        to test out how Markdown convert this into something line ending split array
 | 
						|
        and this is a new line
 | 
						|
        last"""
 | 
						|
        rendering_result = render(msg, content)
 | 
						|
        expected_user_ids: set[int] = {
 | 
						|
            user_profiles["hamlet"].id,
 | 
						|
            user_profiles["cordelia"].id,
 | 
						|
            user_profiles["iago"].id,
 | 
						|
            user_profiles["prospero"].id,
 | 
						|
            user_profiles["othello"].id,
 | 
						|
        }
 | 
						|
        # All users have their alert word appear in the message content
 | 
						|
        self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids)
 | 
						|
 | 
						|
    def test_alert_words_returns_user_ids_with_alert_words_in_french(self) -> None:
 | 
						|
        alert_words_for_users: dict[str, list[str]] = {
 | 
						|
            "hamlet": ["réglementaire", "une politique", "une merveille"],
 | 
						|
            "cordelia": ["énormément", "Prod"],
 | 
						|
            "iago": ["prod"],
 | 
						|
            "prospero": ["deployment"],
 | 
						|
            "othello": ["last"],
 | 
						|
        }
 | 
						|
        user_profiles: dict[str, UserProfile] = {}
 | 
						|
        for username, alert_words in alert_words_for_users.items():
 | 
						|
            user_profile = self.example_user(username)
 | 
						|
            user_profiles.update({username: user_profile})
 | 
						|
            do_add_alert_words(user_profile, alert_words)
 | 
						|
        sender_user_profile = self.example_user("polonius")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        realm_alert_words_automaton = get_alert_word_automaton(sender_user_profile.realm)
 | 
						|
 | 
						|
        def render(msg: Message, content: str) -> MessageRenderingResult:
 | 
						|
            return render_message_markdown(
 | 
						|
                msg, content, realm_alert_words_automaton=realm_alert_words_automaton
 | 
						|
            )
 | 
						|
 | 
						|
        content = """This is to test out alert words work in languages with accented characters too
 | 
						|
        bonjour est (énormément) ce a quoi ressemble le français
 | 
						|
        et j'espère qu'il n'y n' réglementaire a pas de mots d'alerte dans ce texte français
 | 
						|
        """
 | 
						|
        rendering_result = render(msg, content)
 | 
						|
        expected_user_ids: set[int] = {user_profiles["hamlet"].id, user_profiles["cordelia"].id}
 | 
						|
        # Only hamlet and cordelia have their alert-words appear in the message content
 | 
						|
        self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids)
 | 
						|
 | 
						|
    def test_alert_words_returns_empty_user_ids_with_alert_words(self) -> None:
 | 
						|
        alert_words_for_users: dict[str, list[str]] = {
 | 
						|
            "hamlet": [],
 | 
						|
            "cordelia": [],
 | 
						|
            "iago": [],
 | 
						|
            "prospero": [],
 | 
						|
            "othello": [],
 | 
						|
            "aaron": [],
 | 
						|
        }
 | 
						|
        user_profiles: dict[str, UserProfile] = {}
 | 
						|
        for username, alert_words in alert_words_for_users.items():
 | 
						|
            user_profile = self.example_user(username)
 | 
						|
            user_profiles.update({username: user_profile})
 | 
						|
            do_add_alert_words(user_profile, alert_words)
 | 
						|
        sender_user_profile = self.example_user("polonius")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
        realm_alert_words_automaton = get_alert_word_automaton(sender_user_profile.realm)
 | 
						|
 | 
						|
        def render(msg: Message, content: str) -> MessageRenderingResult:
 | 
						|
            return render_message_markdown(
 | 
						|
                msg, content, realm_alert_words_automaton=realm_alert_words_automaton
 | 
						|
            )
 | 
						|
 | 
						|
        content = """hello how is this possible how are you doing today
 | 
						|
        This is to test that the no user_ids who have alrert wourldword is participating
 | 
						|
        in sending of the message
 | 
						|
        """
 | 
						|
        rendering_result = render(msg, content)
 | 
						|
        expected_user_ids: set[int] = set()
 | 
						|
        # None of the users have their alert-words appear in the message content
 | 
						|
        self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids)
 | 
						|
 | 
						|
    def get_mock_alert_words(self, num_words: int, word_length: int) -> list[str]:
 | 
						|
        alert_words = ["x" * word_length] * num_words  # type List[str]
 | 
						|
        return alert_words
 | 
						|
 | 
						|
    def test_alert_words_with_empty_alert_words(self) -> None:
 | 
						|
        alert_words_for_users: dict[str, list[str]] = {
 | 
						|
            "hamlet": [],
 | 
						|
            "cordelia": [],
 | 
						|
            "iago": [],
 | 
						|
            "othello": [],
 | 
						|
        }
 | 
						|
        user_profiles: dict[str, UserProfile] = {}
 | 
						|
        for username, alert_words in alert_words_for_users.items():
 | 
						|
            user_profile = self.example_user(username)
 | 
						|
            user_profiles.update({username: user_profile})
 | 
						|
            do_add_alert_words(user_profile, alert_words)
 | 
						|
        sender_user_profile = self.example_user("polonius")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        realm_alert_words_automaton = get_alert_word_automaton(sender_user_profile.realm)
 | 
						|
 | 
						|
        def render(msg: Message, content: str) -> MessageRenderingResult:
 | 
						|
            return render_message_markdown(
 | 
						|
                msg, content, realm_alert_words_automaton=realm_alert_words_automaton
 | 
						|
            )
 | 
						|
 | 
						|
        content = """This is to test a empty alert words i.e. no user has any alert-words set"""
 | 
						|
        rendering_result = render(msg, content)
 | 
						|
        expected_user_ids: set[int] = set()
 | 
						|
        self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids)
 | 
						|
 | 
						|
    def test_alert_words_returns_user_ids_with_alert_words_with_huge_alert_words(self) -> None:
 | 
						|
        alert_words_for_users: dict[str, list[str]] = {
 | 
						|
            "hamlet": ["issue124"],
 | 
						|
            "cordelia": self.get_mock_alert_words(500, 10),
 | 
						|
            "iago": self.get_mock_alert_words(500, 10),
 | 
						|
            "othello": self.get_mock_alert_words(500, 10),
 | 
						|
        }
 | 
						|
        user_profiles: dict[str, UserProfile] = {}
 | 
						|
        for username, alert_words in alert_words_for_users.items():
 | 
						|
            user_profile = self.example_user(username)
 | 
						|
            user_profiles.update({username: user_profile})
 | 
						|
            do_add_alert_words(user_profile, alert_words)
 | 
						|
        sender_user_profile = self.example_user("polonius")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        realm_alert_words_automaton = get_alert_word_automaton(sender_user_profile.realm)
 | 
						|
 | 
						|
        def render(msg: Message, content: str) -> MessageRenderingResult:
 | 
						|
            return render_message_markdown(
 | 
						|
                msg, content, realm_alert_words_automaton=realm_alert_words_automaton
 | 
						|
            )
 | 
						|
 | 
						|
        content = """The code above will print 10 random values of numbers between 1 and 100.
 | 
						|
        The second line, for x in range(10), determines how many values will be printed (when you use
 | 
						|
        range(x), the number that you use in place of x will be the amount of values that you'll have
 | 
						|
        printed. if you want 20 values, use range(20). use range(5) if you only want 5 values returned,
 | 
						|
        etc.). I was talking about the issue124 on github. Then the third line: print random.randint(1,101) will automatically select a random integer
 | 
						|
        between 1 and 100 for you. The process is fairly simple
 | 
						|
        """
 | 
						|
        rendering_result = render(msg, content)
 | 
						|
        expected_user_ids: set[int] = {user_profiles["hamlet"].id}
 | 
						|
        # Only hamlet has alert-word 'issue124' present in the message content
 | 
						|
        self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids)
 | 
						|
 | 
						|
 | 
						|
class MarkdownCodeBlockTest(ZulipTestCase):
 | 
						|
    def test_default_code_block_language(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        self.assertEqual(realm.default_code_block_language, "")
 | 
						|
        text = "```{}\nconsole.log('Hello World');\n```\n"
 | 
						|
 | 
						|
        # Render without default language
 | 
						|
        msg_with_js = markdown_convert_wrapper(text.format("js"))
 | 
						|
        msg_with_python = markdown_convert_wrapper(text.format("python"))
 | 
						|
        msg_without_language = markdown_convert_wrapper(text.format(""))
 | 
						|
        msg_with_quote = markdown_convert_wrapper(text.format("quote"))
 | 
						|
        msg_with_math = markdown_convert_wrapper(text.format("math"))
 | 
						|
        msg_with_none = markdown_convert_wrapper(text.format("none"))
 | 
						|
 | 
						|
        # Render with default=javascript
 | 
						|
        do_set_realm_property(realm, "default_code_block_language", "javascript", acting_user=None)
 | 
						|
        msg_without_language_default_js = markdown_convert_wrapper(text.format(""))
 | 
						|
        msg_with_python_default_js = markdown_convert_wrapper(text.format("python"))
 | 
						|
 | 
						|
        # Render with default=python
 | 
						|
        do_set_realm_property(realm, "default_code_block_language", "python", acting_user=None)
 | 
						|
        msg_without_language_default_py = markdown_convert_wrapper(text.format(""))
 | 
						|
        msg_with_none_default_py = markdown_convert_wrapper(text.format("none"))
 | 
						|
 | 
						|
        # Render with default=quote
 | 
						|
        do_set_realm_property(realm, "default_code_block_language", "quote", acting_user=None)
 | 
						|
        msg_without_language_default_quote = markdown_convert_wrapper(text.format(""))
 | 
						|
 | 
						|
        # Render with default=math
 | 
						|
        do_set_realm_property(realm, "default_code_block_language", "math", acting_user=None)
 | 
						|
        msg_without_language_default_math = markdown_convert_wrapper(text.format(""))
 | 
						|
 | 
						|
        # Render without default language
 | 
						|
        do_set_realm_property(realm, "default_code_block_language", "", acting_user=None)
 | 
						|
        msg_without_language_final = markdown_convert_wrapper(text.format(""))
 | 
						|
 | 
						|
        self.assertTrue(msg_with_js == msg_without_language_default_js)
 | 
						|
        self.assertTrue(
 | 
						|
            msg_with_python == msg_with_python_default_js == msg_without_language_default_py
 | 
						|
        )
 | 
						|
        self.assertTrue(msg_with_quote == msg_without_language_default_quote)
 | 
						|
        self.assertTrue(msg_with_math == msg_without_language_default_math)
 | 
						|
        self.assertTrue(msg_without_language == msg_without_language_final)
 | 
						|
        self.assertTrue(msg_with_none == msg_with_none_default_py)
 | 
						|
 | 
						|
        # Test checking inside nested quotes
 | 
						|
        nested_text = "````quote\n\n{}\n\n{}````".format(text.format("js"), text.format(""))
 | 
						|
        do_set_realm_property(realm, "default_code_block_language", "javascript", acting_user=None)
 | 
						|
        rendered = markdown_convert_wrapper(nested_text)
 | 
						|
        with_language, without_language = re.findall(r"<pre>(.*?)$", rendered, re.MULTILINE)
 | 
						|
        self.assertTrue(with_language == without_language)
 | 
						|
 | 
						|
        do_set_realm_property(realm, "default_code_block_language", "", acting_user=None)
 | 
						|
        rendered = markdown_convert_wrapper(nested_text)
 | 
						|
        with_language, without_language = re.findall(r"<pre>(.*?)$", rendered, re.MULTILINE)
 | 
						|
        self.assertFalse(with_language == without_language)
 | 
						|
 | 
						|
    def test_disabled_code_block_processor(self) -> None:
 | 
						|
        msg = (
 | 
						|
            "Hello,\n\n"
 | 
						|
            "    I am writing this message to test something. I am writing this message to test"
 | 
						|
            " something."
 | 
						|
        )
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
        expected_output = (
 | 
						|
            "<p>Hello,</p>\n"
 | 
						|
            '<div class="codehilite"><pre><span></span><code>I am writing this message to test'
 | 
						|
            " something. I am writing this message to test something.\n"
 | 
						|
            "</code></pre></div>"
 | 
						|
        )
 | 
						|
        self.assertEqual(converted, expected_output)
 | 
						|
 | 
						|
        realm = do_create_realm(
 | 
						|
            string_id="code_block_processor_test", name="code_block_processor_test"
 | 
						|
        )
 | 
						|
        maybe_update_markdown_engines(realm.id, True)
 | 
						|
        rendering_result = markdown_convert(msg, message_realm=realm, email_gateway=True)
 | 
						|
        expected_output = (
 | 
						|
            "<p>Hello,</p>\n"
 | 
						|
            "<p>I am writing this message to test something. I am writing this message to test"
 | 
						|
            " something.</p>"
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.rendered_content, expected_output)
 | 
						|
 | 
						|
 | 
						|
class MarkdownMentionTest(ZulipTestCase):
 | 
						|
    def test_mention_topic_wildcard(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        for topic_wildcard in topic_wildcards:
 | 
						|
            content = f"@**{topic_wildcard}** test"
 | 
						|
            rendering_result = render_message_markdown(msg, content)
 | 
						|
            self.assertEqual(
 | 
						|
                rendering_result.rendered_content,
 | 
						|
                f'<p><span class="topic-mention">@{topic_wildcard}</span> test</p>',
 | 
						|
            )
 | 
						|
            self.assertTrue(rendering_result.mentions_topic_wildcard)
 | 
						|
            self.assertFalse(rendering_result.mentions_stream_wildcard)
 | 
						|
 | 
						|
    def test_mention_stream_wildcard(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        for stream_wildcard in stream_wildcards:
 | 
						|
            content = f"@**{stream_wildcard}** test"
 | 
						|
            rendering_result = render_message_markdown(msg, content)
 | 
						|
            self.assertEqual(
 | 
						|
                rendering_result.rendered_content,
 | 
						|
                f'<p><span class="user-mention channel-wildcard-mention" data-user-id="*">@{stream_wildcard}</span> test</p>',
 | 
						|
            )
 | 
						|
            self.assertFalse(rendering_result.mentions_topic_wildcard)
 | 
						|
            self.assertTrue(rendering_result.mentions_stream_wildcard)
 | 
						|
 | 
						|
    def test_mention_at_topic_wildcard(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        for topic_wildcard in topic_wildcards:
 | 
						|
            content = f"@{topic_wildcard} test"
 | 
						|
            rendering_result = render_message_markdown(msg, content)
 | 
						|
            self.assertEqual(rendering_result.rendered_content, f"<p>@{topic_wildcard} test</p>")
 | 
						|
            self.assertFalse(rendering_result.mentions_topic_wildcard)
 | 
						|
            self.assertFalse(rendering_result.mentions_stream_wildcard)
 | 
						|
            self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_mention_at_stream_wildcard(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        for stream_wildcard in stream_wildcards:
 | 
						|
            content = f"@{stream_wildcard} test"
 | 
						|
            rendering_result = render_message_markdown(msg, content)
 | 
						|
            self.assertEqual(rendering_result.rendered_content, f"<p>@{stream_wildcard} test</p>")
 | 
						|
            self.assertFalse(rendering_result.mentions_topic_wildcard)
 | 
						|
            self.assertFalse(rendering_result.mentions_stream_wildcard)
 | 
						|
            self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_mention_word_starting_with_at_wildcard(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        content = "test @alleycat.com test"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(rendering_result.rendered_content, "<p>test @alleycat.com test</p>")
 | 
						|
        self.assertFalse(rendering_result.mentions_stream_wildcard)
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_mention_at_normal_user(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        content = "@aaron test"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(rendering_result.rendered_content, "<p>@aaron test</p>")
 | 
						|
        self.assertFalse(rendering_result.mentions_stream_wildcard)
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_mention_single(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
        user_id = user_profile.id
 | 
						|
 | 
						|
        content = "@**King Hamlet**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            f'<p><span class="user-mention" data-user-id="{user_id}">@King Hamlet</span></p>',
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {user_profile.id})
 | 
						|
 | 
						|
        content = f"@**|{user_id}**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            f'<p><span class="user-mention" data-user-id="{user_id}">@King Hamlet</span></p>',
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {user_profile.id})
 | 
						|
 | 
						|
    def test_mention_with_valid_special_characters_before(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        user_id = user_profile.id
 | 
						|
 | 
						|
        valid_characters_before_mention = ["(", "{", "[", "/", "<"]
 | 
						|
        for character in valid_characters_before_mention:
 | 
						|
            content = f"{character}@**King Hamlet**"
 | 
						|
            rendering_result = render_message_markdown(msg, content)
 | 
						|
            self.assertEqual(
 | 
						|
                rendering_result.rendered_content,
 | 
						|
                f'<p>{escape(character)}<span class="user-mention" '
 | 
						|
                f'data-user-id="{user_id}">'
 | 
						|
                "@King Hamlet</span></p>",
 | 
						|
            )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {user_profile.id})
 | 
						|
 | 
						|
    def test_mention_with_invalid_special_characters_before(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
 | 
						|
        invalid_characters_before_mention = [".", ",", ";", ":", "#"]
 | 
						|
        for character in invalid_characters_before_mention:
 | 
						|
            content = f"{character}@**King Hamlet**"
 | 
						|
            rendering_result = render_message_markdown(msg, content)
 | 
						|
            unicode_character = escape(character)
 | 
						|
            self.assertEqual(
 | 
						|
                rendering_result.rendered_content,
 | 
						|
                f"<p>{unicode_character}@<strong>King Hamlet</strong></p>",
 | 
						|
            )
 | 
						|
 | 
						|
    def test_mention_silent(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        user_id = user_profile.id
 | 
						|
 | 
						|
        content = "@_**King Hamlet**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            f'<p><span class="user-mention silent" data-user-id="{user_id}">King Hamlet</span></p>',
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_mention_deactivated_users(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        change_user_is_active(user_profile, False)
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        user_id = user_profile.id
 | 
						|
 | 
						|
        content = "@**King Hamlet**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            f'<p><span class="user-mention silent" data-user-id="{user_id}">King Hamlet</span></p>',
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_mention_silent_deactivated_users(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        change_user_is_active(user_profile, False)
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        user_id = user_profile.id
 | 
						|
 | 
						|
        content = "@_**King Hamlet**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            f'<p><span class="user-mention silent" data-user-id="{user_id}">King Hamlet</span></p>',
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_mention_inaccessible_users(self) -> None:
 | 
						|
        self.set_up_db_for_testing_user_access()
 | 
						|
        polonius = self.example_user("polonius")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        msg = Message(
 | 
						|
            sender=polonius,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=polonius.realm,
 | 
						|
        )
 | 
						|
        content = "@**Othello, the Moor of Venice** @**King Hamlet** test message"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            '<p>@<strong>Othello, the Moor of Venice</strong> <span class="user-mention" '
 | 
						|
            f'data-user-id="{hamlet.id}">'
 | 
						|
            "@King Hamlet</span> test message</p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {hamlet.id})
 | 
						|
 | 
						|
        content = "@_**Othello, the Moor of Venice** @_**King Hamlet** test message"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            '<p>@_<strong>Othello, the Moor of Venice</strong> <span class="user-mention silent" '
 | 
						|
            f'data-user-id="{hamlet.id}">'
 | 
						|
            "King Hamlet</span> test message</p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_silent_stream_wildcard_mention(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        for wildcard in stream_wildcards:
 | 
						|
            content = f"@_**{wildcard}**"
 | 
						|
            rendering_result = render_message_markdown(msg, content)
 | 
						|
            self.assertEqual(
 | 
						|
                rendering_result.rendered_content,
 | 
						|
                f'<p><span class="user-mention channel-wildcard-mention silent" data-user-id="*">{wildcard}</span></p>',
 | 
						|
            )
 | 
						|
            self.assertFalse(rendering_result.mentions_stream_wildcard)
 | 
						|
 | 
						|
    def test_silent_topic_wildcard_mention(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        for wildcard in topic_wildcards:
 | 
						|
            content = f"@_**{wildcard}**"
 | 
						|
            rendering_result = render_message_markdown(msg, content)
 | 
						|
            self.assertEqual(
 | 
						|
                rendering_result.rendered_content,
 | 
						|
                f'<p><span class="topic-mention silent">{wildcard}</span></p>',
 | 
						|
            )
 | 
						|
            self.assertFalse(rendering_result.mentions_topic_wildcard)
 | 
						|
 | 
						|
    def test_mention_invalid_followed_by_valid(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        user_id = user_profile.id
 | 
						|
 | 
						|
        content = "@**Invalid user** and @**King Hamlet**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            '<p>@<strong>Invalid user</strong> and <span class="user-mention" '
 | 
						|
            f'data-user-id="{user_id}">'
 | 
						|
            "@King Hamlet</span></p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {user_profile.id})
 | 
						|
 | 
						|
    def test_invalid_mention_not_uses_valid_mention_data(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
 | 
						|
        # Even though King Hamlet will be present in mention data as
 | 
						|
        # it was fetched for first mention but second mention is
 | 
						|
        # incorrect(as it uses hamlet's id) so it should not be able
 | 
						|
        # to use that data for creating a valid mention.
 | 
						|
 | 
						|
        content = f"@**King Hamlet|{hamlet.id}** and @**aaron|{hamlet.id}**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            f'<p><span class="user-mention" data-user-id="{hamlet.id}">'
 | 
						|
            f"@King Hamlet</span> and @<strong>aaron|{hamlet.id}</strong></p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {hamlet.id})
 | 
						|
 | 
						|
    def test_silent_mention_invalid_followed_by_valid(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        user_id = user_profile.id
 | 
						|
 | 
						|
        content = "@_**Invalid user** and @_**King Hamlet**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            '<p>@_<strong>Invalid user</strong> and <span class="user-mention silent" '
 | 
						|
            f'data-user-id="{user_id}">'
 | 
						|
            "King Hamlet</span></p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
        content = f"@_**|123456789** and @_**|{user_id}**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>@_<strong>|123456789</strong> and "
 | 
						|
            '<span class="user-mention silent" '
 | 
						|
            f'data-user-id="{user_id}">'
 | 
						|
            "King Hamlet</span></p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_possible_mentions(self) -> None:
 | 
						|
        def assert_mentions(
 | 
						|
            content: str,
 | 
						|
            names: set[str],
 | 
						|
            has_topic_wildcards: bool = False,
 | 
						|
            has_stream_wildcards: bool = False,
 | 
						|
        ) -> None:
 | 
						|
            self.assertEqual(
 | 
						|
                possible_mentions(content),
 | 
						|
                PossibleMentions(
 | 
						|
                    mention_texts=names,
 | 
						|
                    message_has_topic_wildcards=has_topic_wildcards,
 | 
						|
                    message_has_stream_wildcards=has_stream_wildcards,
 | 
						|
                ),
 | 
						|
            )
 | 
						|
 | 
						|
        aaron = self.example_user("aaron")
 | 
						|
 | 
						|
        assert_mentions("", set())
 | 
						|
        assert_mentions("boring", set())
 | 
						|
        assert_mentions("@**topic**", set(), True)
 | 
						|
        assert_mentions("@**all**", set(), False, True)
 | 
						|
        assert_mentions("smush@**steve**smush", set())
 | 
						|
 | 
						|
        assert_mentions(
 | 
						|
            f"Hello @**King Hamlet**, @**|{aaron.id}** and @**Cordelia, Lear's daughter**\n@**Foo van Barson|1234** @**all**",
 | 
						|
            {"King Hamlet", f"|{aaron.id}", "Cordelia, Lear's daughter", "Foo van Barson|1234"},
 | 
						|
            False,
 | 
						|
            True,
 | 
						|
        )
 | 
						|
 | 
						|
    def test_mention_multiple(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
 | 
						|
        content = "@**King Hamlet** and @**Cordelia, Lear's daughter**, check this out"
 | 
						|
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>"
 | 
						|
            '<span class="user-mention" '
 | 
						|
            f'data-user-id="{hamlet.id}">@King Hamlet</span> and '
 | 
						|
            '<span class="user-mention" '
 | 
						|
            f'data-user-id="{cordelia.id}">@Cordelia, Lear\'s daughter</span>, '
 | 
						|
            "check this out</p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {hamlet.id, cordelia.id})
 | 
						|
 | 
						|
    def test_mention_in_quotes(self) -> None:
 | 
						|
        othello = self.example_user("othello")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        msg = Message(sender=othello, sending_client=get_client("test"), realm=othello.realm)
 | 
						|
 | 
						|
        content = "> @**King Hamlet** and @**Othello, the Moor of Venice**\n\n @**King Hamlet** and @**Cordelia, Lear's daughter**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<blockquote>\n<p>"
 | 
						|
            f'<span class="user-mention silent" data-user-id="{hamlet.id}">King Hamlet</span>'
 | 
						|
            " and "
 | 
						|
            f'<span class="user-mention silent" data-user-id="{othello.id}">Othello, the Moor of Venice</span>'
 | 
						|
            "</p>\n</blockquote>\n"
 | 
						|
            "<p>"
 | 
						|
            f'<span class="user-mention" data-user-id="{hamlet.id}">@King Hamlet</span>'
 | 
						|
            " and "
 | 
						|
            f'<span class="user-mention" data-user-id="{cordelia.id}">@Cordelia, Lear\'s daughter</span>'
 | 
						|
            "</p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {hamlet.id, cordelia.id})
 | 
						|
 | 
						|
        # Both fenced quote and > quote should be identical for both silent and regular syntax.
 | 
						|
        expected = (
 | 
						|
            "<blockquote>\n<p>"
 | 
						|
            f'<span class="user-mention silent" data-user-id="{hamlet.id}">King Hamlet</span>'
 | 
						|
            "</p>\n</blockquote>"
 | 
						|
        )
 | 
						|
        content = "```quote\n@**King Hamlet**\n```"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(rendering_result.rendered_content, expected)
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
        content = "> @**King Hamlet**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(rendering_result.rendered_content, expected)
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
        content = "```quote\n@_**King Hamlet**\n```"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(rendering_result.rendered_content, expected)
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
        content = "> @_**King Hamlet**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(rendering_result.rendered_content, expected)
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_stream_wildcard_mention_in_quotes(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        message = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        def assert_silent_mention(content: str, wildcard: str) -> None:
 | 
						|
            expected = (
 | 
						|
                "<blockquote>\n<p>"
 | 
						|
                f'<span class="user-mention channel-wildcard-mention silent" data-user-id="*">{wildcard}</span>'
 | 
						|
                "</p>\n</blockquote>"
 | 
						|
            )
 | 
						|
            rendering_result = render_message_markdown(message, content)
 | 
						|
            self.assertEqual(rendering_result.rendered_content, expected)
 | 
						|
            self.assertFalse(rendering_result.mentions_stream_wildcard)
 | 
						|
            self.assertFalse(rendering_result.mentions_topic_wildcard)
 | 
						|
 | 
						|
        for wildcard in stream_wildcards:
 | 
						|
            assert_silent_mention(f"> @**{wildcard}**", wildcard)
 | 
						|
            assert_silent_mention(f"> @_**{wildcard}**", wildcard)
 | 
						|
            assert_silent_mention(f"```quote\n@**{wildcard}**\n```", wildcard)
 | 
						|
            assert_silent_mention(f"```quote\n@_**{wildcard}**\n```", wildcard)
 | 
						|
 | 
						|
    def test_topic_wildcard_mention_in_quotes(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        message = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
 | 
						|
        def assert_silent_mention(content: str, wildcard: str) -> None:
 | 
						|
            expected = (
 | 
						|
                "<blockquote>\n<p>"
 | 
						|
                f'<span class="topic-mention silent">{wildcard}</span>'
 | 
						|
                "</p>\n</blockquote>"
 | 
						|
            )
 | 
						|
            rendering_result = render_message_markdown(message, content)
 | 
						|
            self.assertEqual(rendering_result.rendered_content, expected)
 | 
						|
            self.assertFalse(rendering_result.mentions_stream_wildcard)
 | 
						|
            self.assertFalse(rendering_result.mentions_topic_wildcard)
 | 
						|
 | 
						|
        for wildcard in topic_wildcards:
 | 
						|
            assert_silent_mention(f"> @**{wildcard}**", wildcard)
 | 
						|
            assert_silent_mention(f"> @_**{wildcard}**", wildcard)
 | 
						|
            assert_silent_mention(f"```quote\n@**{wildcard}**\n```", wildcard)
 | 
						|
            assert_silent_mention(f"```quote\n@_**{wildcard}**\n```", wildcard)
 | 
						|
 | 
						|
    def test_mention_duplicate_full_name(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
 | 
						|
        def make_user(email: str, full_name: str) -> UserProfile:
 | 
						|
            return create_user(
 | 
						|
                email=email,
 | 
						|
                password="whatever",
 | 
						|
                realm=realm,
 | 
						|
                full_name=full_name,
 | 
						|
            )
 | 
						|
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        twin1 = make_user("twin1@example.com", "Mark Twin")
 | 
						|
        twin2 = make_user("twin2@example.com", "Mark Twin")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
 | 
						|
 | 
						|
        content = f"@**Mark Twin|{twin1.id}**, @**Mark Twin|{twin2.id}** and @**Cordelia, Lear's daughter**, hi."
 | 
						|
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>"
 | 
						|
            '<span class="user-mention" '
 | 
						|
            f'data-user-id="{twin1.id}">@Mark Twin</span>, '
 | 
						|
            '<span class="user-mention" '
 | 
						|
            f'data-user-id="{twin2.id}">@Mark Twin</span> and '
 | 
						|
            '<span class="user-mention" '
 | 
						|
            f'data-user-id="{cordelia.id}">@Cordelia, Lear\'s daughter</span>, '
 | 
						|
            "hi.</p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {twin1.id, twin2.id, cordelia.id})
 | 
						|
 | 
						|
    def test_mention_invalid(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
 | 
						|
        content = "Hey @**Nonexistent User**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content, "<p>Hey @<strong>Nonexistent User</strong></p>"
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_user_mention_atomic_string(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
 | 
						|
        # Create a linkifier.
 | 
						|
        url_template = r"https://trac.example.com/ticket/{id}"
 | 
						|
        linkifier = RealmFilter(
 | 
						|
            realm=realm, pattern=r"#(?P<id>[0-9]{2,8})", url_template=url_template
 | 
						|
        )
 | 
						|
        linkifier.save()
 | 
						|
        self.assertEqual(
 | 
						|
            repr(linkifier),
 | 
						|
            "<RealmFilter: zulip: #(?P<id>[0-9]{2,8}) https://trac.example.com/ticket/{id}>",
 | 
						|
        )
 | 
						|
        # Create a user that potentially interferes with the pattern.
 | 
						|
        test_user = create_user(
 | 
						|
            email="atomic@example.com",
 | 
						|
            password="whatever",
 | 
						|
            realm=realm,
 | 
						|
            full_name="Atomic #123",
 | 
						|
        )
 | 
						|
        content = "@**Atomic #123**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            f'<p><span class="user-mention" data-user-id="{test_user.id}">@Atomic #123</span></p>',
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {test_user.id})
 | 
						|
        content = "@_**Atomic #123**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            '<p><span class="user-mention silent" '
 | 
						|
            f'data-user-id="{test_user.id}">'
 | 
						|
            "Atomic #123</span></p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def create_user_group_for_test(self, user_group_name: str) -> NamedUserGroup:
 | 
						|
        othello = self.example_user("othello")
 | 
						|
        return check_add_user_group(
 | 
						|
            get_realm("zulip"), user_group_name, [othello], acting_user=othello
 | 
						|
        )
 | 
						|
 | 
						|
    def test_user_group_mention_single(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        user_id = user_profile.id
 | 
						|
        user_group = self.create_user_group_for_test("support")
 | 
						|
 | 
						|
        content = "@**King Hamlet** @*support*"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            '<p><span class="user-mention" '
 | 
						|
            f'data-user-id="{user_id}">'
 | 
						|
            "@King Hamlet</span> "
 | 
						|
            '<span class="user-group-mention" '
 | 
						|
            f'data-user-group-id="{user_group.id}">'
 | 
						|
            "@support</span></p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {user_profile.id})
 | 
						|
        self.assertEqual(rendering_result.mentions_user_group_ids, {user_group.id})
 | 
						|
 | 
						|
    def test_invalid_user_group_followed_by_valid_mention_single(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        user_id = user_profile.id
 | 
						|
        user_group = self.create_user_group_for_test("support")
 | 
						|
 | 
						|
        content = "@**King Hamlet** @*Invalid user group* @*support*"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            '<p><span class="user-mention" '
 | 
						|
            f'data-user-id="{user_id}">'
 | 
						|
            "@King Hamlet</span> "
 | 
						|
            "@<em>Invalid user group</em> "
 | 
						|
            '<span class="user-group-mention" '
 | 
						|
            f'data-user-group-id="{user_group.id}">'
 | 
						|
            "@support</span></p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {user_profile.id})
 | 
						|
        self.assertEqual(rendering_result.mentions_user_group_ids, {user_group.id})
 | 
						|
 | 
						|
    def test_user_group_mention_atomic_string(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
 | 
						|
        user_profile = self.example_user("hamlet")
 | 
						|
        # Create a linkifier.
 | 
						|
        url_template = r"https://trac.example.com/ticket/{id}"
 | 
						|
        linkifier = RealmFilter(
 | 
						|
            realm=realm, pattern=r"#(?P<id>[0-9]{2,8})", url_template=url_template
 | 
						|
        )
 | 
						|
        linkifier.save()
 | 
						|
        self.assertEqual(
 | 
						|
            repr(linkifier),
 | 
						|
            "<RealmFilter: zulip: #(?P<id>[0-9]{2,8}) https://trac.example.com/ticket/{id}>",
 | 
						|
        )
 | 
						|
        # Create a user-group that potentially interferes with the pattern.
 | 
						|
        user_id = user_profile.id
 | 
						|
        user_group = self.create_user_group_for_test("support #123")
 | 
						|
 | 
						|
        content = "@**King Hamlet** @*support #123*"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            '<p><span class="user-mention" '
 | 
						|
            f'data-user-id="{user_id}">'
 | 
						|
            "@King Hamlet</span> "
 | 
						|
            '<span class="user-group-mention" '
 | 
						|
            f'data-user-group-id="{user_group.id}">'
 | 
						|
            "@support #123</span></p>",
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, {user_profile.id})
 | 
						|
        self.assertEqual(rendering_result.mentions_user_group_ids, {user_group.id})
 | 
						|
 | 
						|
    def test_possible_user_group_mentions(self) -> None:
 | 
						|
        def assert_mentions(content: str, names: dict[str, str]) -> None:
 | 
						|
            self.assertEqual(possible_user_group_mentions(content), names)
 | 
						|
 | 
						|
        assert_mentions("", dict())
 | 
						|
        assert_mentions("boring", dict())
 | 
						|
        assert_mentions("@**all**", dict())
 | 
						|
        assert_mentions("smush@*steve*smush", dict())
 | 
						|
 | 
						|
        # capture only the group mention among other mentions.
 | 
						|
        assert_mentions(
 | 
						|
            "@*support* Hello @**King Hamlet** and @**Cordelia, Lear's daughter**\n"
 | 
						|
            "@**Foo van Barson** @**all**",
 | 
						|
            {"support": "non-silent"},
 | 
						|
        )
 | 
						|
        # non-silent mentions
 | 
						|
        assert_mentions(
 | 
						|
            "Attention @*support*, @*frontend* and @*backend*\ngroups.",
 | 
						|
            {"support": "non-silent", "frontend": "non-silent", "backend": "non-silent"},
 | 
						|
        )
 | 
						|
        # silent mentions
 | 
						|
        assert_mentions(
 | 
						|
            "I prefer @_*backend* over @_*frontend*,",
 | 
						|
            {"backend": "silent", "frontend": "silent"},
 | 
						|
        )
 | 
						|
        # silent and non-silent
 | 
						|
        assert_mentions(
 | 
						|
            "Attention @*frontend* please refer to @_*support*,",
 | 
						|
            {"frontend": "non-silent", "support": "silent"},
 | 
						|
        )
 | 
						|
 | 
						|
        # Make sure regular (non-silent) mention always takes precedence,
 | 
						|
        # whether it precedes or follows a silent mention for the SAME group.
 | 
						|
 | 
						|
        # non-silent before silent.
 | 
						|
        assert_mentions(
 | 
						|
            "@*help* the building is on fire!, folks should refer to @_*help*",
 | 
						|
            {"help": "non-silent"},
 | 
						|
        )
 | 
						|
 | 
						|
        # non-silent after silent.
 | 
						|
        assert_mentions(
 | 
						|
            "folks should refer to @_*help*, @*help* the building is on fire! ",
 | 
						|
            {"help": "non-silent"},
 | 
						|
        )
 | 
						|
 | 
						|
    def test_user_group_mention_multiple(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        support = self.create_user_group_for_test("support")
 | 
						|
        backend = self.create_user_group_for_test("backend")
 | 
						|
 | 
						|
        content = "@*support* and @*backend*, check this out"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>"
 | 
						|
            '<span class="user-group-mention" '
 | 
						|
            f'data-user-group-id="{support.id}">'
 | 
						|
            "@support</span> "
 | 
						|
            "and "
 | 
						|
            '<span class="user-group-mention" '
 | 
						|
            f'data-user-group-id="{backend.id}">'
 | 
						|
            "@backend</span>, "
 | 
						|
            "check this out"
 | 
						|
            "</p>",
 | 
						|
        )
 | 
						|
 | 
						|
        self.assertEqual(rendering_result.mentions_user_group_ids, {support.id, backend.id})
 | 
						|
 | 
						|
    def test_user_group_mention_edit(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("hamlet")
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        self.create_user_group_for_test("support")
 | 
						|
        self.login("hamlet")
 | 
						|
 | 
						|
        msg_id = self.send_stream_message(
 | 
						|
            sender_user_profile, "Denmark", topic_name="editing", content="test"
 | 
						|
        )
 | 
						|
 | 
						|
        def update_message_and_check_flag(content: str, mentioned: bool) -> None:
 | 
						|
            result = self.client_patch(
 | 
						|
                "/json/messages/" + str(msg_id),
 | 
						|
                {
 | 
						|
                    "content": content,
 | 
						|
                },
 | 
						|
            )
 | 
						|
            self.assert_json_success(result)
 | 
						|
            um = UserMessage.objects.get(
 | 
						|
                user_profile_id=user_profile.id,
 | 
						|
                message_id=msg_id,
 | 
						|
            )
 | 
						|
            if mentioned:
 | 
						|
                self.assertIn("mentioned", um.flags_list())
 | 
						|
            else:
 | 
						|
                self.assertNotIn("mentioned", um.flags_list())
 | 
						|
 | 
						|
        update_message_and_check_flag("@*support*", True)
 | 
						|
        update_message_and_check_flag("@*support-invalid* edited", False)
 | 
						|
        update_message_and_check_flag("@*support* edited", True)
 | 
						|
        update_message_and_check_flag("edited", False)
 | 
						|
        update_message_and_check_flag("@*support*", True)
 | 
						|
        update_message_and_check_flag("@_*support*", False)
 | 
						|
        update_message_and_check_flag("@_*role:administrators*", False)
 | 
						|
 | 
						|
    def test_user_group_mention_invalid(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
 | 
						|
        content = "Hey @*Nonexistent group*"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content, "<p>Hey @<em>Nonexistent group</em></p>"
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_group_ids, set())
 | 
						|
 | 
						|
    def test_user_group_silent_mention(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        support = self.create_user_group_for_test("support")
 | 
						|
 | 
						|
        content = "We'll add you to @_*support* user group."
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>We'll add you to "
 | 
						|
            f'<span class="user-group-mention silent" data-user-group-id="{support.id}">support</span>'
 | 
						|
            " user group.</p>",
 | 
						|
        )
 | 
						|
 | 
						|
        self.assertEqual(rendering_result.mentions_user_group_ids, set())
 | 
						|
 | 
						|
        admins_group = NamedUserGroup.objects.get(
 | 
						|
            name=SystemGroups.ADMINISTRATORS,
 | 
						|
            realm_for_sharding=sender_user_profile.realm,
 | 
						|
            is_system_group=True,
 | 
						|
        )
 | 
						|
        content = "Please contact @_*role:administrators*"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>Please contact "
 | 
						|
            f'<span class="user-group-mention silent" data-user-group-id="{admins_group.id}">Administrators</span></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_deactivated_user_group_mention(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        support = self.create_user_group_for_test("support")
 | 
						|
        do_deactivate_user_group(support, acting_user=None)
 | 
						|
 | 
						|
        content = "We'll add you to @*support* user group."
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content,
 | 
						|
            "<p>We'll add you to "
 | 
						|
            f'<span class="user-group-mention silent" data-user-group-id="{support.id}">support</span>'
 | 
						|
            " user group.</p>",
 | 
						|
        )
 | 
						|
 | 
						|
        self.assertEqual(rendering_result.mentions_user_group_ids, set())
 | 
						|
 | 
						|
    def test_user_group_mention_in_quotes(self) -> None:
 | 
						|
        user_profile = self.example_user("othello")
 | 
						|
        message = Message(
 | 
						|
            sender=user_profile, sending_client=get_client("test"), realm=user_profile.realm
 | 
						|
        )
 | 
						|
        backend = self.create_user_group_for_test("backend")
 | 
						|
 | 
						|
        def assert_silent_mention(content: str) -> None:
 | 
						|
            expected = (
 | 
						|
                "<blockquote>\n<p>"
 | 
						|
                f'<span class="user-group-mention silent" data-user-group-id="{backend.id}">backend</span>'
 | 
						|
                "</p>\n</blockquote>"
 | 
						|
            )
 | 
						|
            rendering_result = render_message_markdown(message, content)
 | 
						|
            self.assertEqual(rendering_result.rendered_content, expected)
 | 
						|
            self.assertEqual(rendering_result.mentions_user_group_ids, set())
 | 
						|
 | 
						|
        assert_silent_mention("> @*backend*")
 | 
						|
        assert_silent_mention("> @_*backend*")
 | 
						|
        assert_silent_mention("```quote\n@*backend*\n```")
 | 
						|
        assert_silent_mention("```quote\n@_*backend*\n```")
 | 
						|
 | 
						|
 | 
						|
class MarkdownStreamTopicMentionTests(ZulipTestCase):
 | 
						|
    def test_stream_single(self) -> None:
 | 
						|
        denmark = get_stream("Denmark", get_realm("zulip"))
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        content = "#**Denmark**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark">#{denmark.name}</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_invalid_stream_followed_by_valid_mention(self) -> None:
 | 
						|
        denmark = get_stream("Denmark", get_realm("zulip"))
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        content = "#**Invalid** and #**Denmark**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p>#<strong>Invalid</strong> and <a class="stream" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark">#{denmark.name}</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_stream_multiple(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        denmark = get_stream("Denmark", realm)
 | 
						|
        scotland = get_stream("Scotland", realm)
 | 
						|
        content = "Look to #**Denmark** and #**Scotland**, there something"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            "<p>Look to "
 | 
						|
            '<a class="stream" '
 | 
						|
            f'data-stream-id="{denmark.id}" '
 | 
						|
            f'href="/#narrow/channel/{denmark.id}-Denmark">#{denmark.name}</a> and '
 | 
						|
            '<a class="stream" '
 | 
						|
            f'data-stream-id="{scotland.id}" '
 | 
						|
            f'href="/#narrow/channel/{scotland.id}-Scotland">#{scotland.name}</a>, '
 | 
						|
            "there something</p>",
 | 
						|
        )
 | 
						|
 | 
						|
    def test_stream_case_sensitivity(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        case_sens = self.make_stream(stream_name="CaseSens", realm=realm)
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
 | 
						|
        content = "#**CaseSens**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream" data-stream-id="{case_sens.id}" href="/#narrow/channel/{case_sens.id}-{case_sens.name}">#{case_sens.name}</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_stream_case_sensitivity_nonmatching(self) -> None:
 | 
						|
        """#StreamName requires the stream be spelled with the correct case
 | 
						|
        currently.  If we change that in the future, we'll need to change this
 | 
						|
        test."""
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        self.make_stream(stream_name="CaseSens", realm=realm)
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
 | 
						|
        content = "#**casesens**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            "<p>#<strong>casesens</strong></p>",
 | 
						|
        )
 | 
						|
 | 
						|
    def test_topic_single_containing_no_message(self) -> None:
 | 
						|
        denmark = get_stream("Denmark", get_realm("zulip"))
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        content = "#**Denmark>some topic**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark/topic/some.20topic">#{denmark.name} > some topic</a></p>',
 | 
						|
        )
 | 
						|
        # Empty string as topic name.
 | 
						|
        content = "#**Denmark>**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark/topic/">#{denmark.name} > <em>{Message.EMPTY_TOPIC_FALLBACK_NAME}</em></a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_topic_single_containing_messages(self) -> None:
 | 
						|
        denmark = get_stream("Denmark", get_realm("zulip"))
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        self.send_stream_message(
 | 
						|
            sender_user_profile, "Denmark", topic_name="some topic", content="test"
 | 
						|
        )
 | 
						|
        latest_message_id = self.send_stream_message(
 | 
						|
            sender_user_profile, "Denmark", topic_name="some topic", content="test 2"
 | 
						|
        )
 | 
						|
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        content = "#**Denmark>some topic**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark/topic/some.20topic/with/{latest_message_id}">#{denmark.name} > some topic</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_topic_atomic_string(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        # Create a linkifier.
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        url_template = r"https://trac.example.com/ticket/{id}"
 | 
						|
        linkifier = RealmFilter(
 | 
						|
            realm=realm, pattern=r"#(?P<id>[0-9]{2,8})", url_template=url_template
 | 
						|
        )
 | 
						|
        linkifier.save()
 | 
						|
        self.assertEqual(
 | 
						|
            repr(linkifier),
 | 
						|
            "<RealmFilter: zulip: #(?P<id>[0-9]{2,8}) https://trac.example.com/ticket/{id}>",
 | 
						|
        )
 | 
						|
        # Create a topic link that potentially interferes with the pattern.
 | 
						|
        denmark = get_stream("Denmark", realm)
 | 
						|
        first_message_id = self.send_stream_message(
 | 
						|
            sender_user_profile, "Denmark", topic_name="#1234", content="test"
 | 
						|
        )
 | 
						|
        msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
 | 
						|
        content = "#**Denmark>#1234**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark/topic/.231234/with/{first_message_id}">#{denmark.name} > #1234</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_topic_multiple(self) -> None:
 | 
						|
        denmark = get_stream("Denmark", get_realm("zulip"))
 | 
						|
        scotland = get_stream("Scotland", get_realm("zulip"))
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        self.send_stream_message(
 | 
						|
            sender_user_profile, "Denmark", topic_name="some topic", content="test"
 | 
						|
        )
 | 
						|
        latest_message_id = self.send_stream_message(
 | 
						|
            sender_user_profile, "Denmark", topic_name="some topic", content="test 2"
 | 
						|
        )
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        content = "This has two links: #**Denmark>some topic** and #**Scotland>other topic**."
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            "<p>This has two links: "
 | 
						|
            f'<a class="stream-topic" data-stream-id="{denmark.id}" '
 | 
						|
            f'href="/#narrow/channel/{denmark.id}-{denmark.name}/topic/some.20topic/with/{latest_message_id}">'
 | 
						|
            f"#{denmark.name} > some topic</a>"
 | 
						|
            " and "
 | 
						|
            f'<a class="stream-topic" data-stream-id="{scotland.id}" '
 | 
						|
            f'href="/#narrow/channel/{scotland.id}-{scotland.name}/topic/other.20topic">'
 | 
						|
            f"#{scotland.name} > other topic</a>"
 | 
						|
            ".</p>",
 | 
						|
        )
 | 
						|
 | 
						|
    def test_topic_permalink(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        denmark = get_stream("Denmark", get_realm("zulip"))
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        self.send_stream_message(
 | 
						|
            sender_user_profile, "Denmark", topic_name="some topic", content="test"
 | 
						|
        )
 | 
						|
        latest_message_id = self.send_stream_message(
 | 
						|
            sender_user_profile, "Denmark", topic_name="some topic", content="test 2"
 | 
						|
        )
 | 
						|
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
 | 
						|
        # test caching of topic data for user.
 | 
						|
        content = "#**Denmark>some topic**"
 | 
						|
        mention_backend = MentionBackend(realm.id)
 | 
						|
        mention_data = MentionData(mention_backend, content, message_sender=None)
 | 
						|
        render_message_markdown(msg, content, mention_data=mention_data)
 | 
						|
 | 
						|
        with (
 | 
						|
            self.assert_database_query_count(1, keep_cache_warm=True),
 | 
						|
            self.assert_memcached_count(0),
 | 
						|
        ):
 | 
						|
            self.assertEqual(
 | 
						|
                render_message_markdown(msg, content, mention_data=mention_data).rendered_content,
 | 
						|
                f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark/topic/some.20topic/with/{latest_message_id}">#{denmark.name} > some topic</a></p>',
 | 
						|
            )
 | 
						|
 | 
						|
        # test topic linked doesn't have any message in it in case
 | 
						|
        # the topic mentioned doesn't have any messages.
 | 
						|
        content = "#**Denmark>random topic**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark/topic/random.20topic">#{denmark.name} > random topic</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        # Test when trying to render a topic link of a channel with shared
 | 
						|
        # history, if message_sender is None, topic link is permalink.
 | 
						|
        content = "#**Denmark>some topic**"
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert_wrapper(content),
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark/topic/some.20topic/with/{latest_message_id}">#{denmark.name} > some topic</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        # test topic links for channel with protected history
 | 
						|
        core_stream = self.make_stream("core", realm, True, history_public_to_subscribers=False)
 | 
						|
        iago = self.example_user("iago")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
 | 
						|
        self.subscribe(iago, "core")
 | 
						|
        msg_id = self.send_stream_message(iago, "core", topic_name="testing")
 | 
						|
 | 
						|
        msg = Message(
 | 
						|
            sender=iago,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=realm,
 | 
						|
        )
 | 
						|
        content = "#**core>testing**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{core_stream.id}" href="/#narrow/channel/{core_stream.id}-core/topic/testing/with/{msg_id}">#{core_stream.name} > testing</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        # Test permalinks generated when a user with no access to the channel
 | 
						|
        # sends a topic link on behalf of a user who has access to the channel.
 | 
						|
        notification_bot = get_system_bot(settings.NOTIFICATION_BOT, iago.realm_id)
 | 
						|
 | 
						|
        msg = Message(
 | 
						|
            sender=notification_bot,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=realm,
 | 
						|
        )
 | 
						|
        content = "#**core>testing**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content, acting_user=iago).rendered_content,
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{core_stream.id}" href="/#narrow/channel/{core_stream.id}-core/topic/testing/with/{msg_id}">#{core_stream.name} > testing</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        # Test newly subscribed user to a channel with protected history
 | 
						|
        # won't have accessed to this message, and hence, the topic
 | 
						|
        # link would not be a permalink.
 | 
						|
        self.subscribe(hamlet, "core")
 | 
						|
 | 
						|
        msg = Message(
 | 
						|
            sender=hamlet,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=realm,
 | 
						|
        )
 | 
						|
        content = "#**core>testing**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{core_stream.id}" href="/#narrow/channel/{core_stream.id}-core/topic/testing">#{core_stream.name} > testing</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        # Test permalinks would not be generated when a user with no access to the
 | 
						|
        # channel sends a topic link on behalf of another user who has no access to
 | 
						|
        # the channel.
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
 | 
						|
        msg = Message(
 | 
						|
            sender=notification_bot,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=realm,
 | 
						|
        )
 | 
						|
        content = "#**core>testing**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content, acting_user=cordelia).rendered_content,
 | 
						|
            "<p>#<strong>core>testing</strong></p>",
 | 
						|
        )
 | 
						|
 | 
						|
        # Test when trying to render a topic link of a channel with protected
 | 
						|
        # history, if message_sender is None, topic link is not permalink.
 | 
						|
        content = "#**core>testing**"
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert_wrapper(content),
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{core_stream.id}" href="/#narrow/channel/{core_stream.id}-core/topic/testing">#{core_stream.name} > testing</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        private_stream = self.make_stream("private", realm, invite_only=True)
 | 
						|
        content = "#**private>testing**"
 | 
						|
        self.assertEqual(
 | 
						|
            markdown_convert_wrapper(content),
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{private_stream.id}" href="/#narrow/channel/{private_stream.id}-private/topic/testing">#{private_stream.name} > testing</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
        # Permalink would not be generated if a user has metadata
 | 
						|
        # access to a stream but not content access.
 | 
						|
        cordelia_group_member_dict = UserGroupMembersData(
 | 
						|
            direct_members=[cordelia.id], direct_subgroups=[]
 | 
						|
        )
 | 
						|
        do_change_stream_group_based_setting(
 | 
						|
            private_stream,
 | 
						|
            "can_administer_channel_group",
 | 
						|
            cordelia_group_member_dict,
 | 
						|
            acting_user=cordelia,
 | 
						|
        )
 | 
						|
        user_group_membership_details = UserGroupMembershipDetails(user_recursive_group_ids=None)
 | 
						|
        self.assertTrue(
 | 
						|
            user_has_metadata_access(
 | 
						|
                cordelia, private_stream, user_group_membership_details, is_subscribed=False
 | 
						|
            )
 | 
						|
        )
 | 
						|
        self.assertFalse(
 | 
						|
            user_has_content_access(
 | 
						|
                cordelia, private_stream, user_group_membership_details, is_subscribed=False
 | 
						|
            )
 | 
						|
        )
 | 
						|
        msg = Message(
 | 
						|
            sender=cordelia,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=realm,
 | 
						|
        )
 | 
						|
        content = "#**private>testing**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content, acting_user=cordelia).rendered_content,
 | 
						|
            "<p>#<strong>private>testing</strong></p>",
 | 
						|
        )
 | 
						|
 | 
						|
        # User has content access now, so permalink would be generated.
 | 
						|
        do_change_stream_group_based_setting(
 | 
						|
            private_stream,
 | 
						|
            "can_add_subscribers_group",
 | 
						|
            cordelia_group_member_dict,
 | 
						|
            acting_user=cordelia,
 | 
						|
        )
 | 
						|
        user_group_membership_details = UserGroupMembershipDetails(user_recursive_group_ids=None)
 | 
						|
        self.assertTrue(
 | 
						|
            user_has_metadata_access(
 | 
						|
                cordelia, private_stream, user_group_membership_details, is_subscribed=False
 | 
						|
            )
 | 
						|
        )
 | 
						|
        self.assertTrue(
 | 
						|
            user_has_content_access(
 | 
						|
                cordelia, private_stream, user_group_membership_details, is_subscribed=False
 | 
						|
            )
 | 
						|
        )
 | 
						|
        msg = Message(
 | 
						|
            sender=cordelia,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=realm,
 | 
						|
        )
 | 
						|
        content = "#**private>testing**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content, acting_user=cordelia).rendered_content,
 | 
						|
            f'<p><a class="stream-topic" data-stream-id="{private_stream.id}" href="/#narrow/channel/{private_stream.id}-private/topic/testing">#{private_stream.name} > testing</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_message_id_multiple(self) -> None:
 | 
						|
        denmark = get_stream("Denmark", get_realm("zulip"))
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
        content = "As mentioned in #**Denmark>danish@123** and #**Denmark>danish@456**."
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            "<p>As mentioned in "
 | 
						|
            f'<a class="message-link" '
 | 
						|
            f'href="/#narrow/channel/{denmark.id}-{denmark.name}/topic/danish/near/123">'
 | 
						|
            f"#Denmark > danish @ 💬</a>"
 | 
						|
            " and "
 | 
						|
            f'<a class="message-link" '
 | 
						|
            f'href="/#narrow/channel/{denmark.id}-{denmark.name}/topic/danish/near/456">'
 | 
						|
            f"#Denmark > danish @ 💬</a>"
 | 
						|
            ".</p>",
 | 
						|
        )
 | 
						|
 | 
						|
    def test_empty_string_topic_message_link(self) -> None:
 | 
						|
        denmark = get_stream("Denmark", get_realm("zulip"))
 | 
						|
        sender = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender.realm,
 | 
						|
        )
 | 
						|
        content = "#**Denmark>@123**"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="message-link" href="/#narrow/channel/{denmark.id}-{denmark.name}/topic//near/123">#{denmark.name} > <em>{Message.EMPTY_TOPIC_FALLBACK_NAME}</em> @ 💬</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_possible_stream_names(self) -> None:
 | 
						|
        content = """#**test here**
 | 
						|
            This mentions #**Denmark** too.
 | 
						|
            #**garçon** #**천국** @**Ignore Person**
 | 
						|
        """
 | 
						|
        self.assertEqual(
 | 
						|
            possible_linked_stream_names(content),
 | 
						|
            {"test here", "Denmark", "garçon", "천국"},
 | 
						|
        )
 | 
						|
 | 
						|
    def test_stream_unicode(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        uni = self.make_stream(stream_name="привет", realm=realm)
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
 | 
						|
        content = "#**привет**"
 | 
						|
        quoted_name = ".D0.BF.D1.80.D0.B8.D0.B2.D0.B5.D1.82"
 | 
						|
        href = f"/#narrow/channel/{uni.id}-{quoted_name}"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream" data-stream-id="{uni.id}" href="{href}">#{uni.name}</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_stream_atomic_string(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        # Create a linkifier.
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        url_template = r"https://trac.example.com/ticket/{id}"
 | 
						|
        linkifier = RealmFilter(
 | 
						|
            realm=realm, pattern=r"#(?P<id>[0-9]{2,8})", url_template=url_template
 | 
						|
        )
 | 
						|
        linkifier.save()
 | 
						|
        self.assertEqual(
 | 
						|
            repr(linkifier),
 | 
						|
            "<RealmFilter: zulip: #(?P<id>[0-9]{2,8}) https://trac.example.com/ticket/{id}>",
 | 
						|
        )
 | 
						|
        # Create a stream that potentially interferes with the pattern.
 | 
						|
        stream = self.make_stream(stream_name="Stream #1234", realm=realm)
 | 
						|
        msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
 | 
						|
        content = "#**Stream #1234**"
 | 
						|
        href = f"/#narrow/channel/{stream.id}-Stream-.231234"
 | 
						|
        self.assertEqual(
 | 
						|
            render_message_markdown(msg, content).rendered_content,
 | 
						|
            f'<p><a class="stream" data-stream-id="{stream.id}" href="{href}">#{stream.name}</a></p>',
 | 
						|
        )
 | 
						|
 | 
						|
    def test_stream_invalid(self) -> None:
 | 
						|
        sender_user_profile = self.example_user("othello")
 | 
						|
        msg = Message(
 | 
						|
            sender=sender_user_profile,
 | 
						|
            sending_client=get_client("test"),
 | 
						|
            realm=sender_user_profile.realm,
 | 
						|
        )
 | 
						|
 | 
						|
        content = "There #**Nonexistentstream**"
 | 
						|
        rendering_result = render_message_markdown(msg, content)
 | 
						|
        self.assertEqual(
 | 
						|
            rendering_result.rendered_content, "<p>There #<strong>Nonexistentstream</strong></p>"
 | 
						|
        )
 | 
						|
        self.assertEqual(rendering_result.mentions_user_ids, set())
 | 
						|
 | 
						|
    def test_image_preview_title(self) -> None:
 | 
						|
        msg = "[My favorite image](https://example.com/testimage.png)"
 | 
						|
        converted = markdown_convert_wrapper(msg)
 | 
						|
        self.assertEqual(
 | 
						|
            converted,
 | 
						|
            "<p>"
 | 
						|
            '<a href="https://example.com/testimage.png">My favorite image</a>'
 | 
						|
            "</p>\n"
 | 
						|
            '<div class="message_inline_image">'
 | 
						|
            '<a href="https://example.com/testimage.png" title="My favorite image">'
 | 
						|
            '<img src="https://external-content.zulipcdn.net/external_content/5cd6ddfa28639e2e95bb85a7c7910b31f5474e03/68747470733a2f2f6578616d706c652e636f6d2f74657374696d6167652e706e67">'
 | 
						|
            "</a>"
 | 
						|
            "</div>",
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class MarkdownHTMLTest(ZulipTestCase):
 | 
						|
    def test_html_entity_conversion(self) -> None:
 | 
						|
        msg = """\
 | 
						|
            Test raw: Hello, ©
 | 
						|
            Test inline code: `©`
 | 
						|
 | 
						|
            Test fenced code:
 | 
						|
            ```
 | 
						|
            ©
 | 
						|
            ©
 | 
						|
            ```
 | 
						|
 | 
						|
            Test quote:
 | 
						|
            ~~~quote
 | 
						|
            ©
 | 
						|
            ~~~
 | 
						|
 | 
						|
            Test a list:
 | 
						|
            * ©
 | 
						|
            * `©`
 | 
						|
            * ```©```
 | 
						|
 | 
						|
            Test an indented block:
 | 
						|
 | 
						|
                ©"""
 | 
						|
 | 
						|
        expected_output = """\
 | 
						|
            <p>Test raw: Hello, ©<br>
 | 
						|
            Test inline code: <code>&copy;</code></p>
 | 
						|
            <p>Test fenced code:</p>
 | 
						|
            <div class="codehilite"><pre><span></span><code>&copy;
 | 
						|
            &copy;
 | 
						|
            </code></pre></div>
 | 
						|
            <p>Test quote:</p>
 | 
						|
            <blockquote>
 | 
						|
            <p>©</p>
 | 
						|
            </blockquote>
 | 
						|
            <p>Test a list:</p>
 | 
						|
            <ul>
 | 
						|
            <li>©</li>
 | 
						|
            <li><code>&copy;</code></li>
 | 
						|
            <li><code>&copy;</code></li>
 | 
						|
            </ul>
 | 
						|
            <p>Test an indented block:</p>
 | 
						|
            <div class="codehilite"><pre><span></span><code>&copy;
 | 
						|
            </code></pre></div>"""
 | 
						|
 | 
						|
        converted = markdown_convert_wrapper(dedent(msg))
 | 
						|
        self.assertEqual(converted, dedent(expected_output))
 | 
						|
 | 
						|
 | 
						|
class MarkdownApiTests(ZulipTestCase):
 | 
						|
    def test_render_message_api(self) -> None:
 | 
						|
        content = "That is a **bold** statement"
 | 
						|
        result = self.api_post(
 | 
						|
            self.example_user("othello"),
 | 
						|
            "/api/v1/messages/render",
 | 
						|
            dict(content=content),
 | 
						|
        )
 | 
						|
        response_dict = self.assert_json_success(result)
 | 
						|
        self.assertEqual(
 | 
						|
            response_dict["rendered"], "<p>That is a <strong>bold</strong> statement</p>"
 | 
						|
        )
 | 
						|
 | 
						|
    def test_render_mention_stream_api(self) -> None:
 | 
						|
        """Determines whether we're correctly passing the realm context"""
 | 
						|
        content = "This mentions #**Denmark** and @**King Hamlet**."
 | 
						|
        result = self.api_post(
 | 
						|
            self.example_user("othello"),
 | 
						|
            "/api/v1/messages/render",
 | 
						|
            dict(content=content),
 | 
						|
        )
 | 
						|
        response_dict = self.assert_json_success(result)
 | 
						|
        user_id = self.example_user("hamlet").id
 | 
						|
        stream_id = get_stream("Denmark", get_realm("zulip")).id
 | 
						|
        self.assertEqual(
 | 
						|
            response_dict["rendered"],
 | 
						|
            f'<p>This mentions <a class="stream" data-stream-id="{stream_id}" href="/#narrow/channel/{stream_id}-Denmark">#Denmark</a> and <span class="user-mention" data-user-id="{user_id}">@King Hamlet</span>.</p>',
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class MarkdownErrorTests(ZulipTestCase):
 | 
						|
    def test_markdown_error_handling(self) -> None:
 | 
						|
        with self.simulated_markdown_failure(), self.assertRaises(MarkdownRenderingError):
 | 
						|
            markdown_convert_wrapper("")
 | 
						|
 | 
						|
    def test_send_message_errors(self) -> None:
 | 
						|
        message = "whatever"
 | 
						|
        with (
 | 
						|
            self.simulated_markdown_failure(),
 | 
						|
            # We don't use assertRaisesRegex because it seems to not
 | 
						|
            # handle i18n properly here on some systems.
 | 
						|
            self.assertRaises(JsonableError),
 | 
						|
        ):
 | 
						|
            self.send_stream_message(self.example_user("othello"), "Denmark", message)
 | 
						|
 | 
						|
    @override_settings(MAX_MESSAGE_LENGTH=10)
 | 
						|
    def test_ultra_long_rendering(self) -> None:
 | 
						|
        """A rendered message with an ultra-long length (> 100 * MAX_MESSAGE_LENGTH)
 | 
						|
        throws an exception"""
 | 
						|
        msg = "mock rendered message\n" * 10 * settings.MAX_MESSAGE_LENGTH
 | 
						|
 | 
						|
        with (
 | 
						|
            mock.patch("zerver.lib.markdown.unsafe_timeout", return_value=msg),
 | 
						|
            mock.patch("zerver.lib.markdown.markdown_logger"),
 | 
						|
            self.assertRaises(MarkdownRenderingError),
 | 
						|
        ):
 | 
						|
            markdown_convert_wrapper(msg)
 | 
						|
 | 
						|
    def test_curl_code_block_validation(self) -> None:
 | 
						|
        processor = SimulatedFencedBlockPreprocessor(Markdown())
 | 
						|
        processor.run_content_validators = True
 | 
						|
 | 
						|
        markdown_input = [
 | 
						|
            "``` curl",
 | 
						|
            "curl {{ api_url }}/v1/register",
 | 
						|
            "    -u BOT_EMAIL_ADDRESS:BOT_API_KEY",
 | 
						|
            '    -d "queue_id=fb67bf8a-c031-47cc-84cf-ed80accacda8"',
 | 
						|
            "```",
 | 
						|
        ]
 | 
						|
 | 
						|
        with self.assertRaises(MarkdownRenderingError):
 | 
						|
            processor.run(markdown_input)
 | 
						|
 | 
						|
    def test_curl_code_block_without_validation(self) -> None:
 | 
						|
        processor = SimulatedFencedBlockPreprocessor(Markdown())
 | 
						|
 | 
						|
        markdown_input = [
 | 
						|
            "``` curl",
 | 
						|
            "curl {{ api_url }}/v1/register",
 | 
						|
            "    -u BOT_EMAIL_ADDRESS:BOT_API_KEY",
 | 
						|
            '    -d "queue_id=fb67bf8a-c031-47cc-84cf-ed80accacda8"',
 | 
						|
            "```",
 | 
						|
        ]
 | 
						|
        expected = [
 | 
						|
            "",
 | 
						|
            "**curl:curl {{ api_url }}/v1/register",
 | 
						|
            "    -u BOT_EMAIL_ADDRESS:BOT_API_KEY",
 | 
						|
            '    -d "queue_id=fb67bf8a-c031-47cc-84cf-ed80accacda8"**',
 | 
						|
            "",
 | 
						|
            "",
 | 
						|
        ]
 | 
						|
 | 
						|
        result = processor.run(markdown_input)
 | 
						|
        self.assertEqual(result, expected)
 |