zulip/zerver/tests/test_message_send.py

3359 lines
128 KiB
Python

from datetime import timedelta
from email.headerregistry import Address
from typing import Any
from unittest import mock
import orjson
from django.conf import settings
from django.db.models import Q
from django.test import override_settings
from django.utils.timezone import now as timezone_now
from zerver.actions.create_realm import do_create_realm
from zerver.actions.create_user import do_create_user
from zerver.actions.message_send import (
build_message_send_dict,
check_message,
check_send_stream_message,
do_send_messages,
extract_private_recipients,
extract_stream_indicator,
internal_prep_private_message,
internal_prep_stream_message_by_name,
internal_send_group_direct_message,
internal_send_private_message,
internal_send_stream_message,
internal_send_stream_message_by_name,
send_rate_limited_pm_notification_to_bot_owner,
)
from zerver.actions.realm_settings import (
do_change_realm_permission_group_setting,
do_set_realm_property,
)
from zerver.actions.streams import do_change_stream_post_policy, do_deactivate_stream
from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group
from zerver.actions.user_settings import do_change_user_setting
from zerver.actions.users import do_change_can_forge_sender, do_deactivate_user
from zerver.lib.addressee import Addressee
from zerver.lib.exceptions import (
DirectMessageInitiationError,
DirectMessagePermissionError,
JsonableError,
)
from zerver.lib.message import get_raw_unread_data, get_recent_private_conversations
from zerver.lib.message_cache import MessageDict
from zerver.lib.per_request_cache import flush_per_request_caches
from zerver.lib.streams import create_stream_if_needed
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import (
get_user_messages,
make_client,
message_stream_count,
most_recent_message,
most_recent_usermessage,
reset_email_visibility_to_everyone_in_zulip_realm,
)
from zerver.lib.timestamp import datetime_to_timestamp
from zerver.models import (
Message,
NamedUserGroup,
Realm,
RealmDomain,
Recipient,
Stream,
Subscription,
UserMessage,
UserProfile,
)
from zerver.models.constants import MAX_TOPIC_NAME_LENGTH
from zerver.models.groups import SystemGroups
from zerver.models.realms import WildcardMentionPolicyEnum, get_realm
from zerver.models.recipients import get_or_create_direct_message_group
from zerver.models.streams import get_stream
from zerver.models.users import get_system_bot, get_user
from zerver.views.message_send import InvalidMirrorInputError
class MessagePOSTTest(ZulipTestCase):
def _send_and_verify_message(
self, user: UserProfile, stream_name: str, error_msg: str | None = None
) -> None:
if error_msg is None:
msg_id = self.send_stream_message(user, stream_name)
result = self.api_get(user, "/api/v1/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_stream_by_name(self) -> None:
"""
Sending a message to a stream to which you are subscribed is
successful.
"""
recipient_type_name = ["stream", "channel"]
self.login("hamlet")
for recipient_type in recipient_type_name:
result = self.client_post(
"/json/messages",
{
"type": recipient_type,
"to": orjson.dumps("Verona").decode(),
"content": "Test message",
"topic": "Test topic",
},
)
self.assert_json_success(result)
def test_api_message_to_stream_by_name(self) -> None:
"""
Same as above, but for the API view
"""
recipient_type_name = ["stream", "channel"]
user = self.example_user("hamlet")
for recipient_type in recipient_type_name:
result = self.api_post(
user,
"/api/v1/messages",
{
"type": recipient_type,
"to": orjson.dumps("Verona").decode(),
"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": "channel",
"to": orjson.dumps([99999]).decode(),
"content": "Stream message by ID.",
"topic": "Test topic for stream ID message",
},
)
self.assert_json_error(result, "Channel with ID '99999' does not exist")
msg = self.get_last_message()
expected = (
"Your bot `whatever-bot@zulip.testserver` tried to send a message to "
"channel ID 99999, but there is no channel with that ID."
)
self.assertEqual(msg.content, expected)
def test_message_to_stream_with_no_subscribers(self) -> None:
"""
Sending a message to an empty stream succeeds, but sends a warning
to the owner.
"""
realm = get_realm("zulip")
cordelia = self.example_user("cordelia")
bot = self.create_test_bot(
short_name="whatever",
user_profile=cordelia,
)
stream = create_stream_if_needed(realm, "Acropolis")[0]
result = self.api_post(
bot,
"/api/v1/messages",
{
"type": "channel",
"to": orjson.dumps(stream.name).decode(),
"content": "Stream message to an empty stream by name.",
"topic": "Test topic for empty stream name message",
},
)
self.assert_json_success(result)
msg = self.get_last_message()
expected = "Stream message to an empty stream by name."
self.assertEqual(msg.content, expected)
msg = self.get_second_to_last_message()
expected = (
"Your bot `whatever-bot@zulip.testserver` tried to send a message to "
"channel #**Acropolis**. The channel exists but does not have any subscribers."
)
self.assertEqual(msg.content, expected)
def test_message_to_stream_with_no_subscribers_by_id(self) -> None:
"""
Sending a message to an empty stream succeeds, but sends a warning
to the owner.
"""
realm = get_realm("zulip")
cordelia = self.example_user("cordelia")
bot = self.create_test_bot(
short_name="whatever",
user_profile=cordelia,
)
stream = create_stream_if_needed(realm, "Acropolis")[0]
result = self.api_post(
bot,
"/api/v1/messages",
{
"type": "channel",
"to": orjson.dumps([stream.id]).decode(),
"content": "Stream message to an empty stream by id.",
"topic": "Test topic for empty stream id message",
},
)
self.assert_json_success(result)
msg = self.get_last_message()
expected = "Stream message to an empty stream by id."
self.assertEqual(msg.content, expected)
msg = self.get_second_to_last_message()
expected = (
"Your bot `whatever-bot@zulip.testserver` tried to send a message to "
"channel #**Acropolis**. The channel exists but does not have any subscribers."
)
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.
"""
recipient_type_name = ["stream", "channel"]
self.login("hamlet")
realm = get_realm("zulip")
stream = get_stream("Verona", realm)
for recipient_type in recipient_type_name:
content = f"Stream message by ID, type parameter: {recipient_type}."
result = self.client_post(
"/json/messages",
{
"type": recipient_type,
"to": orjson.dumps([stream.id]).decode(),
"content": content,
"topic": "Test topic for stream ID message",
},
)
self.assert_json_success(result)
sent_message = self.get_last_message()
self.assertEqual(sent_message.content, content)
def test_sending_message_as_stream_post_policy_admins(self) -> None:
"""
Sending messages to streams which only the admins can 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, acting_user=admin_profile
)
# 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 channel.",
)
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 channel.",
)
moderator_profile = self.example_user("shiva")
self.login_user(moderator_profile)
# Moderators and their owned bots cannot send to STREAM_POST_POLICY_ADMINS streams
self._send_and_verify_message(
moderator_profile,
stream_name,
"Only organization administrators can send to this channel.",
)
moderator_owned_bot = self.create_test_bot(
short_name="whatever3",
full_name="whatever3",
user_profile=moderator_profile,
)
self._send_and_verify_message(
moderator_owned_bot,
stream_name,
"Only organization administrators can send to this channel.",
)
# 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",
bot_type=UserProfile.DEFAULT_BOT,
acting_user=None,
)
self._send_and_verify_message(
bot_without_owner,
stream_name,
"Only organization administrators can send to this channel.",
)
# Cross realm bots should be allowed
notification_bot = get_system_bot("notification-bot@zulip.com", stream.realm_id)
internal_send_stream_message(
notification_bot, stream, "Test topic", "Test message by notification bot"
)
self.assertEqual(self.get_last_message().content, "Test message by notification bot")
guest_profile = self.example_user("polonius")
# Guests cannot send to non-STREAM_POST_POLICY_EVERYONE streams
self._send_and_verify_message(
guest_profile, stream_name, "Only organization administrators can send to this channel."
)
def test_sending_message_as_stream_post_policy_moderators(self) -> None:
"""
Sending messages to streams which only the moderators can 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_MODERATORS, acting_user=admin_profile
)
# Admins and their owned bots can send to STREAM_POST_POLICY_MODERATORS 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)
moderator_profile = self.example_user("shiva")
self.login_user(moderator_profile)
# Moderators and their owned bots can send to STREAM_POST_POLICY_MODERATORS streams
self._send_and_verify_message(moderator_profile, stream_name)
moderator_owned_bot = self.create_test_bot(
short_name="whatever2",
full_name="whatever2",
user_profile=moderator_profile,
)
self._send_and_verify_message(moderator_owned_bot, stream_name)
non_admin_profile = self.example_user("hamlet")
self.login_user(non_admin_profile)
# Members and their owned bots cannot send to STREAM_POST_POLICY_MODERATORS streams
self._send_and_verify_message(
non_admin_profile,
stream_name,
"Only organization administrators and moderators can send to this channel.",
)
non_admin_owned_bot = self.create_test_bot(
short_name="whatever3",
full_name="whatever3",
user_profile=non_admin_profile,
)
self._send_and_verify_message(
non_admin_owned_bot,
stream_name,
"Only organization administrators and moderators can send to this channel.",
)
# Bots without owner (except cross realm bot) cannot send to STREAM_POST_POLICY_MODERATORS streams.
bot_without_owner = do_create_user(
email="free-bot@zulip.testserver",
password="",
realm=non_admin_profile.realm,
full_name="freebot",
bot_type=UserProfile.DEFAULT_BOT,
acting_user=None,
)
self._send_and_verify_message(
bot_without_owner,
stream_name,
"Only organization administrators and moderators can send to this channel.",
)
# System bots should be allowed
notification_bot = get_system_bot("notification-bot@zulip.com", stream.realm_id)
internal_send_stream_message(
notification_bot, stream, "Test topic", "Test message by notification bot"
)
self.assertEqual(self.get_last_message().content, "Test message by notification bot")
guest_profile = self.example_user("polonius")
# Guests cannot send to non-STREAM_POST_POLICY_EVERYONE streams
self._send_and_verify_message(
guest_profile,
stream_name,
"Only organization administrators and moderators can send to this channel.",
)
def test_sending_message_as_stream_post_policy_restrict_new_members(self) -> None:
"""
Sending messages to streams which new members cannot post to.
"""
admin_profile = self.example_user("iago")
self.login_user(admin_profile)
do_set_realm_property(admin_profile.realm, "waiting_period_threshold", 10, acting_user=None)
admin_profile.date_joined = timezone_now() - timedelta(days=9)
admin_profile.save()
self.assertTrue(admin_profile.is_provisional_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, acting_user=admin_profile
)
# 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() - timedelta(days=9)
non_admin_profile.save()
self.assertTrue(non_admin_profile.is_provisional_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 channel."
)
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 channel."
)
non_admin_profile.date_joined = timezone_now() - timedelta(days=11)
non_admin_profile.save()
self.assertFalse(non_admin_profile.is_provisional_member)
self._send_and_verify_message(non_admin_profile, stream_name)
# We again set bot owner here, as date_joined of non_admin_profile is changed.
non_admin_owned_bot.bot_owner = non_admin_profile
non_admin_owned_bot.save()
self._send_and_verify_message(non_admin_owned_bot, stream_name)
# Bots without owner (except cross realm bot) cannot send to STREAM_POST_POLICY_ADMINS_ONLY and
# STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams
bot_without_owner = do_create_user(
email="free-bot@zulip.testserver",
password="",
realm=non_admin_profile.realm,
full_name="freebot",
bot_type=UserProfile.DEFAULT_BOT,
acting_user=None,
)
self._send_and_verify_message(
bot_without_owner, stream_name, "New members cannot send to this channel."
)
moderator_profile = self.example_user("shiva")
moderator_profile.date_joined = timezone_now() - timedelta(days=9)
moderator_profile.save()
self.assertTrue(moderator_profile.is_moderator)
self.assertFalse(moderator_profile.is_provisional_member)
# Moderators and their owned bots can send to STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS
# streams, even if the moderator is a new user
self._send_and_verify_message(moderator_profile, stream_name)
moderator_owned_bot = self.create_test_bot(
short_name="whatever3",
full_name="whatever3",
user_profile=moderator_profile,
)
moderator_owned_bot.date_joined = timezone_now() - timedelta(days=11)
moderator_owned_bot.save()
self._send_and_verify_message(moderator_owned_bot, stream_name)
# System bots should be allowed
notification_bot = get_system_bot("notification-bot@zulip.com", stream.realm_id)
internal_send_stream_message(
notification_bot, stream, "Test topic", "Test message by notification bot"
)
self.assertEqual(self.get_last_message().content, "Test message by notification bot")
guest_profile = self.example_user("polonius")
# Guests cannot send to non-STREAM_POST_POLICY_EVERYONE streams
self._send_and_verify_message(
guest_profile, stream_name, "Guests cannot send to this channel."
)
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()
# The `to` field is required according to OpenAPI specification
result = self.api_post(
user,
"/api/v1/messages",
{
"type": "channel",
"content": "Test message no to",
"topic": "Test topic",
},
intentionally_undocumented=True,
)
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": "channel",
"to": "nonexistent_stream",
"content": "Test message",
"topic": "Test topic",
},
)
self.assert_json_error(result, "Channel '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": "channel",
"to": """&<"'><non-existent>""",
"content": "Test message",
"topic": "Test topic",
},
)
self.assert_json_error(
result, "Channel '&amp;&lt;&quot;&#x27;&gt;&lt;non-existent&gt;' does not exist"
)
def test_message_to_stream_with_automatically_change_visibility_policy(self) -> None:
"""
Sending a message to a stream with the automatic follow/unmute policy
enabled results in including an extra optional parameter in the response.
"""
user = self.example_user("hamlet")
do_change_user_setting(
user,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND,
acting_user=None,
)
result = self.api_post(
user,
"/api/v1/messages",
{
"type": "channel",
"to": orjson.dumps("Verona").decode(),
"content": "Test message",
"topic": "Test topic",
},
)
content = self.assert_json_success(result)
assert "automatic_new_visibility_policy" in content
self.assertEqual(content["automatic_new_visibility_policy"], 3)
# Hamlet sends another message to the same topic. There will be no change in the visibility
# policy, so the 'automatic_new_visibility_policy' parameter should be absent in the result.
result = self.api_post(
user,
"/api/v1/messages",
{
"type": "channel",
"to": orjson.dumps("Verona").decode(),
"content": "Another Test message",
"topic": "Test topic",
},
)
content = self.assert_json_success(result)
assert "automatic_new_visibility_policy" not in content
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": "direct",
"content": "Test message",
"to": orjson.dumps([othello.email]).decode(),
},
)
self.assert_json_success(result)
message_id = orjson.loads(result.content)["id"]
recent_conversations = get_recent_private_conversations(user_profile)
[(recipient_id, recent_conversation)] = recent_conversations.items()
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": "direct",
"content": "Test message",
"to": orjson.dumps([user_profile.email]).decode(),
},
)
self.assert_json_success(result)
self_message_id = orjson.loads(result.content)["id"]
recent_conversations = get_recent_private_conversations(user_profile)
self.assert_length(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]
[(recipient_id, recent_conversation)] = recent_conversations.items()
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
for both valid strings for `type` parameter.
"""
self.login("hamlet")
recipient_type_name = ["direct", "private"]
for type in recipient_type_name:
result = self.client_post(
"/json/messages",
{
"type": type,
"content": "Test message",
"to": orjson.dumps([self.example_user("othello").id]).decode(),
},
)
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").recipient_id)
def test_group_personal_message_by_id(self) -> None:
"""
Sending a personal message to a valid user ID is successful
for both valid strings for `type` parameter.
"""
self.login("hamlet")
recipient_type_name = ["direct", "private"]
for type in recipient_type_name:
result = self.client_post(
"/json/messages",
{
"type": type,
"content": "Test message",
"to": orjson.dumps(
[self.example_user("othello").id, self.example_user("cordelia").id]
).decode(),
},
)
self.assert_json_success(result)
msg = self.get_last_message()
self.assertEqual("Test message", msg.content)
direct_message_group = get_or_create_direct_message_group(
[
self.example_user("hamlet").id,
self.example_user("othello").id,
self.example_user("cordelia").id,
]
)
self.assertEqual(msg.recipient_id, direct_message_group.recipient_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": "direct",
"content": "Test message",
"to": orjson.dumps([hamlet.id, othello.id]).decode(),
},
)
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": "direct",
"content": "Test message",
"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, acting_user=None)
self.login("hamlet")
result = self.client_post(
"/json/messages",
{
"type": "direct",
"content": "Test message",
"to": orjson.dumps([othello.id]).decode(),
},
)
self.assert_json_error(result, f"'{othello.email}' is no longer using Zulip.")
result = self.client_post(
"/json/messages",
{
"type": "direct",
"content": "Test message",
"to": orjson.dumps([othello.id, cordelia.id]).decode(),
},
)
self.assert_json_error(result, f"'{othello.email}' is no longer using Zulip.")
def test_personal_message_to_inaccessible_users(self) -> None:
othello = self.example_user("othello")
cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet")
iago = self.example_user("iago")
self.set_up_db_for_testing_user_access()
self.login("polonius")
result = self.client_post(
"/json/messages",
{
"type": "direct",
"content": "Test direct message",
"to": orjson.dumps([othello.id]).decode(),
},
)
self.assert_json_error(
result, "You do not have permission to access some of the recipients."
)
result = self.client_post(
"/json/messages",
{
"type": "direct",
"content": "Test direct message",
"to": orjson.dumps([hamlet.id]).decode(),
},
)
self.assert_json_success(result)
msg = self.get_last_message()
self.assertEqual(msg.content, "Test direct message")
result = self.client_post(
"/json/messages",
{
"type": "direct",
"content": "Test group direct message",
"to": orjson.dumps([othello.id, cordelia.id]).decode(),
},
)
self.assert_json_error(
result, "You do not have permission to access some of the recipients."
)
result = self.client_post(
"/json/messages",
{
"type": "direct",
"content": "Test group direct message",
"to": orjson.dumps([hamlet.id, cordelia.id]).decode(),
},
)
self.assert_json_error(
result, "You do not have permission to access some of the recipients."
)
result = self.client_post(
"/json/messages",
{
"type": "direct",
"content": "Test group direct message",
"to": orjson.dumps([hamlet.id, iago.id]).decode(),
},
)
self.assert_json_success(result)
msg = self.get_last_message()
self.assertEqual(msg.content, "Test group direct message")
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",
"to": othello.email,
},
)
self.assert_json_error(result, "Invalid 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": "direct", "content": " ", "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": "channel",
"to": "Verona",
"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": "channel", "to": "Verona", "content": "Test message"},
)
self.assert_json_error(result, "Missing topic")
def test_invalid_topic(self) -> None:
"""
Sending a message with invalid 'Cc', 'Cs' and 'Cn' category of unicode characters
"""
# For 'Cc' category
self.login("hamlet")
result = self.client_post(
"/json/messages",
{
"type": "channel",
"to": "Verona",
"topic": "Test\n\rTopic",
"content": "Test message",
},
)
self.assert_json_error(result, "Invalid character in topic, at position 5!")
# For 'Cn' category
result = self.client_post(
"/json/messages",
{
"type": "channel",
"to": "Verona",
"topic": "Test\ufffeTopic",
"content": "Test message",
},
)
self.assert_json_error(result, "Invalid character in topic, at position 5!")
def test_invalid_recipient_type(self) -> None:
"""
Messages other than the type of "direct", "private", "channel" or "stream" are invalid.
"""
self.login("hamlet")
result = self.client_post(
"/json/messages",
{
"type": "invalid",
"to": "Verona",
"content": "Test message",
"topic": "Test topic",
},
)
self.assert_json_error(result, "Invalid type")
def test_private_message_without_recipients(self) -> None:
"""
Sending a direct message without recipients should fail
"""
self.login("hamlet")
result = self.client_post(
"/json/messages",
{"type": "direct", "content": "Test content", "to": ""},
)
self.assert_json_error(result, "Message must have recipients")
def test_mirrored_direct_message_group(self) -> None:
"""
Sending a mirrored group direct message works
"""
result = self.api_post(
self.mit_user("starnine"),
"/api/v1/messages",
{
"type": "direct",
"sender": self.mit_email("sipbtest"),
"content": "Test message",
"client": "zephyr_mirror",
"to": orjson.dumps(
[self.mit_email("starnine"), self.mit_email("espuser")]
).decode(),
},
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"),
"/api/v1/messages",
{
"type": "direct",
"sender": self.mit_email("sipbtest"),
"content": "Test message",
"client": "zephyr_mirror",
"to": orjson.dumps([self.mit_email("starnine")]).decode(),
},
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": "direct",
"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": "direct",
"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_direct_message_group(self) -> None:
"""
Sending two mirrored direct message groups in the row return the same ID
"""
msg = {
"type": "direct",
"sender": self.mit_email("sipbtest"),
"content": "Test message",
"client": "zephyr_mirror",
"to": orjson.dumps([self.mit_email("espuser"), self.mit_email("starnine")]).decode(),
}
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(orjson.loads(result1.content)["id"], orjson.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": "channel",
"to": "Verona",
"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": "channel",
"to": orjson.dumps("Verona").decode(),
"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!")
# Test if it removes the new line from the beginning of the message.
post_data = {
"type": "channel",
"to": orjson.dumps("Verona").decode(),
"content": "\nAvoid the new line at the beginning of the 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, "Avoid the new line at the beginning of the message."
)
@override_settings(MAX_MESSAGE_LENGTH=25)
def test_long_message(self) -> None:
"""
Sending a message longer than the maximum message length succeeds but is
truncated.
"""
self.login("hamlet")
MAX_MESSAGE_LENGTH = settings.MAX_MESSAGE_LENGTH
long_message = "A" * (MAX_MESSAGE_LENGTH + 1)
post_data = {
"type": "channel",
"to": orjson.dumps("Verona").decode(),
"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_name = "A" * (MAX_TOPIC_NAME_LENGTH + 1)
post_data = {
"type": "channel",
"to": orjson.dumps("Verona").decode(),
"content": "test content",
"topic": long_topic_name,
}
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": "channel",
"to": "Verona",
"content": "Test message",
"topic": "Test topic",
"forged": "true",
},
)
self.assert_json_error(result, "User not authorized for this query")
def test_send_message_when_sender_is_not_set(self) -> None:
result = self.api_post(
self.mit_user("starnine"),
"/api/v1/messages",
{
"type": "direct",
"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": "channel",
"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 = InvalidMirrorInputError()
result = self.api_post(
self.mit_user("starnine"),
"/api/v1/messages",
{
"type": "direct",
"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": "direct",
"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": "direct",
"sender": self.mit_email("sipbtest"),
"content": "Test message",
"client": "zephyr_mirror",
"to": orjson.dumps([user.id]).decode(),
},
subdomain="zephyr",
)
self.assert_json_error(result, "Mirroring not allowed with recipient user IDs")
def test_send_message_irc_mirror(self) -> None:
reset_email_visibility_to_everyone_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.can_forge_sender = 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() - timedelta(minutes=37)
fake_timestamp = datetime_to_timestamp(fake_date_sent)
result = self.api_post(
user,
"/api/v1/messages",
{
"type": "channel",
"forged": "true",
"time": fake_timestamp,
"sender": "irc-user@irc.zulip.com",
"content": "Test message",
"client": "irc_mirror",
"topic": "from irc",
"to": orjson.dumps("IRCLand").decode(),
},
)
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() - timedelta(minutes=22)
fake_timestamp = datetime_to_timestamp(fake_date_sent)
result = self.api_post(
user,
"/api/v1/messages",
{
"type": "channel",
"forged": "yes",
"time": fake_timestamp,
"sender": "irc-user@irc.zulip.com",
"content": "Test message",
"client": "irc_mirror",
"topic": "from irc",
"to": orjson.dumps("IRCLand").decode(),
},
)
self.assert_json_success(result)
msg = self.get_last_message()
self.assertEqual(int(datetime_to_timestamp(msg.date_sent)), int(fake_timestamp))
def test_send_message_in_archived_stream(self) -> None:
self.login("hamlet")
stream_name = "archived stream"
stream = self.make_stream(stream_name)
result = self.client_post(
"/json/messages",
{
"type": "channel",
"to": orjson.dumps([stream.id]).decode(),
"content": "Test message",
"topic": "Test topic",
},
)
self.assert_json_success(result)
do_deactivate_stream(stream, acting_user=None)
result = self.client_post(
"/json/messages",
{
"type": "channel",
"to": orjson.dumps([stream.id]).decode(),
"content": "Second Test message",
"topic": "Test topic",
},
)
self.assert_json_error(result, f"Not authorized to send to channel '{stream.name}'")
def test_unsubscribed_can_forge_sender(self) -> None:
reset_email_visibility_to_everyone_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 cam_forge_sender, 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="channel",
to=orjson.dumps(stream_name).decode(),
client=client,
topic="whatever",
content="whatever",
forged=orjson.dumps(forged).decode(),
)
# Only pass the 'sender' property when doing mirroring behavior.
if forged:
payload["sender"] = sender_email
cordelia.can_forge_sender = False
cordelia.save()
result = self.api_post(cordelia, "/api/v1/messages", payload)
self.assert_json_error_contains(result, "authorized")
cordelia.can_forge_sender = 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="channel",
to=orjson.dumps(stream_name).decode(),
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:
internal_realm = get_realm("zulipinternal")
notification_bot = self.notification_bot(internal_realm)
stream = self.make_stream("notify_channel", internal_realm)
result = self.api_post(
notification_bot,
"/api/v1/messages",
{
"type": "channel",
"to": orjson.dumps("notify_channel").decode(),
"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_guest_user(self) -> None:
sender = self.example_user("polonius")
stream_name = "public stream"
self.make_stream(stream_name, invite_only=False)
payload = dict(
type="channel",
to=orjson.dumps(stream_name).decode(),
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 channel '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 StreamMessagesTest(ZulipTestCase):
def assert_stream_message(
self, stream_name: str, topic_name: str = "test topic", content: str = "test content"
) -> None:
"""
Check that messages sent to a stream reach all subscribers to that stream.
"""
realm = get_realm("zulip")
subscribers = self.users_subscribed_to_stream(stream_name, realm)
# Outgoing webhook bots don't store UserMessage rows; they will be processed later.
subscribers = [
subscriber
for subscriber in subscribers
if subscriber.bot_type != UserProfile.OUTGOING_WEBHOOK_BOT
]
old_subscriber_messages = list(map(message_stream_count, subscribers))
non_subscribers = [
user_profile
for user_profile in UserProfile.objects.all()
if user_profile not in subscribers
]
old_non_subscriber_messages = list(map(message_stream_count, non_subscribers))
non_bot_subscribers = [
user_profile for user_profile in subscribers if not user_profile.is_bot
]
a_subscriber = non_bot_subscribers[0]
self.login_user(a_subscriber)
self.send_stream_message(a_subscriber, stream_name, content=content, topic_name=topic_name)
# Did all of the subscribers get the message?
new_subscriber_messages = list(map(message_stream_count, subscribers))
# Did non-subscribers not get the message?
new_non_subscriber_messages = list(map(message_stream_count, non_subscribers))
self.assertEqual(old_non_subscriber_messages, new_non_subscriber_messages)
self.assertEqual(new_subscriber_messages, [elt + 1 for elt in old_subscriber_messages])
def test_performance(self) -> None:
"""
This test is part of the automated test suite, but
it is more intended as an aid to measuring the
performance of do_send_messages() with consistent
data setup across different commits. You can modify
the values below and run just this test, and then
comment out the print statement toward the bottom.
"""
num_messages = 2
num_extra_users = 10
sender = self.example_user("cordelia")
realm = sender.realm
message_content = "whatever"
stream = get_stream("Denmark", realm)
topic_name = "lunch"
recipient = stream.recipient
assert recipient is not None
sending_client = make_client(name="test suite")
for i in range(num_extra_users):
# Make every other user be idle.
long_term_idle = i % 2 > 0
email = f"foo{i}@example.com"
user = UserProfile.objects.create(
realm=realm,
email=email,
delivery_email=email,
long_term_idle=long_term_idle,
)
Subscription.objects.create(
user_profile=user,
is_user_active=user.is_active,
recipient=recipient,
)
def send_test_message() -> None:
message = Message(
sender=sender,
recipient=recipient,
realm=stream.realm,
content=message_content,
date_sent=timezone_now(),
sending_client=sending_client,
)
message.set_topic_name(topic_name)
message_dict = build_message_send_dict(message=message)
do_send_messages([message_dict])
before_um_count = UserMessage.objects.count()
for i in range(num_messages):
send_test_message()
after_um_count = UserMessage.objects.count()
ums_created = after_um_count - before_um_count
num_active_users = num_extra_users / 2
self.assertTrue(ums_created > (num_active_users * num_messages))
def test_not_too_many_queries(self) -> None:
recipient_list = [
self.example_user("hamlet"),
self.example_user("iago"),
self.example_user("cordelia"),
self.example_user("othello"),
]
for user_profile in recipient_list:
self.subscribe(user_profile, "Denmark")
sender = self.example_user("hamlet")
user = self.example_user("othello")
sending_client = make_client(name="test suite")
stream_name = "Denmark"
topic_name = "foo"
content = "whatever"
# Note: We don't need to assert the db query count for each possible
# combination of 'automatically_follow_topics_policy' and 'automatically_unmute_topics_in_muted_streams_policy',
# as the query count depends only on the actions, i.e., 'ON_INITIATION',
# 'ON_PARTICIPATION', and 'NEVER', and is independent of the final visibility_policy set.
# Asserting query count using one of the above-mentioned settings fulfils our purpose.
# To get accurate count of the queries, we should make sure that
# caches don't come into play. If we count queries while caches are
# filled, we will get a lower count. Caches are not supposed to be
# persistent, so our test can also fail if cache is invalidated
# during the course of the unit test.
flush_per_request_caches()
do_change_user_setting(
user_profile=sender,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
acting_user=None,
)
with self.assert_database_query_count(13):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic_name=topic_name,
body=content,
)
do_change_user_setting(
user_profile=sender,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
# There will be an increase in the query count of 5 while sending
# the first message to a topic.
# 5 queries: 1 to check if it is the first message in the topic +
# 1 to check if the topic is already followed + 3 to follow the topic.
flush_per_request_caches()
with self.assert_database_query_count(18):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic_name="new topic",
body=content,
)
do_change_user_setting(
user_profile=sender,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
self.send_stream_message(self.example_user("iago"), stream_name, "Hello", "topic 2")
# There will be an increase in the query count of 4 while sending
# a message to a topic with visibility policy other than FOLLOWED.
# 1 to check if the topic is already followed + 3 queries to follow the topic.
flush_per_request_caches()
with self.assert_database_query_count(17):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic_name="topic 2",
body=content,
)
# If the topic is already FOLLOWED, there will be an increase in the query
# count of 1 to check if the topic is already followed.
flush_per_request_caches()
with self.assert_database_query_count(14):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic_name="topic 2",
body=content,
)
realm = get_realm("zulip")
subscribers = self.users_subscribed_to_stream(stream_name, realm)
for user in subscribers:
do_change_user_setting(
user_profile=user,
setting_name="automatically_follow_topics_where_mentioned",
setting_value=True,
acting_user=None,
)
# There will be an increase in the query count of 5 while sending
# a message with a mention to a topic if visibility policy for the
# mentioned user is other than FOLLOWED.
# 1 to get the user_id of the mentioned user + 1 to check if the topic
# is already followed + 3 queries to follow the topic.
flush_per_request_caches()
with self.assert_database_query_count(22):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic_name="topic 2",
body="@**" + user.full_name + "**",
)
# If the topic is already FOLLOWED, there will be an increase in the query
# count of 2.
# 1 to get the user_id of the mentioned user + 1 to check if the topic is
# already followed.
flush_per_request_caches()
with self.assert_database_query_count(19):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic_name="topic 2",
body="@**" + user.full_name + "**",
)
flush_per_request_caches()
with self.assert_database_query_count(16):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic_name="topic 2",
body="@**all**",
)
def test_stream_message_dict(self) -> None:
user_profile = self.example_user("iago")
self.subscribe(user_profile, "Denmark")
self.send_stream_message(
self.example_user("hamlet"), "Denmark", content="whatever", topic_name="my topic"
)
message = most_recent_message(user_profile)
dct = MessageDict.ids_to_dict([message.id])[0]
MessageDict.post_process_dicts(
[dct],
apply_markdown=True,
client_gravatar=False,
realm=user_profile.realm,
)
self.assertEqual(dct["display_recipient"], "Denmark")
stream = get_stream("Denmark", user_profile.realm)
self.assertEqual(dct["stream_id"], stream.id)
def test_stream_message_unicode(self) -> None:
receiving_user_profile = self.example_user("iago")
sender = self.example_user("hamlet")
self.subscribe(receiving_user_profile, "Denmark")
self.send_stream_message(sender, "Denmark", content="whatever", topic_name="my topic")
message = most_recent_message(receiving_user_profile)
self.assertEqual(
repr(message),
f"<Message: Denmark / my topic / <UserProfile: {sender.email} {sender.realm!r}>>",
)
def test_message_mentions(self) -> None:
user_profile = self.example_user("iago")
self.subscribe(user_profile, "Denmark")
self.send_stream_message(
self.example_user("hamlet"), "Denmark", content="test @**Iago** rules"
)
message = most_recent_message(user_profile)
assert UserMessage.objects.get(
user_profile=user_profile, message=message
).flags.mentioned.is_set
def test_is_private_flag(self) -> None:
user_profile = self.example_user("iago")
self.subscribe(user_profile, "Denmark")
self.send_stream_message(self.example_user("hamlet"), "Denmark", content="test")
message = most_recent_message(user_profile)
self.assertFalse(
UserMessage.objects.get(
user_profile=user_profile, message=message
).flags.is_private.is_set
)
self.send_personal_message(self.example_user("hamlet"), user_profile, content="test")
message = most_recent_message(user_profile)
self.assertTrue(
UserMessage.objects.get(
user_profile=user_profile, message=message
).flags.is_private.is_set
)
def _send_stream_message(self, user: UserProfile, stream_name: str, content: str) -> set[int]:
with self.capture_send_event_calls(expected_num_events=1) as events:
self.send_stream_message(
user,
stream_name,
content=content,
skip_capture_on_commit_callbacks=True,
)
users = events[0]["users"]
user_ids = {u["id"] for u in users}
return user_ids
def test_unsub_mention(self) -> None:
cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet")
stream_name = "Test stream"
self.subscribe(hamlet, stream_name)
UserMessage.objects.filter(
user_profile=cordelia,
).delete()
def mention_cordelia() -> set[int]:
content = "test @**Cordelia, Lear's daughter** rules"
user_ids = self._send_stream_message(
user=hamlet,
stream_name=stream_name,
content=content,
)
return user_ids
def num_cordelia_messages() -> int:
return UserMessage.objects.filter(
user_profile=cordelia,
).count()
user_ids = mention_cordelia()
self.assertEqual(0, num_cordelia_messages())
self.assertNotIn(cordelia.id, user_ids)
# Make sure test isn't too brittle-subscribing
# Cordelia and mentioning her should give her a
# message.
self.subscribe(cordelia, stream_name)
user_ids = mention_cordelia()
self.assertIn(cordelia.id, user_ids)
self.assertEqual(1, num_cordelia_messages())
def test_message_bot_mentions(self) -> None:
cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet")
realm = hamlet.realm
stream_name = "Test stream"
self.subscribe(hamlet, stream_name)
normal_bot = do_create_user(
email="normal-bot@zulip.com",
password="",
realm=realm,
full_name="Normal Bot",
bot_type=UserProfile.DEFAULT_BOT,
bot_owner=cordelia,
acting_user=None,
)
content = "test @**Normal Bot** rules"
user_ids = self._send_stream_message(
user=hamlet,
stream_name=stream_name,
content=content,
)
self.assertIn(normal_bot.id, user_ids)
user_message = most_recent_usermessage(normal_bot)
self.assertEqual(user_message.message.content, content)
self.assertTrue(user_message.flags.mentioned)
def send_and_verify_topic_wildcard_mention_message(
self, sender_name: str, test_fails: bool = False, topic_participant_count: int = 20
) -> None:
sender = self.example_user(sender_name)
content = "@**topic** test topic wildcard mention"
participants_user_ids = set(range(topic_participant_count))
with mock.patch(
"zerver.actions.message_send.participants_for_topic", return_value=participants_user_ids
):
if not test_fails:
msg_id = self.send_stream_message(sender, "test_stream", content)
result = self.api_get(sender, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
else:
with self.assertRaisesRegex(
JsonableError,
"You do not have permission to use topic wildcard mentions in this topic.",
):
self.send_stream_message(sender, "test_stream", content)
def test_topic_wildcard_mention_restrictions(self) -> None:
cordelia = self.example_user("cordelia")
iago = self.example_user("iago")
polonius = self.example_user("polonius")
shiva = self.example_user("shiva")
realm = cordelia.realm
stream_name = "test_stream"
self.subscribe(cordelia, stream_name)
self.subscribe(iago, stream_name)
self.subscribe(polonius, stream_name)
self.subscribe(shiva, stream_name)
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.EVERYONE,
acting_user=None,
)
self.send_and_verify_topic_wildcard_mention_message("polonius")
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.MEMBERS,
acting_user=None,
)
self.send_and_verify_topic_wildcard_mention_message("polonius", test_fails=True)
# There is no restriction on topics with less than 'Realm.WILDCARD_MENTION_THRESHOLD' participants.
self.send_and_verify_topic_wildcard_mention_message("polonius", topic_participant_count=10)
self.send_and_verify_topic_wildcard_mention_message("cordelia")
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.FULL_MEMBERS,
acting_user=None,
)
do_set_realm_property(realm, "waiting_period_threshold", 10, acting_user=None)
iago.date_joined = timezone_now()
iago.save()
shiva.date_joined = timezone_now()
shiva.save()
cordelia.date_joined = timezone_now()
cordelia.save()
self.send_and_verify_topic_wildcard_mention_message("cordelia", test_fails=True)
self.send_and_verify_topic_wildcard_mention_message("cordelia", topic_participant_count=10)
# Administrators and moderators can use wildcard mentions even if they are new.
self.send_and_verify_topic_wildcard_mention_message("iago")
self.send_and_verify_topic_wildcard_mention_message("shiva")
cordelia.date_joined = timezone_now() - timedelta(days=11)
cordelia.save()
self.send_and_verify_topic_wildcard_mention_message("cordelia")
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.MODERATORS,
acting_user=None,
)
self.send_and_verify_topic_wildcard_mention_message("cordelia", test_fails=True)
self.send_and_verify_topic_wildcard_mention_message("cordelia", topic_participant_count=10)
self.send_and_verify_topic_wildcard_mention_message("shiva")
cordelia.date_joined = timezone_now()
cordelia.save()
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.ADMINS,
acting_user=None,
)
self.send_and_verify_topic_wildcard_mention_message("shiva", test_fails=True)
# There is no restriction on topics with less than 'Realm.WILDCARD_MENTION_THRESHOLD' participants.
self.send_and_verify_topic_wildcard_mention_message("shiva", topic_participant_count=10)
self.send_and_verify_topic_wildcard_mention_message("iago")
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.NOBODY,
acting_user=None,
)
self.send_and_verify_topic_wildcard_mention_message("iago", test_fails=True)
self.send_and_verify_topic_wildcard_mention_message("iago", topic_participant_count=10)
def send_and_verify_stream_wildcard_mention_message(
self, sender_name: str, test_fails: bool = False, sub_count: int = 16
) -> None:
sender = self.example_user(sender_name)
content = "@**all** test stream wildcard mention"
with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=sub_count):
if not test_fails:
msg_id = self.send_stream_message(sender, "test_stream", content)
result = self.api_get(sender, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
else:
with self.assertRaisesRegex(
JsonableError,
"You do not have permission to use channel wildcard mentions in this channel.",
):
self.send_stream_message(sender, "test_stream", content)
def test_stream_wildcard_mention_restrictions(self) -> None:
cordelia = self.example_user("cordelia")
iago = self.example_user("iago")
polonius = self.example_user("polonius")
shiva = self.example_user("shiva")
realm = cordelia.realm
stream_name = "test_stream"
self.subscribe(cordelia, stream_name)
self.subscribe(iago, stream_name)
self.subscribe(polonius, stream_name)
self.subscribe(shiva, stream_name)
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.EVERYONE,
acting_user=None,
)
self.send_and_verify_stream_wildcard_mention_message("polonius")
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.MEMBERS,
acting_user=None,
)
self.send_and_verify_stream_wildcard_mention_message("polonius", test_fails=True)
# There is no restriction on small streams.
self.send_and_verify_stream_wildcard_mention_message("polonius", sub_count=10)
self.send_and_verify_stream_wildcard_mention_message("cordelia")
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.FULL_MEMBERS,
acting_user=None,
)
do_set_realm_property(realm, "waiting_period_threshold", 10, acting_user=None)
iago.date_joined = timezone_now()
iago.save()
shiva.date_joined = timezone_now()
shiva.save()
cordelia.date_joined = timezone_now()
cordelia.save()
self.send_and_verify_stream_wildcard_mention_message("cordelia", test_fails=True)
self.send_and_verify_stream_wildcard_mention_message("cordelia", sub_count=10)
# Administrators and moderators can use wildcard mentions even if they are new.
self.send_and_verify_stream_wildcard_mention_message("iago")
self.send_and_verify_stream_wildcard_mention_message("shiva")
cordelia.date_joined = timezone_now() - timedelta(days=11)
cordelia.save()
self.send_and_verify_stream_wildcard_mention_message("cordelia")
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.MODERATORS,
acting_user=None,
)
self.send_and_verify_stream_wildcard_mention_message("cordelia", test_fails=True)
self.send_and_verify_stream_wildcard_mention_message("cordelia", sub_count=10)
self.send_and_verify_stream_wildcard_mention_message("shiva")
cordelia.date_joined = timezone_now()
cordelia.save()
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.ADMINS,
acting_user=None,
)
self.send_and_verify_stream_wildcard_mention_message("shiva", test_fails=True)
# There is no restriction on small streams.
self.send_and_verify_stream_wildcard_mention_message("shiva", sub_count=10)
self.send_and_verify_stream_wildcard_mention_message("iago")
do_set_realm_property(
realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.NOBODY,
acting_user=None,
)
self.send_and_verify_stream_wildcard_mention_message("iago", test_fails=True)
self.send_and_verify_stream_wildcard_mention_message("iago", sub_count=10)
def test_topic_wildcard_mentioned_flag(self) -> None:
# For topic wildcard mentions, the 'topic_wildcard_mentioned' flag should be
# set for all the user messages for topic participants, irrespective of
# their notifications settings.
cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet")
iago = self.example_user("iago")
for user_profile in [cordelia, hamlet, iago]:
self.subscribe(user_profile, "Denmark")
# user | topic participant | wildcard_mentions_notify setting
# -------- | ----------------- | ----------------------------------
# cordelia | YES | True
# hamlet | YES | False
# iago | NO | True
self.send_stream_message(cordelia, "Denmark", content="test", topic_name="topic-1")
do_change_user_setting(cordelia, "wildcard_mentions_notify", True, acting_user=None)
self.send_stream_message(hamlet, "Denmark", content="Hi @**topic**", topic_name="topic-1")
message = most_recent_message(cordelia)
self.assertTrue(
UserMessage.objects.get(
user_profile=cordelia, message=message
).flags.topic_wildcard_mentioned.is_set
)
self.send_stream_message(hamlet, "Denmark", content="test", topic_name="topic-2")
do_change_user_setting(hamlet, "wildcard_mentions_notify", False, acting_user=None)
self.send_stream_message(cordelia, "Denmark", content="Hi @**topic**", topic_name="topic-2")
message = most_recent_message(hamlet)
self.assertTrue(
UserMessage.objects.get(
user_profile=hamlet, message=message
).flags.topic_wildcard_mentioned.is_set
)
do_change_user_setting(iago, "wildcard_mentions_notify", True, acting_user=None)
self.send_stream_message(hamlet, "Denmark", content="Hi @**topic**", topic_name="topic-3")
message = most_recent_message(iago)
self.assertFalse(
UserMessage.objects.get(
user_profile=iago, message=message
).flags.topic_wildcard_mentioned.is_set
)
def test_invalid_wildcard_mention_policy(self) -> None:
cordelia = self.example_user("cordelia")
self.login_user(cordelia)
self.subscribe(cordelia, "test_stream")
do_set_realm_property(cordelia.realm, "wildcard_mention_policy", 10, acting_user=None)
content = "@**all** test wildcard mention"
with (
mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=16),
self.assertRaisesRegex(AssertionError, "Invalid wildcard mention policy"),
):
self.send_stream_message(cordelia, "test_stream", content)
def test_user_group_mention_restrictions(self) -> None:
iago = self.example_user("iago")
shiva = self.example_user("shiva")
cordelia = self.example_user("cordelia")
othello = self.example_user("othello")
self.subscribe(iago, "test_stream")
self.subscribe(shiva, "test_stream")
self.subscribe(othello, "test_stream")
self.subscribe(cordelia, "test_stream")
leadership = check_add_user_group(
othello.realm, "leadership", [othello], acting_user=othello
)
support = check_add_user_group(othello.realm, "support", [othello], acting_user=othello)
moderators_system_group = NamedUserGroup.objects.get(
realm=iago.realm, name=SystemGroups.MODERATORS, is_system_group=True
)
content = "Test mentioning user group @*leadership*"
msg_id = self.send_stream_message(cordelia, "test_stream", content)
result = self.api_get(cordelia, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
leadership.can_mention_group = moderators_system_group
leadership.save()
with self.assertRaisesRegex(
JsonableError,
f"You are not allowed to mention user group '{leadership.name}'.",
):
self.send_stream_message(cordelia, "test_stream", content)
# The restriction does not apply on silent mention.
content = "Test mentioning user group @_*leadership*"
msg_id = self.send_stream_message(cordelia, "test_stream", content)
result = self.api_get(cordelia, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
content = "Test mentioning user group @*leadership*"
msg_id = self.send_stream_message(shiva, "test_stream", content)
result = self.api_get(shiva, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
msg_id = self.send_stream_message(iago, "test_stream", content)
result = self.api_get(iago, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
test = check_add_user_group(shiva.realm, "test", [shiva], acting_user=shiva)
add_subgroups_to_user_group(leadership, [test], acting_user=None)
support.can_mention_group = leadership
support.save()
content = "Test mentioning user group @*support*"
with self.assertRaisesRegex(
JsonableError,
f"You are not allowed to mention user group '{support.name}'.",
):
self.send_stream_message(iago, "test_stream", content)
msg_id = self.send_stream_message(othello, "test_stream", content)
result = self.api_get(othello, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
msg_id = self.send_stream_message(shiva, "test_stream", content)
result = self.api_get(shiva, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
content = "Test mentioning user group @*support* @*leadership*"
with self.assertRaisesRegex(
JsonableError,
f"You are not allowed to mention user group '{support.name}'.",
):
self.send_stream_message(iago, "test_stream", content)
with self.assertRaisesRegex(
JsonableError,
f"You are not allowed to mention user group '{leadership.name}'.",
):
self.send_stream_message(othello, "test_stream", content)
msg_id = self.send_stream_message(shiva, "test_stream", content)
result = self.api_get(shiva, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
# Test system bots.
content = "Test mentioning user group @*support*"
members_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=iago.realm, is_system_group=True
)
support.can_mention_group = members_group
support.save()
internal_realm = get_realm(settings.SYSTEM_BOT_REALM)
system_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT, internal_realm.id)
with self.assertRaisesRegex(
JsonableError,
f"You are not allowed to mention user group '{support.name}'.",
):
self.send_stream_message(system_bot, "test_stream", content, recipient_realm=iago.realm)
everyone_group = NamedUserGroup.objects.get(
name=SystemGroups.EVERYONE, realm=iago.realm, is_system_group=True
)
support.can_mention_group = everyone_group
support.save()
msg_id = self.send_stream_message(
system_bot, "test_stream", content, recipient_realm=iago.realm
)
result = self.api_get(shiva, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
# Test all the cases when can_mention_group is not a named user group.
content = "Test mentioning user group @*leadership*"
user_group = self.create_or_update_anonymous_group_for_setting(
[othello], [moderators_system_group]
)
leadership.can_mention_group = user_group
leadership.save()
msg_id = self.send_stream_message(othello, "test_stream", content)
result = self.api_get(cordelia, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
msg_id = self.send_stream_message(shiva, "test_stream", content)
result = self.api_get(cordelia, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
msg_id = self.send_stream_message(iago, "test_stream", content)
result = self.api_get(cordelia, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
with self.assertRaisesRegex(
JsonableError,
f"You are not allowed to mention user group '{leadership.name}'.",
):
self.send_stream_message(cordelia, "test_stream", content)
with self.assertRaisesRegex(
JsonableError,
f"You are not allowed to mention user group '{leadership.name}'.",
):
self.send_stream_message(system_bot, "test_stream", content, recipient_realm=iago.realm)
content = "Test mentioning user group @_*leadership*"
msg_id = self.send_stream_message(shiva, "test_stream", content)
result = self.api_get(cordelia, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
def test_stream_message_mirroring(self) -> None:
user = self.mit_user("starnine")
self.subscribe(user, "Verona")
do_change_can_forge_sender(user, True)
result = self.api_post(
user,
"/api/v1/messages",
{
"type": "channel",
"to": orjson.dumps("Verona").decode(),
"sender": self.mit_email("sipbtest"),
"client": "zephyr_mirror",
"topic": "announcement",
"content": "Everyone knows Iago rules",
"forged": "true",
},
subdomain="zephyr",
)
self.assert_json_success(result)
do_change_can_forge_sender(user, False)
result = self.api_post(
user,
"/api/v1/messages",
{
"type": "channel",
"to": "Verona",
"sender": self.mit_email("sipbtest"),
"client": "zephyr_mirror",
"topic": "announcement",
"content": "Everyone knows Iago rules",
"forged": "true",
},
subdomain="zephyr",
)
self.assert_json_error(result, "User not authorized for this query")
def test_message_to_stream(self) -> None:
"""
If you send a message to a stream, everyone subscribed to the stream
receives the messages.
"""
self.assert_stream_message("Scotland")
def test_non_ascii_stream_message(self) -> None:
"""
Sending a stream message containing non-ASCII characters in the stream
name, topic, or message body succeeds.
"""
self.login("hamlet")
# Subscribe everyone to a stream with non-ASCII characters.
non_ascii_stream_name = "hümbüǵ"
realm = get_realm("zulip")
stream = self.make_stream(non_ascii_stream_name)
for user_profile in UserProfile.objects.filter(is_active=True, is_bot=False, realm=realm)[
0:3
]:
self.subscribe(user_profile, stream.name)
self.assert_stream_message(non_ascii_stream_name, topic_name="hümbüǵ", content="hümbüǵ")
def test_get_raw_unread_data_for_group_direct_messages(self) -> None:
users = [
self.example_user("hamlet"),
self.example_user("cordelia"),
self.example_user("iago"),
self.example_user("prospero"),
self.example_user("othello"),
]
message1_id = self.send_group_direct_message(users[0], users, "test content 1")
message2_id = self.send_group_direct_message(users[0], users, "test content 2")
msg_data = get_raw_unread_data(users[1])
# both the messages are present in msg_data
self.assertIn(message1_id, msg_data["huddle_dict"].keys())
self.assertIn(message2_id, msg_data["huddle_dict"].keys())
# only these two messages are present in msg_data
self.assert_length(msg_data["huddle_dict"].keys(), 2)
recent_conversations = get_recent_private_conversations(users[1])
[recent_conversation] = recent_conversations.values()
self.assertEqual(
set(recent_conversation["user_ids"]), {user.id for user in users if user != users[1]}
)
self.assertEqual(recent_conversation["max_message_id"], message2_id)
class PersonalMessageSendTest(ZulipTestCase):
def test_personal_to_self(self) -> None:
"""
If you send a personal to yourself, only you see it.
"""
old_user_profiles = list(UserProfile.objects.all())
test_email = self.nonreg_email("test1")
self.register(test_email, "test1")
old_messages = list(map(message_stream_count, old_user_profiles))
user_profile = self.nonreg_user("test1")
self.send_personal_message(user_profile, user_profile)
new_messages = list(map(message_stream_count, old_user_profiles))
self.assertEqual(old_messages, new_messages)
user_profile = self.nonreg_user("test1")
recipient = Recipient.objects.get(type_id=user_profile.id, type=Recipient.PERSONAL)
self.assertEqual(most_recent_message(user_profile).recipient, recipient)
def assert_personal(
self, sender: UserProfile, receiver: UserProfile, content: str = "testcontent"
) -> None:
"""
Send a direct message from `sender_email` to `receiver_email` and check
that only those two parties actually received the message.
"""
sender_messages = message_stream_count(sender)
receiver_messages = message_stream_count(receiver)
other_user_profiles = UserProfile.objects.filter(~Q(id=sender.id) & ~Q(id=receiver.id))
old_other_messages = list(map(message_stream_count, other_user_profiles))
self.send_personal_message(sender, receiver, content)
# Users outside the conversation don't get the message.
new_other_messages = list(map(message_stream_count, other_user_profiles))
self.assertEqual(old_other_messages, new_other_messages)
# The personal message is in the streams of both the sender and receiver.
self.assertEqual(message_stream_count(sender), sender_messages + 1)
self.assertEqual(message_stream_count(receiver), receiver_messages + 1)
recipient = Recipient.objects.get(type_id=receiver.id, type=Recipient.PERSONAL)
self.assertEqual(most_recent_message(sender).recipient, recipient)
self.assertEqual(most_recent_message(receiver).recipient, recipient)
def test_personal(self) -> None:
"""
If you send a personal, only you and the recipient see it.
"""
self.login("hamlet")
self.assert_personal(
sender=self.example_user("hamlet"),
receiver=self.example_user("othello"),
)
def test_direct_message_initiator_group_setting(self) -> None:
"""
Tests that direct_message_initiator_group_setting works correctly.
"""
user_profile = self.example_user("hamlet")
polonius = self.example_user("polonius")
admin = self.example_user("iago")
cordelia = self.example_user("cordelia")
realm = user_profile.realm
direct_message_group_1 = [user_profile, admin, polonius]
direct_message_group_2 = [user_profile, admin, polonius, cordelia]
administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
self.login_user(user_profile)
self.send_personal_message(user_profile, polonius)
do_change_realm_permission_group_setting(
realm,
"direct_message_initiator_group",
administrators_system_group,
acting_user=None,
)
# We can send to Polonius because we'd previously messaged him.
self.send_personal_message(user_profile, polonius)
# Tests if we can send messages to self irrespective of the value of the setting.
self.send_personal_message(user_profile, user_profile)
# We cannot send to users with whom we does not have any direct message conversation.
with self.assertRaises(DirectMessageInitiationError) as direct_message_initiation_error:
self.send_personal_message(user_profile, cordelia)
self.assertEqual(
str(direct_message_initiation_error.exception),
"You do not have permission to initiate direct message conversations.",
)
with self.assertRaises(DirectMessageInitiationError):
self.send_personal_message(user_profile, admin)
# Have the administrator send a message, and verify that allows the user to reply.
self.send_personal_message(admin, user_profile)
with self.assert_database_query_count(16):
self.send_personal_message(user_profile, admin)
# Tests that user cannot initiate direct message thread in groups.
with self.assertRaises(DirectMessageInitiationError):
self.send_group_direct_message(user_profile, direct_message_group_1)
# Have the administrator send a message to the direct message group, and verify
# that allows the user to reply.
self.send_group_direct_message(admin, direct_message_group_1)
with self.assert_database_query_count(20):
self.send_group_direct_message(user_profile, direct_message_group_1)
# We cannot sent to `direct_message_group_2` as no message has been sent to this group yet.
with self.assertRaises(DirectMessageInitiationError):
self.send_group_direct_message(user_profile, direct_message_group_2)
bot_profile = self.create_test_bot("testbot", user_profile)
notification_bot = get_system_bot("notification-bot@zulip.com", user_profile.realm_id)
# Tests if messages to and from bots are allowed irrespective of the value of the setting.
self.send_personal_message(user_profile, notification_bot)
self.send_personal_message(user_profile, bot_profile)
self.send_personal_message(bot_profile, user_profile)
# Tests if the permission works when the setting is set to a combination of
# groups and users.
user_group = self.create_or_update_anonymous_group_for_setting(
[user_profile],
[administrators_system_group],
)
do_change_realm_permission_group_setting(
realm,
"direct_message_initiator_group",
user_group,
acting_user=None,
)
self.send_personal_message(user_profile, cordelia)
def test_direct_message_permission_group_setting(self) -> None:
"""
Tests that direct_message_permission_group_setting works correctly.
"""
user_profile = self.example_user("hamlet")
cordelia = self.example_user("cordelia")
polonius = self.example_user("polonius")
admin = self.example_user("iago")
realm = user_profile.realm
direct_message_group = [user_profile, cordelia, admin]
direct_message_group_without_admin = [user_profile, cordelia, polonius]
administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
nobody_system_group = NamedUserGroup.objects.get(
name=SystemGroups.NOBODY, realm=realm, is_system_group=True
)
self.login_user(user_profile)
do_change_realm_permission_group_setting(
realm,
"direct_message_permission_group",
administrators_system_group,
acting_user=None,
)
# Tests if the user is allowed to send to administrators.
with self.assert_database_query_count(16):
self.send_personal_message(user_profile, admin)
self.send_personal_message(admin, user_profile)
# Tests if we can send messages to self irrespective of the value of the setting.
self.send_personal_message(user_profile, user_profile)
# We cannot send direct messages unless one of the recipient is in the
# `direct_message_permission_group` (in this case, the
# `administrators_system_group`).
with self.assertRaises(DirectMessagePermissionError) as direct_message_permission_error:
self.send_personal_message(user_profile, cordelia)
self.assertEqual(
str(direct_message_permission_error.exception),
"This conversation does not include any users who can authorize it.",
)
# We can send to this direct message group as it has administrator as one of the
# recipient.
with self.assert_database_query_count(24):
self.send_group_direct_message(user_profile, direct_message_group)
self.send_group_direct_message(admin, direct_message_group)
# But this one does not have an administrator. So, it should throw an error.
with self.assertRaises(DirectMessagePermissionError):
self.send_group_direct_message(user_profile, direct_message_group_without_admin)
bot_profile = self.create_test_bot("testbot", user_profile)
notification_bot = get_system_bot("notification-bot@zulip.com", user_profile.realm_id)
# Tests if messages to and from bots are allowed irrespective of the value of the setting.
self.send_personal_message(user_profile, notification_bot)
self.send_personal_message(user_profile, bot_profile)
self.send_personal_message(bot_profile, user_profile)
# Tests if the permission works when the setting is set to a combination of
# groups and users.
user_group = self.create_or_update_anonymous_group_for_setting(
[user_profile],
[administrators_system_group],
)
do_change_realm_permission_group_setting(
realm,
"direct_message_permission_group",
user_group,
acting_user=None,
)
self.send_personal_message(user_profile, cordelia)
do_change_realm_permission_group_setting(
realm,
"direct_message_permission_group",
nobody_system_group,
acting_user=None,
)
with self.assertRaises(DirectMessagePermissionError) as direct_message_permission_error:
self.send_personal_message(user_profile, cordelia)
self.assertEqual(
str(direct_message_permission_error.exception),
"Direct messages are disabled in this organization.",
)
def test_non_ascii_personal(self) -> None:
"""
Sending a direct message containing non-ASCII characters succeeds.
"""
self.login("hamlet")
self.assert_personal(
sender=self.example_user("hamlet"),
receiver=self.example_user("othello"),
content="hümbüǵ",
)
class ExtractTest(ZulipTestCase):
def test_extract_stream_indicator(self) -> None:
self.assertEqual(
extract_stream_indicator("development"),
"development",
)
self.assertEqual(
extract_stream_indicator("commas,are,fine"),
"commas,are,fine",
)
self.assertEqual(
extract_stream_indicator('"Who hasn\'t done this?"'),
"Who hasn't done this?",
)
self.assertEqual(
extract_stream_indicator("999"),
999,
)
# For legacy reasons it's plausible that users will
# put a single stream into an array and then encode it
# as JSON. We can probably eliminate this support
# by mid 2020 at the latest.
self.assertEqual(
extract_stream_indicator('["social"]'),
"social",
)
self.assertEqual(
extract_stream_indicator("[123]"),
123,
)
with self.assertRaisesRegex(JsonableError, "Invalid data type for channel"):
extract_stream_indicator("{}")
with self.assertRaisesRegex(JsonableError, "Invalid data type for channel"):
extract_stream_indicator("[{}]")
with self.assertRaisesRegex(JsonableError, "Expected exactly one channel"):
extract_stream_indicator('[1,2,"general"]')
def test_extract_private_recipients_emails(self) -> None:
# JSON list w/dups, empties, and trailing whitespace
s = orjson.dumps([" alice@zulip.com ", " bob@zulip.com ", " ", "bob@zulip.com"]).decode()
# sorted() gets confused by extract_private_recipients' return type
# For testing, ignorance here is better than manual casting
result = sorted(extract_private_recipients(s))
self.assertEqual(result, ["alice@zulip.com", "bob@zulip.com"])
# simple string with one name
s = "alice@zulip.com "
self.assertEqual(extract_private_recipients(s), ["alice@zulip.com"])
# JSON-encoded string
s = '"alice@zulip.com"'
self.assertEqual(extract_private_recipients(s), ["alice@zulip.com"])
# bare comma-delimited string
s = "bob@zulip.com, alice@zulip.com"
result = sorted(extract_private_recipients(s))
self.assertEqual(result, ["alice@zulip.com", "bob@zulip.com"])
# JSON-encoded, comma-delimited string
s = '"bob@zulip.com,alice@zulip.com"'
result = sorted(extract_private_recipients(s))
self.assertEqual(result, ["alice@zulip.com", "bob@zulip.com"])
# Invalid data
s = orjson.dumps(dict(color="red")).decode()
with self.assertRaisesRegex(JsonableError, "Invalid data type for recipients"):
extract_private_recipients(s)
s = orjson.dumps([{}]).decode()
with self.assertRaisesRegex(JsonableError, "Invalid data type for recipients"):
extract_private_recipients(s)
# Empty list
self.assertEqual(extract_private_recipients("[]"), [])
# Heterogeneous lists are not supported
mixed = orjson.dumps(["eeshan@example.com", 3, 4]).decode()
with self.assertRaisesRegex(
JsonableError, "Recipient lists may contain emails or user IDs, but not both."
):
extract_private_recipients(mixed)
def test_extract_recipient_ids(self) -> None:
# JSON list w/dups
s = orjson.dumps([3, 3, 12]).decode()
result = sorted(extract_private_recipients(s))
self.assertEqual(result, [3, 12])
# Invalid data
ids = orjson.dumps(dict(recipient=12)).decode()
with self.assertRaisesRegex(JsonableError, "Invalid data type for recipients"):
extract_private_recipients(ids)
# Heterogeneous lists are not supported
mixed = orjson.dumps([3, 4, "eeshan@example.com"]).decode()
with self.assertRaisesRegex(
JsonableError, "Recipient lists may contain emails or user IDs, but not both."
):
extract_private_recipients(mixed)
class InternalPrepTest(ZulipTestCase):
def test_returns_for_internal_sends(self) -> None:
# For our internal_send_* functions we return
# if the prep stages fail. This is mostly defensive
# code, since we are generally creating the messages
# ourselves, but we want to make sure that the functions
# won't actually explode if we give them bad content.
bad_content = ""
realm = get_realm("zulip")
cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet")
othello = self.example_user("othello")
stream = get_stream("Verona", realm)
with self.assertLogs(level="ERROR") as m:
internal_send_private_message(
sender=cordelia,
recipient_user=hamlet,
content=bad_content,
)
self.assertEqual(
m.output[0].split("\n")[0],
"ERROR:root:Error queueing internal message by {}: {}".format(
"cordelia@zulip.com", "Message must not be empty"
),
)
with self.assertLogs(level="ERROR") as m:
internal_send_group_direct_message(
realm=realm,
sender=cordelia,
emails=[hamlet.email, othello.email],
content=bad_content,
)
self.assertEqual(
m.output[0].split("\n")[0],
"ERROR:root:Error queueing internal message by {}: {}".format(
"cordelia@zulip.com", "Message must not be empty"
),
)
with self.assertLogs(level="ERROR") as m:
internal_send_stream_message(
sender=cordelia,
topic_name="whatever",
content=bad_content,
stream=stream,
)
self.assertEqual(
m.output[0].split("\n")[0],
"ERROR:root:Error queueing internal message by {}: {}".format(
"cordelia@zulip.com", "Message must not be empty"
),
)
with self.assertLogs(level="ERROR") as m:
internal_send_stream_message_by_name(
realm=realm,
sender=cordelia,
stream_name=stream.name,
topic_name="whatever",
content=bad_content,
)
self.assertEqual(
m.output[0].split("\n")[0],
"ERROR:root:Error queueing internal message by {}: {}".format(
"cordelia@zulip.com", "Message must not be empty"
),
)
def test_error_handling(self) -> None:
sender = self.example_user("cordelia")
recipient_user = self.example_user("hamlet")
MAX_MESSAGE_LENGTH = settings.MAX_MESSAGE_LENGTH
content = "x" * (MAX_MESSAGE_LENGTH + 10)
result = internal_prep_private_message(
sender=sender, recipient_user=recipient_user, content=content
)
assert result is not None
message = result.message
self.assertIn("message truncated", message.content)
# Simulate sending a message to somebody not in the
# realm of the sender.
recipient_user = self.mit_user("starnine")
with self.assertLogs(level="ERROR") as m:
result = internal_prep_private_message(
sender=sender, recipient_user=recipient_user, content=content
)
self.assertEqual(
m.output[0].split("\n")[0],
"ERROR:root:Error queueing internal message by {}: {}".format(
"cordelia@zulip.com",
"You can't send direct messages outside of your organization.",
),
)
def test_ensure_stream_gets_called(self) -> None:
realm = get_realm("zulip")
sender = self.example_user("cordelia")
stream_name = "test_stream"
topic_name = "whatever"
content = "hello"
internal_prep_stream_message_by_name(
realm=realm,
sender=sender,
stream_name=stream_name,
topic_name=topic_name,
content=content,
)
# This would throw an error if the stream
# wasn't automatically created.
Stream.objects.get(name=stream_name, realm_id=realm.id)
def test_direct_message_to_self_and_bot_in_dm_disabled_org(self) -> None:
"""
Test that a user can send a direct message to themselves and to a bot in a DM disabled organization
"""
sender = self.example_user("hamlet")
nobody_system_group = NamedUserGroup.objects.get(
name=SystemGroups.NOBODY, realm=sender.realm, is_system_group=True
)
do_change_realm_permission_group_setting(
sender.realm,
"direct_message_permission_group",
nobody_system_group,
acting_user=None,
)
sender.realm.save()
# Create a non-bot user
recipient_user = self.example_user("othello")
recipient_user.realm = sender.realm
# Create a new bot user
bot = do_create_user(
email="test-bot@zulip.com",
password="",
realm=sender.realm,
full_name="Test Bot",
bot_type=UserProfile.DEFAULT_BOT,
bot_owner=sender,
acting_user=None,
)
# Test sending a message to self
result = self.api_post(
sender,
"/api/v1/messages",
{
"type": "private",
"to": orjson.dumps([sender.id]).decode(),
"content": "Test message to self",
},
)
self.assert_json_success(result)
msg = self.get_last_message()
expected = "Test message to self"
self.assertEqual(msg.content, expected)
# Test sending a message to non-bot user
result = self.api_post(
sender,
"/api/v1/messages",
{
"type": "private",
"to": orjson.dumps([recipient_user.id]).decode(),
"content": "Test message",
},
)
self.assert_json_error(result, "Direct messages are disabled in this organization.")
# Test sending a message to the bot
result = self.api_post(
sender,
"/api/v1/messages",
{
"type": "private",
"to": orjson.dumps([bot.id]).decode(),
"content": "Test message to bot",
},
)
self.assert_json_success(result)
msg = self.get_last_message()
expected = "Test message to bot"
self.assertEqual(msg.content, expected)
class TestCrossRealmPMs(ZulipTestCase):
def make_realm(self, domain: str) -> Realm:
realm = do_create_realm(string_id=domain, name=domain)
do_set_realm_property(realm, "invite_required", False, acting_user=None)
RealmDomain.objects.create(realm=realm, domain=domain)
return realm
def create_user(self, email: str) -> UserProfile:
subdomain = Address(addr_spec=email).domain
self.register(email, "test", subdomain=subdomain)
# self.register has the side-effect of ending up with a logged in session
# for the new user. We don't want that in these tests.
self.logout()
return get_user(email, get_realm(subdomain))
@override_settings(
CROSS_REALM_BOT_EMAILS=[
"notification-bot@zulip.com",
"welcome-bot@zulip.com",
"support@3.example.com",
]
)
def test_realm_scenarios(self) -> None:
self.make_realm("1.example.com")
r2 = self.make_realm("2.example.com")
self.make_realm("3.example.com")
def assert_message_received(to_user: UserProfile, from_user: UserProfile) -> None:
messages = get_user_messages(to_user)
self.assertEqual(messages[-1].sender.id, from_user.id)
def assert_invalid_user() -> Any:
return self.assertRaisesRegex(JsonableError, "Invalid user ID ")
user1_email = "user1@1.example.com"
user1a_email = "user1a@1.example.com"
user2_email = "user2@2.example.com"
user3_email = "user3@3.example.com"
notification_bot_email = "notification-bot@zulip.com"
support_email = "support@3.example.com" # note: not zulip.com
user1 = self.create_user(user1_email)
user1a = self.create_user(user1a_email)
user2 = self.create_user(user2_email)
user3 = self.create_user(user3_email)
internal_realm = get_realm(settings.SYSTEM_BOT_REALM)
notification_bot = get_system_bot(notification_bot_email, internal_realm.id)
with self.settings(
CROSS_REALM_BOT_EMAILS=["notification-bot@zulip.com", "welcome-bot@zulip.com"]
):
# HACK: We should probably be creating this "bot" user another
# way, but since you can't register a user with a
# cross-realm email, we need to hide this for now.
support_bot = self.create_user(support_email)
# Users can send a direct message to themselves.
self.send_personal_message(user1, user1)
assert_message_received(user1, user1)
# Users on the same realm can send direct messages to each other.
self.send_personal_message(user1, user1a)
assert_message_received(user1a, user1)
# Cross-realm bots in the zulip.com realm can send a direct message
# in any realm.
# (They need lower level APIs to do this.)
internal_send_private_message(
sender=notification_bot,
recipient_user=get_user(user2_email, r2),
content="bla",
)
assert_message_received(user2, notification_bot)
# All users can send a direct message to cross-realm bots in the
# zulip.com realm.
self.send_personal_message(user1, notification_bot)
assert_message_received(notification_bot, user1)
# Verify that internal_send_private_message can also successfully
# be used.
internal_send_private_message(
sender=user2,
recipient_user=notification_bot,
content="blabla",
)
assert_message_received(notification_bot, user2)
# Users can send a direct message to cross-realm bots on non-zulip
# realms.
# (The support bot represents some theoretical bot that we may
# create in the future that does not have zulip.com as its realm.)
self.send_personal_message(user1, support_bot)
assert_message_received(support_bot, user1)
# Allow sending direct messages to two different cross-realm bots
# simultaneously.
# (We don't particularly need this feature, but since users can
# already individually send direct messages to cross-realm bots,
# we shouldn't prevent them from sending multiple bots at once.
# We may revisit this if it's a nuisance for direct message
# groups.)
self.send_group_direct_message(user1, [notification_bot, support_bot])
assert_message_received(notification_bot, user1)
assert_message_received(support_bot, user1)
# Prevent old loophole where I could send direct messages to other
# users as long as I copied a cross-realm bot from the same realm.
with assert_invalid_user():
self.send_group_direct_message(user1, [user3, support_bot])
# Users on three different realms can't send direct messages to
# each other, even if one of the users is a cross-realm bot.
with assert_invalid_user():
self.send_group_direct_message(user1, [user2, notification_bot])
with assert_invalid_user():
self.send_group_direct_message(notification_bot, [user1, user2])
# Users on the different realms cannot send direct messages to
# each other.
with assert_invalid_user():
self.send_personal_message(user1, user2)
# Users on non-zulip realms can't send direct messages to
# "ordinary" Zulip users.
with assert_invalid_user():
self.send_personal_message(user1, self.example_user("hamlet"))
# Users on three different realms cannot send direct messages
# to each other.
with assert_invalid_user():
self.send_group_direct_message(user1, [user2, user3])
class TestAddressee(ZulipTestCase):
def test_addressee_for_user_ids(self) -> None:
realm = get_realm("zulip")
user_ids = [
self.example_user("cordelia").id,
self.example_user("hamlet").id,
self.example_user("othello").id,
]
result = Addressee.for_user_ids(user_ids=user_ids, realm=realm)
user_profiles = result.user_profiles()
result_user_ids = [user_profiles[0].id, user_profiles[1].id, user_profiles[2].id]
self.assertEqual(set(result_user_ids), set(user_ids))
def test_addressee_for_user_ids_nonexistent_id(self) -> None:
def assert_invalid_user_id() -> Any:
return self.assertRaisesRegex(JsonableError, "Invalid user ID ")
with assert_invalid_user_id():
Addressee.for_user_ids(user_ids=[779], realm=get_realm("zulip"))
def test_addressee_legacy_build_for_user_ids(self) -> None:
realm = get_realm("zulip")
self.login("hamlet")
user_ids = [self.example_user("cordelia").id, self.example_user("othello").id]
result = Addressee.legacy_build(
sender=self.example_user("hamlet"),
recipient_type_name="private",
message_to=user_ids,
topic_name="random_topic",
realm=realm,
)
user_profiles = result.user_profiles()
result_user_ids = [user_profiles[0].id, user_profiles[1].id]
self.assertEqual(set(result_user_ids), set(user_ids))
def test_addressee_legacy_build_for_stream_id(self) -> None:
realm = get_realm("zulip")
self.login("iago")
sender = self.example_user("iago")
self.subscribe(sender, "Denmark")
stream = get_stream("Denmark", realm)
result = Addressee.legacy_build(
sender=sender,
recipient_type_name="stream",
message_to=[stream.id],
topic_name="random_topic",
realm=realm,
)
stream_id = result.stream_id()
self.assertEqual(stream.id, stream_id)
class CheckMessageTest(ZulipTestCase):
def test_basic_check_message_call(self) -> None:
sender = self.example_user("othello")
client = make_client(name="test suite")
stream_name = "España y Francia"
self.make_stream(stream_name)
topic_name = "issue"
message_content = "whatever"
addressee = Addressee.for_stream_name(stream_name, topic_name)
ret = check_message(sender, client, addressee, message_content)
self.assertEqual(ret.message.sender.id, sender.id)
def test_check_message_normal_user_cant_send_to_stream_in_another_realm(self) -> None:
mit_user = self.mit_user("sipbtest")
client = make_client(name="test suite")
stream = get_stream("Denmark", get_realm("zulip"))
topic_name = "issue"
message_content = "whatever"
addressee = Addressee.for_stream(stream, topic_name)
with self.assertRaisesRegex(JsonableError, "User not authorized for this query"):
check_message(
mit_user,
client,
addressee,
message_content,
)
def test_check_message_cant_forge_message_as_other_realm_user(self) -> None:
"""
Verifies that the .can_forge_sender permission doesn't allow
forging another realm's user as sender of a message to a stream
in the forwarder's realm.
"""
forwarder_user_profile = self.example_user("othello")
do_change_can_forge_sender(forwarder_user_profile, True)
mit_user = self.mit_user("sipbtest")
internal_realm = get_realm("zulipinternal")
notification_bot = self.notification_bot(internal_realm)
client = make_client(name="test suite")
stream = get_stream("Denmark", forwarder_user_profile.realm)
topic_name = "issue"
message_content = "whatever"
addressee = Addressee.for_stream(stream, topic_name)
with self.assertRaisesRegex(JsonableError, "User not authorized for this query"):
check_message(
mit_user,
client,
addressee,
message_content,
forged=True,
forwarder_user_profile=forwarder_user_profile,
)
with self.assertRaisesRegex(JsonableError, "User not authorized for this query"):
check_message(
notification_bot,
client,
addressee,
message_content,
forged=True,
forwarder_user_profile=forwarder_user_profile,
)
def test_check_message_cant_forge_message_to_stream_in_different_realm(self) -> None:
"""
Verifies that the .can_forge_sender permission doesn't allow
forging another realm's user as sender of a message to a stream
in the forged user's realm..
"""
forwarder_user_profile = self.example_user("othello")
do_change_can_forge_sender(forwarder_user_profile, True)
mit_user = self.mit_user("sipbtest")
internal_realm = get_realm("zulipinternal")
notification_bot = self.notification_bot(internal_realm)
client = make_client(name="test suite")
stream_name = "España y Francia"
stream = self.make_stream(stream_name, realm=mit_user.realm)
self.subscribe(mit_user, stream_name)
topic_name = "issue"
message_content = "whatever"
addressee = Addressee.for_stream(stream, topic_name)
with self.assertRaisesRegex(JsonableError, "User not authorized for this query"):
check_message(
mit_user,
client,
addressee,
message_content,
forged=True,
forwarder_user_profile=forwarder_user_profile,
)
with self.assertRaisesRegex(JsonableError, "User not authorized for this query"):
check_message(
notification_bot,
client,
addressee,
message_content,
forged=True,
forwarder_user_profile=forwarder_user_profile,
)
# Make sure the special case of sending a message forged as cross-realm bot
# to a stream in the bot's realm isn't allowed either.
stream = self.make_stream(stream_name, realm=notification_bot.realm)
self.subscribe(notification_bot, stream_name)
addressee = Addressee.for_stream(stream, topic_name)
with self.assertRaisesRegex(JsonableError, "User not authorized for this query"):
check_message(
notification_bot,
client,
addressee,
message_content,
forged=True,
forwarder_user_profile=forwarder_user_profile,
)
def test_guest_user_can_send_message(self) -> None:
# Guest users can write to web_public streams.
sender = self.example_user("polonius")
client = make_client(name="test suite")
rome_stream = get_stream("Rome", sender.realm)
is_sender_subscriber = Subscription.objects.filter(
user_profile=sender,
recipient__type_id=rome_stream.id,
).exists()
self.assertFalse(is_sender_subscriber)
self.assertTrue(rome_stream.is_web_public)
topic_name = "issue"
message_content = "whatever"
addressee = Addressee.for_stream_name(rome_stream.name, topic_name)
ret = check_message(sender, client, addressee, message_content)
self.assertEqual(ret.message.sender.id, sender.id)
def test_bot_pm_feature(self) -> None:
"""We send a direct message to a bot's owner if their bot sends a
message to an unsubscribed stream"""
parent = self.example_user("othello")
bot = do_create_user(
email="othello-bot@zulip.com",
password="",
realm=parent.realm,
full_name="",
bot_type=UserProfile.DEFAULT_BOT,
bot_owner=parent,
acting_user=None,
)
bot.last_reminder = None
sender = bot
client = make_client(name="test suite")
stream_name = "Россия"
topic_name = "issue"
addressee = Addressee.for_stream_name(stream_name, topic_name)
message_content = "whatever"
old_count = message_stream_count(parent)
# Try sending to stream that doesn't exist sends a reminder to
# the sender
with self.assertRaises(JsonableError):
check_message(sender, client, addressee, message_content)
new_count = message_stream_count(parent)
self.assertEqual(new_count, old_count + 1)
self.assertIn("that channel does not exist.", most_recent_message(parent).content)
# Try sending to stream that exists with no subscribers soon
# after; due to rate-limiting, this should send nothing.
self.make_stream(stream_name)
ret = check_message(sender, client, addressee, message_content)
new_count = message_stream_count(parent)
self.assertEqual(new_count, old_count + 1)
# Try sending to stream that exists with no subscribers longer
# after; this should send an error to the bot owner that the
# stream doesn't exist
assert sender.last_reminder is not None
sender.last_reminder -= timedelta(hours=1)
sender.save(update_fields=["last_reminder"])
ret = check_message(sender, client, addressee, message_content)
new_count = message_stream_count(parent)
self.assertEqual(new_count, old_count + 2)
self.assertEqual(ret.message.sender.email, "othello-bot@zulip.com")
self.assertIn("does not have any subscribers", most_recent_message(parent).content)
def test_bot_pm_error_handling(self) -> None:
# This just test some defensive code.
cordelia = self.example_user("cordelia")
test_bot = self.create_test_bot(
short_name="test",
user_profile=cordelia,
)
content = "whatever"
good_realm = test_bot.realm
wrong_realm = get_realm("zephyr")
wrong_sender = cordelia
send_rate_limited_pm_notification_to_bot_owner(test_bot, wrong_realm, content)
self.assertEqual(test_bot.last_reminder, None)
send_rate_limited_pm_notification_to_bot_owner(wrong_sender, good_realm, content)
self.assertEqual(test_bot.last_reminder, None)
test_bot.realm.deactivated = True
send_rate_limited_pm_notification_to_bot_owner(test_bot, good_realm, content)
self.assertEqual(test_bot.last_reminder, None)
def test_no_topic_message(self) -> None:
realm = get_realm("zulip")
sender = self.example_user("iago")
client = make_client(name="test suite")
stream = get_stream("Denmark", realm)
topic_name = "(no topic)"
message_content = "whatever"
addressee = Addressee.for_stream(stream, topic_name)
do_set_realm_property(realm, "mandatory_topics", True, acting_user=None)
realm.refresh_from_db()
with self.assertRaisesRegex(JsonableError, "Topics are required in this organization"):
check_message(sender, client, addressee, message_content, realm)
do_set_realm_property(realm, "mandatory_topics", False, acting_user=None)
realm.refresh_from_db()
ret = check_message(sender, client, addressee, message_content, realm)
self.assertEqual(ret.message.sender.id, sender.id)