mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	To maintain API compatibility, we render the email notification for missed 1:1 direct messages using DirectMessageGroup with the same format as messages sent to a Personal recipient.
		
			
				
	
	
		
			1933 lines
		
	
	
		
			83 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1933 lines
		
	
	
		
			83 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import random
 | 
						|
import re
 | 
						|
from collections.abc import Sequence
 | 
						|
from datetime import timedelta
 | 
						|
from email.headerregistry import Address
 | 
						|
from unittest import mock
 | 
						|
from unittest.mock import patch
 | 
						|
 | 
						|
import lxml.html
 | 
						|
import orjson
 | 
						|
from django.conf import settings
 | 
						|
from django.core import mail
 | 
						|
from django.core.mail.message import EmailMultiAlternatives
 | 
						|
from django.test import override_settings
 | 
						|
from django.utils.timezone import now as timezone_now
 | 
						|
from django_stubs_ext import StrPromise
 | 
						|
 | 
						|
from zerver.actions.create_user import do_create_user
 | 
						|
from zerver.actions.message_send import internal_send_private_message
 | 
						|
from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group
 | 
						|
from zerver.actions.user_settings import do_change_user_setting
 | 
						|
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
 | 
						|
from zerver.lib.email_notifications import (
 | 
						|
    MissedMessageData,
 | 
						|
    fix_emojis,
 | 
						|
    fix_spoilers_in_html,
 | 
						|
    handle_missedmessage_emails,
 | 
						|
    include_realm_name_in_missedmessage_emails_subject,
 | 
						|
    relative_to_full_url,
 | 
						|
)
 | 
						|
from zerver.lib.emoji import get_emoji_file_name
 | 
						|
from zerver.lib.send_email import FromAddress
 | 
						|
from zerver.lib.test_classes import ZulipTestCase
 | 
						|
from zerver.models import Message, UserMessage, UserProfile, UserTopic
 | 
						|
from zerver.models.realm_emoji import get_name_keyed_dict_for_active_realm_emoji
 | 
						|
from zerver.models.realms import get_realm
 | 
						|
from zerver.models.recipients import get_or_create_direct_message_group
 | 
						|
from zerver.models.scheduled_jobs import NotificationTriggers
 | 
						|
from zerver.models.streams import get_stream
 | 
						|
 | 
						|
 | 
						|
class TestMessageNotificationEmails(ZulipTestCase):
 | 
						|
    def test_read_message(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        self.login("cordelia")
 | 
						|
        result = self.client_post(
 | 
						|
            "/json/messages",
 | 
						|
            {
 | 
						|
                "type": "private",
 | 
						|
                "content": "Test message",
 | 
						|
                "to": orjson.dumps([hamlet.email]).decode(),
 | 
						|
            },
 | 
						|
        )
 | 
						|
        self.assert_json_success(result)
 | 
						|
        message = self.get_last_message()
 | 
						|
 | 
						|
        # The message is marked as read for the sender (Cordelia) by the message send codepath.
 | 
						|
        # We obviously should not send notifications to someone for messages they sent themselves.
 | 
						|
        with mock.patch(
 | 
						|
            "zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip"
 | 
						|
        ) as m:
 | 
						|
            handle_missedmessage_emails(
 | 
						|
                cordelia.id,
 | 
						|
                {message.id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)},
 | 
						|
            )
 | 
						|
        m.assert_not_called()
 | 
						|
 | 
						|
        # If the notification is processed before Hamlet reads the message, he should get the email.
 | 
						|
        with mock.patch(
 | 
						|
            "zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip"
 | 
						|
        ) as m:
 | 
						|
            handle_missedmessage_emails(
 | 
						|
                hamlet.id,
 | 
						|
                {message.id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)},
 | 
						|
            )
 | 
						|
        m.assert_called_once()
 | 
						|
 | 
						|
        # If Hamlet reads the message before receiving the email notification, we should not sent him
 | 
						|
        # an email.
 | 
						|
        usermessage = UserMessage.objects.get(
 | 
						|
            user_profile=hamlet,
 | 
						|
            message=message,
 | 
						|
        )
 | 
						|
        usermessage.flags.read = True
 | 
						|
        usermessage.save()
 | 
						|
        with mock.patch(
 | 
						|
            "zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip"
 | 
						|
        ) as m:
 | 
						|
            handle_missedmessage_emails(
 | 
						|
                hamlet.id,
 | 
						|
                {message.id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)},
 | 
						|
            )
 | 
						|
        m.assert_not_called()
 | 
						|
 | 
						|
    def test_demo_organization_owner_email_not_set(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        realm.demo_organization_scheduled_deletion_date = timezone_now() + timedelta(days=30)
 | 
						|
        realm.save()
 | 
						|
 | 
						|
        # Demo organization owner's don't have an email address set initially
 | 
						|
        desdemona = self.example_user("desdemona")
 | 
						|
        desdemona.delivery_email = ""
 | 
						|
        desdemona.save()
 | 
						|
 | 
						|
        notification_bot = self.notification_bot(realm)
 | 
						|
        internal_send_private_message(
 | 
						|
            sender=notification_bot,
 | 
						|
            recipient_user=desdemona,
 | 
						|
            content="Notification bot message",
 | 
						|
        )
 | 
						|
        message = self.get_last_message()
 | 
						|
        self.assertEqual(message.sender.id, notification_bot.id)
 | 
						|
        self.assertEqual(message.content, "Notification bot message")
 | 
						|
 | 
						|
        with mock.patch(
 | 
						|
            "zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip"
 | 
						|
        ) as m:
 | 
						|
            handle_missedmessage_emails(
 | 
						|
                desdemona.id,
 | 
						|
                {message.id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)},
 | 
						|
            )
 | 
						|
        m.assert_not_called()
 | 
						|
 | 
						|
    def normalize_string(self, s: str | StrPromise) -> str:
 | 
						|
        s = s.strip()
 | 
						|
        return re.sub(r"\s+", " ", s)
 | 
						|
 | 
						|
    def _get_tokens(self) -> list[str]:
 | 
						|
        return ["mm" + str(random.getrandbits(32)) for _ in range(30)]
 | 
						|
 | 
						|
    def _test_cases(
 | 
						|
        self,
 | 
						|
        msg_id: int,
 | 
						|
        verify_body_include: list[str],
 | 
						|
        email_subject: str,
 | 
						|
        verify_html_body: bool = False,
 | 
						|
        show_message_content: bool = True,
 | 
						|
        verify_body_does_not_include: Sequence[str] = [],
 | 
						|
        trigger: str = "",
 | 
						|
        mentioned_user_group_id: int | None = None,
 | 
						|
    ) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        tokens = self._get_tokens()
 | 
						|
        with patch("zerver.lib.email_mirror.generate_missed_message_token", side_effect=tokens):
 | 
						|
            self.handle_missedmessage_emails(
 | 
						|
                hamlet.id,
 | 
						|
                {
 | 
						|
                    msg_id: MissedMessageData(
 | 
						|
                        trigger=trigger, mentioned_user_group_id=mentioned_user_group_id
 | 
						|
                    )
 | 
						|
                },
 | 
						|
            )
 | 
						|
        if settings.EMAIL_GATEWAY_PATTERN != "":
 | 
						|
            reply_to_addresses = [settings.EMAIL_GATEWAY_PATTERN % (t,) for t in tokens]
 | 
						|
            reply_to_emails = [
 | 
						|
                str(Address(display_name="Zulip", addr_spec=address))
 | 
						|
                for address in reply_to_addresses
 | 
						|
            ]
 | 
						|
        else:
 | 
						|
            reply_to_emails = ["noreply@testserver"]
 | 
						|
        msg = mail.outbox[0]
 | 
						|
        assert isinstance(msg, EmailMultiAlternatives)
 | 
						|
        from_email = str(
 | 
						|
            Address(display_name="testserver notifications", addr_spec=FromAddress.NOREPLY)
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 1)
 | 
						|
        self.assertEqual(self.email_envelope_from(msg), settings.NOREPLY_EMAIL_ADDRESS)
 | 
						|
        self.assertEqual(self.email_display_from(msg), from_email)
 | 
						|
        self.assertEqual(msg.subject, email_subject)
 | 
						|
        self.assert_length(msg.reply_to, 1)
 | 
						|
        self.assertIn(msg.reply_to[0], reply_to_emails)
 | 
						|
        if verify_html_body:
 | 
						|
            for text in verify_body_include:
 | 
						|
                assert isinstance(msg.alternatives[0][0], str)
 | 
						|
                html = self.normalize_string(msg.alternatives[0][0])
 | 
						|
                self.assertIn(text, html)
 | 
						|
        else:
 | 
						|
            for text in verify_body_include:
 | 
						|
                self.assertIn(text, self.normalize_string(msg.body))
 | 
						|
        for text in verify_body_does_not_include:
 | 
						|
            self.assertNotIn(text, self.normalize_string(msg.body))
 | 
						|
 | 
						|
        self.assertEqual(msg.extra_headers["List-Id"], "Zulip Dev <zulip.testserver>")
 | 
						|
 | 
						|
    def _realm_name_in_missed_message_email_subject(
 | 
						|
        self, realm_name_in_notifications: bool
 | 
						|
    ) -> None:
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "Extremely personal message!",
 | 
						|
        )
 | 
						|
        verify_body_include = ["Extremely personal message!"]
 | 
						|
        email_subject = "DMs with Othello, the Moor of Venice"
 | 
						|
 | 
						|
        if realm_name_in_notifications:
 | 
						|
            email_subject = "DMs with Othello, the Moor of Venice [Zulip Dev]"
 | 
						|
        self._test_cases(msg_id, verify_body_include, email_subject)
 | 
						|
 | 
						|
    def _extra_context_in_missed_stream_messages_mention(
 | 
						|
        self, show_message_content: bool = True
 | 
						|
    ) -> None:
 | 
						|
        for i in range(11):
 | 
						|
            self.send_stream_message(
 | 
						|
                self.example_user("othello"),
 | 
						|
                "Denmark",
 | 
						|
                content=str(i),
 | 
						|
                topic_name="test" if i % 2 == 0 else "TEST",
 | 
						|
            )
 | 
						|
        self.send_stream_message(self.example_user("othello"), "Denmark", "11", topic_name="test2")
 | 
						|
        msg_id = self.send_stream_message(
 | 
						|
            self.example_user("othello"), "denmark", "@**King Hamlet**"
 | 
						|
        )
 | 
						|
 | 
						|
        if show_message_content:
 | 
						|
            verify_body_include = [
 | 
						|
                "Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > 6 > 7 > 8 > 9 > 10 > @**King Hamlet** -- ",
 | 
						|
                "You are receiving this because you were personally mentioned.",
 | 
						|
            ]
 | 
						|
            email_subject = "#Denmark > test"
 | 
						|
            verify_body_does_not_include: list[str] = []
 | 
						|
        else:
 | 
						|
            # Test in case if message content in missed email message are disabled.
 | 
						|
            verify_body_include = [
 | 
						|
                "This email does not include message content because you have chosen to ",
 | 
						|
                "http://zulip.testserver/help/email-notifications#hide-message-content ",
 | 
						|
                "View or reply in Zulip Dev Zulip",
 | 
						|
                " Manage email preferences: http://zulip.testserver/#settings/notifications",
 | 
						|
            ]
 | 
						|
 | 
						|
            email_subject = "New messages"
 | 
						|
            verify_body_does_not_include = [
 | 
						|
                "Denmark > test",
 | 
						|
                "Othello, the Moor of Venice",
 | 
						|
                "1 2 3 4 5 6 7 8 9 10 @**King Hamlet**",
 | 
						|
                "private",
 | 
						|
                "group",
 | 
						|
                "Reply to this email directly, or view it in Zulip Dev Zulip",
 | 
						|
            ]
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            show_message_content=show_message_content,
 | 
						|
            verify_body_does_not_include=verify_body_does_not_include,
 | 
						|
            trigger=NotificationTriggers.MENTION,
 | 
						|
        )
 | 
						|
 | 
						|
    def _extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic(
 | 
						|
        self,
 | 
						|
        show_message_content: bool = True,
 | 
						|
        *,
 | 
						|
        receiver_is_participant: bool,
 | 
						|
    ) -> None:
 | 
						|
        for i in range(1, 3):
 | 
						|
            self.send_stream_message(self.example_user("othello"), "Denmark", content=str(i))
 | 
						|
        self.send_stream_message(self.example_user("othello"), "Denmark", "11", topic_name="test2")
 | 
						|
 | 
						|
        if receiver_is_participant:
 | 
						|
            self.send_stream_message(self.example_user("hamlet"), "Denmark", content="hello")
 | 
						|
 | 
						|
        msg_id = self.send_stream_message(self.example_user("othello"), "Denmark", "@**topic**")
 | 
						|
        trigger = NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC
 | 
						|
        if not receiver_is_participant:
 | 
						|
            trigger = NotificationTriggers.STREAM_EMAIL
 | 
						|
 | 
						|
        if show_message_content:
 | 
						|
            # If Hamlet (receiver) is not a topic participant, @topic doesn't mention him,
 | 
						|
            # so he won't receive added context (previous messages) in the email.
 | 
						|
            if receiver_is_participant:
 | 
						|
                verify_body_include = [
 | 
						|
                    "Othello, the Moor of Venice: > 1 > 2 King Hamlet: > hello Othello, the Moor of Venice: > @**topic** -- ",
 | 
						|
                    "You are receiving this because all topic participants were mentioned in #Denmark > test.",
 | 
						|
                ]
 | 
						|
            else:
 | 
						|
                verify_body_include = [
 | 
						|
                    "Othello, the Moor of Venice: > @**topic** -- ",
 | 
						|
                    "You are receiving this because you have email notifications enabled for #Denmark.",
 | 
						|
                ]
 | 
						|
            email_subject = "#Denmark > test"
 | 
						|
            verify_body_does_not_include: list[str] = []
 | 
						|
        else:
 | 
						|
            # Test in case if message content in missed email message are disabled.
 | 
						|
            verify_body_include = [
 | 
						|
                "This email does not include message content because you have chosen to ",
 | 
						|
                "http://zulip.testserver/help/email-notifications#hide-message-content ",
 | 
						|
                "View or reply in Zulip Dev Zulip",
 | 
						|
                " Manage email preferences: http://zulip.testserver/#settings/notifications",
 | 
						|
            ]
 | 
						|
            email_subject = "New messages"
 | 
						|
            verify_body_does_not_include = [
 | 
						|
                "Othello, the Moor of Venice",
 | 
						|
                "1 2 3 4 5 @**topic**",
 | 
						|
                "private",
 | 
						|
                "group",
 | 
						|
                "Reply to this email directly, or view it in Zulip Dev Zulip",
 | 
						|
            ]
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            show_message_content=show_message_content,
 | 
						|
            verify_body_does_not_include=verify_body_does_not_include,
 | 
						|
            trigger=trigger,
 | 
						|
        )
 | 
						|
 | 
						|
    def _extra_context_in_missed_stream_messages_stream_wildcard_mention_in_followed_topic(
 | 
						|
        self, show_message_content: bool = True
 | 
						|
    ) -> None:
 | 
						|
        for i in range(1, 6):
 | 
						|
            self.send_stream_message(self.example_user("othello"), "Denmark", content=str(i))
 | 
						|
        self.send_stream_message(self.example_user("othello"), "Denmark", "11", topic_name="test2")
 | 
						|
        msg_id = self.send_stream_message(self.example_user("othello"), "Denmark", "@**all**")
 | 
						|
 | 
						|
        if show_message_content:
 | 
						|
            verify_body_include = [
 | 
						|
                "Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > @**all** -- ",
 | 
						|
                "You are receiving this because you have wildcard mention notifications enabled for topics you follow.",
 | 
						|
            ]
 | 
						|
            email_subject = "#Denmark > test"
 | 
						|
            verify_body_does_not_include: list[str] = []
 | 
						|
        else:
 | 
						|
            # Test in case if message content in missed email message are disabled.
 | 
						|
            verify_body_include = [
 | 
						|
                "This email does not include message content because you have chosen to ",
 | 
						|
                "http://zulip.testserver/help/email-notifications#hide-message-content ",
 | 
						|
                "View or reply in Zulip Dev Zulip",
 | 
						|
                " Manage email preferences: http://zulip.testserver/#settings/notifications",
 | 
						|
            ]
 | 
						|
            email_subject = "New messages"
 | 
						|
            verify_body_does_not_include = [
 | 
						|
                "Denmark > test",
 | 
						|
                "Othello, the Moor of Venice",
 | 
						|
                "1 2 3 4 5 @**all**",
 | 
						|
                "private",
 | 
						|
                "group",
 | 
						|
                "Reply to this email directly, or view it in Zulip Dev Zulip",
 | 
						|
            ]
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            show_message_content=show_message_content,
 | 
						|
            verify_body_does_not_include=verify_body_does_not_include,
 | 
						|
            trigger=NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC,
 | 
						|
        )
 | 
						|
 | 
						|
    def _extra_context_in_missed_stream_messages_topic_wildcard_mention(
 | 
						|
        self,
 | 
						|
        show_message_content: bool = True,
 | 
						|
        *,
 | 
						|
        receiver_is_participant: bool,
 | 
						|
    ) -> None:
 | 
						|
        for i in range(1, 3):
 | 
						|
            self.send_stream_message(self.example_user("othello"), "Denmark", content=str(i))
 | 
						|
        self.send_stream_message(self.example_user("othello"), "Denmark", "11", topic_name="test2")
 | 
						|
 | 
						|
        if receiver_is_participant:
 | 
						|
            self.send_stream_message(self.example_user("hamlet"), "Denmark", content="hello")
 | 
						|
 | 
						|
        msg_id = self.send_stream_message(self.example_user("othello"), "denmark", "@**topic**")
 | 
						|
        trigger = NotificationTriggers.TOPIC_WILDCARD_MENTION
 | 
						|
        if not receiver_is_participant:
 | 
						|
            trigger = NotificationTriggers.STREAM_EMAIL
 | 
						|
 | 
						|
        if show_message_content:
 | 
						|
            # If Hamlet (receiver) is not a topic participant, @topic doesn't mention him,
 | 
						|
            # so he won't receive added context (previous messages) in the email.
 | 
						|
            if receiver_is_participant:
 | 
						|
                verify_body_include = [
 | 
						|
                    "Othello, the Moor of Venice: > 1 > 2 King Hamlet: > hello Othello, the Moor of Venice: > @**topic** -- ",
 | 
						|
                    "You are receiving this because all topic participants were mentioned in #Denmark > test.",
 | 
						|
                ]
 | 
						|
            else:
 | 
						|
                verify_body_include = [
 | 
						|
                    "Othello, the Moor of Venice: > @**topic** -- ",
 | 
						|
                    "You are receiving this because you have email notifications enabled for #Denmark.",
 | 
						|
                ]
 | 
						|
            email_subject = "#Denmark > test"
 | 
						|
            verify_body_does_not_include: list[str] = []
 | 
						|
        else:
 | 
						|
            # Test in case if message content in missed email message are disabled.
 | 
						|
            verify_body_include = [
 | 
						|
                "This email does not include message content because you have chosen to ",
 | 
						|
                "http://zulip.testserver/help/email-notifications#hide-message-content ",
 | 
						|
                "View or reply in Zulip Dev Zulip",
 | 
						|
                " Manage email preferences: http://zulip.testserver/#settings/notifications",
 | 
						|
            ]
 | 
						|
            email_subject = "New messages"
 | 
						|
            verify_body_does_not_include = [
 | 
						|
                "Othello, the Moor of Venice",
 | 
						|
                "1 2 3 4 5 @**topic**",
 | 
						|
                "private",
 | 
						|
                "group",
 | 
						|
                "Reply to this email directly, or view it in Zulip Dev Zulip",
 | 
						|
            ]
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            show_message_content=show_message_content,
 | 
						|
            verify_body_does_not_include=verify_body_does_not_include,
 | 
						|
            trigger=trigger,
 | 
						|
        )
 | 
						|
 | 
						|
    def _extra_context_in_missed_stream_messages_stream_wildcard_mention(
 | 
						|
        self, show_message_content: bool = True
 | 
						|
    ) -> None:
 | 
						|
        for i in range(1, 6):
 | 
						|
            self.send_stream_message(self.example_user("othello"), "Denmark", content=str(i))
 | 
						|
        self.send_stream_message(self.example_user("othello"), "Denmark", "11", topic_name="test2")
 | 
						|
        msg_id = self.send_stream_message(self.example_user("othello"), "denmark", "@**all**")
 | 
						|
 | 
						|
        if show_message_content:
 | 
						|
            verify_body_include = [
 | 
						|
                "Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > @**all** -- ",
 | 
						|
                "You are receiving this because everyone was mentioned in #Denmark.",
 | 
						|
            ]
 | 
						|
            email_subject = "#Denmark > test"
 | 
						|
            verify_body_does_not_include: list[str] = []
 | 
						|
        else:
 | 
						|
            # Test in case if message content in missed email message are disabled.
 | 
						|
            verify_body_include = [
 | 
						|
                "This email does not include message content because you have chosen to ",
 | 
						|
                "http://zulip.testserver/help/email-notifications#hide-message-content ",
 | 
						|
                "View or reply in Zulip Dev Zulip",
 | 
						|
                " Manage email preferences: http://zulip.testserver/#settings/notifications",
 | 
						|
            ]
 | 
						|
            email_subject = "New messages"
 | 
						|
            verify_body_does_not_include = [
 | 
						|
                "Denmark > test",
 | 
						|
                "Othello, the Moor of Venice",
 | 
						|
                "1 2 3 4 5 @**all**",
 | 
						|
                "private",
 | 
						|
                "group",
 | 
						|
                "Reply to this email directly, or view it in Zulip Dev Zulip",
 | 
						|
            ]
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            show_message_content=show_message_content,
 | 
						|
            verify_body_does_not_include=verify_body_does_not_include,
 | 
						|
            trigger=NotificationTriggers.STREAM_WILDCARD_MENTION,
 | 
						|
        )
 | 
						|
 | 
						|
    def _extra_context_in_missed_stream_messages_email_notify(self) -> None:
 | 
						|
        for i in range(11):
 | 
						|
            self.send_stream_message(self.example_user("othello"), "Denmark", content=str(i))
 | 
						|
        self.send_stream_message(self.example_user("othello"), "Denmark", "11", topic_name="test2")
 | 
						|
        msg_id = self.send_stream_message(self.example_user("othello"), "denmark", "12")
 | 
						|
        verify_body_include = [
 | 
						|
            "Othello, the Moor of Venice: > 12 -- ",
 | 
						|
            "You are receiving this because you have email notifications enabled for #Denmark.",
 | 
						|
        ]
 | 
						|
        email_subject = "#Denmark > test"
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            trigger=NotificationTriggers.STREAM_EMAIL,
 | 
						|
        )
 | 
						|
 | 
						|
    def _extra_context_in_missed_stream_messages_mention_two_senders(
 | 
						|
        self,
 | 
						|
    ) -> None:
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        self.subscribe(cordelia, "Denmark")
 | 
						|
 | 
						|
        for i in range(3):
 | 
						|
            self.send_stream_message(cordelia, "Denmark", str(i))
 | 
						|
        msg_id = self.send_stream_message(
 | 
						|
            self.example_user("othello"), "Denmark", "@**King Hamlet**"
 | 
						|
        )
 | 
						|
        verify_body_include = [
 | 
						|
            "Cordelia, Lear's daughter: > 0 > 1 > 2 Othello, the Moor of Venice: > @**King Hamlet** -- ",
 | 
						|
            "You are receiving this because you were personally mentioned.",
 | 
						|
        ]
 | 
						|
        email_subject = "#Denmark > test"
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            trigger=NotificationTriggers.MENTION,
 | 
						|
        )
 | 
						|
 | 
						|
    def _resolved_topic_missed_stream_messages_thread_friendly(self) -> None:
 | 
						|
        topic_name = "threading and so forth"
 | 
						|
        othello_user = self.example_user("othello")
 | 
						|
        msg_id = -1
 | 
						|
        for i in range(3):
 | 
						|
            msg_id = self.send_stream_message(
 | 
						|
                othello_user,
 | 
						|
                "Denmark",
 | 
						|
                content=str(i),
 | 
						|
                topic_name=topic_name,
 | 
						|
            )
 | 
						|
 | 
						|
        self.assert_json_success(self.resolve_topic_containing_message(othello_user, msg_id))
 | 
						|
 | 
						|
        verify_body_include = [
 | 
						|
            "Othello, the Moor of Venice: > 2 -- ",
 | 
						|
            "You are receiving this because you have email notifications enabled for #Denmark.",
 | 
						|
        ]
 | 
						|
        email_subject = "[resolved] #Denmark > threading and so forth"
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            trigger=NotificationTriggers.STREAM_EMAIL,
 | 
						|
        )
 | 
						|
 | 
						|
    def _extra_context_in_missed_personal_messages(
 | 
						|
        self,
 | 
						|
        show_message_content: bool = True,
 | 
						|
        message_content_disabled_by_user: bool = False,
 | 
						|
        message_content_disabled_by_realm: bool = False,
 | 
						|
    ) -> None:
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "Extremely personal message!",
 | 
						|
        )
 | 
						|
 | 
						|
        if show_message_content:
 | 
						|
            verify_body_include = ["> Extremely personal message!"]
 | 
						|
            email_subject = "DMs with Othello, the Moor of Venice"
 | 
						|
            verify_body_does_not_include: list[str] = []
 | 
						|
        else:
 | 
						|
            if message_content_disabled_by_realm:
 | 
						|
                verify_body_include = [
 | 
						|
                    "This email does not include message content because your organization",
 | 
						|
                    "http://zulip.testserver/help/hide-message-content-in-emails",
 | 
						|
                    "View or reply in Zulip Dev Zulip",
 | 
						|
                    " Manage email preferences: http://zulip.testserver/#settings/notifications",
 | 
						|
                ]
 | 
						|
            elif message_content_disabled_by_user:
 | 
						|
                verify_body_include = [
 | 
						|
                    "This email does not include message content because you have chosen to ",
 | 
						|
                    "http://zulip.testserver/help/email-notifications#hide-message-content ",
 | 
						|
                    "View or reply in Zulip Dev Zulip",
 | 
						|
                    " Manage email preferences: http://zulip.testserver/#settings/notifications",
 | 
						|
                ]
 | 
						|
            email_subject = "New messages"
 | 
						|
            verify_body_does_not_include = [
 | 
						|
                "Othello, the Moor of Venice",
 | 
						|
                "Extremely personal message!",
 | 
						|
                "mentioned",
 | 
						|
                "group",
 | 
						|
                "Reply to this email directly, or view it in Zulip Dev Zulip",
 | 
						|
            ]
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            show_message_content=show_message_content,
 | 
						|
            verify_body_does_not_include=verify_body_does_not_include,
 | 
						|
        )
 | 
						|
 | 
						|
    def _reply_to_email_in_missed_personal_messages(self) -> None:
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "Extremely personal message!",
 | 
						|
        )
 | 
						|
        verify_body_include = ["Reply to this email directly, or view it in Zulip Dev Zulip"]
 | 
						|
        email_subject = "DMs with Othello, the Moor of Venice"
 | 
						|
        self._test_cases(msg_id, verify_body_include, email_subject)
 | 
						|
 | 
						|
    def _reply_warning_in_missed_personal_messages(self) -> None:
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "Extremely personal message!",
 | 
						|
        )
 | 
						|
        verify_body_include = ["Do not reply to this email."]
 | 
						|
        email_subject = "DMs with Othello, the Moor of Venice"
 | 
						|
        self._test_cases(msg_id, verify_body_include, email_subject)
 | 
						|
 | 
						|
    def _extra_context_in_missed_group_direct_messages_two_others(
 | 
						|
        self, show_message_content: bool = True
 | 
						|
    ) -> None:
 | 
						|
        msg_id = self.send_group_direct_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            [
 | 
						|
                self.example_user("hamlet"),
 | 
						|
                self.example_user("iago"),
 | 
						|
            ],
 | 
						|
            "Group personal message!",
 | 
						|
        )
 | 
						|
 | 
						|
        if show_message_content:
 | 
						|
            verify_body_include = [
 | 
						|
                "Othello, the Moor of Venice: > Group personal message! -- Reply"
 | 
						|
            ]
 | 
						|
            email_subject = "Group DMs with Iago and Othello, the Moor of Venice"
 | 
						|
            verify_body_does_not_include: list[str] = []
 | 
						|
        else:
 | 
						|
            verify_body_include = [
 | 
						|
                "This email does not include message content because you have chosen to ",
 | 
						|
                "http://zulip.testserver/help/email-notifications#hide-message-content ",
 | 
						|
                "View or reply in Zulip Dev Zulip",
 | 
						|
                " Manage email preferences: http://zulip.testserver/#settings/notifications",
 | 
						|
            ]
 | 
						|
            email_subject = "New messages"
 | 
						|
            verify_body_does_not_include = [
 | 
						|
                "Iago",
 | 
						|
                "Othello, the Moor of Venice Othello, the Moor of Venice",
 | 
						|
                "Group personal message!",
 | 
						|
                "mentioned",
 | 
						|
                "Reply to this email directly, or view it in Zulip Dev Zulip",
 | 
						|
            ]
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            show_message_content=show_message_content,
 | 
						|
            verify_body_does_not_include=verify_body_does_not_include,
 | 
						|
        )
 | 
						|
 | 
						|
    def _extra_context_in_missed_group_direct_messages_three_others(self) -> None:
 | 
						|
        msg_id = self.send_group_direct_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            [
 | 
						|
                self.example_user("hamlet"),
 | 
						|
                self.example_user("iago"),
 | 
						|
                self.example_user("cordelia"),
 | 
						|
            ],
 | 
						|
            "Group personal message!",
 | 
						|
        )
 | 
						|
 | 
						|
        verify_body_include = ["Othello, the Moor of Venice: > Group personal message! -- Reply"]
 | 
						|
        email_subject = (
 | 
						|
            "Group DMs with Cordelia, Lear's daughter, Iago, and Othello, the Moor of Venice"
 | 
						|
        )
 | 
						|
        self._test_cases(msg_id, verify_body_include, email_subject)
 | 
						|
 | 
						|
    def _extra_context_in_missed_group_direct_messages_many_others(self) -> None:
 | 
						|
        msg_id = self.send_group_direct_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            [
 | 
						|
                self.example_user("hamlet"),
 | 
						|
                self.example_user("iago"),
 | 
						|
                self.example_user("cordelia"),
 | 
						|
                self.example_user("prospero"),
 | 
						|
            ],
 | 
						|
            "Group personal message!",
 | 
						|
        )
 | 
						|
 | 
						|
        verify_body_include = ["Othello, the Moor of Venice: > Group personal message! -- Reply"]
 | 
						|
        email_subject = "Group DMs with Cordelia, Lear's daughter, Iago, and 2 others"
 | 
						|
        self._test_cases(msg_id, verify_body_include, email_subject)
 | 
						|
 | 
						|
    def _deleted_message_in_missed_stream_messages(self) -> None:
 | 
						|
        msg_id = self.send_stream_message(
 | 
						|
            self.example_user("othello"), "denmark", "@**King Hamlet** to be deleted"
 | 
						|
        )
 | 
						|
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        self.login("othello")
 | 
						|
        result = self.client_patch("/json/messages/" + str(msg_id), {"content": " "})
 | 
						|
        self.assert_json_success(result)
 | 
						|
        handle_missedmessage_emails(
 | 
						|
            hamlet.id, {msg_id: MissedMessageData(trigger=NotificationTriggers.MENTION)}
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 0)
 | 
						|
 | 
						|
    def _deleted_message_in_missed_personal_messages(self) -> None:
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "Extremely personal message! to be deleted!",
 | 
						|
        )
 | 
						|
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        self.login("othello")
 | 
						|
        result = self.client_patch("/json/messages/" + str(msg_id), {"content": " "})
 | 
						|
        self.assert_json_success(result)
 | 
						|
        handle_missedmessage_emails(
 | 
						|
            hamlet.id, {msg_id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)}
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 0)
 | 
						|
 | 
						|
    def _deleted_message_in_missed_group_direct_messages(self) -> None:
 | 
						|
        msg_id = self.send_group_direct_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            [
 | 
						|
                self.example_user("hamlet"),
 | 
						|
                self.example_user("iago"),
 | 
						|
            ],
 | 
						|
            "Group personal message!",
 | 
						|
        )
 | 
						|
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        iago = self.example_user("iago")
 | 
						|
        self.login("othello")
 | 
						|
        result = self.client_patch("/json/messages/" + str(msg_id), {"content": " "})
 | 
						|
        self.assert_json_success(result)
 | 
						|
        handle_missedmessage_emails(
 | 
						|
            hamlet.id, {msg_id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)}
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 0)
 | 
						|
        handle_missedmessage_emails(
 | 
						|
            iago.id, {msg_id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)}
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 0)
 | 
						|
 | 
						|
    def test_smaller_user_group_mention_priority(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
 | 
						|
        hamlet_only = check_add_user_group(
 | 
						|
            get_realm("zulip"), "hamlet_only", [hamlet], acting_user=hamlet
 | 
						|
        )
 | 
						|
        hamlet_and_cordelia = check_add_user_group(
 | 
						|
            get_realm("zulip"), "hamlet_and_cordelia", [hamlet, cordelia], acting_user=hamlet
 | 
						|
        )
 | 
						|
 | 
						|
        hamlet_only_message_id = self.send_stream_message(othello, "Denmark", "@*hamlet_only*")
 | 
						|
        hamlet_and_cordelia_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@*hamlet_and_cordelia*"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                hamlet_only_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.MENTION, mentioned_user_group_id=hamlet_only.id
 | 
						|
                ),
 | 
						|
                hamlet_and_cordelia_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.MENTION,
 | 
						|
                    mentioned_user_group_id=hamlet_and_cordelia.id,
 | 
						|
                ),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        expected_email_include = [
 | 
						|
            "Othello, the Moor of Venice: > @*hamlet_only* > @*hamlet_and_cordelia* -- ",
 | 
						|
            "You are receiving this because @hamlet_only was mentioned.",
 | 
						|
        ]
 | 
						|
 | 
						|
        for text in expected_email_include:
 | 
						|
            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
 | 
						|
 | 
						|
    def test_personal_over_user_group_mention_priority(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        hamlet_and_cordelia = check_add_user_group(
 | 
						|
            get_realm("zulip"), "hamlet_and_cordelia", [hamlet, cordelia], acting_user=hamlet
 | 
						|
        )
 | 
						|
 | 
						|
        user_group_mentioned_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@*hamlet_and_cordelia*"
 | 
						|
        )
 | 
						|
        personal_mentioned_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@**King Hamlet**"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                user_group_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.MENTION,
 | 
						|
                    mentioned_user_group_id=hamlet_and_cordelia.id,
 | 
						|
                ),
 | 
						|
                personal_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.MENTION
 | 
						|
                ),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        expected_email_include = [
 | 
						|
            "Othello, the Moor of Venice: > @*hamlet_and_cordelia* > @**King Hamlet** -- ",
 | 
						|
            "You are receiving this because you were personally mentioned.",
 | 
						|
        ]
 | 
						|
 | 
						|
        for text in expected_email_include:
 | 
						|
            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
 | 
						|
 | 
						|
    def test_user_group_over_topic_wildcard_mention_in_followed_topic_priority(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        hamlet_and_cordelia = check_add_user_group(
 | 
						|
            get_realm("zulip"), "hamlet_and_cordelia", [hamlet, cordelia], acting_user=hamlet
 | 
						|
        )
 | 
						|
 | 
						|
        topic_wildcard_mentioned_in_followed_topic_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@**topic**"
 | 
						|
        )
 | 
						|
        user_group_mentioned_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@*hamlet_and_cordelia*"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                topic_wildcard_mentioned_in_followed_topic_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC
 | 
						|
                ),
 | 
						|
                user_group_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.MENTION,
 | 
						|
                    mentioned_user_group_id=hamlet_and_cordelia.id,
 | 
						|
                ),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        expected_email_include = [
 | 
						|
            "Othello, the Moor of Venice: > @**topic** > @*hamlet_and_cordelia* -- ",
 | 
						|
            "You are receiving this because @hamlet_and_cordelia was mentioned.",
 | 
						|
        ]
 | 
						|
 | 
						|
        for text in expected_email_include:
 | 
						|
            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
 | 
						|
 | 
						|
    def test_topic_wildcard_in_followed_topic_over_stream_wildcard_mention_in_followed_topic_priority(
 | 
						|
        self,
 | 
						|
    ) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        stream_wildcard_mentioned_in_followed_topic_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@**all**"
 | 
						|
        )
 | 
						|
        topic_wildcard_mentioned_in_followed_topic_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@**topic**"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                stream_wildcard_mentioned_in_followed_topic_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC
 | 
						|
                ),
 | 
						|
                topic_wildcard_mentioned_in_followed_topic_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC
 | 
						|
                ),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        expected_email_include = [
 | 
						|
            "Othello, the Moor of Venice: > @**all** > @**topic** -- ",
 | 
						|
            "You are receiving this because all topic participants were mentioned in #Denmark > test.",
 | 
						|
        ]
 | 
						|
 | 
						|
        for text in expected_email_include:
 | 
						|
            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
 | 
						|
 | 
						|
    def test_stream_wildcard_in_followed_topic_over_topic_wildcard_mention_priority(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        topic_wildcard_mentioned_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@**topic**"
 | 
						|
        )
 | 
						|
        stream_wildcard_mentioned_in_followed_topic_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@**all**"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                topic_wildcard_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.TOPIC_WILDCARD_MENTION
 | 
						|
                ),
 | 
						|
                stream_wildcard_mentioned_in_followed_topic_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC
 | 
						|
                ),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        expected_email_include = [
 | 
						|
            "Othello, the Moor of Venice: > @**topic** > @**all** -- ",
 | 
						|
            "You are receiving this because you have wildcard mention notifications enabled for topics you follow.",
 | 
						|
        ]
 | 
						|
 | 
						|
        for text in expected_email_include:
 | 
						|
            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
 | 
						|
 | 
						|
    def test_topic_wildcard_over_stream_wildcard_mention_priority(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        stream_wildcard_mentioned_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@**all**"
 | 
						|
        )
 | 
						|
        topic_wildcard_mentioned_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@**topic**"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                stream_wildcard_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.STREAM_WILDCARD_MENTION
 | 
						|
                ),
 | 
						|
                topic_wildcard_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.TOPIC_WILDCARD_MENTION
 | 
						|
                ),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        expected_email_include = [
 | 
						|
            "Othello, the Moor of Venice: > @**all** > @**topic** -- ",
 | 
						|
            "You are receiving this because all topic participants were mentioned in #Denmark > test.",
 | 
						|
        ]
 | 
						|
 | 
						|
        for text in expected_email_include:
 | 
						|
            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
 | 
						|
 | 
						|
    def test_stream_wildcard_mention_over_followed_topic_notify_priority(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        followed_topic_mentioned_message_id = self.send_stream_message(othello, "Denmark", "1")
 | 
						|
        stream_wildcard_mentioned_message_id = self.send_stream_message(
 | 
						|
            othello, "Denmark", "@**all**"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                followed_topic_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.FOLLOWED_TOPIC_EMAIL
 | 
						|
                ),
 | 
						|
                stream_wildcard_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.STREAM_WILDCARD_MENTION
 | 
						|
                ),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        expected_email_include = [
 | 
						|
            "Othello, the Moor of Venice: > 1 > @**all** -- ",
 | 
						|
            "You are receiving this because everyone was mentioned in #Denmark.",
 | 
						|
        ]
 | 
						|
 | 
						|
        for text in expected_email_include:
 | 
						|
            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
 | 
						|
 | 
						|
    def test_followed_topic_notify_over_stream_message_notify_priority(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", "0")
 | 
						|
        followed_topic_mentioned_message_id = self.send_stream_message(othello, "Denmark", "1")
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                stream_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.STREAM_EMAIL
 | 
						|
                ),
 | 
						|
                followed_topic_mentioned_message_id: MissedMessageData(
 | 
						|
                    trigger=NotificationTriggers.FOLLOWED_TOPIC_EMAIL
 | 
						|
                ),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        expected_email_include = [
 | 
						|
            "Othello, the Moor of Venice: > 0 > 1 -- ",
 | 
						|
            "You are receiving this because you have email notifications enabled for topics you follow.",
 | 
						|
        ]
 | 
						|
 | 
						|
        for text in expected_email_include:
 | 
						|
            self.assertIn(text, self.normalize_string(mail.outbox[0].body))
 | 
						|
 | 
						|
    def test_include_realm_name_in_missedmessage_emails_subject(self) -> None:
 | 
						|
        user = self.example_user("hamlet")
 | 
						|
 | 
						|
        # Test with 'realm_name_in_notification_policy' set to 'Always'
 | 
						|
        do_change_user_setting(
 | 
						|
            user,
 | 
						|
            "realm_name_in_email_notifications_policy",
 | 
						|
            UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_ALWAYS,
 | 
						|
            acting_user=None,
 | 
						|
        )
 | 
						|
        self.assertTrue(include_realm_name_in_missedmessage_emails_subject(user))
 | 
						|
 | 
						|
        # Test with 'realm_name_in_notification_policy' set to 'Never'
 | 
						|
        do_change_user_setting(
 | 
						|
            user,
 | 
						|
            "realm_name_in_email_notifications_policy",
 | 
						|
            UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_NEVER,
 | 
						|
            acting_user=None,
 | 
						|
        )
 | 
						|
        self.assertFalse(include_realm_name_in_missedmessage_emails_subject(user))
 | 
						|
 | 
						|
        # Test with 'realm_name_in_notification_policy' set to 'Automatic'
 | 
						|
        do_change_user_setting(
 | 
						|
            user,
 | 
						|
            "realm_name_in_email_notifications_policy",
 | 
						|
            UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_AUTOMATIC,
 | 
						|
            acting_user=None,
 | 
						|
        )
 | 
						|
        # Case 1: if user is part of a single realm, then realm_name is not present in notifications.
 | 
						|
        self.assertFalse(include_realm_name_in_missedmessage_emails_subject(user))
 | 
						|
 | 
						|
        # Case 2: if user is part of multiple realms, then realm_name should be present in notifications.
 | 
						|
        # Create and verify a cross realm user.
 | 
						|
        cross_realm_user = do_create_user(
 | 
						|
            user.delivery_email, None, get_realm("lear"), user.full_name, acting_user=None
 | 
						|
        )
 | 
						|
        self.assertEqual(cross_realm_user.delivery_email, user.delivery_email)
 | 
						|
 | 
						|
        self.assertTrue(include_realm_name_in_missedmessage_emails_subject(cross_realm_user))
 | 
						|
 | 
						|
    def test_realm_name_in_email_notifications_policy(self) -> None:
 | 
						|
        # Test with realm_name_in_email_notifications_policy set to Never.
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        hamlet.realm_name_in_email_notifications_policy = (
 | 
						|
            UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_NEVER
 | 
						|
        )
 | 
						|
        hamlet.save(update_fields=["realm_name_in_email_notifications_policy"])
 | 
						|
        with mock.patch(
 | 
						|
            "zerver.lib.email_notifications.include_realm_name_in_missedmessage_emails_subject",
 | 
						|
            return_value=False,
 | 
						|
        ):
 | 
						|
            is_allowed = include_realm_name_in_missedmessage_emails_subject(hamlet)
 | 
						|
            self._realm_name_in_missed_message_email_subject(is_allowed)
 | 
						|
 | 
						|
        # Test with realm_name_in_email_notifications_policy set to Always.
 | 
						|
 | 
						|
        # Note: We don't need to test separately for 'realm_name_in_email_notifications_policy'
 | 
						|
        # set to 'Automatic'.
 | 
						|
        # Here, we are concerned about the subject after the mocked function returns True/False.
 | 
						|
        # We already have separate test to check the appropriate behaviour of
 | 
						|
        # 'include_realm_name_in_missedmessage_emails_subject' for Automatic, Always, Never.
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        hamlet.realm_name_in_email_notifications_policy = (
 | 
						|
            UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_ALWAYS
 | 
						|
        )
 | 
						|
        hamlet.save(update_fields=["realm_name_in_email_notifications_policy"])
 | 
						|
        with mock.patch(
 | 
						|
            "zerver.lib.email_notifications.include_realm_name_in_missedmessage_emails_subject",
 | 
						|
            return_value=True,
 | 
						|
        ):
 | 
						|
            is_allowed = include_realm_name_in_missedmessage_emails_subject(hamlet)
 | 
						|
            # Empty the test outbox
 | 
						|
            mail.outbox = []
 | 
						|
            self._realm_name_in_missed_message_email_subject(is_allowed)
 | 
						|
 | 
						|
    def test_message_content_disabled_in_missed_message_notifications(self) -> None:
 | 
						|
        # Test when user disabled message content in email notifications.
 | 
						|
        do_change_user_setting(
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "message_content_in_email_notifications",
 | 
						|
            False,
 | 
						|
            acting_user=None,
 | 
						|
        )
 | 
						|
        self._extra_context_in_missed_stream_messages_mention(show_message_content=False)
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic(
 | 
						|
            show_message_content=False,
 | 
						|
            receiver_is_participant=True,
 | 
						|
        )
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_stream_messages_stream_wildcard_mention_in_followed_topic(
 | 
						|
            show_message_content=False
 | 
						|
        )
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_stream_messages_topic_wildcard_mention(
 | 
						|
            show_message_content=False,
 | 
						|
            receiver_is_participant=True,
 | 
						|
        )
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_stream_messages_stream_wildcard_mention(
 | 
						|
            show_message_content=False
 | 
						|
        )
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_personal_messages(
 | 
						|
            show_message_content=False, message_content_disabled_by_user=True
 | 
						|
        )
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_group_direct_messages_two_others(show_message_content=False)
 | 
						|
 | 
						|
    def test_extra_context_in_missed_stream_messages(self) -> None:
 | 
						|
        self._extra_context_in_missed_stream_messages_mention()
 | 
						|
 | 
						|
    def test_extra_context_in_missed_stream_messages_topic_wildcard_in_followed_topic(
 | 
						|
        self,
 | 
						|
    ) -> None:
 | 
						|
        self._extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic(
 | 
						|
            receiver_is_participant=True
 | 
						|
        )
 | 
						|
 | 
						|
    def test_extra_context_in_missed_stream_messages_topic_wildcard_in_followed_topic_receiver_not_participant(
 | 
						|
        self,
 | 
						|
    ) -> None:
 | 
						|
        self._extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic(
 | 
						|
            receiver_is_participant=False
 | 
						|
        )
 | 
						|
 | 
						|
    def test_extra_context_in_missed_stream_messages_stream_wildcard_in_followed_topic(
 | 
						|
        self,
 | 
						|
    ) -> None:
 | 
						|
        self._extra_context_in_missed_stream_messages_stream_wildcard_mention_in_followed_topic()
 | 
						|
 | 
						|
    def test_extra_context_in_missed_stream_messages_topic_wildcard(self) -> None:
 | 
						|
        self._extra_context_in_missed_stream_messages_topic_wildcard_mention(
 | 
						|
            receiver_is_participant=True
 | 
						|
        )
 | 
						|
 | 
						|
    def test_extra_context_in_missed_stream_messages_topic_wildcard_receiver_not_participant(
 | 
						|
        self,
 | 
						|
    ) -> None:
 | 
						|
        self._extra_context_in_missed_stream_messages_topic_wildcard_mention(
 | 
						|
            receiver_is_participant=False
 | 
						|
        )
 | 
						|
 | 
						|
    def test_extra_context_in_missed_stream_messages_stream_wildcard(self) -> None:
 | 
						|
        self._extra_context_in_missed_stream_messages_stream_wildcard_mention()
 | 
						|
 | 
						|
    def test_extra_context_in_missed_stream_messages_two_senders(self) -> None:
 | 
						|
        self._extra_context_in_missed_stream_messages_mention_two_senders()
 | 
						|
 | 
						|
    def test_reply_to_email_in_missed_personal_messages(self) -> None:
 | 
						|
        self._reply_to_email_in_missed_personal_messages()
 | 
						|
 | 
						|
    def test_extra_context_in_missed_stream_messages_email_notify(self) -> None:
 | 
						|
        self._extra_context_in_missed_stream_messages_email_notify()
 | 
						|
 | 
						|
    def test_resolved_topic_missed_stream_messages_thread_friendly(self) -> None:
 | 
						|
        self._resolved_topic_missed_stream_messages_thread_friendly()
 | 
						|
 | 
						|
    @override_settings(EMAIL_GATEWAY_PATTERN="")
 | 
						|
    def test_reply_warning_in_missed_personal_messages(self) -> None:
 | 
						|
        self._reply_warning_in_missed_personal_messages()
 | 
						|
 | 
						|
    def test_extra_context_in_missed_personal_messages(self) -> None:
 | 
						|
        self._extra_context_in_missed_personal_messages()
 | 
						|
 | 
						|
    def test_extra_context_in_missed_group_direct_messages_two_others(self) -> None:
 | 
						|
        self._extra_context_in_missed_group_direct_messages_two_others()
 | 
						|
 | 
						|
    def test_extra_context_in_missed_group_direct_messages_three_others(self) -> None:
 | 
						|
        self._extra_context_in_missed_group_direct_messages_three_others()
 | 
						|
 | 
						|
    def test_extra_context_in_missed_group_direct_messages_many_others(self) -> None:
 | 
						|
        self._extra_context_in_missed_group_direct_messages_many_others()
 | 
						|
 | 
						|
    def test_deleted_message_in_missed_stream_messages(self) -> None:
 | 
						|
        self._deleted_message_in_missed_stream_messages()
 | 
						|
 | 
						|
    def test_deleted_message_in_missed_personal_messages(self) -> None:
 | 
						|
        self._deleted_message_in_missed_personal_messages()
 | 
						|
 | 
						|
    def test_deleted_message_in_missed_group_direct_messages(self) -> None:
 | 
						|
        self._deleted_message_in_missed_group_direct_messages()
 | 
						|
 | 
						|
    def test_realm_message_content_allowed_in_email_notifications(self) -> None:
 | 
						|
        user = self.example_user("hamlet")
 | 
						|
 | 
						|
        # When message content is allowed at realm level
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        realm.message_content_allowed_in_email_notifications = True
 | 
						|
        realm.save(update_fields=["message_content_allowed_in_email_notifications"])
 | 
						|
 | 
						|
        # Emails have missed message content when message content is enabled by the user
 | 
						|
        do_change_user_setting(
 | 
						|
            user, "message_content_in_email_notifications", True, acting_user=None
 | 
						|
        )
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_personal_messages(show_message_content=True)
 | 
						|
 | 
						|
        # Emails don't have missed message content when message content is disabled by the user
 | 
						|
        do_change_user_setting(
 | 
						|
            user, "message_content_in_email_notifications", False, acting_user=None
 | 
						|
        )
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_personal_messages(
 | 
						|
            show_message_content=False, message_content_disabled_by_user=True
 | 
						|
        )
 | 
						|
 | 
						|
        # When message content is not allowed at realm level
 | 
						|
        # Emails don't have message content irrespective of message content setting of the user
 | 
						|
        realm = get_realm("zulip")
 | 
						|
        realm.message_content_allowed_in_email_notifications = False
 | 
						|
        realm.save(update_fields=["message_content_allowed_in_email_notifications"])
 | 
						|
 | 
						|
        do_change_user_setting(
 | 
						|
            user, "message_content_in_email_notifications", True, acting_user=None
 | 
						|
        )
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_personal_messages(
 | 
						|
            show_message_content=False, message_content_disabled_by_realm=True
 | 
						|
        )
 | 
						|
 | 
						|
        do_change_user_setting(
 | 
						|
            user, "message_content_in_email_notifications", False, acting_user=None
 | 
						|
        )
 | 
						|
        mail.outbox = []
 | 
						|
        self._extra_context_in_missed_personal_messages(
 | 
						|
            show_message_content=False,
 | 
						|
            message_content_disabled_by_user=True,
 | 
						|
            message_content_disabled_by_realm=True,
 | 
						|
        )
 | 
						|
 | 
						|
    def test_realm_emoji_in_missed_message(self) -> None:
 | 
						|
        realm = get_realm("zulip")
 | 
						|
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "Extremely personal message with a realm emoji :green_tick:!",
 | 
						|
        )
 | 
						|
        realm_emoji_dict = get_name_keyed_dict_for_active_realm_emoji(realm.id)
 | 
						|
        realm_emoji_file_name = get_emoji_file_name(
 | 
						|
            "image/png", int(realm_emoji_dict["green_tick"]["id"])
 | 
						|
        )
 | 
						|
        realm_emoji_url = (
 | 
						|
            f"http://zulip.testserver/user_avatars/{realm.id}/emoji/images/{realm_emoji_file_name}"
 | 
						|
        )
 | 
						|
        verify_body_include = [
 | 
						|
            f'<img alt=":green_tick:" src="{realm_emoji_url}" title="green tick" height="20" width="20">'
 | 
						|
        ]
 | 
						|
        email_subject = "DMs with Othello, the Moor of Venice"
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            verify_html_body=True,
 | 
						|
        )
 | 
						|
 | 
						|
    def test_emojiset_in_missed_message(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        hamlet.emojiset = "twitter"
 | 
						|
        hamlet.save(update_fields=["emojiset"])
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "Extremely personal message with a hamburger :hamburger:!",
 | 
						|
        )
 | 
						|
        verify_body_include = [
 | 
						|
            '<img alt=":hamburger:" src="http://testserver/static/generated/emoji/images-twitter-64/1f354.png" title="hamburger" height="20" width="20">'
 | 
						|
        ]
 | 
						|
        email_subject = "DMs with Othello, the Moor of Venice"
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            verify_html_body=True,
 | 
						|
        )
 | 
						|
 | 
						|
    def test_stream_link_in_missed_message(self) -> None:
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "Come and join us in #**Verona**.",
 | 
						|
        )
 | 
						|
        stream_id = get_stream("Verona", get_realm("zulip")).id
 | 
						|
        href = f"http://zulip.testserver/#narrow/channel/{stream_id}-Verona"
 | 
						|
        verify_body_include = [
 | 
						|
            f'<a class="stream" href="{href}" data-stream-id="{stream_id}">#Verona</a'
 | 
						|
        ]
 | 
						|
        email_subject = "DMs with Othello, the Moor of Venice"
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            verify_html_body=True,
 | 
						|
        )
 | 
						|
 | 
						|
    def test_pm_link_in_missed_message_header(self) -> None:
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            cordelia,
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "Let's test a direct message link in email notifications",
 | 
						|
        )
 | 
						|
 | 
						|
        encoded_name = "Cordelia,-Lear's-daughter"
 | 
						|
        verify_body_include = [
 | 
						|
            f"view it in Zulip Dev Zulip: http://zulip.testserver/#narrow/dm/{cordelia.id}-{encoded_name}"
 | 
						|
        ]
 | 
						|
        email_subject = "DMs with Cordelia, Lear's daughter"
 | 
						|
        self._test_cases(msg_id, verify_body_include, email_subject)
 | 
						|
 | 
						|
    def test_pm_link_in_missed_message_header_with_multiple_user_with_the_same_name(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        iago = self.example_user("iago")
 | 
						|
        iago_2 = self.example_user("ZOE")
 | 
						|
        iago_2.full_name = "iago"
 | 
						|
        iago_2.save()
 | 
						|
 | 
						|
        msg_id = self.send_group_direct_message(
 | 
						|
            iago,
 | 
						|
            [hamlet, iago_2],
 | 
						|
            "Group personal message!",
 | 
						|
        )
 | 
						|
 | 
						|
        verify_body_include = ["Iago: > Group personal message! -- Reply"]
 | 
						|
        email_subject = "Group DMs with iago and Iago"
 | 
						|
        self._test_cases(msg_id, verify_body_include, email_subject)
 | 
						|
 | 
						|
    def test_pm_link_in_missed_message_header_using_direct_message_group(self) -> None:
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
 | 
						|
        get_or_create_direct_message_group(id_list=[cordelia.id, hamlet.id])
 | 
						|
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            cordelia,
 | 
						|
            hamlet,
 | 
						|
            "Let's test a direct message link in email notifications",
 | 
						|
        )
 | 
						|
 | 
						|
        encoded_name = "Cordelia,-Lear's-daughter"
 | 
						|
        verify_body_include = [
 | 
						|
            f"view it in Zulip Dev Zulip: http://zulip.testserver/#narrow/dm/{cordelia.id}-{encoded_name}"
 | 
						|
        ]
 | 
						|
        email_subject = "DMs with Cordelia, Lear's daughter"
 | 
						|
        self._test_cases(msg_id, verify_body_include, email_subject)
 | 
						|
 | 
						|
    def test_sender_name_in_missed_message(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        msg_id_1 = self.send_stream_message(
 | 
						|
            self.example_user("iago"), "Denmark", "@**King Hamlet**"
 | 
						|
        )
 | 
						|
        msg_id_2 = self.send_stream_message(self.example_user("iago"), "Verona", "* 1\n *2")
 | 
						|
        msg_id_3 = self.send_personal_message(self.example_user("iago"), hamlet, "Hello")
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                msg_id_1: MissedMessageData(trigger=NotificationTriggers.MENTION),
 | 
						|
                msg_id_2: MissedMessageData(trigger=NotificationTriggers.STREAM_EMAIL),
 | 
						|
                msg_id_3: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        assert isinstance(mail.outbox[0], EmailMultiAlternatives)
 | 
						|
        assert isinstance(mail.outbox[0].alternatives[0][0], str)
 | 
						|
        self.assertIn("Iago:\n> @**King Hamlet**\n\n--\nYou are", mail.outbox[0].body)
 | 
						|
        # If message content starts with <p> tag the sender name is appended inside the <p> tag.
 | 
						|
        self.assertIn(
 | 
						|
            '<p><b>Iago</b>: <span class="user-mention"',
 | 
						|
            mail.outbox[0].alternatives[0][0],
 | 
						|
        )
 | 
						|
 | 
						|
        assert isinstance(mail.outbox[1], EmailMultiAlternatives)
 | 
						|
        assert isinstance(mail.outbox[1].alternatives[0][0], str)
 | 
						|
        self.assertIn("Iago:\n> * 1\n>  *2\n\n--\nYou are receiving", mail.outbox[1].body)
 | 
						|
        # If message content does not starts with <p> tag sender name is appended before the <p> tag
 | 
						|
        self.assertIn(
 | 
						|
            "       <b>Iago</b>: <div><ul>\n<li>1<br>\n *2</li>\n</ul></div>\n",
 | 
						|
            mail.outbox[1].alternatives[0][0],
 | 
						|
        )
 | 
						|
 | 
						|
        assert isinstance(mail.outbox[2], EmailMultiAlternatives)
 | 
						|
        assert isinstance(mail.outbox[2].alternatives[0][0], str)
 | 
						|
        self.assertEqual("> Hello\n\n--\n\nReply", mail.outbox[2].body[:18])
 | 
						|
        # Sender name is not appended to message for missed direct messages
 | 
						|
        self.assertIn(
 | 
						|
            ">\n                    \n                        <div><p>Hello</p></div>\n",
 | 
						|
            mail.outbox[2].alternatives[0][0],
 | 
						|
        )
 | 
						|
 | 
						|
    def test_sender_name_in_missed_pm_using_direct_message_group(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        iago = self.example_user("iago")
 | 
						|
 | 
						|
        get_or_create_direct_message_group(id_list=[hamlet.id, iago.id])
 | 
						|
 | 
						|
        msg_id = self.send_personal_message(iago, hamlet, "Hello")
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {msg_id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)},
 | 
						|
        )
 | 
						|
 | 
						|
        assert isinstance(mail.outbox[0], EmailMultiAlternatives)
 | 
						|
        assert isinstance(mail.outbox[0].alternatives[0][0], str)
 | 
						|
        # Sender name is not appended for missed 1:1 direct messages
 | 
						|
        self.assertEqual("> Hello\n\n--\n\nReply", mail.outbox[0].body[:18])
 | 
						|
        self.assertIn(
 | 
						|
            ">\n                    \n                        <div><p>Hello</p></div>\n",
 | 
						|
            mail.outbox[0].alternatives[0][0],
 | 
						|
        )
 | 
						|
 | 
						|
    def test_your_name_in_missed_pm_to_self_using_direct_message_group(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
 | 
						|
        get_or_create_direct_message_group(id_list=[hamlet.id])
 | 
						|
 | 
						|
        msg_id = self.send_personal_message(hamlet, hamlet, "Hello", read_by_sender=False)
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {msg_id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)},
 | 
						|
        )
 | 
						|
 | 
						|
        assert isinstance(mail.outbox[0], EmailMultiAlternatives)
 | 
						|
        self.assertEqual(mail.outbox[0].subject, "DMs with King Hamlet")
 | 
						|
        assert isinstance(mail.outbox[0].alternatives[0][0], str)
 | 
						|
        # Sender name is not appended for missed 1:1 direct messages
 | 
						|
        self.assertEqual("> Hello\n\n--\n\nReply", mail.outbox[0].body[:18])
 | 
						|
        self.assertIn(
 | 
						|
            ">\n                    \n                        <div><p>Hello</p></div>\n",
 | 
						|
            mail.outbox[0].alternatives[0][0],
 | 
						|
        )
 | 
						|
 | 
						|
    def test_multiple_missed_personal_messages(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        msg_id_1 = self.send_personal_message(
 | 
						|
            self.example_user("othello"), hamlet, "Personal Message 1"
 | 
						|
        )
 | 
						|
        msg_id_2 = self.send_personal_message(
 | 
						|
            self.example_user("iago"), hamlet, "Personal Message 2"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                msg_id_1: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE),
 | 
						|
                msg_id_2: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE),
 | 
						|
            },
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 2)
 | 
						|
        email_subject = "DMs with Othello, the Moor of Venice"
 | 
						|
        self.assertEqual(mail.outbox[0].subject, email_subject)
 | 
						|
        email_subject = "DMs with Iago"
 | 
						|
        self.assertEqual(mail.outbox[1].subject, email_subject)
 | 
						|
 | 
						|
    def test_multiple_stream_messages(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
        iago = self.example_user("iago")
 | 
						|
 | 
						|
        message_ids: dict[int, MissedMessageData] = {}
 | 
						|
        for i in range(1, 4):
 | 
						|
            msg_id = self.send_stream_message(othello, "Denmark", content=str(i))
 | 
						|
            message_ids[msg_id] = MissedMessageData(trigger=NotificationTriggers.STREAM_EMAIL)
 | 
						|
        for i in range(4, 7):
 | 
						|
            msg_id = self.send_stream_message(iago, "Denmark", content=str(i))
 | 
						|
            message_ids[msg_id] = MissedMessageData(trigger=NotificationTriggers.STREAM_EMAIL)
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            message_ids,
 | 
						|
        )
 | 
						|
 | 
						|
        email_subject = "#Denmark > test"
 | 
						|
        verify_body_include = [
 | 
						|
            "Othello, the Moor of Venice: > 1 > 2 > 3 Iago: > 4 > 5 > 6 -- ",
 | 
						|
            "You are receiving this because you have email notifications enabled for #Denmark.",
 | 
						|
        ]
 | 
						|
        self.assert_length(mail.outbox, 1)
 | 
						|
        msg = mail.outbox[0]
 | 
						|
        self.assertEqual(msg.subject, email_subject)
 | 
						|
        for text in verify_body_include:
 | 
						|
            self.assertIn(text, self.normalize_string(msg.body))
 | 
						|
 | 
						|
    def test_multiple_stream_messages_and_mentions(self) -> None:
 | 
						|
        """Subject should be stream name and topic as usual."""
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        msg_id_1 = self.send_stream_message(self.example_user("iago"), "Denmark", "Regular message")
 | 
						|
        msg_id_2 = self.send_stream_message(
 | 
						|
            self.example_user("othello"), "Denmark", "@**King Hamlet**"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                msg_id_1: MissedMessageData(trigger=NotificationTriggers.STREAM_EMAIL),
 | 
						|
                msg_id_2: MissedMessageData(trigger=NotificationTriggers.MENTION),
 | 
						|
            },
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 1)
 | 
						|
        email_subject = "#Denmark > test"
 | 
						|
        self.assertEqual(mail.outbox[0].subject, email_subject)
 | 
						|
 | 
						|
    def test_message_access_in_emails(self) -> None:
 | 
						|
        # Messages sent to a protected history-private stream shouldn't be
 | 
						|
        # accessible/available in emails before subscribing
 | 
						|
        stream_name = "private_stream"
 | 
						|
        self.make_stream(stream_name, invite_only=True, history_public_to_subscribers=False)
 | 
						|
        user = self.example_user("iago")
 | 
						|
        self.subscribe(user, stream_name)
 | 
						|
        late_subscribed_user = self.example_user("hamlet")
 | 
						|
 | 
						|
        self.send_stream_message(user, stream_name, "Before subscribing")
 | 
						|
 | 
						|
        self.subscribe(late_subscribed_user, stream_name)
 | 
						|
 | 
						|
        self.send_stream_message(user, stream_name, "After subscribing")
 | 
						|
 | 
						|
        mention_msg_id = self.send_stream_message(user, stream_name, "@**King Hamlet**")
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            late_subscribed_user.id,
 | 
						|
            {mention_msg_id: MissedMessageData(trigger=NotificationTriggers.MENTION)},
 | 
						|
        )
 | 
						|
 | 
						|
        self.assert_length(mail.outbox, 1)
 | 
						|
        self.assertEqual(mail.outbox[0].subject, "#private_stream > test")  # email subject
 | 
						|
        email_text = mail.outbox[0].message().as_string()
 | 
						|
        self.assertNotIn("Before subscribing", email_text)
 | 
						|
        self.assertIn("After subscribing", email_text)
 | 
						|
        self.assertIn("@**King Hamlet**", email_text)
 | 
						|
 | 
						|
    def test_stream_mentions_multiple_people(self) -> None:
 | 
						|
        """Subject should be stream name and topic as usual."""
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
 | 
						|
        self.subscribe(cordelia, "Denmark")
 | 
						|
 | 
						|
        msg_id_1 = self.send_stream_message(
 | 
						|
            self.example_user("iago"), "Denmark", "@**King Hamlet**"
 | 
						|
        )
 | 
						|
        msg_id_2 = self.send_stream_message(
 | 
						|
            self.example_user("othello"), "Denmark", "@**King Hamlet**"
 | 
						|
        )
 | 
						|
        msg_id_3 = self.send_stream_message(cordelia, "Denmark", "Regular message")
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                msg_id_1: MissedMessageData(trigger=NotificationTriggers.MENTION),
 | 
						|
                msg_id_2: MissedMessageData(trigger=NotificationTriggers.MENTION),
 | 
						|
                msg_id_3: MissedMessageData(trigger=NotificationTriggers.STREAM_EMAIL),
 | 
						|
            },
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 1)
 | 
						|
        email_subject = "#Denmark > test"
 | 
						|
        self.assertEqual(mail.outbox[0].subject, email_subject)
 | 
						|
 | 
						|
    def test_multiple_stream_messages_different_topics(self) -> None:
 | 
						|
        """Should receive separate emails for each topic within a stream."""
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        msg_id_1 = self.send_stream_message(self.example_user("othello"), "Denmark", "Message1")
 | 
						|
        msg_id_2 = self.send_stream_message(
 | 
						|
            self.example_user("iago"), "Denmark", "Message2", topic_name="test2"
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                msg_id_1: MissedMessageData(trigger=NotificationTriggers.STREAM_EMAIL),
 | 
						|
                msg_id_2: MissedMessageData(trigger=NotificationTriggers.STREAM_EMAIL),
 | 
						|
            },
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 2)
 | 
						|
        email_subjects = {mail.outbox[0].subject, mail.outbox[1].subject}
 | 
						|
        valid_email_subjects = {"#Denmark > test", "#Denmark > test2"}
 | 
						|
        self.assertEqual(email_subjects, valid_email_subjects)
 | 
						|
 | 
						|
    def test_relative_to_full_url(self) -> None:
 | 
						|
        def convert(test_data: str) -> str:
 | 
						|
            fragment = lxml.html.fragment_fromstring(test_data, create_parent=True)
 | 
						|
            relative_to_full_url(fragment, "http://example.com")
 | 
						|
            return lxml.html.tostring(fragment, encoding="unicode")
 | 
						|
 | 
						|
        zulip_realm = get_realm("zulip")
 | 
						|
        zephyr_realm = get_realm("zephyr")
 | 
						|
        # Run `relative_to_full_url()` function over test fixtures present in
 | 
						|
        # 'markdown_test_cases.json' and check that it converts all the relative
 | 
						|
        # URLs to absolute URLs.
 | 
						|
        fixtures = orjson.loads(self.fixture_data("markdown_test_cases.json"))
 | 
						|
        test_fixtures = {}
 | 
						|
        for test in fixtures["regular_tests"]:
 | 
						|
            test_fixtures[test["name"]] = test
 | 
						|
        for test_name in test_fixtures:
 | 
						|
            test_data = test_fixtures[test_name]["expected_output"]
 | 
						|
            output_data = convert(test_data)
 | 
						|
            if re.search(r"""(?<=\=['"])/(?=[^<]+>)""", output_data) is not None:
 | 
						|
                raise AssertionError(
 | 
						|
                    "Relative URL present in email: "
 | 
						|
                    + output_data
 | 
						|
                    + "\nFailed test case's name is: "
 | 
						|
                    + test_name
 | 
						|
                    + "\nIt is present in markdown_test_cases.json"
 | 
						|
                )
 | 
						|
 | 
						|
        # Specific test cases.
 | 
						|
 | 
						|
        # A path similar to our emoji path, but not in a link:
 | 
						|
        test_data = "<p>Check out the file at: '/static/generated/emoji/images/emoji/'</p>"
 | 
						|
        actual_output = convert(test_data)
 | 
						|
        expected_output = (
 | 
						|
            "<div><p>Check out the file at: '/static/generated/emoji/images/emoji/'</p></div>"
 | 
						|
        )
 | 
						|
        self.assertEqual(actual_output, expected_output)
 | 
						|
 | 
						|
        # An uploaded file
 | 
						|
        test_data = '<a href="/user_uploads/{realm_id}/1f/some_random_value">/user_uploads/{realm_id}/1f/some_random_value</a>'
 | 
						|
        test_data = test_data.format(realm_id=zephyr_realm.id)
 | 
						|
        actual_output = convert(test_data)
 | 
						|
        expected_output = (
 | 
						|
            '<div><a href="http://example.com/user_uploads/{realm_id}/1f/some_random_value">'
 | 
						|
            "/user_uploads/{realm_id}/1f/some_random_value</a></div>"
 | 
						|
        )
 | 
						|
        expected_output = expected_output.format(realm_id=zephyr_realm.id)
 | 
						|
        self.assertEqual(actual_output, expected_output)
 | 
						|
 | 
						|
        # A profile picture like syntax, but not actually in an HTML tag
 | 
						|
        test_data = '<p>Set src="/avatar/username@example.com?s=30"</p>'
 | 
						|
        actual_output = convert(test_data)
 | 
						|
        expected_output = '<div><p>Set src="/avatar/username@example.com?s=30"</p></div>'
 | 
						|
        self.assertEqual(actual_output, expected_output)
 | 
						|
 | 
						|
        # A narrow URL which begins with a '#'.
 | 
						|
        test_data = (
 | 
						|
            '<p><a href="#narrow/channel/test/topic/test.20topic/near/142"'
 | 
						|
            ' title="#narrow/channel/test/topic/test.20topic/near/142">Conversation</a></p>'
 | 
						|
        )
 | 
						|
        actual_output = convert(test_data)
 | 
						|
        expected_output = (
 | 
						|
            '<div><p><a href="http://example.com/#narrow/channel/test/topic/test.20topic/near/142"'
 | 
						|
            ' title="http://example.com/#narrow/channel/test/topic/test.20topic/near/142">Conversation</a></p></div>'
 | 
						|
        )
 | 
						|
        self.assertEqual(actual_output, expected_output)
 | 
						|
 | 
						|
        # Scrub inline images.
 | 
						|
        test_data = (
 | 
						|
            "<p>See this <a"
 | 
						|
            ' href="/user_uploads/{realm_id}/52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg"'
 | 
						|
            ' target="_blank" title="avatar_103.jpeg">avatar_103.jpeg</a>.</p>'
 | 
						|
            '<div class="message_inline_image"><a'
 | 
						|
            ' href="/user_uploads/{realm_id}/52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg"'
 | 
						|
            ' target="_blank" title="avatar_103.jpeg"><img'
 | 
						|
            ' src="/user_uploads/{realm_id}/52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg"></a></div>'
 | 
						|
        )
 | 
						|
        test_data = test_data.format(realm_id=zulip_realm.id)
 | 
						|
        actual_output = convert(test_data)
 | 
						|
        expected_output = (
 | 
						|
            "<div><p>See this <a"
 | 
						|
            ' href="http://example.com/user_uploads/{realm_id}/52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg"'
 | 
						|
            ' target="_blank" title="avatar_103.jpeg">avatar_103.jpeg</a>.</p></div>'
 | 
						|
        )
 | 
						|
        expected_output = expected_output.format(realm_id=zulip_realm.id)
 | 
						|
        self.assertEqual(actual_output, expected_output)
 | 
						|
 | 
						|
        # A message containing only an inline image URL preview, we do
 | 
						|
        # somewhat more extensive surgery.
 | 
						|
        test_data = (
 | 
						|
            '<div class="message_inline_image"><a'
 | 
						|
            ' href="https://www.google.com/images/srpr/logo4w.png"'
 | 
						|
            ' target="_blank" title="https://www.google.com/images/srpr/logo4w.png">'
 | 
						|
            '<img data-src-fullsize="/thumbnail/https%3A//www.google.com/images/srpr/logo4w.png?size=0x0"'
 | 
						|
            ' src="/thumbnail/https%3A//www.google.com/images/srpr/logo4w.png?size=0x100"></a></div>'
 | 
						|
        )
 | 
						|
        actual_output = convert(test_data)
 | 
						|
        expected_output = (
 | 
						|
            '<div><p><a href="https://www.google.com/images/srpr/logo4w.png"'
 | 
						|
            ' target="_blank" title="https://www.google.com/images/srpr/logo4w.png">'
 | 
						|
            "https://www.google.com/images/srpr/logo4w.png</a></p></div>"
 | 
						|
        )
 | 
						|
        self.assertEqual(actual_output, expected_output)
 | 
						|
 | 
						|
    def test_spoilers_in_html_emails(self) -> None:
 | 
						|
        test_data = '<div class="spoiler-block"><div class="spoiler-header">\n\n<p><a>header</a> text</p>\n</div><div class="spoiler-content" aria-hidden="true">\n\n<p>content</p>\n</div></div>\n\n<p>outside spoiler</p>'
 | 
						|
        fragment = lxml.html.fromstring(test_data)
 | 
						|
        fix_spoilers_in_html(fragment, "en")
 | 
						|
        actual_output = lxml.html.tostring(fragment, encoding="unicode")
 | 
						|
        expected_output = '<div><div class="spoiler-block">\n\n<p><a>header</a> text <span class="spoiler-title" title="Open Zulip to see the spoiler content">(Open Zulip to see the spoiler content)</span></p>\n</div>\n\n<p>outside spoiler</p></div>'
 | 
						|
        self.assertEqual(actual_output, expected_output)
 | 
						|
 | 
						|
        # test against our markdown_test_cases so these features do not get out of sync.
 | 
						|
        fixtures = orjson.loads(self.fixture_data("markdown_test_cases.json"))
 | 
						|
        test_fixtures = {}
 | 
						|
        for test in fixtures["regular_tests"]:
 | 
						|
            if "spoiler" in test["name"]:
 | 
						|
                test_fixtures[test["name"]] = test
 | 
						|
        for test_name in test_fixtures:
 | 
						|
            fragment = lxml.html.fromstring(test_fixtures[test_name]["expected_output"])
 | 
						|
            fix_spoilers_in_html(fragment, "en")
 | 
						|
            output_data = lxml.html.tostring(fragment, encoding="unicode")
 | 
						|
            assert "spoiler-header" not in output_data
 | 
						|
            assert "spoiler-content" not in output_data
 | 
						|
            assert "spoiler-block" in output_data
 | 
						|
            assert "spoiler-title" in output_data
 | 
						|
 | 
						|
    def test_spoilers_in_text_emails(self) -> None:
 | 
						|
        content = "@**King Hamlet**\n\n```spoiler header text\nsecret-text\n```"
 | 
						|
        msg_id = self.send_stream_message(self.example_user("othello"), "Denmark", content)
 | 
						|
        verify_body_include = ["header text", "Open Zulip to see the spoiler content"]
 | 
						|
        verify_body_does_not_include = ["secret-text"]
 | 
						|
        email_subject = "#Denmark > test"
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            trigger=NotificationTriggers.MENTION,
 | 
						|
            verify_body_does_not_include=verify_body_does_not_include,
 | 
						|
        )
 | 
						|
 | 
						|
    def test_fix_emoji(self) -> None:
 | 
						|
        # An emoji.
 | 
						|
        test_data = (
 | 
						|
            '<p>See <span aria-label="cloud with lightning and rain" class="emoji emoji-26c8"'
 | 
						|
            ' role="img" title="cloud with lightning and'
 | 
						|
            ' rain">:cloud_with_lightning_and_rain:</span>.</p>'
 | 
						|
        )
 | 
						|
        fragment = lxml.html.fromstring(test_data)
 | 
						|
        fix_emojis(fragment, "google")
 | 
						|
        actual_output = lxml.html.tostring(fragment, encoding="unicode")
 | 
						|
        expected_output = (
 | 
						|
            '<p>See <img alt=":cloud_with_lightning_and_rain:"'
 | 
						|
            ' src="http://testserver/static/generated/emoji/images-google-64/26c8.png"'
 | 
						|
            ' title="cloud with lightning and rain" height="20" width="20">.</p>'
 | 
						|
        )
 | 
						|
        self.assertEqual(actual_output, expected_output)
 | 
						|
 | 
						|
    def test_latex_math_formulas_in_email(self) -> None:
 | 
						|
        msg_id = self.send_stream_message(
 | 
						|
            self.example_user("iago"), "Denmark", "Equation: $$d^* = +\\infty$$ is correct."
 | 
						|
        )
 | 
						|
        verify_body_include = ["Equation: <span>$$d^* = +\\infty$$</span> is correct"]
 | 
						|
        email_subject = "#Denmark > test"
 | 
						|
        self._test_cases(
 | 
						|
            msg_id,
 | 
						|
            verify_body_include,
 | 
						|
            email_subject,
 | 
						|
            verify_html_body=True,
 | 
						|
            trigger=NotificationTriggers.STREAM_EMAIL,
 | 
						|
        )
 | 
						|
 | 
						|
    def test_empty_backticks_in_missed_message(self) -> None:
 | 
						|
        msg_id = self.send_personal_message(
 | 
						|
            self.example_user("othello"),
 | 
						|
            self.example_user("hamlet"),
 | 
						|
            "```\n```",
 | 
						|
        )
 | 
						|
        verify_body_include = ["view it in Zulip Dev Zulip"]
 | 
						|
        email_subject = "DMs with Othello, the Moor of Venice"
 | 
						|
        self._test_cases(msg_id, verify_body_include, email_subject, verify_html_body=True)
 | 
						|
 | 
						|
    @override_settings(MAX_GROUP_SIZE_FOR_MENTION_REACTIVATION=2)
 | 
						|
    def test_long_term_idle_user_missed_message(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
        cordelia = self.example_user("cordelia")
 | 
						|
        zulip_realm = get_realm("zulip")
 | 
						|
 | 
						|
        # user groups having upto 'MAX_GROUP_SIZE_FOR_MENTION_REACTIVATION'
 | 
						|
        # members are small user groups.
 | 
						|
        small_user_group = check_add_user_group(
 | 
						|
            zulip_realm, "small_user_group", [hamlet, othello], acting_user=hamlet
 | 
						|
        )
 | 
						|
 | 
						|
        large_user_group = check_add_user_group(
 | 
						|
            zulip_realm, "large_user_group", [hamlet], acting_user=hamlet
 | 
						|
        )
 | 
						|
        subgroup = check_add_user_group(
 | 
						|
            zulip_realm, "subgroup", [othello, cordelia], acting_user=hamlet
 | 
						|
        )
 | 
						|
        add_subgroups_to_user_group(large_user_group, [subgroup], acting_user=None)
 | 
						|
 | 
						|
        def reset_hamlet_as_soft_deactivated_user() -> None:
 | 
						|
            nonlocal hamlet
 | 
						|
            hamlet = self.example_user("hamlet")
 | 
						|
            self.soft_deactivate_user(hamlet)
 | 
						|
 | 
						|
        # Do note that the event dicts for the missed messages are constructed by hand
 | 
						|
        # The part of testing the consumption of missed messages by the worker is left to
 | 
						|
        # test_queue_worker.test_missed_message_worker
 | 
						|
 | 
						|
        # Personal mention in a stream message should soft reactivate the user
 | 
						|
        def send_personal_mention() -> None:
 | 
						|
            mention = f"@**{hamlet.full_name}**"
 | 
						|
            stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | 
						|
            self.handle_missedmessage_emails(
 | 
						|
                hamlet.id,
 | 
						|
                {
 | 
						|
                    stream_mentioned_message_id: MissedMessageData(
 | 
						|
                        trigger=NotificationTriggers.MENTION
 | 
						|
                    )
 | 
						|
                },
 | 
						|
            )
 | 
						|
 | 
						|
        reset_hamlet_as_soft_deactivated_user()
 | 
						|
        self.expect_soft_reactivation(hamlet, send_personal_mention)
 | 
						|
 | 
						|
        # Direct message should soft reactivate the user
 | 
						|
        def send_direct_message() -> None:
 | 
						|
            # Soft reactivate the user by sending a personal message
 | 
						|
            personal_message_id = self.send_personal_message(othello, hamlet, "Message")
 | 
						|
            self.handle_missedmessage_emails(
 | 
						|
                hamlet.id,
 | 
						|
                {
 | 
						|
                    personal_message_id: MissedMessageData(
 | 
						|
                        trigger=NotificationTriggers.DIRECT_MESSAGE
 | 
						|
                    )
 | 
						|
                },
 | 
						|
            )
 | 
						|
 | 
						|
        reset_hamlet_as_soft_deactivated_user()
 | 
						|
        self.expect_soft_reactivation(hamlet, send_direct_message)
 | 
						|
 | 
						|
        # Hamlet FOLLOWS the topic.
 | 
						|
        # 'wildcard_mentions_notify' is disabled to verify the corner case when only
 | 
						|
        # 'enable_followed_topic_wildcard_mentions_notify' is enabled (True by default).
 | 
						|
        do_set_user_topic_visibility_policy(
 | 
						|
            hamlet,
 | 
						|
            get_stream("Denmark", hamlet.realm),
 | 
						|
            "test",
 | 
						|
            visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
 | 
						|
        )
 | 
						|
        do_change_user_setting(hamlet, "wildcard_mentions_notify", False, acting_user=None)
 | 
						|
 | 
						|
        # Topic wildcard mention in followed topic should soft reactivate the user
 | 
						|
        # hamlet should be a topic participant
 | 
						|
        self.send_stream_message(hamlet, "Denmark", "test message")
 | 
						|
 | 
						|
        def send_topic_wildcard_mention() -> None:
 | 
						|
            mention = "@**topic**"
 | 
						|
            stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | 
						|
            self.handle_missedmessage_emails(
 | 
						|
                hamlet.id,
 | 
						|
                {
 | 
						|
                    stream_mentioned_message_id: MissedMessageData(
 | 
						|
                        trigger=NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC
 | 
						|
                    ),
 | 
						|
                },
 | 
						|
            )
 | 
						|
 | 
						|
        reset_hamlet_as_soft_deactivated_user()
 | 
						|
        self.expect_soft_reactivation(hamlet, send_topic_wildcard_mention)
 | 
						|
 | 
						|
        # Stream wildcard mention in followed topic should NOT soft reactivate the user
 | 
						|
        def send_stream_wildcard_mention() -> None:
 | 
						|
            mention = "@**all**"
 | 
						|
            stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | 
						|
            self.handle_missedmessage_emails(
 | 
						|
                hamlet.id,
 | 
						|
                {
 | 
						|
                    stream_mentioned_message_id: MissedMessageData(
 | 
						|
                        trigger=NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC
 | 
						|
                    ),
 | 
						|
                },
 | 
						|
            )
 | 
						|
 | 
						|
        reset_hamlet_as_soft_deactivated_user()
 | 
						|
        self.expect_to_stay_long_term_idle(hamlet, send_stream_wildcard_mention)
 | 
						|
 | 
						|
        # Reset
 | 
						|
        do_set_user_topic_visibility_policy(
 | 
						|
            hamlet,
 | 
						|
            get_stream("Denmark", hamlet.realm),
 | 
						|
            "test",
 | 
						|
            visibility_policy=UserTopic.VisibilityPolicy.INHERIT,
 | 
						|
        )
 | 
						|
        do_change_user_setting(hamlet, "wildcard_mentions_notify", True, acting_user=None)
 | 
						|
 | 
						|
        # Topic Wildcard mention should soft reactivate the user
 | 
						|
        reset_hamlet_as_soft_deactivated_user()
 | 
						|
        self.expect_soft_reactivation(hamlet, send_topic_wildcard_mention)
 | 
						|
 | 
						|
        # Stream Wildcard mention should NOT soft reactivate the user
 | 
						|
        reset_hamlet_as_soft_deactivated_user()
 | 
						|
        self.expect_to_stay_long_term_idle(hamlet, send_stream_wildcard_mention)
 | 
						|
 | 
						|
        # Small group mention should soft reactivate the user
 | 
						|
        def send_small_group_mention() -> None:
 | 
						|
            mention = "@*small_user_group*"
 | 
						|
            stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | 
						|
            self.handle_missedmessage_emails(
 | 
						|
                hamlet.id,
 | 
						|
                {
 | 
						|
                    stream_mentioned_message_id: MissedMessageData(
 | 
						|
                        trigger=NotificationTriggers.MENTION,
 | 
						|
                        mentioned_user_group_id=small_user_group.id,
 | 
						|
                    ),
 | 
						|
                },
 | 
						|
            )
 | 
						|
 | 
						|
        reset_hamlet_as_soft_deactivated_user()
 | 
						|
        self.expect_soft_reactivation(hamlet, send_small_group_mention)
 | 
						|
 | 
						|
        # Large group mention should NOT soft reactivate the user
 | 
						|
        def send_large_group_mention() -> None:
 | 
						|
            mention = "@*large_user_group*"
 | 
						|
            stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | 
						|
            self.handle_missedmessage_emails(
 | 
						|
                hamlet.id,
 | 
						|
                {
 | 
						|
                    stream_mentioned_message_id: MissedMessageData(
 | 
						|
                        trigger=NotificationTriggers.MENTION,
 | 
						|
                        mentioned_user_group_id=large_user_group.id,
 | 
						|
                    ),
 | 
						|
                },
 | 
						|
            )
 | 
						|
 | 
						|
        reset_hamlet_as_soft_deactivated_user()
 | 
						|
        self.expect_to_stay_long_term_idle(hamlet, send_large_group_mention)
 | 
						|
 | 
						|
    def test_followed_topic_missed_message(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
        msg_id = self.send_stream_message(othello, "Denmark")
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {msg_id: MissedMessageData(trigger=NotificationTriggers.FOLLOWED_TOPIC_EMAIL)},
 | 
						|
        )
 | 
						|
        self.assert_length(mail.outbox, 1)
 | 
						|
        email_subject = mail.outbox[0].subject
 | 
						|
        email_body = mail.outbox[0].body
 | 
						|
        self.assertEqual("#Denmark > test", email_subject)
 | 
						|
        self.assertIn(
 | 
						|
            "You are receiving this because you have email notifications enabled for topics you follow.",
 | 
						|
            email_body,
 | 
						|
        )
 | 
						|
 | 
						|
    def test_empty_string_topic_missed_message(self) -> None:
 | 
						|
        hamlet = self.example_user("hamlet")
 | 
						|
        othello = self.example_user("othello")
 | 
						|
 | 
						|
        message_id = self.send_stream_message(
 | 
						|
            othello,
 | 
						|
            "Denmark",
 | 
						|
            content="@**topic**",
 | 
						|
            topic_name="",
 | 
						|
        )
 | 
						|
 | 
						|
        self.handle_missedmessage_emails(
 | 
						|
            hamlet.id,
 | 
						|
            {
 | 
						|
                message_id: MissedMessageData(trigger=NotificationTriggers.TOPIC_WILDCARD_MENTION),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
        expected_email_subject = f"#Denmark > {Message.EMPTY_TOPIC_FALLBACK_NAME}"
 | 
						|
        expected_email_body_includes = f"You are receiving this because all topic participants were mentioned in #Denmark > {Message.EMPTY_TOPIC_FALLBACK_NAME}."
 | 
						|
        self.assertEqual(mail.outbox[0].subject, expected_email_subject)
 | 
						|
        self.assertIn(expected_email_body_includes, self.normalize_string(mail.outbox[0].body))
 |