mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	tests: Extract test_message_send.py for message sending tests.
This commit extracts out MessagePOSTTest class from test_messages.py intially. In future commits other related message sending tests will be moved from test_messages.py to test_message_send.py.
This commit is contained in:
		
							
								
								
									
										918
									
								
								zerver/tests/test_message_send.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										918
									
								
								zerver/tests/test_message_send.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,918 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					from typing import Any, Optional
 | 
				
			||||||
 | 
					from unittest import mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ujson
 | 
				
			||||||
 | 
					from django.db import IntegrityError
 | 
				
			||||||
 | 
					from django.utils.timezone import now as timezone_now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from zerver.decorator import JsonableError
 | 
				
			||||||
 | 
					from zerver.lib.actions import (
 | 
				
			||||||
 | 
					    create_mirror_user_if_needed,
 | 
				
			||||||
 | 
					    do_change_stream_post_policy,
 | 
				
			||||||
 | 
					    do_create_user,
 | 
				
			||||||
 | 
					    do_deactivate_user,
 | 
				
			||||||
 | 
					    do_set_realm_property,
 | 
				
			||||||
 | 
					    internal_send_stream_message,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from zerver.lib.create_user import create_user_profile
 | 
				
			||||||
 | 
					from zerver.lib.message import get_recent_private_conversations
 | 
				
			||||||
 | 
					from zerver.lib.test_classes import ZulipTestCase
 | 
				
			||||||
 | 
					from zerver.lib.test_helpers import reset_emails_in_zulip_realm
 | 
				
			||||||
 | 
					from zerver.lib.timestamp import datetime_to_timestamp
 | 
				
			||||||
 | 
					from zerver.models import (
 | 
				
			||||||
 | 
					    MAX_MESSAGE_LENGTH,
 | 
				
			||||||
 | 
					    MAX_TOPIC_NAME_LENGTH,
 | 
				
			||||||
 | 
					    Stream,
 | 
				
			||||||
 | 
					    UserProfile,
 | 
				
			||||||
 | 
					    get_huddle_recipient,
 | 
				
			||||||
 | 
					    get_realm,
 | 
				
			||||||
 | 
					    get_stream,
 | 
				
			||||||
 | 
					    get_system_bot,
 | 
				
			||||||
 | 
					    get_user,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from zerver.views.message_send import InvalidMirrorInput
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MessagePOSTTest(ZulipTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _send_and_verify_message(self, user: UserProfile, stream_name: str, error_msg: Optional[str]=None) -> None:
 | 
				
			||||||
 | 
					        if error_msg is None:
 | 
				
			||||||
 | 
					            msg_id = self.send_stream_message(user, stream_name)
 | 
				
			||||||
 | 
					            result = self.api_get(user, '/json/messages/' + str(msg_id))
 | 
				
			||||||
 | 
					            self.assert_json_success(result)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            with self.assertRaisesRegex(JsonableError, error_msg):
 | 
				
			||||||
 | 
					                self.send_stream_message(user, stream_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_message_to_self(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a message to a stream to which you are subscribed is
 | 
				
			||||||
 | 
					        successful.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                     "to": "Verona",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "topic": "Test topic"})
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_api_message_to_self(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Same as above, but for the API view
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        user = self.example_user('hamlet')
 | 
				
			||||||
 | 
					        result = self.api_post(user, "/api/v1/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                          "to": "Verona",
 | 
				
			||||||
 | 
					                                                          "client": "test suite",
 | 
				
			||||||
 | 
					                                                          "content": "Test message",
 | 
				
			||||||
 | 
					                                                          "topic": "Test topic"})
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_message_to_stream_with_nonexistent_id(self) -> None:
 | 
				
			||||||
 | 
					        cordelia = self.example_user('cordelia')
 | 
				
			||||||
 | 
					        bot = self.create_test_bot(
 | 
				
			||||||
 | 
					            short_name='whatever',
 | 
				
			||||||
 | 
					            user_profile=cordelia,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        result = self.api_post(
 | 
				
			||||||
 | 
					            bot, "/api/v1/messages",
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                "type": "stream",
 | 
				
			||||||
 | 
					                "to": ujson.dumps([99999]),
 | 
				
			||||||
 | 
					                "client": "test suite",
 | 
				
			||||||
 | 
					                "content": "Stream message by ID.",
 | 
				
			||||||
 | 
					                "topic": "Test topic for stream ID message",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Stream with ID '99999' does not exist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        msg = self.get_last_message()
 | 
				
			||||||
 | 
					        expected = ("Your bot `whatever-bot@zulip.testserver` tried to send a message to "
 | 
				
			||||||
 | 
					                    "stream ID 99999, but there is no stream with that ID.")
 | 
				
			||||||
 | 
					        self.assertEqual(msg.content, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_message_to_stream_by_id(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a message to a stream (by stream ID) to which you are
 | 
				
			||||||
 | 
					        subscribed is successful.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        realm = get_realm('zulip')
 | 
				
			||||||
 | 
					        stream = get_stream('Verona', realm)
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                     "to": ujson.dumps([stream.id]),
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "content": "Stream message by ID.",
 | 
				
			||||||
 | 
					                                                     "topic": "Test topic for stream ID message"})
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        sent_message = self.get_last_message()
 | 
				
			||||||
 | 
					        self.assertEqual(sent_message.content, "Stream message by ID.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_sending_message_as_stream_post_policy_admins(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending messages to streams which only the admins can create and post to.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        admin_profile = self.example_user("iago")
 | 
				
			||||||
 | 
					        self.login_user(admin_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stream_name = "Verona"
 | 
				
			||||||
 | 
					        stream = get_stream(stream_name, admin_profile.realm)
 | 
				
			||||||
 | 
					        do_change_stream_post_policy(stream, Stream.STREAM_POST_POLICY_ADMINS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Admins and their owned bots can send to STREAM_POST_POLICY_ADMINS streams
 | 
				
			||||||
 | 
					        self._send_and_verify_message(admin_profile, stream_name)
 | 
				
			||||||
 | 
					        admin_owned_bot = self.create_test_bot(
 | 
				
			||||||
 | 
					            short_name='whatever1',
 | 
				
			||||||
 | 
					            full_name='whatever1',
 | 
				
			||||||
 | 
					            user_profile=admin_profile,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self._send_and_verify_message(admin_owned_bot, stream_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        non_admin_profile = self.example_user("hamlet")
 | 
				
			||||||
 | 
					        self.login_user(non_admin_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Non admins and their owned bots cannot send to STREAM_POST_POLICY_ADMINS streams
 | 
				
			||||||
 | 
					        self._send_and_verify_message(non_admin_profile, stream_name,
 | 
				
			||||||
 | 
					                                      "Only organization administrators can send to this stream.")
 | 
				
			||||||
 | 
					        non_admin_owned_bot = self.create_test_bot(
 | 
				
			||||||
 | 
					            short_name='whatever2',
 | 
				
			||||||
 | 
					            full_name='whatever2',
 | 
				
			||||||
 | 
					            user_profile=non_admin_profile,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self._send_and_verify_message(non_admin_owned_bot, stream_name,
 | 
				
			||||||
 | 
					                                      "Only organization administrators can send to this stream.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Bots without owner (except cross realm bot) cannot send to announcement only streams
 | 
				
			||||||
 | 
					        bot_without_owner = do_create_user(
 | 
				
			||||||
 | 
					            email='free-bot@zulip.testserver',
 | 
				
			||||||
 | 
					            password='',
 | 
				
			||||||
 | 
					            realm=non_admin_profile.realm,
 | 
				
			||||||
 | 
					            full_name='freebot',
 | 
				
			||||||
 | 
					            short_name='freebot',
 | 
				
			||||||
 | 
					            bot_type=UserProfile.DEFAULT_BOT,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self._send_and_verify_message(bot_without_owner, stream_name,
 | 
				
			||||||
 | 
					                                      "Only organization administrators can send to this stream.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Cross realm bots should be allowed
 | 
				
			||||||
 | 
					        notification_bot = get_system_bot("notification-bot@zulip.com")
 | 
				
			||||||
 | 
					        internal_send_stream_message(stream.realm, notification_bot, stream,
 | 
				
			||||||
 | 
					                                     'Test topic', 'Test message by notification bot')
 | 
				
			||||||
 | 
					        self.assertEqual(self.get_last_message().content, 'Test message by notification bot')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_sending_message_as_stream_post_policy_restrict_new_members(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending messages to streams which new members cannot create and post to.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        admin_profile = self.example_user("iago")
 | 
				
			||||||
 | 
					        self.login_user(admin_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        do_set_realm_property(admin_profile.realm, 'waiting_period_threshold', 10)
 | 
				
			||||||
 | 
					        admin_profile.date_joined = timezone_now() - datetime.timedelta(days=9)
 | 
				
			||||||
 | 
					        admin_profile.save()
 | 
				
			||||||
 | 
					        self.assertTrue(admin_profile.is_new_member)
 | 
				
			||||||
 | 
					        self.assertTrue(admin_profile.is_realm_admin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stream_name = "Verona"
 | 
				
			||||||
 | 
					        stream = get_stream(stream_name, admin_profile.realm)
 | 
				
			||||||
 | 
					        do_change_stream_post_policy(stream, Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Admins and their owned bots can send to STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams,
 | 
				
			||||||
 | 
					        # even if the admin is a new user
 | 
				
			||||||
 | 
					        self._send_and_verify_message(admin_profile, stream_name)
 | 
				
			||||||
 | 
					        admin_owned_bot = self.create_test_bot(
 | 
				
			||||||
 | 
					            short_name='whatever1',
 | 
				
			||||||
 | 
					            full_name='whatever1',
 | 
				
			||||||
 | 
					            user_profile=admin_profile,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self._send_and_verify_message(admin_owned_bot, stream_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        non_admin_profile = self.example_user("hamlet")
 | 
				
			||||||
 | 
					        self.login_user(non_admin_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        non_admin_profile.date_joined = timezone_now() - datetime.timedelta(days=9)
 | 
				
			||||||
 | 
					        non_admin_profile.save()
 | 
				
			||||||
 | 
					        self.assertTrue(non_admin_profile.is_new_member)
 | 
				
			||||||
 | 
					        self.assertFalse(non_admin_profile.is_realm_admin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Non admins and their owned bots can send to STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams,
 | 
				
			||||||
 | 
					        # if the user is not a new member
 | 
				
			||||||
 | 
					        self._send_and_verify_message(non_admin_profile, stream_name,
 | 
				
			||||||
 | 
					                                      "New members cannot send to this stream.")
 | 
				
			||||||
 | 
					        non_admin_owned_bot = self.create_test_bot(
 | 
				
			||||||
 | 
					            short_name='whatever2',
 | 
				
			||||||
 | 
					            full_name='whatever2',
 | 
				
			||||||
 | 
					            user_profile=non_admin_profile,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self._send_and_verify_message(non_admin_owned_bot, stream_name,
 | 
				
			||||||
 | 
					                                      "New members cannot send to this stream.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Bots without owner (except cross realm bot) cannot send to announcement only stream
 | 
				
			||||||
 | 
					        bot_without_owner = do_create_user(
 | 
				
			||||||
 | 
					            email='free-bot@zulip.testserver',
 | 
				
			||||||
 | 
					            password='',
 | 
				
			||||||
 | 
					            realm=non_admin_profile.realm,
 | 
				
			||||||
 | 
					            full_name='freebot',
 | 
				
			||||||
 | 
					            short_name='freebot',
 | 
				
			||||||
 | 
					            bot_type=UserProfile.DEFAULT_BOT,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self._send_and_verify_message(bot_without_owner, stream_name,
 | 
				
			||||||
 | 
					                                      "New members cannot send to this stream.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Cross realm bots should be allowed
 | 
				
			||||||
 | 
					        notification_bot = get_system_bot("notification-bot@zulip.com")
 | 
				
			||||||
 | 
					        internal_send_stream_message(stream.realm, notification_bot, stream,
 | 
				
			||||||
 | 
					                                     'Test topic', 'Test message by notification bot')
 | 
				
			||||||
 | 
					        self.assertEqual(self.get_last_message().content, 'Test message by notification bot')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_api_message_with_default_to(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending messages without a to field should be sent to the default
 | 
				
			||||||
 | 
					        stream for the user_profile.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        user = self.example_user('hamlet')
 | 
				
			||||||
 | 
					        user.default_sending_stream_id = get_stream('Verona', user.realm).id
 | 
				
			||||||
 | 
					        user.save()
 | 
				
			||||||
 | 
					        result = self.api_post(user, "/api/v1/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                          "client": "test suite",
 | 
				
			||||||
 | 
					                                                          "content": "Test message no to",
 | 
				
			||||||
 | 
					                                                          "topic": "Test topic"})
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sent_message = self.get_last_message()
 | 
				
			||||||
 | 
					        self.assertEqual(sent_message.content, "Test message no to")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_message_to_nonexistent_stream(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a message to a nonexistent stream fails.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        self.assertFalse(Stream.objects.filter(name="nonexistent_stream"))
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                     "to": "nonexistent_stream",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "topic": "Test topic"})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Stream 'nonexistent_stream' does not exist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_message_to_nonexistent_stream_with_bad_characters(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Nonexistent stream name with bad characters should be escaped properly.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        self.assertFalse(Stream.objects.filter(name="""&<"'><non-existent>"""))
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                     "to": """&<"'><non-existent>""",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "topic": "Test topic"})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Stream '&<"'><non-existent>' does not exist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_personal_message(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a personal message to a valid username is successful.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        user_profile = self.example_user("hamlet")
 | 
				
			||||||
 | 
					        self.login_user(user_profile)
 | 
				
			||||||
 | 
					        othello = self.example_user('othello')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "private",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "to": othello.email})
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        message_id = ujson.loads(result.content.decode())['id']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        recent_conversations = get_recent_private_conversations(user_profile)
 | 
				
			||||||
 | 
					        self.assertEqual(len(recent_conversations), 1)
 | 
				
			||||||
 | 
					        recent_conversation = list(recent_conversations.values())[0]
 | 
				
			||||||
 | 
					        recipient_id = list(recent_conversations.keys())[0]
 | 
				
			||||||
 | 
					        self.assertEqual(set(recent_conversation['user_ids']), {othello.id})
 | 
				
			||||||
 | 
					        self.assertEqual(recent_conversation['max_message_id'], message_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Now send a message to yourself and see how that interacts with the data structure
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "private",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "to": user_profile.email})
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        self_message_id = ujson.loads(result.content.decode())['id']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        recent_conversations = get_recent_private_conversations(user_profile)
 | 
				
			||||||
 | 
					        self.assertEqual(len(recent_conversations), 2)
 | 
				
			||||||
 | 
					        recent_conversation = recent_conversations[recipient_id]
 | 
				
			||||||
 | 
					        self.assertEqual(set(recent_conversation['user_ids']), {othello.id})
 | 
				
			||||||
 | 
					        self.assertEqual(recent_conversation['max_message_id'], message_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Now verify we have the appropriate self-pm data structure
 | 
				
			||||||
 | 
					        del recent_conversations[recipient_id]
 | 
				
			||||||
 | 
					        recent_conversation = list(recent_conversations.values())[0]
 | 
				
			||||||
 | 
					        recipient_id = list(recent_conversations.keys())[0]
 | 
				
			||||||
 | 
					        self.assertEqual(set(recent_conversation['user_ids']), set())
 | 
				
			||||||
 | 
					        self.assertEqual(recent_conversation['max_message_id'], self_message_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_personal_message_by_id(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a personal message to a valid user ID is successful.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post(
 | 
				
			||||||
 | 
					            "/json/messages",
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                "type": "private",
 | 
				
			||||||
 | 
					                "content": "Test message",
 | 
				
			||||||
 | 
					                "client": "test suite",
 | 
				
			||||||
 | 
					                "to": ujson.dumps([self.example_user("othello").id]),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        msg = self.get_last_message()
 | 
				
			||||||
 | 
					        self.assertEqual("Test message", msg.content)
 | 
				
			||||||
 | 
					        self.assertEqual(msg.recipient_id, self.example_user("othello").id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_group_personal_message_by_id(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a personal message to a valid user ID is successful.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post(
 | 
				
			||||||
 | 
					            "/json/messages",
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                "type": "private",
 | 
				
			||||||
 | 
					                "content": "Test message",
 | 
				
			||||||
 | 
					                "client": "test suite",
 | 
				
			||||||
 | 
					                "to": ujson.dumps([self.example_user("othello").id,
 | 
				
			||||||
 | 
					                                   self.example_user("cordelia").id]),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        msg = self.get_last_message()
 | 
				
			||||||
 | 
					        self.assertEqual("Test message", msg.content)
 | 
				
			||||||
 | 
					        self.assertEqual(msg.recipient_id, get_huddle_recipient(
 | 
				
			||||||
 | 
					            {self.example_user("hamlet").id,
 | 
				
			||||||
 | 
					             self.example_user("othello").id,
 | 
				
			||||||
 | 
					             self.example_user("cordelia").id}).id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_personal_message_copying_self(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a personal message to yourself plus another user is successful,
 | 
				
			||||||
 | 
					        and counts as a message just to that user.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        hamlet = self.example_user('hamlet')
 | 
				
			||||||
 | 
					        othello = self.example_user('othello')
 | 
				
			||||||
 | 
					        self.login_user(hamlet)
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {
 | 
				
			||||||
 | 
					            "type": "private",
 | 
				
			||||||
 | 
					            "content": "Test message",
 | 
				
			||||||
 | 
					            "client": "test suite",
 | 
				
			||||||
 | 
					            "to": ujson.dumps([hamlet.id, othello.id])})
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        msg = self.get_last_message()
 | 
				
			||||||
 | 
					        # Verify that we're not actually on the "recipient list"
 | 
				
			||||||
 | 
					        self.assertNotIn("Hamlet", str(msg.recipient))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_personal_message_to_nonexistent_user(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a personal message to an invalid email returns error JSON.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "private",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "to": "nonexistent"})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Invalid email 'nonexistent'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_personal_message_to_deactivated_user(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a personal message to a deactivated user returns error JSON.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        othello = self.example_user('othello')
 | 
				
			||||||
 | 
					        cordelia = self.example_user('cordelia')
 | 
				
			||||||
 | 
					        do_deactivate_user(othello)
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {
 | 
				
			||||||
 | 
					            "type": "private",
 | 
				
			||||||
 | 
					            "content": "Test message",
 | 
				
			||||||
 | 
					            "client": "test suite",
 | 
				
			||||||
 | 
					            "to": ujson.dumps([othello.id])})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, f"'{othello.email}' is no longer using Zulip.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {
 | 
				
			||||||
 | 
					            "type": "private",
 | 
				
			||||||
 | 
					            "content": "Test message",
 | 
				
			||||||
 | 
					            "client": "test suite",
 | 
				
			||||||
 | 
					            "to": ujson.dumps([othello.id, cordelia.id])})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, f"'{othello.email}' is no longer using Zulip.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_invalid_type(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a message of unknown type returns error JSON.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        othello = self.example_user('othello')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "invalid type",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "to": othello.email})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Invalid message type")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_empty_message(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a message that is empty or only whitespace should fail
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        othello = self.example_user('othello')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "private",
 | 
				
			||||||
 | 
					                                                     "content": " ",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "to": othello.email})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Message must not be empty")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_empty_string_topic(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a message that has empty string topic should fail
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                     "to": "Verona",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "topic": ""})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Topic can't be empty")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_missing_topic(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a message without topic should fail
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                     "to": "Verona",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "content": "Test message"})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Missing topic")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_invalid_message_type(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Messages other than the type of "private" or "stream" are considered as invalid
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "invalid",
 | 
				
			||||||
 | 
					                                                     "to": "Verona",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "topic": "Test topic"})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Invalid message type")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_private_message_without_recipients(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending private message without recipients should fail
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "private",
 | 
				
			||||||
 | 
					                                                     "content": "Test content",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "to": ""})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Message must have recipients")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_mirrored_huddle(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a mirrored huddle message works
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        result = self.api_post(self.mit_user("starnine"),
 | 
				
			||||||
 | 
					                               "/json/messages", {"type": "private",
 | 
				
			||||||
 | 
					                                                  "sender": self.mit_email("sipbtest"),
 | 
				
			||||||
 | 
					                                                  "content": "Test message",
 | 
				
			||||||
 | 
					                                                  "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					                                                  "to": ujson.dumps([self.mit_email("starnine"),
 | 
				
			||||||
 | 
					                                                                     self.mit_email("espuser")])},
 | 
				
			||||||
 | 
					                               subdomain="zephyr")
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_mirrored_personal(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a mirrored personal message works
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        result = self.api_post(self.mit_user("starnine"),
 | 
				
			||||||
 | 
					                               "/json/messages", {"type": "private",
 | 
				
			||||||
 | 
					                                                  "sender": self.mit_email("sipbtest"),
 | 
				
			||||||
 | 
					                                                  "content": "Test message",
 | 
				
			||||||
 | 
					                                                  "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					                                                  "to": self.mit_email("starnine")},
 | 
				
			||||||
 | 
					                               subdomain="zephyr")
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_mirrored_personal_browser(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a mirrored personal message via the browser should not work.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        user = self.mit_user('starnine')
 | 
				
			||||||
 | 
					        self.login_user(user)
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages",
 | 
				
			||||||
 | 
					                                  {"type": "private",
 | 
				
			||||||
 | 
					                                   "sender": self.mit_email("sipbtest"),
 | 
				
			||||||
 | 
					                                   "content": "Test message",
 | 
				
			||||||
 | 
					                                   "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					                                   "to": self.mit_email("starnine")},
 | 
				
			||||||
 | 
					                                  subdomain="zephyr")
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Invalid mirrored message")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_mirrored_personal_to_someone_else(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a mirrored personal message to someone else is not allowed.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        result = self.api_post(self.mit_user("starnine"), "/api/v1/messages",
 | 
				
			||||||
 | 
					                               {"type": "private",
 | 
				
			||||||
 | 
					                                "sender": self.mit_email("sipbtest"),
 | 
				
			||||||
 | 
					                                "content": "Test message",
 | 
				
			||||||
 | 
					                                "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					                                "to": self.mit_email("espuser")},
 | 
				
			||||||
 | 
					                               subdomain="zephyr")
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "User not authorized for this query")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_duplicated_mirrored_huddle(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending two mirrored huddles in the row return the same ID
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        msg = {"type": "private",
 | 
				
			||||||
 | 
					               "sender": self.mit_email("sipbtest"),
 | 
				
			||||||
 | 
					               "content": "Test message",
 | 
				
			||||||
 | 
					               "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					               "to": ujson.dumps([self.mit_email("espuser"),
 | 
				
			||||||
 | 
					                                  self.mit_email("starnine")])}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with mock.patch('DNS.dnslookup', return_value=[['starnine:*:84233:101:Athena Consulting Exchange User,,,:/mit/starnine:/bin/bash']]):
 | 
				
			||||||
 | 
					            result1 = self.api_post(self.mit_user("starnine"), "/api/v1/messages", msg,
 | 
				
			||||||
 | 
					                                    subdomain="zephyr")
 | 
				
			||||||
 | 
					            self.assert_json_success(result1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with mock.patch('DNS.dnslookup', return_value=[['espuser:*:95494:101:Esp Classroom,,,:/mit/espuser:/bin/athena/bash']]):
 | 
				
			||||||
 | 
					            result2 = self.api_post(self.mit_user("espuser"), "/api/v1/messages", msg,
 | 
				
			||||||
 | 
					                                    subdomain="zephyr")
 | 
				
			||||||
 | 
					            self.assert_json_success(result2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(ujson.loads(result1.content)['id'],
 | 
				
			||||||
 | 
					                         ujson.loads(result2.content)['id'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_message_with_null_bytes(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        A message with null bytes in it is handled.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        post_data = {"type": "stream", "to": "Verona", "client": "test suite",
 | 
				
			||||||
 | 
					                     "content": "  I like null bytes \x00 in my content", "topic": "Test topic"}
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", post_data)
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Message must not contain null bytes")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_strip_message(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        A message with mixed whitespace at the end is cleaned up.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        post_data = {"type": "stream", "to": "Verona", "client": "test suite",
 | 
				
			||||||
 | 
					                     "content": "  I like whitespace at the end! \n\n \n", "topic": "Test topic"}
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", post_data)
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        sent_message = self.get_last_message()
 | 
				
			||||||
 | 
					        self.assertEqual(sent_message.content, "  I like whitespace at the end!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_long_message(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a message longer than the maximum message length succeeds but is
 | 
				
			||||||
 | 
					        truncated.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        long_message = "A" * (MAX_MESSAGE_LENGTH + 1)
 | 
				
			||||||
 | 
					        post_data = {"type": "stream", "to": "Verona", "client": "test suite",
 | 
				
			||||||
 | 
					                     "content": long_message, "topic": "Test topic"}
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", post_data)
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sent_message = self.get_last_message()
 | 
				
			||||||
 | 
					        self.assertEqual(sent_message.content,
 | 
				
			||||||
 | 
					                         "A" * (MAX_MESSAGE_LENGTH - 20) + "\n[message truncated]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_long_topic(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sending a message with a topic longer than the maximum topic length
 | 
				
			||||||
 | 
					        succeeds, but the topic is truncated.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        long_topic = "A" * (MAX_TOPIC_NAME_LENGTH + 1)
 | 
				
			||||||
 | 
					        post_data = {"type": "stream", "to": "Verona", "client": "test suite",
 | 
				
			||||||
 | 
					                     "content": "test content", "topic": long_topic}
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", post_data)
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sent_message = self.get_last_message()
 | 
				
			||||||
 | 
					        self.assertEqual(sent_message.topic_name(),
 | 
				
			||||||
 | 
					                         "A" * (MAX_TOPIC_NAME_LENGTH - 3) + "...")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_send_forged_message_as_not_superuser(self) -> None:
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                     "to": "Verona",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "topic": "Test topic",
 | 
				
			||||||
 | 
					                                                     "forged": "true"})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "User not authorized for this query")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_send_message_as_not_superuser_to_different_domain(self) -> None:
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        result = self.client_post("/json/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                     "to": "Verona",
 | 
				
			||||||
 | 
					                                                     "client": "test suite",
 | 
				
			||||||
 | 
					                                                     "content": "Test message",
 | 
				
			||||||
 | 
					                                                     "topic": "Test topic",
 | 
				
			||||||
 | 
					                                                     "realm_str": "mit"})
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "User not authorized for this query")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_send_message_as_superuser_to_domain_that_dont_exist(self) -> None:
 | 
				
			||||||
 | 
					        user = self.example_user("default_bot")
 | 
				
			||||||
 | 
					        password = "test_password"
 | 
				
			||||||
 | 
					        user.set_password(password)
 | 
				
			||||||
 | 
					        user.is_api_super_user = True
 | 
				
			||||||
 | 
					        user.save()
 | 
				
			||||||
 | 
					        result = self.api_post(user,
 | 
				
			||||||
 | 
					                               "/api/v1/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                    "to": "Verona",
 | 
				
			||||||
 | 
					                                                    "client": "test suite",
 | 
				
			||||||
 | 
					                                                    "content": "Test message",
 | 
				
			||||||
 | 
					                                                    "topic": "Test topic",
 | 
				
			||||||
 | 
					                                                    "realm_str": "non-existing"})
 | 
				
			||||||
 | 
					        user.is_api_super_user = False
 | 
				
			||||||
 | 
					        user.save()
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Unknown organization 'non-existing'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_send_message_when_sender_is_not_set(self) -> None:
 | 
				
			||||||
 | 
					        result = self.api_post(self.mit_user("starnine"), "/api/v1/messages",
 | 
				
			||||||
 | 
					                               {"type": "private",
 | 
				
			||||||
 | 
					                                "content": "Test message",
 | 
				
			||||||
 | 
					                                "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					                                "to": self.mit_email("starnine")},
 | 
				
			||||||
 | 
					                               subdomain="zephyr")
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Missing sender")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_send_message_as_not_superuser_when_type_is_not_private(self) -> None:
 | 
				
			||||||
 | 
					        result = self.api_post(self.mit_user("starnine"), "/api/v1/messages",
 | 
				
			||||||
 | 
					                               {"type": "not-private",
 | 
				
			||||||
 | 
					                                "sender": self.mit_email("sipbtest"),
 | 
				
			||||||
 | 
					                                "content": "Test message",
 | 
				
			||||||
 | 
					                                "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					                                "to": self.mit_email("starnine")},
 | 
				
			||||||
 | 
					                               subdomain="zephyr")
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "User not authorized for this query")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @mock.patch("zerver.views.message_send.create_mirrored_message_users")
 | 
				
			||||||
 | 
					    def test_send_message_create_mirrored_message_user_returns_invalid_input(
 | 
				
			||||||
 | 
					            self, create_mirrored_message_users_mock: Any) -> None:
 | 
				
			||||||
 | 
					        create_mirrored_message_users_mock.side_effect = InvalidMirrorInput()
 | 
				
			||||||
 | 
					        result = self.api_post(self.mit_user("starnine"), "/api/v1/messages",
 | 
				
			||||||
 | 
					                               {"type": "private",
 | 
				
			||||||
 | 
					                                "sender": self.mit_email("sipbtest"),
 | 
				
			||||||
 | 
					                                "content": "Test message",
 | 
				
			||||||
 | 
					                                "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					                                "to": self.mit_email("starnine")},
 | 
				
			||||||
 | 
					                               subdomain="zephyr")
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Invalid mirrored message")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @mock.patch("zerver.views.message_send.create_mirrored_message_users")
 | 
				
			||||||
 | 
					    def test_send_message_when_client_is_zephyr_mirror_but_string_id_is_not_zephyr(
 | 
				
			||||||
 | 
					            self, create_mirrored_message_users_mock: Any) -> None:
 | 
				
			||||||
 | 
					        create_mirrored_message_users_mock.return_value = mock.Mock()
 | 
				
			||||||
 | 
					        user = self.mit_user("starnine")
 | 
				
			||||||
 | 
					        user.realm.string_id = 'notzephyr'
 | 
				
			||||||
 | 
					        user.realm.save()
 | 
				
			||||||
 | 
					        result = self.api_post(user, "/api/v1/messages",
 | 
				
			||||||
 | 
					                               {"type": "private",
 | 
				
			||||||
 | 
					                                "sender": self.mit_email("sipbtest"),
 | 
				
			||||||
 | 
					                                "content": "Test message",
 | 
				
			||||||
 | 
					                                "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					                                "to": user.email},
 | 
				
			||||||
 | 
					                               subdomain="notzephyr")
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Zephyr mirroring is not allowed in this organization")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @mock.patch("zerver.views.message_send.create_mirrored_message_users")
 | 
				
			||||||
 | 
					    def test_send_message_when_client_is_zephyr_mirror_but_recipient_is_user_id(
 | 
				
			||||||
 | 
					            self, create_mirrored_message_users_mock: Any) -> None:
 | 
				
			||||||
 | 
					        create_mirrored_message_users_mock.return_value = mock.Mock()
 | 
				
			||||||
 | 
					        user = self.mit_user("starnine")
 | 
				
			||||||
 | 
					        self.login_user(user)
 | 
				
			||||||
 | 
					        result = self.api_post(user, "/api/v1/messages",
 | 
				
			||||||
 | 
					                               {"type": "private",
 | 
				
			||||||
 | 
					                                "sender": self.mit_email("sipbtest"),
 | 
				
			||||||
 | 
					                                "content": "Test message",
 | 
				
			||||||
 | 
					                                "client": "zephyr_mirror",
 | 
				
			||||||
 | 
					                                "to": ujson.dumps([user.id])},
 | 
				
			||||||
 | 
					                               subdomain="zephyr")
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Mirroring not allowed with recipient user IDs")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_send_message_irc_mirror(self) -> None:
 | 
				
			||||||
 | 
					        reset_emails_in_zulip_realm()
 | 
				
			||||||
 | 
					        self.login('hamlet')
 | 
				
			||||||
 | 
					        bot_info = {
 | 
				
			||||||
 | 
					            'full_name': 'IRC bot',
 | 
				
			||||||
 | 
					            'short_name': 'irc',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        result = self.client_post("/json/bots", bot_info)
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        email = "irc-bot@zulip.testserver"
 | 
				
			||||||
 | 
					        user = get_user(email, get_realm('zulip'))
 | 
				
			||||||
 | 
					        user.is_api_super_user = True
 | 
				
			||||||
 | 
					        user.save()
 | 
				
			||||||
 | 
					        user = get_user(email, get_realm('zulip'))
 | 
				
			||||||
 | 
					        self.subscribe(user, "IRCland")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Simulate a mirrored message with a slightly old timestamp.
 | 
				
			||||||
 | 
					        fake_date_sent = timezone_now() - datetime.timedelta(minutes=37)
 | 
				
			||||||
 | 
					        fake_timestamp = datetime_to_timestamp(fake_date_sent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = self.api_post(user, "/api/v1/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                          "forged": "true",
 | 
				
			||||||
 | 
					                                                          "time": fake_timestamp,
 | 
				
			||||||
 | 
					                                                          "sender": "irc-user@irc.zulip.com",
 | 
				
			||||||
 | 
					                                                          "content": "Test message",
 | 
				
			||||||
 | 
					                                                          "client": "irc_mirror",
 | 
				
			||||||
 | 
					                                                          "topic": "from irc",
 | 
				
			||||||
 | 
					                                                          "to": "IRCLand"})
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        msg = self.get_last_message()
 | 
				
			||||||
 | 
					        self.assertEqual(int(datetime_to_timestamp(msg.date_sent)), int(fake_timestamp))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Now test again using forged=yes
 | 
				
			||||||
 | 
					        fake_date_sent = timezone_now() - datetime.timedelta(minutes=22)
 | 
				
			||||||
 | 
					        fake_timestamp = datetime_to_timestamp(fake_date_sent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = self.api_post(user, "/api/v1/messages", {"type": "stream",
 | 
				
			||||||
 | 
					                                                          "forged": "yes",
 | 
				
			||||||
 | 
					                                                          "time": fake_timestamp,
 | 
				
			||||||
 | 
					                                                          "sender": "irc-user@irc.zulip.com",
 | 
				
			||||||
 | 
					                                                          "content": "Test message",
 | 
				
			||||||
 | 
					                                                          "client": "irc_mirror",
 | 
				
			||||||
 | 
					                                                          "topic": "from irc",
 | 
				
			||||||
 | 
					                                                          "to": "IRCLand"})
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        msg = self.get_last_message()
 | 
				
			||||||
 | 
					        self.assertEqual(int(datetime_to_timestamp(msg.date_sent)), int(fake_timestamp))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_unsubscribed_api_super_user(self) -> None:
 | 
				
			||||||
 | 
					        reset_emails_in_zulip_realm()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cordelia = self.example_user('cordelia')
 | 
				
			||||||
 | 
					        stream_name = 'private_stream'
 | 
				
			||||||
 | 
					        self.make_stream(stream_name, invite_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.unsubscribe(cordelia, stream_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # As long as Cordelia is a super_user, she can send messages
 | 
				
			||||||
 | 
					        # to ANY stream, even one she is not unsubscribed to, and
 | 
				
			||||||
 | 
					        # she can do it for herself or on behalf of a mirrored user.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def test_with(sender_email: str, client: str, forged: bool) -> None:
 | 
				
			||||||
 | 
					            payload = dict(
 | 
				
			||||||
 | 
					                type="stream",
 | 
				
			||||||
 | 
					                to=stream_name,
 | 
				
			||||||
 | 
					                client=client,
 | 
				
			||||||
 | 
					                topic='whatever',
 | 
				
			||||||
 | 
					                content='whatever',
 | 
				
			||||||
 | 
					                forged=ujson.dumps(forged),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Only pass the 'sender' property when doing mirroring behavior.
 | 
				
			||||||
 | 
					            if forged:
 | 
				
			||||||
 | 
					                payload['sender'] = sender_email
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cordelia.is_api_super_user = False
 | 
				
			||||||
 | 
					            cordelia.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result = self.api_post(cordelia, "/api/v1/messages", payload)
 | 
				
			||||||
 | 
					            self.assert_json_error_contains(result, 'authorized')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cordelia.is_api_super_user = True
 | 
				
			||||||
 | 
					            cordelia.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result = self.api_post(cordelia, "/api/v1/messages", payload)
 | 
				
			||||||
 | 
					            self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        test_with(
 | 
				
			||||||
 | 
					            sender_email=cordelia.email,
 | 
				
			||||||
 | 
					            client='test suite',
 | 
				
			||||||
 | 
					            forged=False,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        test_with(
 | 
				
			||||||
 | 
					            sender_email='irc_person@zulip.com',
 | 
				
			||||||
 | 
					            client='irc_mirror',
 | 
				
			||||||
 | 
					            forged=True,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_bot_can_send_to_owner_stream(self) -> None:
 | 
				
			||||||
 | 
					        cordelia = self.example_user('cordelia')
 | 
				
			||||||
 | 
					        bot = self.create_test_bot(
 | 
				
			||||||
 | 
					            short_name='whatever',
 | 
				
			||||||
 | 
					            user_profile=cordelia,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stream_name = 'private_stream'
 | 
				
			||||||
 | 
					        self.make_stream(stream_name, invite_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        payload = dict(
 | 
				
			||||||
 | 
					            type="stream",
 | 
				
			||||||
 | 
					            to=stream_name,
 | 
				
			||||||
 | 
					            client='test suite',
 | 
				
			||||||
 | 
					            topic='whatever',
 | 
				
			||||||
 | 
					            content='whatever',
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = self.api_post(bot, "/api/v1/messages", payload)
 | 
				
			||||||
 | 
					        self.assert_json_error_contains(result, 'Not authorized to send')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # We subscribe the bot owner! (aka cordelia)
 | 
				
			||||||
 | 
					        assert bot.bot_owner is not None
 | 
				
			||||||
 | 
					        self.subscribe(bot.bot_owner, stream_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = self.api_post(bot, "/api/v1/messages", payload)
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_cross_realm_bots_can_use_api_on_own_subdomain(self) -> None:
 | 
				
			||||||
 | 
					        # Cross realm bots should use internal_send_*_message, not the API:
 | 
				
			||||||
 | 
					        notification_bot = self.notification_bot()
 | 
				
			||||||
 | 
					        stream = self.make_stream("notify_channel", get_realm("zulipinternal"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = self.api_post(notification_bot,
 | 
				
			||||||
 | 
					                               "/api/v1/messages",
 | 
				
			||||||
 | 
					                               {"type": "stream",
 | 
				
			||||||
 | 
					                                "to": "notify_channel",
 | 
				
			||||||
 | 
					                                "client": "test suite",
 | 
				
			||||||
 | 
					                                "content": "Test message",
 | 
				
			||||||
 | 
					                                "topic": "Test topic"},
 | 
				
			||||||
 | 
					                               subdomain='zulipinternal')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					        message = self.get_last_message()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(message.content, "Test message")
 | 
				
			||||||
 | 
					        self.assertEqual(message.sender, notification_bot)
 | 
				
			||||||
 | 
					        self.assertEqual(message.recipient.type_id, stream.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_create_mirror_user_despite_race(self) -> None:
 | 
				
			||||||
 | 
					        realm = get_realm('zulip')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        email = 'fred@example.com'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        email_to_full_name = lambda email: 'fred'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def create_user(**kwargs: Any) -> UserProfile:
 | 
				
			||||||
 | 
					            self.assertEqual(kwargs['full_name'], 'fred')
 | 
				
			||||||
 | 
					            self.assertEqual(kwargs['email'], email)
 | 
				
			||||||
 | 
					            self.assertEqual(kwargs['active'], False)
 | 
				
			||||||
 | 
					            self.assertEqual(kwargs['is_mirror_dummy'], True)
 | 
				
			||||||
 | 
					            # We create an actual user here to simulate a race.
 | 
				
			||||||
 | 
					            # We use the minimal, un-mocked function.
 | 
				
			||||||
 | 
					            kwargs['bot_type'] = None
 | 
				
			||||||
 | 
					            kwargs['bot_owner'] = None
 | 
				
			||||||
 | 
					            kwargs['tos_version'] = None
 | 
				
			||||||
 | 
					            kwargs['timezone'] = timezone_now()
 | 
				
			||||||
 | 
					            create_user_profile(**kwargs).save()
 | 
				
			||||||
 | 
					            raise IntegrityError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with mock.patch('zerver.lib.actions.create_user',
 | 
				
			||||||
 | 
					                        side_effect=create_user) as m:
 | 
				
			||||||
 | 
					            mirror_fred_user = create_mirror_user_if_needed(
 | 
				
			||||||
 | 
					                realm,
 | 
				
			||||||
 | 
					                email,
 | 
				
			||||||
 | 
					                email_to_full_name,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(mirror_fred_user.delivery_email, email)
 | 
				
			||||||
 | 
					        m.assert_called()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_guest_user(self) -> None:
 | 
				
			||||||
 | 
					        sender = self.example_user('polonius')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stream_name = 'public stream'
 | 
				
			||||||
 | 
					        self.make_stream(stream_name, invite_only=False)
 | 
				
			||||||
 | 
					        payload = dict(
 | 
				
			||||||
 | 
					            type="stream",
 | 
				
			||||||
 | 
					            to=stream_name,
 | 
				
			||||||
 | 
					            client='test suite',
 | 
				
			||||||
 | 
					            topic='whatever',
 | 
				
			||||||
 | 
					            content='whatever',
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Guest user can't send message to unsubscribed public streams
 | 
				
			||||||
 | 
					        result = self.api_post(sender, "/api/v1/messages", payload)
 | 
				
			||||||
 | 
					        self.assert_json_error(result, "Not authorized to send to stream 'public stream'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.subscribe(sender, stream_name)
 | 
				
			||||||
 | 
					        # Guest user can send message to subscribed public streams
 | 
				
			||||||
 | 
					        result = self.api_post(sender, "/api/v1/messages", payload)
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
@@ -1,11 +1,10 @@
 | 
				
			|||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
from typing import Any, Dict, List, Optional, Set, Union
 | 
					from typing import Any, Dict, List, Set, Union
 | 
				
			||||||
from unittest import mock
 | 
					from unittest import mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ujson
 | 
					import ujson
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.db import IntegrityError
 | 
					 | 
				
			||||||
from django.db.models import Q
 | 
					from django.db.models import Q
 | 
				
			||||||
from django.http import HttpResponse
 | 
					from django.http import HttpResponse
 | 
				
			||||||
from django.test import override_settings
 | 
					from django.test import override_settings
 | 
				
			||||||
@@ -17,14 +16,11 @@ from zerver.decorator import JsonableError
 | 
				
			|||||||
from zerver.lib.actions import (
 | 
					from zerver.lib.actions import (
 | 
				
			||||||
    check_message,
 | 
					    check_message,
 | 
				
			||||||
    check_send_stream_message,
 | 
					    check_send_stream_message,
 | 
				
			||||||
    create_mirror_user_if_needed,
 | 
					 | 
				
			||||||
    do_add_alert_words,
 | 
					    do_add_alert_words,
 | 
				
			||||||
    do_change_is_api_super_user,
 | 
					    do_change_is_api_super_user,
 | 
				
			||||||
    do_change_stream_invite_only,
 | 
					    do_change_stream_invite_only,
 | 
				
			||||||
    do_change_stream_post_policy,
 | 
					 | 
				
			||||||
    do_claim_attachments,
 | 
					    do_claim_attachments,
 | 
				
			||||||
    do_create_user,
 | 
					    do_create_user,
 | 
				
			||||||
    do_deactivate_user,
 | 
					 | 
				
			||||||
    do_send_messages,
 | 
					    do_send_messages,
 | 
				
			||||||
    do_set_realm_property,
 | 
					    do_set_realm_property,
 | 
				
			||||||
    do_update_message,
 | 
					    do_update_message,
 | 
				
			||||||
@@ -44,7 +40,6 @@ from zerver.lib.actions import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
from zerver.lib.addressee import Addressee
 | 
					from zerver.lib.addressee import Addressee
 | 
				
			||||||
from zerver.lib.cache import cache_delete, get_stream_cache_key, to_dict_cache_key_id
 | 
					from zerver.lib.cache import cache_delete, get_stream_cache_key, to_dict_cache_key_id
 | 
				
			||||||
from zerver.lib.create_user import create_user_profile
 | 
					 | 
				
			||||||
from zerver.lib.markdown import MentionData
 | 
					from zerver.lib.markdown import MentionData
 | 
				
			||||||
from zerver.lib.markdown import version as markdown_version
 | 
					from zerver.lib.markdown import version as markdown_version
 | 
				
			||||||
from zerver.lib.message import (
 | 
					from zerver.lib.message import (
 | 
				
			||||||
@@ -74,17 +69,14 @@ from zerver.lib.test_helpers import (
 | 
				
			|||||||
    most_recent_message,
 | 
					    most_recent_message,
 | 
				
			||||||
    most_recent_usermessage,
 | 
					    most_recent_usermessage,
 | 
				
			||||||
    queries_captured,
 | 
					    queries_captured,
 | 
				
			||||||
    reset_emails_in_zulip_realm,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from zerver.lib.timestamp import convert_to_UTC, datetime_to_timestamp
 | 
					from zerver.lib.timestamp import convert_to_UTC
 | 
				
			||||||
from zerver.lib.timezone import get_timezone
 | 
					from zerver.lib.timezone import get_timezone
 | 
				
			||||||
from zerver.lib.topic import DB_TOPIC_NAME, TOPIC_LINKS
 | 
					from zerver.lib.topic import DB_TOPIC_NAME, TOPIC_LINKS
 | 
				
			||||||
from zerver.lib.types import DisplayRecipientT, UserDisplayRecipient
 | 
					from zerver.lib.types import DisplayRecipientT, UserDisplayRecipient
 | 
				
			||||||
from zerver.lib.upload import create_attachment
 | 
					from zerver.lib.upload import create_attachment
 | 
				
			||||||
from zerver.lib.url_encoding import near_message_url
 | 
					from zerver.lib.url_encoding import near_message_url
 | 
				
			||||||
from zerver.models import (
 | 
					from zerver.models import (
 | 
				
			||||||
    MAX_MESSAGE_LENGTH,
 | 
					 | 
				
			||||||
    MAX_TOPIC_NAME_LENGTH,
 | 
					 | 
				
			||||||
    Attachment,
 | 
					    Attachment,
 | 
				
			||||||
    Message,
 | 
					    Message,
 | 
				
			||||||
    Reaction,
 | 
					    Reaction,
 | 
				
			||||||
@@ -102,14 +94,12 @@ from zerver.models import (
 | 
				
			|||||||
    bulk_get_huddle_user_ids,
 | 
					    bulk_get_huddle_user_ids,
 | 
				
			||||||
    flush_per_request_caches,
 | 
					    flush_per_request_caches,
 | 
				
			||||||
    get_display_recipient,
 | 
					    get_display_recipient,
 | 
				
			||||||
    get_huddle_recipient,
 | 
					 | 
				
			||||||
    get_huddle_user_ids,
 | 
					    get_huddle_user_ids,
 | 
				
			||||||
    get_realm,
 | 
					    get_realm,
 | 
				
			||||||
    get_stream,
 | 
					    get_stream,
 | 
				
			||||||
    get_system_bot,
 | 
					    get_system_bot,
 | 
				
			||||||
    get_user,
 | 
					    get_user,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from zerver.views.message_send import InvalidMirrorInput
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MiscMessageTest(ZulipTestCase):
 | 
					class MiscMessageTest(ZulipTestCase):
 | 
				
			||||||
@@ -1359,890 +1349,6 @@ class SewMessageAndReactionTest(ZulipTestCase):
 | 
				
			|||||||
            self.assertTrue(data['id'])
 | 
					            self.assertTrue(data['id'])
 | 
				
			||||||
            self.assertTrue(data['content'])
 | 
					            self.assertTrue(data['content'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class MessagePOSTTest(ZulipTestCase):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _send_and_verify_message(self, user: UserProfile, stream_name: str, error_msg: Optional[str]=None) -> None:
 | 
					 | 
				
			||||||
        if error_msg is None:
 | 
					 | 
				
			||||||
            msg_id = self.send_stream_message(user, stream_name)
 | 
					 | 
				
			||||||
            result = self.api_get(user, '/json/messages/' + str(msg_id))
 | 
					 | 
				
			||||||
            self.assert_json_success(result)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            with self.assertRaisesRegex(JsonableError, error_msg):
 | 
					 | 
				
			||||||
                self.send_stream_message(user, stream_name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_message_to_self(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a message to a stream to which you are subscribed is
 | 
					 | 
				
			||||||
        successful.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                     "to": "Verona",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "topic": "Test topic"})
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_api_message_to_self(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Same as above, but for the API view
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        user = self.example_user('hamlet')
 | 
					 | 
				
			||||||
        result = self.api_post(user, "/api/v1/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                          "to": "Verona",
 | 
					 | 
				
			||||||
                                                          "client": "test suite",
 | 
					 | 
				
			||||||
                                                          "content": "Test message",
 | 
					 | 
				
			||||||
                                                          "topic": "Test topic"})
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_message_to_stream_with_nonexistent_id(self) -> None:
 | 
					 | 
				
			||||||
        cordelia = self.example_user('cordelia')
 | 
					 | 
				
			||||||
        bot = self.create_test_bot(
 | 
					 | 
				
			||||||
            short_name='whatever',
 | 
					 | 
				
			||||||
            user_profile=cordelia,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        result = self.api_post(
 | 
					 | 
				
			||||||
            bot, "/api/v1/messages",
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                "type": "stream",
 | 
					 | 
				
			||||||
                "to": ujson.dumps([99999]),
 | 
					 | 
				
			||||||
                "client": "test suite",
 | 
					 | 
				
			||||||
                "content": "Stream message by ID.",
 | 
					 | 
				
			||||||
                "topic": "Test topic for stream ID message",
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Stream with ID '99999' does not exist")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        msg = self.get_last_message()
 | 
					 | 
				
			||||||
        expected = ("Your bot `whatever-bot@zulip.testserver` tried to send a message to "
 | 
					 | 
				
			||||||
                    "stream ID 99999, but there is no stream with that ID.")
 | 
					 | 
				
			||||||
        self.assertEqual(msg.content, expected)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_message_to_stream_by_id(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a message to a stream (by stream ID) to which you are
 | 
					 | 
				
			||||||
        subscribed is successful.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        realm = get_realm('zulip')
 | 
					 | 
				
			||||||
        stream = get_stream('Verona', realm)
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                     "to": ujson.dumps([stream.id]),
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "content": "Stream message by ID.",
 | 
					 | 
				
			||||||
                                                     "topic": "Test topic for stream ID message"})
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
        sent_message = self.get_last_message()
 | 
					 | 
				
			||||||
        self.assertEqual(sent_message.content, "Stream message by ID.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_sending_message_as_stream_post_policy_admins(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending messages to streams which only the admins can create and post to.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        admin_profile = self.example_user("iago")
 | 
					 | 
				
			||||||
        self.login_user(admin_profile)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        stream_name = "Verona"
 | 
					 | 
				
			||||||
        stream = get_stream(stream_name, admin_profile.realm)
 | 
					 | 
				
			||||||
        do_change_stream_post_policy(stream, Stream.STREAM_POST_POLICY_ADMINS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Admins and their owned bots can send to STREAM_POST_POLICY_ADMINS streams
 | 
					 | 
				
			||||||
        self._send_and_verify_message(admin_profile, stream_name)
 | 
					 | 
				
			||||||
        admin_owned_bot = self.create_test_bot(
 | 
					 | 
				
			||||||
            short_name='whatever1',
 | 
					 | 
				
			||||||
            full_name='whatever1',
 | 
					 | 
				
			||||||
            user_profile=admin_profile,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self._send_and_verify_message(admin_owned_bot, stream_name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        non_admin_profile = self.example_user("hamlet")
 | 
					 | 
				
			||||||
        self.login_user(non_admin_profile)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Non admins and their owned bots cannot send to STREAM_POST_POLICY_ADMINS streams
 | 
					 | 
				
			||||||
        self._send_and_verify_message(non_admin_profile, stream_name,
 | 
					 | 
				
			||||||
                                      "Only organization administrators can send to this stream.")
 | 
					 | 
				
			||||||
        non_admin_owned_bot = self.create_test_bot(
 | 
					 | 
				
			||||||
            short_name='whatever2',
 | 
					 | 
				
			||||||
            full_name='whatever2',
 | 
					 | 
				
			||||||
            user_profile=non_admin_profile,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self._send_and_verify_message(non_admin_owned_bot, stream_name,
 | 
					 | 
				
			||||||
                                      "Only organization administrators can send to this stream.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Bots without owner (except cross realm bot) cannot send to announcement only streams
 | 
					 | 
				
			||||||
        bot_without_owner = do_create_user(
 | 
					 | 
				
			||||||
            email='free-bot@zulip.testserver',
 | 
					 | 
				
			||||||
            password='',
 | 
					 | 
				
			||||||
            realm=non_admin_profile.realm,
 | 
					 | 
				
			||||||
            full_name='freebot',
 | 
					 | 
				
			||||||
            short_name='freebot',
 | 
					 | 
				
			||||||
            bot_type=UserProfile.DEFAULT_BOT,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self._send_and_verify_message(bot_without_owner, stream_name,
 | 
					 | 
				
			||||||
                                      "Only organization administrators can send to this stream.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Cross realm bots should be allowed
 | 
					 | 
				
			||||||
        notification_bot = get_system_bot("notification-bot@zulip.com")
 | 
					 | 
				
			||||||
        internal_send_stream_message(stream.realm, notification_bot, stream,
 | 
					 | 
				
			||||||
                                     'Test topic', 'Test message by notification bot')
 | 
					 | 
				
			||||||
        self.assertEqual(self.get_last_message().content, 'Test message by notification bot')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_sending_message_as_stream_post_policy_restrict_new_members(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending messages to streams which new members cannot create and post to.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        admin_profile = self.example_user("iago")
 | 
					 | 
				
			||||||
        self.login_user(admin_profile)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        do_set_realm_property(admin_profile.realm, 'waiting_period_threshold', 10)
 | 
					 | 
				
			||||||
        admin_profile.date_joined = timezone_now() - datetime.timedelta(days=9)
 | 
					 | 
				
			||||||
        admin_profile.save()
 | 
					 | 
				
			||||||
        self.assertTrue(admin_profile.is_new_member)
 | 
					 | 
				
			||||||
        self.assertTrue(admin_profile.is_realm_admin)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        stream_name = "Verona"
 | 
					 | 
				
			||||||
        stream = get_stream(stream_name, admin_profile.realm)
 | 
					 | 
				
			||||||
        do_change_stream_post_policy(stream, Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Admins and their owned bots can send to STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams,
 | 
					 | 
				
			||||||
        # even if the admin is a new user
 | 
					 | 
				
			||||||
        self._send_and_verify_message(admin_profile, stream_name)
 | 
					 | 
				
			||||||
        admin_owned_bot = self.create_test_bot(
 | 
					 | 
				
			||||||
            short_name='whatever1',
 | 
					 | 
				
			||||||
            full_name='whatever1',
 | 
					 | 
				
			||||||
            user_profile=admin_profile,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self._send_and_verify_message(admin_owned_bot, stream_name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        non_admin_profile = self.example_user("hamlet")
 | 
					 | 
				
			||||||
        self.login_user(non_admin_profile)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        non_admin_profile.date_joined = timezone_now() - datetime.timedelta(days=9)
 | 
					 | 
				
			||||||
        non_admin_profile.save()
 | 
					 | 
				
			||||||
        self.assertTrue(non_admin_profile.is_new_member)
 | 
					 | 
				
			||||||
        self.assertFalse(non_admin_profile.is_realm_admin)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Non admins and their owned bots can send to STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams,
 | 
					 | 
				
			||||||
        # if the user is not a new member
 | 
					 | 
				
			||||||
        self._send_and_verify_message(non_admin_profile, stream_name,
 | 
					 | 
				
			||||||
                                      "New members cannot send to this stream.")
 | 
					 | 
				
			||||||
        non_admin_owned_bot = self.create_test_bot(
 | 
					 | 
				
			||||||
            short_name='whatever2',
 | 
					 | 
				
			||||||
            full_name='whatever2',
 | 
					 | 
				
			||||||
            user_profile=non_admin_profile,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self._send_and_verify_message(non_admin_owned_bot, stream_name,
 | 
					 | 
				
			||||||
                                      "New members cannot send to this stream.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Bots without owner (except cross realm bot) cannot send to announcement only stream
 | 
					 | 
				
			||||||
        bot_without_owner = do_create_user(
 | 
					 | 
				
			||||||
            email='free-bot@zulip.testserver',
 | 
					 | 
				
			||||||
            password='',
 | 
					 | 
				
			||||||
            realm=non_admin_profile.realm,
 | 
					 | 
				
			||||||
            full_name='freebot',
 | 
					 | 
				
			||||||
            short_name='freebot',
 | 
					 | 
				
			||||||
            bot_type=UserProfile.DEFAULT_BOT,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self._send_and_verify_message(bot_without_owner, stream_name,
 | 
					 | 
				
			||||||
                                      "New members cannot send to this stream.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Cross realm bots should be allowed
 | 
					 | 
				
			||||||
        notification_bot = get_system_bot("notification-bot@zulip.com")
 | 
					 | 
				
			||||||
        internal_send_stream_message(stream.realm, notification_bot, stream,
 | 
					 | 
				
			||||||
                                     'Test topic', 'Test message by notification bot')
 | 
					 | 
				
			||||||
        self.assertEqual(self.get_last_message().content, 'Test message by notification bot')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_api_message_with_default_to(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending messages without a to field should be sent to the default
 | 
					 | 
				
			||||||
        stream for the user_profile.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        user = self.example_user('hamlet')
 | 
					 | 
				
			||||||
        user.default_sending_stream_id = get_stream('Verona', user.realm).id
 | 
					 | 
				
			||||||
        user.save()
 | 
					 | 
				
			||||||
        result = self.api_post(user, "/api/v1/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                          "client": "test suite",
 | 
					 | 
				
			||||||
                                                          "content": "Test message no to",
 | 
					 | 
				
			||||||
                                                          "topic": "Test topic"})
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        sent_message = self.get_last_message()
 | 
					 | 
				
			||||||
        self.assertEqual(sent_message.content, "Test message no to")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_message_to_nonexistent_stream(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a message to a nonexistent stream fails.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        self.assertFalse(Stream.objects.filter(name="nonexistent_stream"))
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                     "to": "nonexistent_stream",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "topic": "Test topic"})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Stream 'nonexistent_stream' does not exist")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_message_to_nonexistent_stream_with_bad_characters(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Nonexistent stream name with bad characters should be escaped properly.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        self.assertFalse(Stream.objects.filter(name="""&<"'><non-existent>"""))
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                     "to": """&<"'><non-existent>""",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "topic": "Test topic"})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Stream '&<"'><non-existent>' does not exist")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_personal_message(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a personal message to a valid username is successful.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        user_profile = self.example_user("hamlet")
 | 
					 | 
				
			||||||
        self.login_user(user_profile)
 | 
					 | 
				
			||||||
        othello = self.example_user('othello')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "private",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "to": othello.email})
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
        message_id = ujson.loads(result.content.decode())['id']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        recent_conversations = get_recent_private_conversations(user_profile)
 | 
					 | 
				
			||||||
        self.assertEqual(len(recent_conversations), 1)
 | 
					 | 
				
			||||||
        recent_conversation = list(recent_conversations.values())[0]
 | 
					 | 
				
			||||||
        recipient_id = list(recent_conversations.keys())[0]
 | 
					 | 
				
			||||||
        self.assertEqual(set(recent_conversation['user_ids']), {othello.id})
 | 
					 | 
				
			||||||
        self.assertEqual(recent_conversation['max_message_id'], message_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Now send a message to yourself and see how that interacts with the data structure
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "private",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "to": user_profile.email})
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
        self_message_id = ujson.loads(result.content.decode())['id']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        recent_conversations = get_recent_private_conversations(user_profile)
 | 
					 | 
				
			||||||
        self.assertEqual(len(recent_conversations), 2)
 | 
					 | 
				
			||||||
        recent_conversation = recent_conversations[recipient_id]
 | 
					 | 
				
			||||||
        self.assertEqual(set(recent_conversation['user_ids']), {othello.id})
 | 
					 | 
				
			||||||
        self.assertEqual(recent_conversation['max_message_id'], message_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Now verify we have the appropriate self-pm data structure
 | 
					 | 
				
			||||||
        del recent_conversations[recipient_id]
 | 
					 | 
				
			||||||
        recent_conversation = list(recent_conversations.values())[0]
 | 
					 | 
				
			||||||
        recipient_id = list(recent_conversations.keys())[0]
 | 
					 | 
				
			||||||
        self.assertEqual(set(recent_conversation['user_ids']), set())
 | 
					 | 
				
			||||||
        self.assertEqual(recent_conversation['max_message_id'], self_message_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_personal_message_by_id(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a personal message to a valid user ID is successful.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post(
 | 
					 | 
				
			||||||
            "/json/messages",
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                "type": "private",
 | 
					 | 
				
			||||||
                "content": "Test message",
 | 
					 | 
				
			||||||
                "client": "test suite",
 | 
					 | 
				
			||||||
                "to": ujson.dumps([self.example_user("othello").id]),
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        msg = self.get_last_message()
 | 
					 | 
				
			||||||
        self.assertEqual("Test message", msg.content)
 | 
					 | 
				
			||||||
        self.assertEqual(msg.recipient_id, self.example_user("othello").id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_group_personal_message_by_id(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a personal message to a valid user ID is successful.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post(
 | 
					 | 
				
			||||||
            "/json/messages",
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                "type": "private",
 | 
					 | 
				
			||||||
                "content": "Test message",
 | 
					 | 
				
			||||||
                "client": "test suite",
 | 
					 | 
				
			||||||
                "to": ujson.dumps([self.example_user("othello").id,
 | 
					 | 
				
			||||||
                                   self.example_user("cordelia").id]),
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        msg = self.get_last_message()
 | 
					 | 
				
			||||||
        self.assertEqual("Test message", msg.content)
 | 
					 | 
				
			||||||
        self.assertEqual(msg.recipient_id, get_huddle_recipient(
 | 
					 | 
				
			||||||
            {self.example_user("hamlet").id,
 | 
					 | 
				
			||||||
             self.example_user("othello").id,
 | 
					 | 
				
			||||||
             self.example_user("cordelia").id}).id,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_personal_message_copying_self(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a personal message to yourself plus another user is successful,
 | 
					 | 
				
			||||||
        and counts as a message just to that user.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        hamlet = self.example_user('hamlet')
 | 
					 | 
				
			||||||
        othello = self.example_user('othello')
 | 
					 | 
				
			||||||
        self.login_user(hamlet)
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {
 | 
					 | 
				
			||||||
            "type": "private",
 | 
					 | 
				
			||||||
            "content": "Test message",
 | 
					 | 
				
			||||||
            "client": "test suite",
 | 
					 | 
				
			||||||
            "to": ujson.dumps([hamlet.id, othello.id])})
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
        msg = self.get_last_message()
 | 
					 | 
				
			||||||
        # Verify that we're not actually on the "recipient list"
 | 
					 | 
				
			||||||
        self.assertNotIn("Hamlet", str(msg.recipient))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_personal_message_to_nonexistent_user(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a personal message to an invalid email returns error JSON.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "private",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "to": "nonexistent"})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Invalid email 'nonexistent'")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_personal_message_to_deactivated_user(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a personal message to a deactivated user returns error JSON.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        othello = self.example_user('othello')
 | 
					 | 
				
			||||||
        cordelia = self.example_user('cordelia')
 | 
					 | 
				
			||||||
        do_deactivate_user(othello)
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {
 | 
					 | 
				
			||||||
            "type": "private",
 | 
					 | 
				
			||||||
            "content": "Test message",
 | 
					 | 
				
			||||||
            "client": "test suite",
 | 
					 | 
				
			||||||
            "to": ujson.dumps([othello.id])})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, f"'{othello.email}' is no longer using Zulip.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {
 | 
					 | 
				
			||||||
            "type": "private",
 | 
					 | 
				
			||||||
            "content": "Test message",
 | 
					 | 
				
			||||||
            "client": "test suite",
 | 
					 | 
				
			||||||
            "to": ujson.dumps([othello.id, cordelia.id])})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, f"'{othello.email}' is no longer using Zulip.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_invalid_type(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a message of unknown type returns error JSON.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        othello = self.example_user('othello')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "invalid type",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "to": othello.email})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Invalid message type")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_empty_message(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a message that is empty or only whitespace should fail
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        othello = self.example_user('othello')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "private",
 | 
					 | 
				
			||||||
                                                     "content": " ",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "to": othello.email})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Message must not be empty")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_empty_string_topic(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a message that has empty string topic should fail
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                     "to": "Verona",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "topic": ""})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Topic can't be empty")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_missing_topic(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a message without topic should fail
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                     "to": "Verona",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "content": "Test message"})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Missing topic")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_invalid_message_type(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Messages other than the type of "private" or "stream" are considered as invalid
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "invalid",
 | 
					 | 
				
			||||||
                                                     "to": "Verona",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "topic": "Test topic"})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Invalid message type")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_private_message_without_recipients(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending private message without recipients should fail
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "private",
 | 
					 | 
				
			||||||
                                                     "content": "Test content",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "to": ""})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Message must have recipients")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_mirrored_huddle(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a mirrored huddle message works
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        result = self.api_post(self.mit_user("starnine"),
 | 
					 | 
				
			||||||
                               "/json/messages", {"type": "private",
 | 
					 | 
				
			||||||
                                                  "sender": self.mit_email("sipbtest"),
 | 
					 | 
				
			||||||
                                                  "content": "Test message",
 | 
					 | 
				
			||||||
                                                  "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
                                                  "to": ujson.dumps([self.mit_email("starnine"),
 | 
					 | 
				
			||||||
                                                                     self.mit_email("espuser")])},
 | 
					 | 
				
			||||||
                               subdomain="zephyr")
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_mirrored_personal(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a mirrored personal message works
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        result = self.api_post(self.mit_user("starnine"),
 | 
					 | 
				
			||||||
                               "/json/messages", {"type": "private",
 | 
					 | 
				
			||||||
                                                  "sender": self.mit_email("sipbtest"),
 | 
					 | 
				
			||||||
                                                  "content": "Test message",
 | 
					 | 
				
			||||||
                                                  "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
                                                  "to": self.mit_email("starnine")},
 | 
					 | 
				
			||||||
                               subdomain="zephyr")
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_mirrored_personal_browser(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a mirrored personal message via the browser should not work.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        user = self.mit_user('starnine')
 | 
					 | 
				
			||||||
        self.login_user(user)
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages",
 | 
					 | 
				
			||||||
                                  {"type": "private",
 | 
					 | 
				
			||||||
                                   "sender": self.mit_email("sipbtest"),
 | 
					 | 
				
			||||||
                                   "content": "Test message",
 | 
					 | 
				
			||||||
                                   "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
                                   "to": self.mit_email("starnine")},
 | 
					 | 
				
			||||||
                                  subdomain="zephyr")
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Invalid mirrored message")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_mirrored_personal_to_someone_else(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a mirrored personal message to someone else is not allowed.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        result = self.api_post(self.mit_user("starnine"), "/api/v1/messages",
 | 
					 | 
				
			||||||
                               {"type": "private",
 | 
					 | 
				
			||||||
                                "sender": self.mit_email("sipbtest"),
 | 
					 | 
				
			||||||
                                "content": "Test message",
 | 
					 | 
				
			||||||
                                "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
                                "to": self.mit_email("espuser")},
 | 
					 | 
				
			||||||
                               subdomain="zephyr")
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "User not authorized for this query")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_duplicated_mirrored_huddle(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending two mirrored huddles in the row return the same ID
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        msg = {"type": "private",
 | 
					 | 
				
			||||||
               "sender": self.mit_email("sipbtest"),
 | 
					 | 
				
			||||||
               "content": "Test message",
 | 
					 | 
				
			||||||
               "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
               "to": ujson.dumps([self.mit_email("espuser"),
 | 
					 | 
				
			||||||
                                  self.mit_email("starnine")])}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with mock.patch('DNS.dnslookup', return_value=[['starnine:*:84233:101:Athena Consulting Exchange User,,,:/mit/starnine:/bin/bash']]):
 | 
					 | 
				
			||||||
            result1 = self.api_post(self.mit_user("starnine"), "/api/v1/messages", msg,
 | 
					 | 
				
			||||||
                                    subdomain="zephyr")
 | 
					 | 
				
			||||||
            self.assert_json_success(result1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with mock.patch('DNS.dnslookup', return_value=[['espuser:*:95494:101:Esp Classroom,,,:/mit/espuser:/bin/athena/bash']]):
 | 
					 | 
				
			||||||
            result2 = self.api_post(self.mit_user("espuser"), "/api/v1/messages", msg,
 | 
					 | 
				
			||||||
                                    subdomain="zephyr")
 | 
					 | 
				
			||||||
            self.assert_json_success(result2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.assertEqual(ujson.loads(result1.content)['id'],
 | 
					 | 
				
			||||||
                         ujson.loads(result2.content)['id'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_message_with_null_bytes(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        A message with null bytes in it is handled.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        post_data = {"type": "stream", "to": "Verona", "client": "test suite",
 | 
					 | 
				
			||||||
                     "content": "  I like null bytes \x00 in my content", "topic": "Test topic"}
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", post_data)
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Message must not contain null bytes")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_strip_message(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        A message with mixed whitespace at the end is cleaned up.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        post_data = {"type": "stream", "to": "Verona", "client": "test suite",
 | 
					 | 
				
			||||||
                     "content": "  I like whitespace at the end! \n\n \n", "topic": "Test topic"}
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", post_data)
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
        sent_message = self.get_last_message()
 | 
					 | 
				
			||||||
        self.assertEqual(sent_message.content, "  I like whitespace at the end!")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_long_message(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a message longer than the maximum message length succeeds but is
 | 
					 | 
				
			||||||
        truncated.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        long_message = "A" * (MAX_MESSAGE_LENGTH + 1)
 | 
					 | 
				
			||||||
        post_data = {"type": "stream", "to": "Verona", "client": "test suite",
 | 
					 | 
				
			||||||
                     "content": long_message, "topic": "Test topic"}
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", post_data)
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        sent_message = self.get_last_message()
 | 
					 | 
				
			||||||
        self.assertEqual(sent_message.content,
 | 
					 | 
				
			||||||
                         "A" * (MAX_MESSAGE_LENGTH - 20) + "\n[message truncated]")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_long_topic(self) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Sending a message with a topic longer than the maximum topic length
 | 
					 | 
				
			||||||
        succeeds, but the topic is truncated.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        long_topic = "A" * (MAX_TOPIC_NAME_LENGTH + 1)
 | 
					 | 
				
			||||||
        post_data = {"type": "stream", "to": "Verona", "client": "test suite",
 | 
					 | 
				
			||||||
                     "content": "test content", "topic": long_topic}
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", post_data)
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        sent_message = self.get_last_message()
 | 
					 | 
				
			||||||
        self.assertEqual(sent_message.topic_name(),
 | 
					 | 
				
			||||||
                         "A" * (MAX_TOPIC_NAME_LENGTH - 3) + "...")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_send_forged_message_as_not_superuser(self) -> None:
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                     "to": "Verona",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "topic": "Test topic",
 | 
					 | 
				
			||||||
                                                     "forged": "true"})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "User not authorized for this query")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_send_message_as_not_superuser_to_different_domain(self) -> None:
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        result = self.client_post("/json/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                     "to": "Verona",
 | 
					 | 
				
			||||||
                                                     "client": "test suite",
 | 
					 | 
				
			||||||
                                                     "content": "Test message",
 | 
					 | 
				
			||||||
                                                     "topic": "Test topic",
 | 
					 | 
				
			||||||
                                                     "realm_str": "mit"})
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "User not authorized for this query")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_send_message_as_superuser_to_domain_that_dont_exist(self) -> None:
 | 
					 | 
				
			||||||
        user = self.example_user("default_bot")
 | 
					 | 
				
			||||||
        password = "test_password"
 | 
					 | 
				
			||||||
        user.set_password(password)
 | 
					 | 
				
			||||||
        user.is_api_super_user = True
 | 
					 | 
				
			||||||
        user.save()
 | 
					 | 
				
			||||||
        result = self.api_post(user,
 | 
					 | 
				
			||||||
                               "/api/v1/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                    "to": "Verona",
 | 
					 | 
				
			||||||
                                                    "client": "test suite",
 | 
					 | 
				
			||||||
                                                    "content": "Test message",
 | 
					 | 
				
			||||||
                                                    "topic": "Test topic",
 | 
					 | 
				
			||||||
                                                    "realm_str": "non-existing"})
 | 
					 | 
				
			||||||
        user.is_api_super_user = False
 | 
					 | 
				
			||||||
        user.save()
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Unknown organization 'non-existing'")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_send_message_when_sender_is_not_set(self) -> None:
 | 
					 | 
				
			||||||
        result = self.api_post(self.mit_user("starnine"), "/api/v1/messages",
 | 
					 | 
				
			||||||
                               {"type": "private",
 | 
					 | 
				
			||||||
                                "content": "Test message",
 | 
					 | 
				
			||||||
                                "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
                                "to": self.mit_email("starnine")},
 | 
					 | 
				
			||||||
                               subdomain="zephyr")
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Missing sender")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_send_message_as_not_superuser_when_type_is_not_private(self) -> None:
 | 
					 | 
				
			||||||
        result = self.api_post(self.mit_user("starnine"), "/api/v1/messages",
 | 
					 | 
				
			||||||
                               {"type": "not-private",
 | 
					 | 
				
			||||||
                                "sender": self.mit_email("sipbtest"),
 | 
					 | 
				
			||||||
                                "content": "Test message",
 | 
					 | 
				
			||||||
                                "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
                                "to": self.mit_email("starnine")},
 | 
					 | 
				
			||||||
                               subdomain="zephyr")
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "User not authorized for this query")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @mock.patch("zerver.views.message_send.create_mirrored_message_users")
 | 
					 | 
				
			||||||
    def test_send_message_create_mirrored_message_user_returns_invalid_input(
 | 
					 | 
				
			||||||
            self, create_mirrored_message_users_mock: Any) -> None:
 | 
					 | 
				
			||||||
        create_mirrored_message_users_mock.side_effect = InvalidMirrorInput()
 | 
					 | 
				
			||||||
        result = self.api_post(self.mit_user("starnine"), "/api/v1/messages",
 | 
					 | 
				
			||||||
                               {"type": "private",
 | 
					 | 
				
			||||||
                                "sender": self.mit_email("sipbtest"),
 | 
					 | 
				
			||||||
                                "content": "Test message",
 | 
					 | 
				
			||||||
                                "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
                                "to": self.mit_email("starnine")},
 | 
					 | 
				
			||||||
                               subdomain="zephyr")
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Invalid mirrored message")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @mock.patch("zerver.views.message_send.create_mirrored_message_users")
 | 
					 | 
				
			||||||
    def test_send_message_when_client_is_zephyr_mirror_but_string_id_is_not_zephyr(
 | 
					 | 
				
			||||||
            self, create_mirrored_message_users_mock: Any) -> None:
 | 
					 | 
				
			||||||
        create_mirrored_message_users_mock.return_value = mock.Mock()
 | 
					 | 
				
			||||||
        user = self.mit_user("starnine")
 | 
					 | 
				
			||||||
        user.realm.string_id = 'notzephyr'
 | 
					 | 
				
			||||||
        user.realm.save()
 | 
					 | 
				
			||||||
        result = self.api_post(user, "/api/v1/messages",
 | 
					 | 
				
			||||||
                               {"type": "private",
 | 
					 | 
				
			||||||
                                "sender": self.mit_email("sipbtest"),
 | 
					 | 
				
			||||||
                                "content": "Test message",
 | 
					 | 
				
			||||||
                                "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
                                "to": user.email},
 | 
					 | 
				
			||||||
                               subdomain="notzephyr")
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Zephyr mirroring is not allowed in this organization")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @mock.patch("zerver.views.message_send.create_mirrored_message_users")
 | 
					 | 
				
			||||||
    def test_send_message_when_client_is_zephyr_mirror_but_recipient_is_user_id(
 | 
					 | 
				
			||||||
            self, create_mirrored_message_users_mock: Any) -> None:
 | 
					 | 
				
			||||||
        create_mirrored_message_users_mock.return_value = mock.Mock()
 | 
					 | 
				
			||||||
        user = self.mit_user("starnine")
 | 
					 | 
				
			||||||
        self.login_user(user)
 | 
					 | 
				
			||||||
        result = self.api_post(user, "/api/v1/messages",
 | 
					 | 
				
			||||||
                               {"type": "private",
 | 
					 | 
				
			||||||
                                "sender": self.mit_email("sipbtest"),
 | 
					 | 
				
			||||||
                                "content": "Test message",
 | 
					 | 
				
			||||||
                                "client": "zephyr_mirror",
 | 
					 | 
				
			||||||
                                "to": ujson.dumps([user.id])},
 | 
					 | 
				
			||||||
                               subdomain="zephyr")
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Mirroring not allowed with recipient user IDs")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_send_message_irc_mirror(self) -> None:
 | 
					 | 
				
			||||||
        reset_emails_in_zulip_realm()
 | 
					 | 
				
			||||||
        self.login('hamlet')
 | 
					 | 
				
			||||||
        bot_info = {
 | 
					 | 
				
			||||||
            'full_name': 'IRC bot',
 | 
					 | 
				
			||||||
            'short_name': 'irc',
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        result = self.client_post("/json/bots", bot_info)
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        email = "irc-bot@zulip.testserver"
 | 
					 | 
				
			||||||
        user = get_user(email, get_realm('zulip'))
 | 
					 | 
				
			||||||
        user.is_api_super_user = True
 | 
					 | 
				
			||||||
        user.save()
 | 
					 | 
				
			||||||
        user = get_user(email, get_realm('zulip'))
 | 
					 | 
				
			||||||
        self.subscribe(user, "IRCland")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Simulate a mirrored message with a slightly old timestamp.
 | 
					 | 
				
			||||||
        fake_date_sent = timezone_now() - datetime.timedelta(minutes=37)
 | 
					 | 
				
			||||||
        fake_timestamp = datetime_to_timestamp(fake_date_sent)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        result = self.api_post(user, "/api/v1/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                          "forged": "true",
 | 
					 | 
				
			||||||
                                                          "time": fake_timestamp,
 | 
					 | 
				
			||||||
                                                          "sender": "irc-user@irc.zulip.com",
 | 
					 | 
				
			||||||
                                                          "content": "Test message",
 | 
					 | 
				
			||||||
                                                          "client": "irc_mirror",
 | 
					 | 
				
			||||||
                                                          "topic": "from irc",
 | 
					 | 
				
			||||||
                                                          "to": "IRCLand"})
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        msg = self.get_last_message()
 | 
					 | 
				
			||||||
        self.assertEqual(int(datetime_to_timestamp(msg.date_sent)), int(fake_timestamp))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Now test again using forged=yes
 | 
					 | 
				
			||||||
        fake_date_sent = timezone_now() - datetime.timedelta(minutes=22)
 | 
					 | 
				
			||||||
        fake_timestamp = datetime_to_timestamp(fake_date_sent)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        result = self.api_post(user, "/api/v1/messages", {"type": "stream",
 | 
					 | 
				
			||||||
                                                          "forged": "yes",
 | 
					 | 
				
			||||||
                                                          "time": fake_timestamp,
 | 
					 | 
				
			||||||
                                                          "sender": "irc-user@irc.zulip.com",
 | 
					 | 
				
			||||||
                                                          "content": "Test message",
 | 
					 | 
				
			||||||
                                                          "client": "irc_mirror",
 | 
					 | 
				
			||||||
                                                          "topic": "from irc",
 | 
					 | 
				
			||||||
                                                          "to": "IRCLand"})
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        msg = self.get_last_message()
 | 
					 | 
				
			||||||
        self.assertEqual(int(datetime_to_timestamp(msg.date_sent)), int(fake_timestamp))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_unsubscribed_api_super_user(self) -> None:
 | 
					 | 
				
			||||||
        reset_emails_in_zulip_realm()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cordelia = self.example_user('cordelia')
 | 
					 | 
				
			||||||
        stream_name = 'private_stream'
 | 
					 | 
				
			||||||
        self.make_stream(stream_name, invite_only=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.unsubscribe(cordelia, stream_name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # As long as Cordelia is a super_user, she can send messages
 | 
					 | 
				
			||||||
        # to ANY stream, even one she is not unsubscribed to, and
 | 
					 | 
				
			||||||
        # she can do it for herself or on behalf of a mirrored user.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def test_with(sender_email: str, client: str, forged: bool) -> None:
 | 
					 | 
				
			||||||
            payload = dict(
 | 
					 | 
				
			||||||
                type="stream",
 | 
					 | 
				
			||||||
                to=stream_name,
 | 
					 | 
				
			||||||
                client=client,
 | 
					 | 
				
			||||||
                topic='whatever',
 | 
					 | 
				
			||||||
                content='whatever',
 | 
					 | 
				
			||||||
                forged=ujson.dumps(forged),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Only pass the 'sender' property when doing mirroring behavior.
 | 
					 | 
				
			||||||
            if forged:
 | 
					 | 
				
			||||||
                payload['sender'] = sender_email
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cordelia.is_api_super_user = False
 | 
					 | 
				
			||||||
            cordelia.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            result = self.api_post(cordelia, "/api/v1/messages", payload)
 | 
					 | 
				
			||||||
            self.assert_json_error_contains(result, 'authorized')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cordelia.is_api_super_user = True
 | 
					 | 
				
			||||||
            cordelia.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            result = self.api_post(cordelia, "/api/v1/messages", payload)
 | 
					 | 
				
			||||||
            self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        test_with(
 | 
					 | 
				
			||||||
            sender_email=cordelia.email,
 | 
					 | 
				
			||||||
            client='test suite',
 | 
					 | 
				
			||||||
            forged=False,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        test_with(
 | 
					 | 
				
			||||||
            sender_email='irc_person@zulip.com',
 | 
					 | 
				
			||||||
            client='irc_mirror',
 | 
					 | 
				
			||||||
            forged=True,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_bot_can_send_to_owner_stream(self) -> None:
 | 
					 | 
				
			||||||
        cordelia = self.example_user('cordelia')
 | 
					 | 
				
			||||||
        bot = self.create_test_bot(
 | 
					 | 
				
			||||||
            short_name='whatever',
 | 
					 | 
				
			||||||
            user_profile=cordelia,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        stream_name = 'private_stream'
 | 
					 | 
				
			||||||
        self.make_stream(stream_name, invite_only=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        payload = dict(
 | 
					 | 
				
			||||||
            type="stream",
 | 
					 | 
				
			||||||
            to=stream_name,
 | 
					 | 
				
			||||||
            client='test suite',
 | 
					 | 
				
			||||||
            topic='whatever',
 | 
					 | 
				
			||||||
            content='whatever',
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        result = self.api_post(bot, "/api/v1/messages", payload)
 | 
					 | 
				
			||||||
        self.assert_json_error_contains(result, 'Not authorized to send')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # We subscribe the bot owner! (aka cordelia)
 | 
					 | 
				
			||||||
        assert bot.bot_owner is not None
 | 
					 | 
				
			||||||
        self.subscribe(bot.bot_owner, stream_name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        result = self.api_post(bot, "/api/v1/messages", payload)
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_cross_realm_bots_can_use_api_on_own_subdomain(self) -> None:
 | 
					 | 
				
			||||||
        # Cross realm bots should use internal_send_*_message, not the API:
 | 
					 | 
				
			||||||
        notification_bot = self.notification_bot()
 | 
					 | 
				
			||||||
        stream = self.make_stream("notify_channel", get_realm("zulipinternal"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        result = self.api_post(notification_bot,
 | 
					 | 
				
			||||||
                               "/api/v1/messages",
 | 
					 | 
				
			||||||
                               {"type": "stream",
 | 
					 | 
				
			||||||
                                "to": "notify_channel",
 | 
					 | 
				
			||||||
                                "client": "test suite",
 | 
					 | 
				
			||||||
                                "content": "Test message",
 | 
					 | 
				
			||||||
                                "topic": "Test topic"},
 | 
					 | 
				
			||||||
                               subdomain='zulipinternal')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
        message = self.get_last_message()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.assertEqual(message.content, "Test message")
 | 
					 | 
				
			||||||
        self.assertEqual(message.sender, notification_bot)
 | 
					 | 
				
			||||||
        self.assertEqual(message.recipient.type_id, stream.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_create_mirror_user_despite_race(self) -> None:
 | 
					 | 
				
			||||||
        realm = get_realm('zulip')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        email = 'fred@example.com'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        email_to_full_name = lambda email: 'fred'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def create_user(**kwargs: Any) -> UserProfile:
 | 
					 | 
				
			||||||
            self.assertEqual(kwargs['full_name'], 'fred')
 | 
					 | 
				
			||||||
            self.assertEqual(kwargs['email'], email)
 | 
					 | 
				
			||||||
            self.assertEqual(kwargs['active'], False)
 | 
					 | 
				
			||||||
            self.assertEqual(kwargs['is_mirror_dummy'], True)
 | 
					 | 
				
			||||||
            # We create an actual user here to simulate a race.
 | 
					 | 
				
			||||||
            # We use the minimal, un-mocked function.
 | 
					 | 
				
			||||||
            kwargs['bot_type'] = None
 | 
					 | 
				
			||||||
            kwargs['bot_owner'] = None
 | 
					 | 
				
			||||||
            kwargs['tos_version'] = None
 | 
					 | 
				
			||||||
            kwargs['timezone'] = timezone_now()
 | 
					 | 
				
			||||||
            create_user_profile(**kwargs).save()
 | 
					 | 
				
			||||||
            raise IntegrityError()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with mock.patch('zerver.lib.actions.create_user',
 | 
					 | 
				
			||||||
                        side_effect=create_user) as m:
 | 
					 | 
				
			||||||
            mirror_fred_user = create_mirror_user_if_needed(
 | 
					 | 
				
			||||||
                realm,
 | 
					 | 
				
			||||||
                email,
 | 
					 | 
				
			||||||
                email_to_full_name,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.assertEqual(mirror_fred_user.delivery_email, email)
 | 
					 | 
				
			||||||
        m.assert_called()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_guest_user(self) -> None:
 | 
					 | 
				
			||||||
        sender = self.example_user('polonius')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        stream_name = 'public stream'
 | 
					 | 
				
			||||||
        self.make_stream(stream_name, invite_only=False)
 | 
					 | 
				
			||||||
        payload = dict(
 | 
					 | 
				
			||||||
            type="stream",
 | 
					 | 
				
			||||||
            to=stream_name,
 | 
					 | 
				
			||||||
            client='test suite',
 | 
					 | 
				
			||||||
            topic='whatever',
 | 
					 | 
				
			||||||
            content='whatever',
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Guest user can't send message to unsubscribed public streams
 | 
					 | 
				
			||||||
        result = self.api_post(sender, "/api/v1/messages", payload)
 | 
					 | 
				
			||||||
        self.assert_json_error(result, "Not authorized to send to stream 'public stream'")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.subscribe(sender, stream_name)
 | 
					 | 
				
			||||||
        # Guest user can send message to subscribed public streams
 | 
					 | 
				
			||||||
        result = self.api_post(sender, "/api/v1/messages", payload)
 | 
					 | 
				
			||||||
        self.assert_json_success(result)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ScheduledMessageTest(ZulipTestCase):
 | 
					class ScheduledMessageTest(ZulipTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def last_scheduled_message(self) -> ScheduledMessage:
 | 
					    def last_scheduled_message(self) -> ScheduledMessage:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user