2017-03-08 12:28:24 +01:00
|
|
|
import random
|
|
|
|
import re
|
2017-09-21 01:11:49 +02:00
|
|
|
import ujson
|
2018-11-29 16:32:17 +01:00
|
|
|
import ldap
|
2017-03-08 12:28:24 +01:00
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.core import mail
|
|
|
|
from django.test import override_settings
|
2018-11-29 16:32:17 +01:00
|
|
|
from django_auth_ldap.config import LDAPSearch
|
2017-07-02 04:49:34 +02:00
|
|
|
from email.utils import formataddr
|
2017-03-08 12:28:24 +01:00
|
|
|
from mock import patch, MagicMock
|
2019-02-02 23:53:44 +01:00
|
|
|
from typing import List, Optional
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2019-03-15 18:51:39 +01:00
|
|
|
from zerver.lib.email_notifications import fix_emojis, handle_missedmessage_emails, \
|
2018-11-14 12:46:56 +01:00
|
|
|
enqueue_welcome_emails, relative_to_full_url
|
2019-02-02 23:53:44 +01:00
|
|
|
from zerver.lib.actions import do_change_notification_settings
|
2017-03-08 12:28:24 +01:00
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
2017-06-26 19:43:32 +02:00
|
|
|
from zerver.lib.send_email import FromAddress
|
2018-11-14 12:46:56 +01:00
|
|
|
from zerver.lib.test_helpers import MockLDAP
|
2017-05-08 17:54:11 +02:00
|
|
|
from zerver.models import (
|
2018-02-15 21:02:47 +01:00
|
|
|
get_realm,
|
|
|
|
get_stream,
|
2017-05-08 17:54:11 +02:00
|
|
|
UserProfile,
|
2018-11-14 12:46:56 +01:00
|
|
|
ScheduledEmail
|
2017-05-08 17:54:11 +02:00
|
|
|
)
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2018-11-14 12:46:56 +01:00
|
|
|
class TestFollowupEmails(ZulipTestCase):
|
|
|
|
def test_day1_email_context(self) -> None:
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
enqueue_welcome_emails(hamlet)
|
2019-01-04 01:50:21 +01:00
|
|
|
scheduled_emails = ScheduledEmail.objects.filter(users=hamlet)
|
2018-11-14 12:46:56 +01:00
|
|
|
email_data = ujson.loads(scheduled_emails[0].data)
|
|
|
|
self.assertEqual(email_data["context"]["email"], self.example_email("hamlet"))
|
|
|
|
self.assertEqual(email_data["context"]["is_realm_admin"], False)
|
|
|
|
self.assertEqual(email_data["context"]["getting_started_link"], "https://zulipchat.com")
|
|
|
|
self.assertNotIn("ldap_username", email_data["context"])
|
|
|
|
|
|
|
|
ScheduledEmail.objects.all().delete()
|
|
|
|
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
enqueue_welcome_emails(iago)
|
2019-01-04 01:50:21 +01:00
|
|
|
scheduled_emails = ScheduledEmail.objects.filter(users=iago)
|
2018-11-14 12:46:56 +01:00
|
|
|
email_data = ujson.loads(scheduled_emails[0].data)
|
|
|
|
self.assertEqual(email_data["context"]["email"], self.example_email("iago"))
|
|
|
|
self.assertEqual(email_data["context"]["is_realm_admin"], True)
|
|
|
|
self.assertEqual(email_data["context"]["getting_started_link"],
|
|
|
|
"http://zulip.testserver/help/getting-your-organization-started-with-zulip")
|
|
|
|
self.assertNotIn("ldap_username", email_data["context"])
|
|
|
|
|
2018-11-29 16:32:17 +01:00
|
|
|
# See https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap-including-active-directory
|
|
|
|
# for case details.
|
2018-11-14 12:46:56 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',
|
|
|
|
'zproject.backends.ZulipDummyBackend'))
|
2018-11-29 16:32:17 +01:00
|
|
|
def test_day1_email_ldap_case_a_login_credentials(self) -> None:
|
|
|
|
ldap_user_attr_map = {'full_name': 'fn', 'short_name': 'sn'}
|
|
|
|
|
|
|
|
ldap_patcher = patch('django_auth_ldap.config.ldap.initialize')
|
|
|
|
mock_initialize = ldap_patcher.start()
|
|
|
|
mock_ldap = MockLDAP()
|
|
|
|
mock_initialize.return_value = mock_ldap
|
|
|
|
|
|
|
|
mock_ldap.directory = {
|
|
|
|
"uid=newuser@zulip.com,ou=users,dc=zulip,dc=com": {
|
2019-01-16 08:36:27 +01:00
|
|
|
'userPassword': ['testing', ],
|
2018-11-29 16:32:17 +01:00
|
|
|
'fn': ['full_name'],
|
|
|
|
'sn': ['shortname'],
|
|
|
|
}
|
|
|
|
}
|
2018-11-14 12:46:56 +01:00
|
|
|
|
2018-11-29 16:32:17 +01:00
|
|
|
ldap_search = LDAPSearch("ou=users,dc=zulip,dc=com", ldap.SCOPE_SUBTREE, "(email=%(user)s)")
|
|
|
|
with self.settings(
|
|
|
|
AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map,
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com',
|
|
|
|
AUTH_LDAP_USER_SEARCH=ldap_search):
|
|
|
|
self.login_with_return("newuser@zulip.com", "testing")
|
|
|
|
user = UserProfile.objects.get(email="newuser@zulip.com")
|
2019-01-04 01:50:21 +01:00
|
|
|
scheduled_emails = ScheduledEmail.objects.filter(users=user)
|
2018-11-29 16:32:17 +01:00
|
|
|
|
|
|
|
self.assertEqual(len(scheduled_emails), 2)
|
|
|
|
email_data = ujson.loads(scheduled_emails[0].data)
|
|
|
|
self.assertEqual(email_data["context"]["ldap"], True)
|
|
|
|
self.assertEqual(email_data["context"]["ldap_username"], "newuser@zulip.com")
|
|
|
|
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',
|
|
|
|
'zproject.backends.ZulipDummyBackend'))
|
|
|
|
def test_day1_email_ldap_case_b_login_credentials(self) -> None:
|
2018-11-14 12:46:56 +01:00
|
|
|
ldap_user_attr_map = {'full_name': 'fn', 'short_name': 'sn'}
|
|
|
|
|
|
|
|
ldap_patcher = patch('django_auth_ldap.config.ldap.initialize')
|
|
|
|
mock_initialize = ldap_patcher.start()
|
|
|
|
mock_ldap = MockLDAP()
|
|
|
|
mock_initialize.return_value = mock_ldap
|
|
|
|
|
|
|
|
mock_ldap.directory = {
|
|
|
|
'uid=newuser,ou=users,dc=zulip,dc=com': {
|
2019-01-16 08:36:27 +01:00
|
|
|
'userPassword': ['testing', ],
|
2018-11-29 16:32:17 +01:00
|
|
|
'fn': ['full_name'],
|
2018-11-14 12:46:56 +01:00
|
|
|
'sn': ['shortname'],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='zulip.com',
|
|
|
|
AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map,
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
|
|
|
|
2018-11-29 16:32:17 +01:00
|
|
|
self.login_with_return("newuser@zulip.com", "testing")
|
|
|
|
|
|
|
|
user = UserProfile.objects.get(email="newuser@zulip.com")
|
2019-01-04 01:50:21 +01:00
|
|
|
scheduled_emails = ScheduledEmail.objects.filter(users=user)
|
2018-11-29 16:32:17 +01:00
|
|
|
|
|
|
|
self.assertEqual(len(scheduled_emails), 2)
|
|
|
|
email_data = ujson.loads(scheduled_emails[0].data)
|
|
|
|
self.assertEqual(email_data["context"]["ldap"], True)
|
|
|
|
self.assertEqual(email_data["context"]["ldap_username"], "newuser")
|
|
|
|
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',
|
|
|
|
'zproject.backends.ZulipDummyBackend'))
|
|
|
|
def test_day1_email_ldap_case_c_login_credentials(self) -> None:
|
|
|
|
ldap_user_attr_map = {'full_name': 'fn', 'short_name': 'sn'}
|
|
|
|
|
|
|
|
ldap_patcher = patch('django_auth_ldap.config.ldap.initialize')
|
|
|
|
mock_initialize = ldap_patcher.start()
|
|
|
|
mock_ldap = MockLDAP()
|
|
|
|
mock_initialize.return_value = mock_ldap
|
|
|
|
|
|
|
|
mock_ldap.directory = {
|
|
|
|
'uid=newuser,ou=users,dc=zulip,dc=com': {
|
2019-01-16 08:36:27 +01:00
|
|
|
'userPassword': ['testing', ],
|
2018-11-29 16:32:17 +01:00
|
|
|
'fn': ['full_name'],
|
|
|
|
'sn': ['shortname'],
|
|
|
|
'email': ['newuser_email@zulip.com'],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
with self.settings(
|
|
|
|
LDAP_EMAIL_ATTR='email',
|
|
|
|
AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map,
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
|
|
|
self.login_with_return("newuser", "testing")
|
|
|
|
user = UserProfile.objects.get(email="newuser_email@zulip.com")
|
2019-01-04 01:50:21 +01:00
|
|
|
scheduled_emails = ScheduledEmail.objects.filter(users=user)
|
2018-11-14 12:46:56 +01:00
|
|
|
|
|
|
|
self.assertEqual(len(scheduled_emails), 2)
|
|
|
|
email_data = ujson.loads(scheduled_emails[0].data)
|
2018-11-29 16:32:17 +01:00
|
|
|
self.assertEqual(email_data["context"]["ldap"], True)
|
|
|
|
self.assertNotIn("ldap_username", email_data["context"])
|
2018-11-14 12:46:56 +01:00
|
|
|
|
2018-11-14 12:58:35 +01:00
|
|
|
def test_followup_emails_count(self) -> None:
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
|
|
|
|
enqueue_welcome_emails(self.example_user("hamlet"))
|
|
|
|
# Hamlet has account only in Zulip realm so both day1 and day2 emails should be sent
|
2019-04-29 07:00:03 +02:00
|
|
|
scheduled_emails = ScheduledEmail.objects.filter(users=hamlet).order_by(
|
|
|
|
"scheduled_timestamp")
|
2018-11-14 12:58:35 +01:00
|
|
|
self.assertEqual(2, len(scheduled_emails))
|
2019-01-04 01:50:21 +01:00
|
|
|
self.assertEqual(ujson.loads(scheduled_emails[1].data)["template_prefix"], 'zerver/emails/followup_day2')
|
|
|
|
self.assertEqual(ujson.loads(scheduled_emails[0].data)["template_prefix"], 'zerver/emails/followup_day1')
|
2018-11-14 12:58:35 +01:00
|
|
|
|
|
|
|
ScheduledEmail.objects.all().delete()
|
|
|
|
|
|
|
|
enqueue_welcome_emails(cordelia)
|
2019-01-04 01:50:21 +01:00
|
|
|
scheduled_emails = ScheduledEmail.objects.filter(users=cordelia)
|
2018-11-14 12:58:35 +01:00
|
|
|
# Cordelia has account in more than 1 realm so day2 email should not be sent
|
|
|
|
self.assertEqual(len(scheduled_emails), 1)
|
|
|
|
email_data = ujson.loads(scheduled_emails[0].data)
|
|
|
|
self.assertEqual(email_data["template_prefix"], 'zerver/emails/followup_day1')
|
|
|
|
|
2017-03-08 12:28:24 +01:00
|
|
|
class TestMissedMessages(ZulipTestCase):
|
2018-05-11 01:39:38 +02:00
|
|
|
def normalize_string(self, s: str) -> str:
|
2017-03-08 12:28:24 +01:00
|
|
|
s = s.strip()
|
|
|
|
return re.sub(r'\s+', ' ', s)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def _get_tokens(self) -> List[str]:
|
2017-03-08 12:28:24 +01:00
|
|
|
return [str(random.getrandbits(32)) for _ in range(30)]
|
|
|
|
|
2018-11-11 16:07:05 +01:00
|
|
|
def _test_cases(self, tokens: List[str], msg_id: int, body: str, email_subject: str,
|
2017-11-29 13:42:39 +01:00
|
|
|
send_as_user: bool, verify_html_body: bool=False,
|
|
|
|
show_message_content: bool=True,
|
2018-07-14 07:46:13 +02:00
|
|
|
verify_body_does_not_include: Optional[List[str]]=None,
|
|
|
|
trigger: str='') -> None:
|
2017-05-07 17:21:26 +02:00
|
|
|
othello = self.example_user('othello')
|
|
|
|
hamlet = self.example_user('hamlet')
|
2018-07-14 07:46:13 +02:00
|
|
|
handle_missedmessage_emails(hamlet.id, [{'message_id': msg_id, 'trigger': trigger}])
|
2017-03-08 04:46:49 +01:00
|
|
|
if settings.EMAIL_GATEWAY_PATTERN != "":
|
2019-04-20 01:00:46 +02:00
|
|
|
reply_to_addresses = [settings.EMAIL_GATEWAY_PATTERN % (u'mm' + t,) for t in tokens]
|
2017-06-26 19:43:32 +02:00
|
|
|
reply_to_emails = [formataddr(("Zulip", address)) for address in reply_to_addresses]
|
2017-03-08 04:46:49 +01:00
|
|
|
else:
|
2017-08-16 03:32:36 +02:00
|
|
|
reply_to_emails = ["noreply@testserver"]
|
2017-03-08 12:28:24 +01:00
|
|
|
msg = mail.outbox[0]
|
2017-07-25 22:55:09 +02:00
|
|
|
from_email = formataddr(("Zulip missed messages", FromAddress.NOREPLY))
|
2017-03-08 12:28:24 +01:00
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
if send_as_user:
|
|
|
|
from_email = '"%s" <%s>' % (othello.full_name, othello.email)
|
|
|
|
self.assertEqual(msg.from_email, from_email)
|
2018-11-11 16:07:05 +01:00
|
|
|
self.assertEqual(msg.subject, email_subject)
|
2017-05-05 01:31:07 +02:00
|
|
|
self.assertEqual(len(msg.reply_to), 1)
|
2017-06-26 19:43:32 +02:00
|
|
|
self.assertIn(msg.reply_to[0], reply_to_emails)
|
2017-07-07 22:34:22 +02:00
|
|
|
if verify_html_body:
|
|
|
|
self.assertIn(body, self.normalize_string(msg.alternatives[0][0]))
|
|
|
|
else:
|
|
|
|
self.assertIn(body, self.normalize_string(msg.body))
|
2017-11-29 13:42:39 +01:00
|
|
|
if verify_body_does_not_include is not None:
|
|
|
|
for text in verify_body_does_not_include:
|
|
|
|
self.assertNotIn(text, self.normalize_string(msg.body))
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2018-02-06 20:04:14 +01:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
|
|
|
def _realm_name_in_missed_message_email_subject(self,
|
|
|
|
realm_name_in_notifications: bool,
|
|
|
|
mock_random_token: MagicMock) -> None:
|
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
msg_id = self.send_personal_message(
|
|
|
|
self.example_email('othello'),
|
|
|
|
self.example_email('hamlet'),
|
|
|
|
'Extremely personal message!',
|
|
|
|
)
|
2019-06-11 12:39:42 +02:00
|
|
|
body = 'Extremely personal message!'
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'PMs with Othello, the Moor of Venice'
|
2018-02-06 20:04:14 +01:00
|
|
|
|
|
|
|
if realm_name_in_notifications:
|
2019-07-08 13:13:15 +02:00
|
|
|
email_subject = 'PMs with Othello, the Moor of Venice [Zulip Dev]'
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, False)
|
2018-02-06 20:04:14 +01:00
|
|
|
|
2017-03-08 12:28:24 +01:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _extra_context_in_missed_stream_messages_mention(self, send_as_user: bool,
|
2017-11-29 13:42:39 +01:00
|
|
|
mock_random_token: MagicMock,
|
|
|
|
show_message_content: bool=True) -> None:
|
2017-03-08 12:28:24 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
for i in range(0, 11):
|
2017-10-28 17:52:15 +02:00
|
|
|
self.send_stream_message(self.example_email('othello'), "Denmark", content=str(i))
|
|
|
|
self.send_stream_message(
|
|
|
|
self.example_email('othello'), "Denmark",
|
|
|
|
'11', topic_name='test2')
|
|
|
|
msg_id = self.send_stream_message(
|
|
|
|
self.example_email('othello'), "denmark",
|
|
|
|
'@**King Hamlet**')
|
2017-11-29 13:42:39 +01:00
|
|
|
|
|
|
|
if show_message_content:
|
2019-07-20 02:16:17 +02:00
|
|
|
body = ("Othello, the Moor of Venice: 1 2 3 4 5 6 7 8 9 10 @**King Hamlet** -- "
|
2019-07-11 13:58:29 +02:00
|
|
|
"You are receiving this because you were mentioned")
|
2019-07-08 08:02:17 +02:00
|
|
|
email_subject = '#Denmark > test'
|
2018-03-10 08:34:14 +01:00
|
|
|
verify_body_does_not_include = [] # type: List[str]
|
2017-11-29 13:42:39 +01:00
|
|
|
else:
|
|
|
|
# Test in case if message content in missed email message are disabled.
|
2019-06-11 12:39:42 +02:00
|
|
|
body = 'Manage email preferences: http://zulip.testserver/#settings/notifications'
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'New missed messages'
|
2017-11-29 13:42:39 +01:00
|
|
|
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',
|
2019-06-18 14:15:23 +02:00
|
|
|
'Reply to this email directly, or view it in Zulip']
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user,
|
2017-11-29 13:42:39 +01:00
|
|
|
show_message_content=show_message_content,
|
2018-07-14 07:46:13 +02:00
|
|
|
verify_body_does_not_include=verify_body_does_not_include,
|
|
|
|
trigger='mentioned')
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2017-11-21 06:11:13 +01:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
|
|
|
def _extra_context_in_missed_stream_messages_email_notify(self, send_as_user: bool,
|
|
|
|
mock_random_token: MagicMock) -> None:
|
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
for i in range(0, 11):
|
|
|
|
self.send_stream_message(self.example_email('othello'), "Denmark", content=str(i))
|
|
|
|
self.send_stream_message(
|
|
|
|
self.example_email('othello'), "Denmark",
|
|
|
|
'11', topic_name='test2')
|
|
|
|
msg_id = self.send_stream_message(
|
|
|
|
self.example_email('othello'), "denmark",
|
|
|
|
'12')
|
2019-07-20 02:16:17 +02:00
|
|
|
body = ("Othello, the Moor of Venice: 1 2 3 4 5 6 7 8 9 10 12 -- "
|
2019-07-11 13:58:29 +02:00
|
|
|
"You are receiving this because you have email notifications enabled for this stream.")
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = '#Denmark > test'
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user, trigger='stream_email_notify')
|
2017-11-21 06:11:13 +01:00
|
|
|
|
2017-05-12 22:47:34 +02:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _extra_context_in_missed_stream_messages_mention_two_senders(
|
|
|
|
self, send_as_user: bool, mock_random_token: MagicMock) -> None:
|
2017-05-12 22:47:34 +02:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
for i in range(0, 3):
|
2017-10-28 17:52:15 +02:00
|
|
|
self.send_stream_message(self.example_email('cordelia'), "Denmark", str(i))
|
|
|
|
msg_id = self.send_stream_message(
|
|
|
|
self.example_email('othello'), "Denmark",
|
|
|
|
'@**King Hamlet**')
|
2019-07-20 02:16:17 +02:00
|
|
|
body = ("Cordelia Lear: 0 1 2 Othello, the Moor of Venice: @**King Hamlet** -- "
|
2019-07-11 13:58:29 +02:00
|
|
|
"You are receiving this because you were mentioned")
|
2019-07-08 08:02:17 +02:00
|
|
|
email_subject = '#Denmark > test'
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user, trigger='mentioned')
|
2017-05-12 22:47:34 +02:00
|
|
|
|
2017-03-08 12:28:24 +01:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _extra_context_in_personal_missed_stream_messages(self, send_as_user: bool,
|
2017-11-29 13:42:39 +01:00
|
|
|
mock_random_token: MagicMock,
|
|
|
|
show_message_content: bool=True) -> None:
|
2017-03-08 12:28:24 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_personal_message(
|
|
|
|
self.example_email('othello'),
|
|
|
|
self.example_email('hamlet'),
|
|
|
|
'Extremely personal message!',
|
|
|
|
)
|
2017-11-29 13:42:39 +01:00
|
|
|
|
|
|
|
if show_message_content:
|
2019-06-11 12:39:42 +02:00
|
|
|
body = 'Extremely personal message!'
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'PMs with Othello, the Moor of Venice'
|
2018-03-10 08:34:14 +01:00
|
|
|
verify_body_does_not_include = [] # type: List[str]
|
2017-11-29 13:42:39 +01:00
|
|
|
else:
|
2019-06-11 12:39:42 +02:00
|
|
|
body = 'Manage email preferences: http://zulip.testserver/#settings/notifications'
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'New missed messages'
|
2017-11-29 13:42:39 +01:00
|
|
|
verify_body_does_not_include = ['Othello, the Moor of Venice', 'Extremely personal message!',
|
2019-06-18 14:15:23 +02:00
|
|
|
'mentioned', 'group', 'Reply to this email directly, or view it in Zulip']
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user,
|
2017-11-29 13:42:39 +01:00
|
|
|
show_message_content=show_message_content,
|
|
|
|
verify_body_does_not_include=verify_body_does_not_include)
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2017-03-08 04:46:49 +01:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _reply_to_email_in_personal_missed_stream_messages(self, send_as_user: bool,
|
|
|
|
mock_random_token: MagicMock) -> None:
|
2017-03-08 04:46:49 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_personal_message(
|
|
|
|
self.example_email('othello'),
|
|
|
|
self.example_email('hamlet'),
|
|
|
|
'Extremely personal message!',
|
|
|
|
)
|
2019-06-18 14:15:23 +02:00
|
|
|
body = 'Reply to this email directly, or view it in Zulip'
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'PMs with Othello, the Moor of Venice'
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user)
|
2017-03-08 04:46:49 +01:00
|
|
|
|
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _reply_warning_in_personal_missed_stream_messages(self, send_as_user: bool,
|
|
|
|
mock_random_token: MagicMock) -> None:
|
2017-03-08 04:46:49 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_personal_message(
|
|
|
|
self.example_email('othello'),
|
|
|
|
self.example_email('hamlet'),
|
|
|
|
'Extremely personal message!',
|
|
|
|
)
|
2019-06-18 14:15:23 +02:00
|
|
|
body = 'Do not reply to this message.'
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'PMs with Othello, the Moor of Venice'
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user)
|
2017-03-08 04:46:49 +01:00
|
|
|
|
2017-03-08 12:28:24 +01:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _extra_context_in_huddle_missed_stream_messages_two_others(self, send_as_user: bool,
|
2017-11-29 13:42:39 +01:00
|
|
|
mock_random_token: MagicMock,
|
|
|
|
show_message_content: bool=True) -> None:
|
2017-03-08 12:28:24 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_huddle_message(
|
|
|
|
self.example_email('othello'),
|
|
|
|
[
|
|
|
|
self.example_email('hamlet'),
|
|
|
|
self.example_email('iago'),
|
|
|
|
],
|
|
|
|
'Group personal message!',
|
|
|
|
)
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2017-11-29 13:42:39 +01:00
|
|
|
if show_message_content:
|
2019-07-20 02:16:17 +02:00
|
|
|
body = 'Othello, the Moor of Venice: Group personal message! -- Reply'
|
2018-11-11 16:07:05 +01:00
|
|
|
email_subject = 'Group PMs with Iago and Othello, the Moor of Venice'
|
2018-03-10 08:34:14 +01:00
|
|
|
verify_body_does_not_include = [] # type: List[str]
|
2017-11-29 13:42:39 +01:00
|
|
|
else:
|
2019-06-11 12:39:42 +02:00
|
|
|
body = 'Manage email preferences: http://zulip.testserver/#settings/notifications'
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'New missed messages'
|
2017-11-29 13:42:39 +01:00
|
|
|
verify_body_does_not_include = ['Iago', 'Othello, the Moor of Venice Othello, the Moor of Venice',
|
2018-03-10 05:12:26 +01:00
|
|
|
'Group personal message!', 'mentioned',
|
2019-06-18 14:15:23 +02:00
|
|
|
'Reply to this email directly, or view it in Zulip']
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user,
|
2017-11-29 13:42:39 +01:00
|
|
|
show_message_content=show_message_content,
|
|
|
|
verify_body_does_not_include=verify_body_does_not_include)
|
2017-05-03 09:22:58 +02:00
|
|
|
|
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _extra_context_in_huddle_missed_stream_messages_three_others(self, send_as_user: bool,
|
|
|
|
mock_random_token: MagicMock) -> None:
|
2017-05-03 09:22:58 +02:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_huddle_message(
|
|
|
|
self.example_email('othello'),
|
|
|
|
[
|
|
|
|
self.example_email('hamlet'),
|
|
|
|
self.example_email('iago'),
|
|
|
|
self.example_email('cordelia'),
|
|
|
|
],
|
|
|
|
'Group personal message!',
|
|
|
|
)
|
2017-05-03 09:22:58 +02:00
|
|
|
|
2019-07-20 02:16:17 +02:00
|
|
|
body = 'Othello, the Moor of Venice: Group personal message! -- Reply'
|
2018-11-11 16:07:05 +01:00
|
|
|
email_subject = 'Group PMs with Cordelia Lear, Iago, and Othello, the Moor of Venice'
|
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user)
|
2017-05-03 09:22:58 +02:00
|
|
|
|
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _extra_context_in_huddle_missed_stream_messages_many_others(self, send_as_user: bool,
|
|
|
|
mock_random_token: MagicMock) -> None:
|
2017-05-03 09:22:58 +02:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_huddle_message(self.example_email('othello'),
|
|
|
|
[self.example_email('hamlet'),
|
|
|
|
self.example_email('iago'),
|
|
|
|
self.example_email('cordelia'),
|
|
|
|
self.example_email('prospero')],
|
|
|
|
'Group personal message!')
|
2017-05-03 09:22:58 +02:00
|
|
|
|
2019-07-20 02:16:17 +02:00
|
|
|
body = 'Othello, the Moor of Venice: Group personal message! -- Reply'
|
2018-11-11 16:07:05 +01:00
|
|
|
email_subject = 'Group PMs with Cordelia Lear, Iago, and 2 others'
|
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user)
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2017-03-14 08:38:01 +01:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _deleted_message_in_missed_stream_messages(self, send_as_user: bool,
|
|
|
|
mock_random_token: MagicMock) -> None:
|
2017-03-14 08:38:01 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_stream_message(
|
|
|
|
self.example_email('othello'), "denmark",
|
|
|
|
'@**King Hamlet** to be deleted')
|
2017-03-14 08:38:01 +01:00
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
hamlet = self.example_user('hamlet')
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('othello')
|
|
|
|
self.login(email)
|
2017-03-14 08:38:01 +01:00
|
|
|
result = self.client_patch('/json/messages/' + str(msg_id),
|
2017-03-23 21:06:04 +01:00
|
|
|
{'message_id': msg_id, 'content': ' '})
|
2017-03-14 08:38:01 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
handle_missedmessage_emails(hamlet.id, [{'message_id': msg_id}])
|
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
|
|
|
|
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _deleted_message_in_personal_missed_stream_messages(self, send_as_user: bool,
|
|
|
|
mock_random_token: MagicMock) -> None:
|
2017-03-14 08:38:01 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_personal_message(self.example_email('othello'),
|
|
|
|
self.example_email('hamlet'),
|
|
|
|
'Extremely personal message! to be deleted!')
|
2017-03-14 08:38:01 +01:00
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
hamlet = self.example_user('hamlet')
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('othello')
|
|
|
|
self.login(email)
|
2017-03-14 08:38:01 +01:00
|
|
|
result = self.client_patch('/json/messages/' + str(msg_id),
|
2017-03-23 21:06:04 +01:00
|
|
|
{'message_id': msg_id, 'content': ' '})
|
2017-03-14 08:38:01 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
handle_missedmessage_emails(hamlet.id, [{'message_id': msg_id}])
|
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
|
|
|
|
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-17 07:00:53 +01:00
|
|
|
def _deleted_message_in_huddle_missed_stream_messages(self, send_as_user: bool,
|
|
|
|
mock_random_token: MagicMock) -> None:
|
2017-03-14 08:38:01 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_huddle_message(
|
|
|
|
self.example_email('othello'),
|
|
|
|
[
|
|
|
|
self.example_email('hamlet'),
|
|
|
|
self.example_email('iago'),
|
|
|
|
],
|
|
|
|
'Group personal message!',
|
|
|
|
)
|
2017-03-14 08:38:01 +01:00
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
iago = self.example_user('iago')
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('othello')
|
|
|
|
self.login(email)
|
2017-03-14 08:38:01 +01:00
|
|
|
result = self.client_patch('/json/messages/' + str(msg_id),
|
2017-03-23 21:06:04 +01:00
|
|
|
{'message_id': msg_id, 'content': ' '})
|
2017-03-14 08:38:01 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
handle_missedmessage_emails(hamlet.id, [{'message_id': msg_id}])
|
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
|
|
|
handle_missedmessage_emails(iago.id, [{'message_id': msg_id}])
|
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
|
|
|
|
2018-02-06 20:04:14 +01:00
|
|
|
def test_realm_name_in_notifications(self) -> None:
|
|
|
|
# Test with realm_name_in_notifications for hamlet disabled.
|
|
|
|
self._realm_name_in_missed_message_email_subject(False)
|
|
|
|
|
|
|
|
# Enable realm_name_in_notifications for hamlet and test again.
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
hamlet.realm_name_in_notifications = True
|
|
|
|
hamlet.save(update_fields=['realm_name_in_notifications'])
|
|
|
|
|
|
|
|
# Empty the test outbox
|
|
|
|
mail.outbox = []
|
|
|
|
self._realm_name_in_missed_message_email_subject(True)
|
|
|
|
|
2017-11-29 13:42:39 +01:00
|
|
|
def test_message_content_disabled_in_missed_message_notifications(self) -> None:
|
|
|
|
# Test when user disabled message content in email notifications.
|
|
|
|
do_change_notification_settings(self.example_user("hamlet"),
|
|
|
|
"message_content_in_email_notifications", False)
|
|
|
|
self._extra_context_in_missed_stream_messages_mention(False, show_message_content=False)
|
|
|
|
mail.outbox = []
|
|
|
|
self._extra_context_in_personal_missed_stream_messages(False, show_message_content=False)
|
|
|
|
mail.outbox = []
|
|
|
|
self._extra_context_in_huddle_missed_stream_messages_two_others(False, show_message_content=False)
|
|
|
|
|
2017-03-08 12:28:24 +01:00
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_missed_stream_messages_as_user(self) -> None:
|
2017-05-12 22:47:34 +02:00
|
|
|
self._extra_context_in_missed_stream_messages_mention(True)
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_missed_stream_messages(self) -> None:
|
2017-05-12 22:47:34 +02:00
|
|
|
self._extra_context_in_missed_stream_messages_mention(False)
|
|
|
|
|
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_missed_stream_messages_as_user_two_senders(self) -> None:
|
2017-05-12 22:47:34 +02:00
|
|
|
self._extra_context_in_missed_stream_messages_mention_two_senders(True)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_missed_stream_messages_two_senders(self) -> None:
|
2017-05-12 22:47:34 +02:00
|
|
|
self._extra_context_in_missed_stream_messages_mention_two_senders(False)
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_reply_to_email_in_personal_missed_stream_messages(self) -> None:
|
2017-03-08 04:46:49 +01:00
|
|
|
self._reply_to_email_in_personal_missed_stream_messages(False)
|
|
|
|
|
2017-11-21 06:11:13 +01:00
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
|
|
|
def test_extra_context_in_missed_stream_messages_email_notify_as_user(self) -> None:
|
|
|
|
self._extra_context_in_missed_stream_messages_email_notify(True)
|
|
|
|
|
|
|
|
def test_extra_context_in_missed_stream_messages_email_notify(self) -> None:
|
|
|
|
self._extra_context_in_missed_stream_messages_email_notify(False)
|
|
|
|
|
2017-03-08 04:46:49 +01:00
|
|
|
@override_settings(EMAIL_GATEWAY_PATTERN="")
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_reply_warning_in_personal_missed_stream_messages(self) -> None:
|
2017-03-08 04:46:49 +01:00
|
|
|
self._reply_warning_in_personal_missed_stream_messages(False)
|
|
|
|
|
2017-03-08 12:28:24 +01:00
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_personal_missed_stream_messages_as_user(self) -> None:
|
2017-03-08 12:28:24 +01:00
|
|
|
self._extra_context_in_personal_missed_stream_messages(True)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_personal_missed_stream_messages(self) -> None:
|
2017-03-08 12:28:24 +01:00
|
|
|
self._extra_context_in_personal_missed_stream_messages(False)
|
|
|
|
|
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_huddle_missed_stream_messages_two_others_as_user(self) -> None:
|
2017-05-03 09:22:58 +02:00
|
|
|
self._extra_context_in_huddle_missed_stream_messages_two_others(True)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_huddle_missed_stream_messages_two_others(self) -> None:
|
2017-05-03 09:22:58 +02:00
|
|
|
self._extra_context_in_huddle_missed_stream_messages_two_others(False)
|
|
|
|
|
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_huddle_missed_stream_messages_three_others_as_user(self) -> None:
|
2017-05-03 09:22:58 +02:00
|
|
|
self._extra_context_in_huddle_missed_stream_messages_three_others(True)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_huddle_missed_stream_messages_three_others(self) -> None:
|
2017-05-03 09:22:58 +02:00
|
|
|
self._extra_context_in_huddle_missed_stream_messages_three_others(False)
|
|
|
|
|
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_huddle_missed_stream_messages_many_others_as_user(self) -> None:
|
2017-05-03 09:22:58 +02:00
|
|
|
self._extra_context_in_huddle_missed_stream_messages_many_others(True)
|
2017-03-08 12:28:24 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_extra_context_in_huddle_missed_stream_messages_many_others(self) -> None:
|
2017-05-03 09:22:58 +02:00
|
|
|
self._extra_context_in_huddle_missed_stream_messages_many_others(False)
|
2017-03-14 08:38:01 +01:00
|
|
|
|
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_deleted_message_in_missed_stream_messages_as_user(self) -> None:
|
2017-03-14 08:38:01 +01:00
|
|
|
self._deleted_message_in_missed_stream_messages(True)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_deleted_message_in_missed_stream_messages(self) -> None:
|
2017-03-14 08:38:01 +01:00
|
|
|
self._deleted_message_in_missed_stream_messages(False)
|
|
|
|
|
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_deleted_message_in_personal_missed_stream_messages_as_user(self) -> None:
|
2017-03-14 08:38:01 +01:00
|
|
|
self._deleted_message_in_personal_missed_stream_messages(True)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_deleted_message_in_personal_missed_stream_messages(self) -> None:
|
2017-03-14 08:38:01 +01:00
|
|
|
self._deleted_message_in_personal_missed_stream_messages(False)
|
|
|
|
|
|
|
|
@override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_deleted_message_in_huddle_missed_stream_messages_as_user(self) -> None:
|
2017-03-14 08:38:01 +01:00
|
|
|
self._deleted_message_in_huddle_missed_stream_messages(True)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_deleted_message_in_huddle_missed_stream_messages(self) -> None:
|
2017-03-14 08:38:01 +01:00
|
|
|
self._deleted_message_in_huddle_missed_stream_messages(False)
|
2017-07-07 22:34:22 +02:00
|
|
|
|
2019-01-14 14:04:08 +01:00
|
|
|
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_notification_settings(user, "message_content_in_email_notifications", True)
|
|
|
|
mail.outbox = []
|
|
|
|
self._extra_context_in_personal_missed_stream_messages(False, show_message_content=True)
|
|
|
|
|
|
|
|
# Emails don't have missed message content when message content is disabled by the user
|
|
|
|
do_change_notification_settings(user, "message_content_in_email_notifications", False)
|
|
|
|
mail.outbox = []
|
|
|
|
self._extra_context_in_personal_missed_stream_messages(False, show_message_content=False)
|
|
|
|
|
|
|
|
# When message content is not allowed at realm level
|
|
|
|
# Emails don't have missed message 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_notification_settings(user, "message_content_in_email_notifications", True)
|
|
|
|
mail.outbox = []
|
|
|
|
self._extra_context_in_personal_missed_stream_messages(False, show_message_content=False)
|
|
|
|
|
|
|
|
do_change_notification_settings(user, "message_content_in_email_notifications", False)
|
|
|
|
mail.outbox = []
|
|
|
|
self._extra_context_in_personal_missed_stream_messages(False, show_message_content=False)
|
|
|
|
|
2017-07-07 22:34:22 +02:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_realm_emoji_in_missed_message(self, mock_random_token: MagicMock) -> None:
|
2017-07-07 22:34:22 +02:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
2019-03-15 18:56:56 +01:00
|
|
|
realm = get_realm("zulip")
|
2017-07-07 22:34:22 +02:00
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_personal_message(
|
|
|
|
self.example_email('othello'), self.example_email('hamlet'),
|
|
|
|
'Extremely personal message with a realm emoji :green_tick:!')
|
2019-03-15 18:56:56 +01:00
|
|
|
realm_emoji_id = realm.get_active_emoji()['green_tick']['id']
|
|
|
|
realm_emoji_url = "http://zulip.testserver/user_avatars/%s/emoji/images/%s.png" % (
|
|
|
|
realm.id, realm_emoji_id,)
|
2018-03-11 18:55:20 +01:00
|
|
|
body = '<img alt=":green_tick:" src="%s" title="green tick" style="height: 20px;">' % (realm_emoji_url,)
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'PMs with Othello, the Moor of Venice'
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user=False, verify_html_body=True)
|
2017-10-28 00:55:16 +02:00
|
|
|
|
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_emojiset_in_missed_message(self, mock_random_token: MagicMock) -> None:
|
2017-10-28 00:55:16 +02:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
hamlet.emojiset = 'apple'
|
|
|
|
hamlet.save(update_fields=['emojiset'])
|
|
|
|
msg_id = self.send_personal_message(
|
|
|
|
self.example_email('othello'), self.example_email('hamlet'),
|
|
|
|
'Extremely personal message with a hamburger :hamburger:!')
|
|
|
|
body = '<img alt=":hamburger:" src="http://zulip.testserver/static/generated/emoji/images-apple-64/1f354.png" title="hamburger" style="height: 20px;">'
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'PMs with Othello, the Moor of Venice'
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user=False, verify_html_body=True)
|
2017-07-15 01:07:52 +02:00
|
|
|
|
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_stream_link_in_missed_message(self, mock_random_token: MagicMock) -> None:
|
2017-07-15 01:07:52 +02:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id = self.send_personal_message(
|
|
|
|
self.example_email('othello'), self.example_email('hamlet'),
|
|
|
|
'Come and join us in #**Verona**.')
|
2018-02-15 21:02:47 +01:00
|
|
|
stream_id = get_stream('Verona', get_realm('zulip')).id
|
|
|
|
href = "http://zulip.testserver/#narrow/stream/{stream_id}-Verona".format(stream_id=stream_id)
|
|
|
|
body = '<a class="stream" data-stream-id="5" href="{href}">#Verona</a'.format(href=href)
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'PMs with Othello, the Moor of Venice'
|
2018-11-11 16:07:05 +01:00
|
|
|
self._test_cases(tokens, msg_id, body, email_subject, send_as_user=False, verify_html_body=True)
|
2017-08-25 04:15:05 +02:00
|
|
|
|
2019-07-11 13:04:11 +02:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
|
|
|
def test_sender_name_in_missed_message(self, mock_random_token: MagicMock) -> None:
|
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
msg_id_1 = self.send_stream_message(self.example_email('iago'),
|
|
|
|
"Denmark",
|
|
|
|
'@**King Hamlet**')
|
|
|
|
msg_id_2 = self.send_stream_message(self.example_email('iago'),
|
|
|
|
"Verona",
|
|
|
|
'* 1\n *2')
|
|
|
|
msg_id_3 = self.send_personal_message(self.example_email('iago'),
|
|
|
|
hamlet.email,
|
|
|
|
'Hello')
|
|
|
|
|
|
|
|
handle_missedmessage_emails(hamlet.id, [
|
|
|
|
{'message_id': msg_id_1, "trigger": "mentioned"},
|
|
|
|
{'message_id': msg_id_2, "trigger": "stream_email_notify"},
|
|
|
|
{'message_id': msg_id_3},
|
|
|
|
])
|
|
|
|
|
2019-07-20 02:16:17 +02:00
|
|
|
self.assertIn('Iago: @**King Hamlet**\n\n--\nYou are', mail.outbox[0].body)
|
2019-07-11 13:04:11 +02:00
|
|
|
# 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])
|
|
|
|
|
2019-07-20 02:16:17 +02:00
|
|
|
self.assertIn('Iago: * 1\n *2\n\n--\nYou are receiving', mail.outbox[1].body)
|
2019-07-11 13:04:11 +02:00
|
|
|
# If message content does not starts with <p> tag sender name is appended before the <p> tag
|
|
|
|
self.assertIn(' <b>Iago</b>: <ul>\n<li>1<br/>\n *2</li>\n</ul>\n',
|
|
|
|
mail.outbox[1].alternatives[0][0])
|
|
|
|
|
2019-07-20 02:16:17 +02:00
|
|
|
self.assertEqual('Hello\n\n--\n\nReply', mail.outbox[2].body[:16])
|
2019-07-11 13:04:11 +02:00
|
|
|
# Sender name is not appended to message for PM missed messages
|
|
|
|
self.assertIn('>\n \n <p>Hello</p>\n',
|
|
|
|
mail.outbox[2].alternatives[0][0])
|
|
|
|
|
2017-08-25 04:15:05 +02:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_multiple_missed_personal_messages(self, mock_random_token: MagicMock) -> None:
|
2017-08-25 04:15:05 +02:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
hamlet = self.example_user('hamlet')
|
2017-10-28 17:52:15 +02:00
|
|
|
msg_id_1 = self.send_personal_message(self.example_email('othello'),
|
|
|
|
hamlet.email,
|
|
|
|
'Personal Message 1')
|
|
|
|
msg_id_2 = self.send_personal_message(self.example_email('iago'),
|
|
|
|
hamlet.email,
|
|
|
|
'Personal Message 2')
|
2017-08-25 04:15:05 +02:00
|
|
|
|
|
|
|
handle_missedmessage_emails(hamlet.id, [
|
|
|
|
{'message_id': msg_id_1},
|
|
|
|
{'message_id': msg_id_2},
|
|
|
|
])
|
|
|
|
self.assertEqual(len(mail.outbox), 2)
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'PMs with Othello, the Moor of Venice'
|
2018-11-11 16:07:05 +01:00
|
|
|
self.assertEqual(mail.outbox[0].subject, email_subject)
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = 'PMs with Iago'
|
2018-11-11 16:07:05 +01:00
|
|
|
self.assertEqual(mail.outbox[1].subject, email_subject)
|
2017-09-21 01:11:49 +02:00
|
|
|
|
2017-11-21 06:11:13 +01:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
|
|
|
def test_multiple_stream_messages(self, mock_random_token: MagicMock) -> None:
|
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
msg_id_1 = self.send_stream_message(self.example_email('othello'),
|
|
|
|
"Denmark",
|
|
|
|
'Message1')
|
|
|
|
msg_id_2 = self.send_stream_message(self.example_email('iago'),
|
|
|
|
"Denmark",
|
|
|
|
'Message2')
|
|
|
|
|
|
|
|
handle_missedmessage_emails(hamlet.id, [
|
|
|
|
{'message_id': msg_id_1, "trigger": "stream_email_notify"},
|
|
|
|
{'message_id': msg_id_2, "trigger": "stream_email_notify"},
|
|
|
|
])
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
2019-01-10 15:58:51 +01:00
|
|
|
email_subject = '#Denmark > test'
|
2018-11-11 16:07:05 +01:00
|
|
|
self.assertEqual(mail.outbox[0].subject, email_subject)
|
2017-11-21 06:11:13 +01:00
|
|
|
|
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
|
|
|
def test_multiple_stream_messages_and_mentions(self, mock_random_token: MagicMock) -> None:
|
2019-07-08 08:02:17 +02:00
|
|
|
"""Subject should be stream name and topic as usual."""
|
2017-11-21 06:11:13 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
msg_id_1 = self.send_stream_message(self.example_email('iago'),
|
|
|
|
"Denmark",
|
|
|
|
'Regular message')
|
|
|
|
msg_id_2 = self.send_stream_message(self.example_email('othello'),
|
|
|
|
"Denmark",
|
|
|
|
'@**King Hamlet**')
|
|
|
|
|
|
|
|
handle_missedmessage_emails(hamlet.id, [
|
|
|
|
{'message_id': msg_id_1, "trigger": "stream_email_notify"},
|
|
|
|
{'message_id': msg_id_2, "trigger": "mentioned"},
|
|
|
|
])
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
2019-07-08 08:02:17 +02:00
|
|
|
email_subject = '#Denmark > test'
|
2018-11-11 16:07:05 +01:00
|
|
|
self.assertEqual(mail.outbox[0].subject, email_subject)
|
2017-11-21 06:11:13 +01:00
|
|
|
|
2018-07-26 20:19:45 +02:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
|
|
|
def test_message_access_in_emails(self, mock_random_token: MagicMock) -> None:
|
|
|
|
# Messages sent to a protected history-private stream shouldn't be
|
|
|
|
# accessible/available in emails before subscribing
|
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
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.email,
|
|
|
|
stream_name,
|
|
|
|
'Before subscribing')
|
|
|
|
|
|
|
|
self.subscribe(late_subscribed_user, stream_name)
|
|
|
|
|
|
|
|
self.send_stream_message(user.email,
|
|
|
|
stream_name,
|
|
|
|
"After subscribing")
|
|
|
|
|
|
|
|
mention_msg_id = self.send_stream_message(user.email,
|
|
|
|
stream_name,
|
|
|
|
'@**King Hamlet**')
|
|
|
|
|
|
|
|
handle_missedmessage_emails(late_subscribed_user.id, [
|
|
|
|
{'message_id': mention_msg_id, "trigger": "mentioned"},
|
|
|
|
])
|
|
|
|
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
2019-07-08 08:02:17 +02:00
|
|
|
self.assertEqual(mail.outbox[0].subject, '#private_stream > test') # email subject
|
2018-07-26 20:19:45 +02:00
|
|
|
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)
|
|
|
|
|
2017-11-21 06:11:13 +01:00
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
|
|
|
def test_stream_mentions_multiple_people(self, mock_random_token: MagicMock) -> None:
|
2019-07-08 08:02:17 +02:00
|
|
|
"""Subject should be stream name and topic as usual."""
|
2017-11-21 06:11:13 +01:00
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
msg_id_1 = self.send_stream_message(self.example_email('iago'),
|
|
|
|
"Denmark",
|
|
|
|
'@**King Hamlet**')
|
|
|
|
msg_id_2 = self.send_stream_message(self.example_email('othello'),
|
|
|
|
"Denmark",
|
|
|
|
'@**King Hamlet**')
|
|
|
|
msg_id_3 = self.send_stream_message(self.example_email('cordelia'),
|
|
|
|
"Denmark",
|
|
|
|
'Regular message')
|
|
|
|
|
|
|
|
handle_missedmessage_emails(hamlet.id, [
|
|
|
|
{'message_id': msg_id_1, "trigger": "mentioned"},
|
|
|
|
{'message_id': msg_id_2, "trigger": "mentioned"},
|
|
|
|
{'message_id': msg_id_3, "trigger": "stream_email_notify"},
|
|
|
|
])
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
2019-07-08 08:02:17 +02:00
|
|
|
email_subject = '#Denmark > test'
|
2018-11-11 16:07:05 +01:00
|
|
|
self.assertEqual(mail.outbox[0].subject, email_subject)
|
2017-11-21 06:11:13 +01:00
|
|
|
|
|
|
|
@patch('zerver.lib.email_mirror.generate_random_token')
|
|
|
|
def test_multiple_stream_messages_different_topics(self, mock_random_token: MagicMock) -> None:
|
|
|
|
"""Should receive separate emails for each topic within a stream."""
|
|
|
|
tokens = self._get_tokens()
|
|
|
|
mock_random_token.side_effect = tokens
|
|
|
|
|
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
msg_id_1 = self.send_stream_message(self.example_email('othello'),
|
|
|
|
"Denmark",
|
|
|
|
'Message1')
|
|
|
|
msg_id_2 = self.send_stream_message(self.example_email('iago'),
|
|
|
|
"Denmark",
|
|
|
|
'Message2',
|
|
|
|
topic_name="test2")
|
|
|
|
|
|
|
|
handle_missedmessage_emails(hamlet.id, [
|
|
|
|
{'message_id': msg_id_1, "trigger": "stream_email_notify"},
|
|
|
|
{'message_id': msg_id_2, "trigger": "stream_email_notify"},
|
|
|
|
])
|
|
|
|
self.assertEqual(len(mail.outbox), 2)
|
2018-11-11 16:07:05 +01:00
|
|
|
email_subjects = {mail.outbox[0].subject, mail.outbox[1].subject}
|
2019-01-10 15:58:51 +01:00
|
|
|
valid_email_subjects = {'#Denmark > test', '#Denmark > test2'}
|
2018-11-11 16:07:05 +01:00
|
|
|
self.assertEqual(email_subjects, valid_email_subjects)
|
2017-11-21 06:11:13 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_relative_to_full_url(self) -> None:
|
2017-09-21 01:11:49 +02:00
|
|
|
# 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.
|
2018-04-20 03:57:21 +02:00
|
|
|
fixtures = ujson.loads(self.fixture_data("markdown_test_cases.json"))
|
2017-09-21 01:11:49 +02:00
|
|
|
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 = relative_to_full_url("http://example.com", test_data)
|
2018-07-02 00:05:24 +02:00
|
|
|
if re.search(r"""(?<=\=['"])/(?=[^<]+>)""", output_data) is not None:
|
2017-09-21 01:11:49 +02:00
|
|
|
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.
|
2017-09-22 19:16:24 +02:00
|
|
|
|
|
|
|
# A path similar to our emoji path, but not in a link:
|
2017-09-21 01:11:49 +02:00
|
|
|
test_data = "<p>Check out the file at: '/static/generated/emoji/images/emoji/'</p>"
|
|
|
|
actual_output = relative_to_full_url("http://example.com", test_data)
|
|
|
|
expected_output = "<p>Check out the file at: '/static/generated/emoji/images/emoji/'</p>"
|
|
|
|
self.assertEqual(actual_output, expected_output)
|
|
|
|
|
2017-09-22 19:16:24 +02:00
|
|
|
# An uploaded file
|
2017-09-21 01:11:49 +02:00
|
|
|
test_data = '<a href="/user_uploads/2/1f/some_random_value">/user_uploads/2/1f/some_random_value</a>'
|
|
|
|
actual_output = relative_to_full_url("http://example.com", test_data)
|
|
|
|
expected_output = '<a href="http://example.com/user_uploads/2/1f/some_random_value">' + \
|
|
|
|
'/user_uploads/2/1f/some_random_value</a>'
|
|
|
|
self.assertEqual(actual_output, expected_output)
|
|
|
|
|
2019-03-09 17:43:48 +01:00
|
|
|
# A profile picture like syntax, but not actually in an HTML tag
|
2017-09-21 01:11:49 +02:00
|
|
|
test_data = '<p>Set src="/avatar/username@example.com?s=30"</p>'
|
|
|
|
actual_output = relative_to_full_url("http://example.com", test_data)
|
|
|
|
expected_output = '<p>Set src="/avatar/username@example.com?s=30"</p>'
|
|
|
|
self.assertEqual(actual_output, expected_output)
|
2017-09-27 19:39:42 +02:00
|
|
|
|
2017-10-11 22:41:19 +02:00
|
|
|
# A narrow URL which begins with a '#'.
|
2018-11-11 02:17:53 +01:00
|
|
|
test_data = '<p><a href="#narrow/stream/test/topic/test.20topic/near/142"' + \
|
|
|
|
'title="#narrow/stream/test/topic/test.20topic/near/142">Conversation</a></p>'
|
2017-10-11 22:41:19 +02:00
|
|
|
actual_output = relative_to_full_url("http://example.com", test_data)
|
2018-11-11 02:17:53 +01:00
|
|
|
expected_output = '<p><a href="http://example.com/#narrow/stream/test/topic/test.20topic/near/142" ' + \
|
|
|
|
'title="http://example.com/#narrow/stream/test/topic/test.20topic/near/142">Conversation</a></p>'
|
2017-10-11 22:41:19 +02:00
|
|
|
self.assertEqual(actual_output, expected_output)
|
|
|
|
|
2017-10-13 16:59:58 +02:00
|
|
|
# Scrub inline images.
|
|
|
|
test_data = '<p>See this <a href="/user_uploads/1/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/1/52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg" ' + \
|
|
|
|
'target="_blank" title="avatar_103.jpeg"><img src="/user_uploads/1/52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg"></a></div>'
|
|
|
|
actual_output = relative_to_full_url("http://example.com", test_data)
|
|
|
|
expected_output = '<div><p>See this <a href="http://example.com/user_uploads/1/52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg" target="_blank" ' + \
|
|
|
|
'title="avatar_103.jpeg">avatar_103.jpeg</a>.</p></div>'
|
|
|
|
self.assertEqual(actual_output, expected_output)
|
|
|
|
|
2018-01-24 19:23:51 +01:00
|
|
|
# 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">' + \
|
2018-07-30 21:10:33 +02:00
|
|
|
'<img data-src-fullsize="/thumbnail/https%3A//www.google.com/images/srpr/logo4w.png?size=0x0" ' + \
|
2018-01-24 19:23:51 +01:00
|
|
|
'src="/thumbnail/https%3A//www.google.com/images/srpr/logo4w.png?size=0x100"></a></div>'
|
|
|
|
actual_output = relative_to_full_url("http://example.com", test_data)
|
|
|
|
expected_output = '<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>'
|
|
|
|
self.assertEqual(actual_output, expected_output)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_fix_emoji(self) -> None:
|
2017-09-27 19:39:42 +02:00
|
|
|
# An emoji.
|
2019-01-14 08:45:37 +01:00
|
|
|
test_data = '<p>See <span aria-label="cloud with lightning and rain" class="emoji emoji-26c8" role="img" title="cloud with lightning and rain">' + \
|
2017-09-27 19:39:42 +02:00
|
|
|
':cloud_with_lightning_and_rain:</span>.</p>'
|
2017-10-28 00:55:16 +02:00
|
|
|
actual_output = fix_emojis(test_data, "http://example.com", "google")
|
|
|
|
expected_output = '<p>See <img alt=":cloud_with_lightning_and_rain:" src="http://example.com/static/generated/emoji/images-google-64/26c8.png" ' + \
|
|
|
|
'title="cloud with lightning and rain" style="height: 20px;">.</p>'
|
2017-09-27 19:39:42 +02:00
|
|
|
self.assertEqual(actual_output, expected_output)
|