2015-10-29 16:09:09 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2017-04-18 17:28:55 +02:00
|
|
|
import subprocess
|
|
|
|
|
|
|
|
from django.http import HttpResponse
|
2017-08-27 22:01:20 +02:00
|
|
|
from django.utils.timezone import now as timezone_now
|
2015-10-29 16:09:09 +01:00
|
|
|
|
|
|
|
from zerver.lib.test_helpers import (
|
|
|
|
most_recent_message,
|
2015-10-31 16:31:17 +01:00
|
|
|
most_recent_usermessage,
|
2017-04-18 17:28:55 +02:00
|
|
|
POSTRequestMock)
|
2015-10-29 16:09:09 +01:00
|
|
|
|
2016-11-10 19:30:09 +01:00
|
|
|
from zerver.lib.test_classes import (
|
|
|
|
ZulipTestCase,
|
|
|
|
)
|
|
|
|
|
2015-10-29 16:09:09 +01:00
|
|
|
from zerver.models import (
|
2017-05-08 17:54:11 +02:00
|
|
|
get_display_recipient,
|
|
|
|
get_realm,
|
|
|
|
get_stream,
|
2017-08-27 22:01:20 +02:00
|
|
|
get_client,
|
|
|
|
Recipient,
|
|
|
|
UserProfile,
|
|
|
|
UserActivity,
|
|
|
|
Realm
|
2015-10-29 16:09:09 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
from zerver.lib.actions import (
|
2017-11-22 11:00:23 +01:00
|
|
|
create_stream_if_needed,
|
2015-10-31 16:31:17 +01:00
|
|
|
encode_email_address,
|
2017-08-27 22:01:20 +02:00
|
|
|
do_create_user
|
2015-10-29 16:09:09 +01:00
|
|
|
)
|
|
|
|
from zerver.lib.email_mirror import (
|
|
|
|
process_message, process_stream_message, ZulipEmailForwardError,
|
2015-10-31 16:31:17 +01:00
|
|
|
create_missed_message_address,
|
2016-09-22 18:41:10 +02:00
|
|
|
get_missed_message_token_from_address,
|
2015-10-29 16:09:09 +01:00
|
|
|
)
|
2015-10-31 16:31:17 +01:00
|
|
|
|
2017-11-22 11:00:23 +01:00
|
|
|
from zerver.lib.digest import gather_new_streams, handle_digest_email, enqueue_emails
|
2017-06-26 19:43:32 +02:00
|
|
|
from zerver.lib.send_email import FromAddress
|
2015-10-31 16:31:17 +01:00
|
|
|
from zerver.lib.notifications import (
|
|
|
|
handle_missedmessage_emails,
|
|
|
|
)
|
2017-03-05 08:26:28 +01:00
|
|
|
from zerver.management.commands import email_mirror
|
2015-10-31 16:31:17 +01:00
|
|
|
|
2015-10-29 16:09:09 +01:00
|
|
|
from email.mime.text import MIMEText
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
import time
|
|
|
|
import re
|
|
|
|
import ujson
|
2016-05-12 20:35:35 +02:00
|
|
|
import mock
|
2016-07-28 15:36:26 +02:00
|
|
|
import os
|
|
|
|
import sys
|
2017-11-06 02:56:09 +01:00
|
|
|
from io import StringIO
|
2016-07-31 16:59:52 +02:00
|
|
|
from django.conf import settings
|
2016-07-28 15:36:26 +02:00
|
|
|
|
2017-03-03 19:01:52 +01:00
|
|
|
from typing import Any, Callable, Dict, Mapping, Union, Text
|
2015-10-29 16:09:09 +01:00
|
|
|
|
2016-09-22 18:41:10 +02:00
|
|
|
class TestEmailMirrorLibrary(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_get_missed_message_token(self) -> None:
|
2016-09-22 18:41:10 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def get_token(address: Text) -> Text:
|
2016-09-22 18:41:10 +02:00
|
|
|
with self.settings(EMAIL_GATEWAY_PATTERN="%s@example.com"):
|
|
|
|
return get_missed_message_token_from_address(address)
|
|
|
|
|
|
|
|
address = 'mm' + ('x' * 32) + '@example.com'
|
|
|
|
token = get_token(address)
|
|
|
|
self.assertEqual(token, 'x' * 32)
|
|
|
|
|
|
|
|
# This next section was a bug at one point--we'd treat ordinary
|
|
|
|
# user addresses that happened to begin with "mm" as being
|
|
|
|
# the special mm+32chars tokens.
|
|
|
|
address = 'mmathers@example.com'
|
|
|
|
with self.assertRaises(ZulipEmailForwardError):
|
|
|
|
get_token(address)
|
2015-10-29 16:09:09 +01:00
|
|
|
|
2016-09-22 18:55:18 +02:00
|
|
|
# Now test the case where we our address does not match the
|
|
|
|
# EMAIL_GATEWAY_PATTERN.
|
|
|
|
# This used to crash in an ugly way; we want to throw a proper
|
|
|
|
# exception.
|
|
|
|
address = 'alice@not-the-domain-we-were-expecting.com'
|
|
|
|
with self.assertRaises(ZulipEmailForwardError):
|
|
|
|
get_token(address)
|
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class TestStreamEmailMessagesSuccess(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_receive_stream_email_messages_success(self) -> None:
|
2015-10-29 16:09:09 +01:00
|
|
|
|
|
|
|
# build dummy messages for stream
|
|
|
|
# test valid incoming stream message is processed properly
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2017-05-24 05:08:49 +02:00
|
|
|
self.login(user_profile.email)
|
2017-08-25 06:01:29 +02:00
|
|
|
self.subscribe(user_profile, "Denmark")
|
2015-10-29 16:09:09 +01:00
|
|
|
stream = get_stream("Denmark", user_profile.realm)
|
|
|
|
|
|
|
|
stream_to_address = encode_email_address(stream)
|
|
|
|
|
2017-06-01 00:21:08 +02:00
|
|
|
incoming_valid_message = MIMEText('TestStreamEmailMessages Body') # type: Any # https://github.com/python/typeshed/issues/275
|
2015-10-29 16:09:09 +01:00
|
|
|
|
|
|
|
incoming_valid_message['Subject'] = 'TestStreamEmailMessages Subject'
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['From'] = self.example_email('hamlet')
|
2015-10-29 16:09:09 +01:00
|
|
|
incoming_valid_message['To'] = stream_to_address
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['Reply-to'] = self.example_email('othello')
|
2015-10-29 16:09:09 +01:00
|
|
|
|
|
|
|
process_message(incoming_valid_message)
|
|
|
|
|
|
|
|
# Hamlet is subscribed to this stream so should see the email message from Othello.
|
|
|
|
message = most_recent_message(user_profile)
|
|
|
|
|
|
|
|
self.assertEqual(message.content, "TestStreamEmailMessages Body")
|
|
|
|
self.assertEqual(get_display_recipient(message.recipient), stream.name)
|
2016-07-14 17:48:11 +02:00
|
|
|
self.assertEqual(message.topic_name(), incoming_valid_message['Subject'])
|
2015-10-29 16:09:09 +01:00
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class TestStreamEmailMessagesEmptyBody(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_receive_stream_email_messages_empty_body(self) -> None:
|
2015-10-29 16:09:09 +01:00
|
|
|
|
|
|
|
# build dummy messages for stream
|
|
|
|
# test message with empty body is not sent
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2017-05-24 05:08:49 +02:00
|
|
|
self.login(user_profile.email)
|
2017-08-25 06:01:29 +02:00
|
|
|
self.subscribe(user_profile, "Denmark")
|
2015-10-29 16:09:09 +01:00
|
|
|
stream = get_stream("Denmark", user_profile.realm)
|
|
|
|
|
|
|
|
stream_to_address = encode_email_address(stream)
|
|
|
|
headers = {}
|
2017-05-24 05:08:49 +02:00
|
|
|
headers['Reply-To'] = self.example_email('othello')
|
2015-10-29 16:09:09 +01:00
|
|
|
|
|
|
|
# empty body
|
2017-06-01 00:21:08 +02:00
|
|
|
incoming_valid_message = MIMEText('') # type: Any # https://github.com/python/typeshed/issues/275
|
2015-10-29 16:09:09 +01:00
|
|
|
|
|
|
|
incoming_valid_message['Subject'] = 'TestStreamEmailMessages Subject'
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['From'] = self.example_email('hamlet')
|
2015-10-29 16:09:09 +01:00
|
|
|
incoming_valid_message['To'] = stream_to_address
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['Reply-to'] = self.example_email('othello')
|
2015-10-29 16:09:09 +01:00
|
|
|
|
|
|
|
exception_message = ""
|
2017-06-01 00:21:08 +02:00
|
|
|
debug_info = {} # type: Dict[str, Any]
|
2015-10-29 16:09:09 +01:00
|
|
|
|
|
|
|
# process_message eats the exception & logs an error which can't be parsed here
|
|
|
|
# so calling process_stream_message directly
|
|
|
|
try:
|
|
|
|
process_stream_message(incoming_valid_message['To'],
|
2016-12-03 00:04:17 +01:00
|
|
|
incoming_valid_message['Subject'],
|
|
|
|
incoming_valid_message,
|
|
|
|
debug_info)
|
2015-11-01 17:08:33 +01:00
|
|
|
except ZulipEmailForwardError as e:
|
2015-10-29 16:09:09 +01:00
|
|
|
# empty body throws exception
|
2016-07-13 17:09:49 +02:00
|
|
|
exception_message = str(e)
|
2015-10-29 16:09:09 +01:00
|
|
|
self.assertEqual(exception_message, "Unable to find plaintext or HTML message body")
|
2015-10-31 16:31:17 +01:00
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class TestMissedPersonalMessageEmailMessages(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_receive_missed_personal_message_email_messages(self) -> None:
|
2015-10-31 16:31:17 +01:00
|
|
|
|
|
|
|
# build dummy messages for missed messages email reply
|
|
|
|
# have Hamlet send Othello a PM. Othello will reply via email
|
|
|
|
# Hamlet will receive the message.
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('hamlet')
|
|
|
|
self.login(email)
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/json/messages", {"type": "private",
|
2015-12-01 00:18:33 +01:00
|
|
|
"content": "test_receive_missed_message_email_messages",
|
|
|
|
"client": "test suite",
|
2017-05-24 05:08:49 +02:00
|
|
|
"to": self.example_email('othello')})
|
2015-10-31 16:31:17 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('othello')
|
2015-10-31 16:31:17 +01:00
|
|
|
usermessage = most_recent_usermessage(user_profile)
|
|
|
|
|
|
|
|
# we don't want to send actual emails but we do need to create and store the
|
|
|
|
# token for looking up who did reply.
|
|
|
|
mm_address = create_missed_message_address(user_profile, usermessage.message)
|
|
|
|
|
2017-06-01 00:21:08 +02:00
|
|
|
incoming_valid_message = MIMEText('TestMissedMessageEmailMessages Body') # type: Any # https://github.com/python/typeshed/issues/275
|
2015-10-31 16:31:17 +01:00
|
|
|
|
|
|
|
incoming_valid_message['Subject'] = 'TestMissedMessageEmailMessages Subject'
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['From'] = self.example_email('othello')
|
2015-10-31 16:31:17 +01:00
|
|
|
incoming_valid_message['To'] = mm_address
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['Reply-to'] = self.example_email('othello')
|
2015-10-31 16:31:17 +01:00
|
|
|
|
|
|
|
process_message(incoming_valid_message)
|
|
|
|
|
2017-05-25 01:40:26 +02:00
|
|
|
# self.login(self.example_email("hamlet"))
|
2015-10-31 16:31:17 +01:00
|
|
|
# confirm that Hamlet got the message
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2015-10-31 16:31:17 +01:00
|
|
|
message = most_recent_message(user_profile)
|
|
|
|
|
|
|
|
self.assertEqual(message.content, "TestMissedMessageEmailMessages Body")
|
2017-05-07 17:21:26 +02:00
|
|
|
self.assertEqual(message.sender, self.example_user('othello'))
|
2015-10-31 16:31:17 +01:00
|
|
|
self.assertEqual(message.recipient.id, user_profile.id)
|
|
|
|
self.assertEqual(message.recipient.type, Recipient.PERSONAL)
|
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class TestMissedHuddleMessageEmailMessages(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_receive_missed_huddle_message_email_messages(self) -> None:
|
2015-10-31 16:31:17 +01:00
|
|
|
|
|
|
|
# build dummy messages for missed messages email reply
|
|
|
|
# have Othello send Iago and Cordelia a PM. Cordelia will reply via email
|
|
|
|
# Iago and Othello will receive the message.
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('othello')
|
|
|
|
self.login(email)
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/json/messages", {"type": "private",
|
2015-12-01 00:18:33 +01:00
|
|
|
"content": "test_receive_missed_message_email_messages",
|
|
|
|
"client": "test suite",
|
2017-05-24 05:08:49 +02:00
|
|
|
"to": ujson.dumps([self.example_email('cordelia'),
|
|
|
|
self.example_email('iago')])})
|
2015-10-31 16:31:17 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('cordelia')
|
2015-10-31 16:31:17 +01:00
|
|
|
usermessage = most_recent_usermessage(user_profile)
|
|
|
|
|
|
|
|
# we don't want to send actual emails but we do need to create and store the
|
|
|
|
# token for looking up who did reply.
|
|
|
|
mm_address = create_missed_message_address(user_profile, usermessage.message)
|
|
|
|
|
2017-06-01 00:21:08 +02:00
|
|
|
incoming_valid_message = MIMEText('TestMissedHuddleMessageEmailMessages Body') # type: Any # https://github.com/python/typeshed/issues/275
|
2015-10-31 16:31:17 +01:00
|
|
|
|
|
|
|
incoming_valid_message['Subject'] = 'TestMissedHuddleMessageEmailMessages Subject'
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['From'] = self.example_email('cordelia')
|
2015-10-31 16:31:17 +01:00
|
|
|
incoming_valid_message['To'] = mm_address
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['Reply-to'] = self.example_email('cordelia')
|
2015-10-31 16:31:17 +01:00
|
|
|
|
|
|
|
process_message(incoming_valid_message)
|
|
|
|
|
|
|
|
# Confirm Iago received the message.
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('iago')
|
2015-10-31 16:31:17 +01:00
|
|
|
message = most_recent_message(user_profile)
|
|
|
|
|
|
|
|
self.assertEqual(message.content, "TestMissedHuddleMessageEmailMessages Body")
|
2017-05-07 17:21:26 +02:00
|
|
|
self.assertEqual(message.sender, self.example_user('cordelia'))
|
2015-10-31 16:31:17 +01:00
|
|
|
self.assertEqual(message.recipient.type, Recipient.HUDDLE)
|
|
|
|
|
|
|
|
# Confirm Othello received the message.
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('othello')
|
2015-10-31 16:31:17 +01:00
|
|
|
message = most_recent_message(user_profile)
|
|
|
|
|
|
|
|
self.assertEqual(message.content, "TestMissedHuddleMessageEmailMessages Body")
|
2017-05-07 17:21:26 +02:00
|
|
|
self.assertEqual(message.sender, self.example_user('cordelia'))
|
2015-10-31 16:31:17 +01:00
|
|
|
self.assertEqual(message.recipient.type, Recipient.HUDDLE)
|
2016-05-12 20:35:35 +02:00
|
|
|
|
2017-05-23 21:57:22 +02:00
|
|
|
class TestEmptyGatewaySetting(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_missed_message(self) -> None:
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('othello')
|
|
|
|
self.login(email)
|
2016-07-31 16:59:52 +02:00
|
|
|
result = self.client_post("/json/messages", {"type": "private",
|
|
|
|
"content": "test_receive_missed_message_email_messages",
|
|
|
|
"client": "test suite",
|
2017-05-24 05:08:49 +02:00
|
|
|
"to": ujson.dumps([self.example_email('cordelia'),
|
|
|
|
self.example_email('iago')])})
|
2016-07-31 16:59:52 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('cordelia')
|
2016-07-31 16:59:52 +02:00
|
|
|
usermessage = most_recent_usermessage(user_profile)
|
|
|
|
with self.settings(EMAIL_GATEWAY_PATTERN=''):
|
|
|
|
mm_address = create_missed_message_address(user_profile, usermessage.message)
|
2017-06-26 19:43:32 +02:00
|
|
|
self.assertEqual(mm_address, FromAddress.NOREPLY)
|
2016-07-31 16:59:52 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_encode_email_addr(self) -> None:
|
2017-05-23 23:26:03 +02:00
|
|
|
stream = get_stream("Denmark", get_realm("zulip"))
|
|
|
|
|
|
|
|
with self.settings(EMAIL_GATEWAY_PATTERN=''):
|
|
|
|
test_address = encode_email_address(stream)
|
|
|
|
self.assertEqual(test_address, '')
|
2016-07-31 16:59:52 +02:00
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class TestDigestEmailMessages(ZulipTestCase):
|
2016-05-12 20:35:35 +02:00
|
|
|
@mock.patch('zerver.lib.digest.enough_traffic')
|
|
|
|
@mock.patch('zerver.lib.digest.send_future_email')
|
2017-11-17 07:00:53 +01:00
|
|
|
def test_receive_digest_email_messages(self, mock_send_future_email: mock.MagicMock,
|
|
|
|
mock_enough_traffic: mock.MagicMock) -> None:
|
2016-05-12 20:35:35 +02:00
|
|
|
|
|
|
|
# build dummy messages for missed messages email reply
|
|
|
|
# have Hamlet send Othello a PM. Othello will reply via email
|
|
|
|
# Hamlet will receive the message.
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('hamlet')
|
|
|
|
self.login(email)
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/json/messages", {"type": "private",
|
2016-05-12 20:35:35 +02:00
|
|
|
"content": "test_receive_missed_message_email_messages",
|
|
|
|
"client": "test suite",
|
2017-05-24 05:08:49 +02:00
|
|
|
"to": self.example_email('othello')})
|
2016-05-12 20:35:35 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('othello')
|
2017-02-19 02:01:01 +01:00
|
|
|
cutoff = time.mktime(datetime.datetime(year=2016, month=1, day=1).timetuple())
|
2016-10-11 14:24:16 +02:00
|
|
|
|
2016-05-12 20:35:35 +02:00
|
|
|
handle_digest_email(user_profile.id, cutoff)
|
|
|
|
self.assertEqual(mock_send_future_email.call_count, 1)
|
2017-07-11 05:34:32 +02:00
|
|
|
self.assertEqual(mock_send_future_email.call_args[1]['to_user_id'], user_profile.id)
|
2016-07-12 14:55:02 +02:00
|
|
|
|
2017-08-27 22:01:20 +02:00
|
|
|
@mock.patch('zerver.lib.digest.queue_digest_recipient')
|
|
|
|
@mock.patch('zerver.lib.digest.timezone_now')
|
2017-11-17 07:00:53 +01:00
|
|
|
def test_inactive_users_queued_for_digest(self, mock_django_timezone: mock.MagicMock,
|
|
|
|
mock_queue_digest_recipient: mock.MagicMock) -> None:
|
2017-08-27 22:01:20 +02:00
|
|
|
cutoff = timezone_now()
|
|
|
|
# Test Tuesday
|
|
|
|
mock_django_timezone.return_value = datetime.datetime(year=2016, month=1, day=5)
|
2017-11-09 13:54:20 +01:00
|
|
|
all_user_profiles = UserProfile.objects.filter(
|
|
|
|
is_active=True, is_bot=False, enable_digest_emails=True)
|
|
|
|
# Check that all users without an a UserActivity entry are considered
|
|
|
|
# inactive users and get enqueued.
|
|
|
|
enqueue_emails(cutoff)
|
|
|
|
self.assertEqual(mock_queue_digest_recipient.call_count, all_user_profiles.count())
|
|
|
|
mock_queue_digest_recipient.reset_mock()
|
2017-08-27 22:01:20 +02:00
|
|
|
for realm in Realm.objects.filter(deactivated=False, show_digest_email=True):
|
2017-11-09 13:54:20 +01:00
|
|
|
user_profiles = all_user_profiles.filter(realm=realm)
|
|
|
|
for user_profile in user_profiles:
|
2017-08-27 22:01:20 +02:00
|
|
|
UserActivity.objects.create(
|
|
|
|
last_visit=cutoff - datetime.timedelta(days=1),
|
|
|
|
user_profile=user_profile,
|
|
|
|
count=0,
|
|
|
|
client=get_client('test_client'))
|
|
|
|
# Check that inactive users are enqueued
|
|
|
|
enqueue_emails(cutoff)
|
2017-11-09 13:54:20 +01:00
|
|
|
self.assertEqual(mock_queue_digest_recipient.call_count, all_user_profiles.count())
|
2017-08-27 22:01:20 +02:00
|
|
|
|
2017-11-08 15:30:33 +01:00
|
|
|
@mock.patch('zerver.lib.digest.enough_traffic', return_value=True)
|
2017-08-27 22:01:20 +02:00
|
|
|
@mock.patch('zerver.lib.digest.timezone_now')
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_active_users_not_enqueued(self, mock_django_timezone: mock.MagicMock,
|
|
|
|
mock_enough_traffic: mock.MagicMock) -> None:
|
2017-08-27 22:01:20 +02:00
|
|
|
cutoff = timezone_now()
|
|
|
|
# A Tuesday
|
|
|
|
mock_django_timezone.return_value = datetime.datetime(year=2016, month=1, day=5)
|
2017-11-08 15:30:33 +01:00
|
|
|
realms = Realm.objects.filter(deactivated=False, show_digest_email=True)
|
|
|
|
for realm in realms:
|
|
|
|
user_profiles = UserProfile.objects.filter(realm=realm)
|
|
|
|
for counter, user_profile in enumerate(user_profiles, 1):
|
2017-08-27 22:01:20 +02:00
|
|
|
UserActivity.objects.create(
|
|
|
|
last_visit=cutoff + datetime.timedelta(days=1),
|
|
|
|
user_profile=user_profile,
|
|
|
|
count=0,
|
|
|
|
client=get_client('test_client'))
|
|
|
|
# Check that an active user is not enqueued
|
2017-11-08 15:30:33 +01:00
|
|
|
with mock.patch('zerver.lib.digest.queue_digest_recipient') as mock_queue_digest_recipient:
|
|
|
|
enqueue_emails(cutoff)
|
|
|
|
self.assertEqual(mock_queue_digest_recipient.call_count, 0)
|
2017-08-27 22:01:20 +02:00
|
|
|
|
|
|
|
@mock.patch('zerver.lib.digest.queue_digest_recipient')
|
|
|
|
@mock.patch('zerver.lib.digest.timezone_now')
|
2017-11-17 07:00:53 +01:00
|
|
|
def test_only_enqueue_on_valid_day(self, mock_django_timezone: mock.MagicMock,
|
|
|
|
mock_queue_digest_recipient: mock.MagicMock) -> None:
|
2017-08-27 22:01:20 +02:00
|
|
|
# Not a Tuesday
|
|
|
|
mock_django_timezone.return_value = datetime.datetime(year=2016, month=1, day=6)
|
|
|
|
|
|
|
|
# Check that digests are not sent on days other than Tuesday.
|
|
|
|
cutoff = timezone_now()
|
|
|
|
enqueue_emails(cutoff)
|
|
|
|
self.assertEqual(mock_queue_digest_recipient.call_count, 0)
|
|
|
|
|
|
|
|
@mock.patch('zerver.lib.digest.queue_digest_recipient')
|
|
|
|
@mock.patch('zerver.lib.digest.timezone_now')
|
2017-11-17 07:00:53 +01:00
|
|
|
def test_no_email_digest_for_bots(self, mock_django_timezone: mock.MagicMock,
|
|
|
|
mock_queue_digest_recipient: mock.MagicMock) -> None:
|
2017-08-27 22:01:20 +02:00
|
|
|
cutoff = timezone_now()
|
|
|
|
# A Tuesday
|
|
|
|
mock_django_timezone.return_value = datetime.datetime(year=2016, month=1, day=5)
|
|
|
|
bot = do_create_user('some_bot@example.com', 'password', get_realm('zulip'), 'some_bot', '',
|
|
|
|
bot_type=UserProfile.DEFAULT_BOT)
|
|
|
|
UserActivity.objects.create(
|
|
|
|
last_visit=cutoff - datetime.timedelta(days=1),
|
|
|
|
user_profile=bot,
|
|
|
|
count=0,
|
|
|
|
client=get_client('test_client'))
|
|
|
|
|
|
|
|
# Check that bots are not sent emails
|
|
|
|
enqueue_emails(cutoff)
|
|
|
|
for arg in mock_queue_digest_recipient.call_args_list:
|
|
|
|
user = arg[0][0]
|
|
|
|
self.assertNotEqual(user.id, bot.id)
|
|
|
|
|
2017-11-22 11:00:23 +01:00
|
|
|
@mock.patch('zerver.lib.digest.timezone_now')
|
|
|
|
def test_new_stream_link(self, mock_django_timezone: mock.MagicMock) -> None:
|
|
|
|
cutoff = datetime.datetime(year=2017, month=11, day=1)
|
|
|
|
mock_django_timezone.return_value = datetime.datetime(year=2017, month=11, day=5)
|
|
|
|
cordelia = self.example_user('cordelia')
|
|
|
|
create_stream_if_needed(cordelia.realm, 'New stream')
|
|
|
|
new_stream = gather_new_streams(cordelia, cutoff)[1]
|
|
|
|
expected_html = "<a href='http://zulip.testserver/#narrow/stream/New.20stream'>New stream</a>"
|
|
|
|
self.assertIn(expected_html, new_stream['html'])
|
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class TestReplyExtraction(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_reply_is_extracted_from_plain(self) -> None:
|
2016-07-12 14:55:02 +02:00
|
|
|
|
|
|
|
# build dummy messages for stream
|
|
|
|
# test valid incoming stream message is processed properly
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('hamlet')
|
|
|
|
self.login(email)
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2017-08-25 06:01:29 +02:00
|
|
|
self.subscribe(user_profile, "Denmark")
|
2016-07-12 14:55:02 +02:00
|
|
|
stream = get_stream("Denmark", user_profile.realm)
|
|
|
|
|
|
|
|
stream_to_address = encode_email_address(stream)
|
2016-11-30 22:49:02 +01:00
|
|
|
text = """Reply
|
2016-07-12 14:55:02 +02:00
|
|
|
|
|
|
|
-----Original Message-----
|
|
|
|
|
|
|
|
Quote"""
|
|
|
|
|
2017-06-01 00:21:08 +02:00
|
|
|
incoming_valid_message = MIMEText(text) # type: Any # https://github.com/python/typeshed/issues/275
|
2016-07-12 14:55:02 +02:00
|
|
|
|
|
|
|
incoming_valid_message['Subject'] = 'TestStreamEmailMessages Subject'
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['From'] = self.example_email('hamlet')
|
2016-07-12 14:55:02 +02:00
|
|
|
incoming_valid_message['To'] = stream_to_address
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['Reply-to'] = self.example_email('othello')
|
2016-07-12 14:55:02 +02:00
|
|
|
|
|
|
|
process_message(incoming_valid_message)
|
|
|
|
|
|
|
|
# Hamlet is subscribed to this stream so should see the email message from Othello.
|
|
|
|
message = most_recent_message(user_profile)
|
|
|
|
|
|
|
|
self.assertEqual(message.content, "Reply")
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_reply_is_extracted_from_html(self) -> None:
|
2016-07-12 14:55:02 +02:00
|
|
|
|
|
|
|
# build dummy messages for stream
|
|
|
|
# test valid incoming stream message is processed properly
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('hamlet')
|
|
|
|
self.login(email)
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2017-08-25 06:01:29 +02:00
|
|
|
self.subscribe(user_profile, "Denmark")
|
2016-07-12 14:55:02 +02:00
|
|
|
stream = get_stream("Denmark", user_profile.realm)
|
|
|
|
|
|
|
|
stream_to_address = encode_email_address(stream)
|
|
|
|
html = """
|
|
|
|
<html>
|
|
|
|
<body>
|
|
|
|
<p>Reply</p>
|
|
|
|
<blockquote>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
On 11-Apr-2011, at 6:54 PM, Bob <bob@example.com> wrote:
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
Quote
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</blockquote>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
"""
|
|
|
|
|
2017-06-01 00:21:08 +02:00
|
|
|
incoming_valid_message = MIMEText(html, 'html') # type: Any # https://github.com/python/typeshed/issues/275
|
2016-07-12 14:55:02 +02:00
|
|
|
|
|
|
|
incoming_valid_message['Subject'] = 'TestStreamEmailMessages Subject'
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['From'] = self.example_email('hamlet')
|
2016-07-12 14:55:02 +02:00
|
|
|
incoming_valid_message['To'] = stream_to_address
|
2017-05-24 05:08:49 +02:00
|
|
|
incoming_valid_message['Reply-to'] = self.example_email('othello')
|
2016-07-12 14:55:02 +02:00
|
|
|
|
|
|
|
process_message(incoming_valid_message)
|
|
|
|
|
|
|
|
# Hamlet is subscribed to this stream so should see the email message from Othello.
|
|
|
|
message = most_recent_message(user_profile)
|
|
|
|
|
|
|
|
self.assertEqual(message.content, 'Reply')
|
2016-07-28 15:36:26 +02:00
|
|
|
|
2017-09-22 08:15:01 +02:00
|
|
|
MAILS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "fixtures", "email")
|
2016-07-28 15:36:26 +02:00
|
|
|
|
|
|
|
|
2017-05-24 05:08:49 +02:00
|
|
|
class TestScriptMTA(ZulipTestCase):
|
2017-04-18 17:28:55 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_success(self) -> None:
|
2017-04-18 17:28:55 +02:00
|
|
|
script = os.path.join(os.path.dirname(__file__),
|
|
|
|
'../../scripts/lib/email-mirror-postfix')
|
2016-07-28 15:36:26 +02:00
|
|
|
|
2017-05-24 05:08:49 +02:00
|
|
|
sender = self.example_email('hamlet')
|
2017-01-04 05:30:48 +01:00
|
|
|
stream = get_stream("Denmark", get_realm("zulip"))
|
2016-07-28 15:36:26 +02:00
|
|
|
stream_to_address = encode_email_address(stream)
|
|
|
|
|
|
|
|
template_path = os.path.join(MAILS_DIR, "simple.txt")
|
|
|
|
with open(template_path) as template_file:
|
|
|
|
mail_template = template_file.read()
|
|
|
|
mail = mail_template.format(stream_to_address=stream_to_address, sender=sender)
|
2017-04-18 17:28:55 +02:00
|
|
|
read_pipe, write_pipe = os.pipe()
|
|
|
|
os.write(write_pipe, mail.encode())
|
|
|
|
os.close(write_pipe)
|
|
|
|
subprocess.check_call(
|
2017-12-23 20:11:50 +01:00
|
|
|
[script, '-r', stream_to_address, '-s', settings.SHARED_SECRET, '-t'],
|
2017-04-18 17:28:55 +02:00
|
|
|
stdin=read_pipe)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_error_no_recipient(self) -> None:
|
2017-04-18 17:28:55 +02:00
|
|
|
script = os.path.join(os.path.dirname(__file__),
|
|
|
|
'../../scripts/lib/email-mirror-postfix')
|
|
|
|
|
2017-05-24 05:08:49 +02:00
|
|
|
sender = self.example_email('hamlet')
|
2017-04-18 17:28:55 +02:00
|
|
|
stream = get_stream("Denmark", get_realm("zulip"))
|
|
|
|
stream_to_address = encode_email_address(stream)
|
|
|
|
template_path = os.path.join(MAILS_DIR, "simple.txt")
|
|
|
|
with open(template_path) as template_file:
|
|
|
|
mail_template = template_file.read()
|
|
|
|
mail = mail_template.format(stream_to_address=stream_to_address, sender=sender)
|
|
|
|
read_pipe, write_pipe = os.pipe()
|
|
|
|
os.write(write_pipe, mail.encode())
|
|
|
|
os.close(write_pipe)
|
|
|
|
success_call = True
|
|
|
|
try:
|
|
|
|
subprocess.check_output([script, '-s', settings.SHARED_SECRET, '-t'],
|
|
|
|
stdin=read_pipe)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
self.assertEqual(
|
|
|
|
e.output,
|
|
|
|
b'5.1.1 Bad destination mailbox address: No missed message email address.\n'
|
|
|
|
)
|
|
|
|
self.assertEqual(e.returncode, 67)
|
|
|
|
success_call = False
|
|
|
|
self.assertFalse(success_call)
|
|
|
|
|
|
|
|
|
|
|
|
class TestEmailMirrorTornadoView(ZulipTestCase):
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def send_private_message(self) -> Text:
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('othello')
|
|
|
|
self.login(email)
|
2017-04-18 17:28:55 +02:00
|
|
|
result = self.client_post(
|
|
|
|
"/json/messages",
|
|
|
|
{
|
|
|
|
"type": "private",
|
|
|
|
"content": "test_receive_missed_message_email_messages",
|
|
|
|
"client": "test suite",
|
2017-05-24 05:08:49 +02:00
|
|
|
"to": ujson.dumps([self.example_email('cordelia'), self.example_email('iago')])
|
2017-04-18 17:28:55 +02:00
|
|
|
})
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('cordelia')
|
2017-04-18 17:28:55 +02:00
|
|
|
user_message = most_recent_usermessage(user_profile)
|
|
|
|
return create_missed_message_address(user_profile, user_message.message)
|
|
|
|
|
|
|
|
@mock.patch('zerver.lib.email_mirror.queue_json_publish')
|
2017-11-17 07:00:53 +01:00
|
|
|
def send_offline_message(self, to_address: str, sender: str,
|
|
|
|
mock_queue_json_publish: mock.Mock) -> HttpResponse:
|
2017-04-18 17:28:55 +02:00
|
|
|
template_path = os.path.join(MAILS_DIR, "simple.txt")
|
|
|
|
with open(template_path) as template_file:
|
|
|
|
mail_template = template_file.read()
|
|
|
|
mail = mail_template.format(stream_to_address=to_address, sender=sender)
|
2016-07-28 15:36:26 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def check_queue_json_publish(queue_name: str,
|
|
|
|
event: Union[Mapping[str, Any], str],
|
2017-11-24 13:18:46 +01:00
|
|
|
processor: Callable[[Any], None]=None) -> None:
|
2016-07-28 15:36:26 +02:00
|
|
|
self.assertEqual(queue_name, "email_mirror")
|
2017-04-18 17:28:55 +02:00
|
|
|
self.assertEqual(event, {"rcpt_to": to_address, "message": mail})
|
|
|
|
|
2016-07-28 15:36:26 +02:00
|
|
|
mock_queue_json_publish.side_effect = check_queue_json_publish
|
2017-04-18 17:28:55 +02:00
|
|
|
request_data = {
|
|
|
|
"recipient": to_address,
|
|
|
|
"msg_text": mail
|
|
|
|
}
|
|
|
|
post_data = dict(
|
|
|
|
data=ujson.dumps(request_data),
|
|
|
|
secret=settings.SHARED_SECRET
|
|
|
|
)
|
|
|
|
return self.client_post('/email_mirror_message', post_data)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_success_stream(self) -> None:
|
2017-04-18 17:28:55 +02:00
|
|
|
stream = get_stream("Denmark", get_realm("zulip"))
|
|
|
|
stream_to_address = encode_email_address(stream)
|
2017-05-24 05:08:49 +02:00
|
|
|
result = self.send_offline_message(stream_to_address, self.example_email('hamlet'))
|
2017-04-18 17:28:55 +02:00
|
|
|
self.assert_json_success(result)
|
2016-07-28 15:36:26 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_error_to_stream_with_wrong_address(self) -> None:
|
2017-04-18 17:28:55 +02:00
|
|
|
stream = get_stream("Denmark", get_realm("zulip"))
|
|
|
|
stream_to_address = encode_email_address(stream)
|
|
|
|
stream_to_address = stream_to_address.replace("Denmark", "Wrong_stream")
|
2016-07-28 15:36:26 +02:00
|
|
|
|
2017-05-24 05:08:49 +02:00
|
|
|
result = self.send_offline_message(stream_to_address, self.example_email('hamlet'))
|
2017-04-18 17:28:55 +02:00
|
|
|
self.assert_json_error(
|
|
|
|
result,
|
|
|
|
"5.1.1 Bad destination mailbox address: "
|
|
|
|
"Please use the address specified in your Streams page.")
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_success_to_private(self) -> None:
|
2017-04-18 17:28:55 +02:00
|
|
|
mm_address = self.send_private_message()
|
2017-05-24 05:08:49 +02:00
|
|
|
result = self.send_offline_message(mm_address, self.example_email('cordelia'))
|
2017-04-18 17:28:55 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_using_mm_address_twice(self) -> None:
|
2017-04-18 17:28:55 +02:00
|
|
|
mm_address = self.send_private_message()
|
2017-05-24 05:08:49 +02:00
|
|
|
self.send_offline_message(mm_address, self.example_email('cordelia'))
|
|
|
|
result = self.send_offline_message(mm_address, self.example_email('cordelia'))
|
2017-04-18 17:28:55 +02:00
|
|
|
self.assert_json_error(
|
|
|
|
result,
|
|
|
|
"5.1.1 Bad destination mailbox address: Bad or expired missed message address.")
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_wrong_missed_email_private_message(self) -> None:
|
2017-04-18 17:28:55 +02:00
|
|
|
self.send_private_message()
|
|
|
|
mm_address = 'mm' + ('x' * 32) + '@testserver'
|
2017-05-24 05:08:49 +02:00
|
|
|
result = self.send_offline_message(mm_address, self.example_email('cordelia'))
|
2017-04-18 17:28:55 +02:00
|
|
|
self.assert_json_error(
|
|
|
|
result,
|
|
|
|
"5.1.1 Bad destination mailbox address: Bad or expired missed message address.")
|