zulip/zerver/tests/test_message_move_stream.py

1909 lines
73 KiB
Python

from datetime import timedelta
import orjson
from zerver.actions.message_delete import do_delete_messages
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
from zerver.actions.user_groups import check_add_user_group
from zerver.actions.users import do_change_user_role
from zerver.lib.message import has_message_access
from zerver.lib.test_classes import ZulipTestCase, get_topic_messages
from zerver.lib.test_helpers import queries_captured
from zerver.lib.url_encoding import near_stream_message_url
from zerver.models import Message, NamedUserGroup, Stream, UserMessage, UserProfile
from zerver.models.groups import SystemGroups
from zerver.models.realms import get_realm
from zerver.models.streams import get_stream
class MessageMoveStreamTest(ZulipTestCase):
def assert_has_usermessage(self, user_profile_id: int, message_id: int) -> None:
self.assertEqual(
UserMessage.objects.filter(
user_profile_id=user_profile_id, message_id=message_id
).exists(),
True,
)
def assert_lacks_usermessage(self, user_profile_id: int, message_id: int) -> None:
self.assertEqual(
UserMessage.objects.filter(
user_profile_id=user_profile_id, message_id=message_id
).exists(),
False,
)
def prepare_move_topics(
self,
user_email: str,
old_stream: str,
new_stream: str,
topic_name: str,
language: str | None = None,
) -> tuple[UserProfile, Stream, Stream, int, int]:
user_profile = self.example_user(user_email)
if language is not None:
user_profile.default_language = language
user_profile.save(update_fields=["default_language"])
self.login(user_email)
try:
stream = get_stream(old_stream, user_profile.realm)
messages = get_topic_messages(user_profile, stream, "test")
do_delete_messages(user_profile.realm, messages, acting_user=None)
except Stream.DoesNotExist:
stream = self.make_stream(old_stream)
try:
stream_to = get_stream(new_stream, user_profile.realm)
messages = get_topic_messages(user_profile, stream_to, "test")
do_delete_messages(user_profile.realm, messages, acting_user=None)
except Stream.DoesNotExist:
stream_to = self.make_stream(new_stream)
self.subscribe(user_profile, stream.name)
self.subscribe(user_profile, stream_to.name)
msg_id = self.send_stream_message(
user_profile, stream.name, topic_name=topic_name, content="First"
)
msg_id_lt = self.send_stream_message(
user_profile, stream.name, topic_name=topic_name, content="Second"
)
self.send_stream_message(user_profile, stream.name, topic_name=topic_name, content="third")
return (user_profile, stream, stream_to, msg_id, msg_id_lt)
def test_move_message_cant_move_private_message(self) -> None:
hamlet = self.example_user("hamlet")
self.login("hamlet")
cordelia = self.example_user("cordelia")
msg_id = self.send_personal_message(hamlet, cordelia)
verona = get_stream("Verona", hamlet.realm)
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": verona.id,
},
)
self.assert_json_error(result, "Direct messages cannot be moved to channels.")
def test_move_message_to_stream_with_content(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"content": "Not allowed",
},
)
self.assert_json_error(result, "Cannot change message content while changing channel")
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 3)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 0)
def test_change_all_propagate_mode_for_moving_old_messages(self) -> None:
user_profile = self.example_user("hamlet")
realm = user_profile.realm
id1 = self.send_stream_message(user_profile, "Denmark", topic_name="topic1")
id2 = self.send_stream_message(user_profile, "Denmark", topic_name="topic1")
id3 = self.send_stream_message(user_profile, "Denmark", topic_name="topic1")
id4 = self.send_stream_message(user_profile, "Denmark", topic_name="topic1")
self.send_stream_message(user_profile, "Denmark", topic_name="topic1")
members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
members_system_group,
acting_user=None,
)
message = Message.objects.get(id=id1)
message.date_sent -= timedelta(days=10)
message.save()
message = Message.objects.get(id=id2)
message.date_sent -= timedelta(days=8)
message.save()
message = Message.objects.get(id=id3)
message.date_sent -= timedelta(days=5)
message.save()
verona = get_stream("Verona", user_profile.realm)
denmark = get_stream("Denmark", user_profile.realm)
old_topic_name = "topic1"
old_stream = denmark
def test_moving_all_topic_messages(
new_topic_name: str | None = None, new_stream: Stream | None = None
) -> None:
self.login("hamlet")
params_dict: dict[str, str | int] = {
"propagate_mode": "change_all",
"send_notification_to_new_thread": "false",
}
if new_topic_name is not None:
params_dict["topic"] = new_topic_name
else:
new_topic_name = old_topic_name
if new_stream is not None:
params_dict["stream_id"] = new_stream.id
else:
new_stream = old_stream
result = self.client_patch(
f"/json/messages/{id4}",
params_dict,
)
self.assert_json_error(
result,
"You only have permission to move the 3/5 most recent messages in this topic.",
)
# Check message count in old topic and/or stream.
messages = get_topic_messages(user_profile, old_stream, old_topic_name)
self.assert_length(messages, 5)
# Check message count in new topic and/or stream.
messages = get_topic_messages(user_profile, new_stream, new_topic_name)
self.assert_length(messages, 0)
json = orjson.loads(result.content)
first_message_id_allowed_to_move = json["first_message_id_allowed_to_move"]
params_dict["propagate_mode"] = "change_later"
result = self.client_patch(
f"/json/messages/{first_message_id_allowed_to_move}",
params_dict,
)
self.assert_json_success(result)
# Check message count in old topic and/or stream.
messages = get_topic_messages(user_profile, old_stream, old_topic_name)
self.assert_length(messages, 2)
# Check message count in new topic and/or stream.
messages = get_topic_messages(user_profile, new_stream, new_topic_name)
self.assert_length(messages, 3)
self.login("shiva")
# Move these messages to the original topic and stream, to test the case
# when user is moderator.
result = self.client_patch(
f"/json/messages/{id4}",
{
"topic": old_topic_name,
"stream_id": old_stream.id,
"propagate_mode": "change_all",
"send_notification_to_new_thread": "false",
},
)
params_dict["propagate_mode"] = "change_all"
result = self.client_patch(
f"/json/messages/{id4}",
params_dict,
)
self.assert_json_success(result)
# Check message count in old topic and/or stream.
messages = get_topic_messages(user_profile, old_stream, old_topic_name)
self.assert_length(messages, 0)
# Check message count in new topic and/or stream.
messages = get_topic_messages(user_profile, new_stream, new_topic_name)
self.assert_length(messages, 5)
# Test only topic editing case.
test_moving_all_topic_messages(new_topic_name="topic edited")
# Move these messages to the original topic to test the next case.
self.client_patch(
f"/json/messages/{id4}",
{
"topic": old_topic_name,
"propagate_mode": "change_all",
"send_notification_to_new_thread": "false",
},
)
# Test only stream editing case
test_moving_all_topic_messages(new_stream=verona)
# Move these messages to the original stream to test the next case.
self.client_patch(
f"/json/messages/{id4}",
{
"stream_id": denmark.id,
"propagate_mode": "change_all",
"send_notification_to_new_thread": "false",
},
)
# Set time limit for moving messages between streams to 2 weeks.
do_set_realm_property(
user_profile.realm,
"move_messages_between_streams_limit_seconds",
604800 * 2,
acting_user=None,
)
# Test editing both topic and stream together.
test_moving_all_topic_messages(new_topic_name="edited", new_stream=verona)
# Move these messages to the original stream and topic to test the next case.
self.client_patch(
f"/json/messages/{id4}",
{
"stream_id": denmark.id,
"topic": old_topic_name,
"propagate_mode": "change_all",
"send_notification_to_new_thread": "false",
},
)
# Test editing both topic and stream with no limit set.
self.login("hamlet")
do_set_realm_property(
user_profile.realm,
"move_messages_within_stream_limit_seconds",
None,
acting_user=None,
)
do_set_realm_property(
user_profile.realm,
"move_messages_between_streams_limit_seconds",
None,
acting_user=None,
)
new_stream = verona
new_topic_name = "edited"
result = self.client_patch(
f"/json/messages/{id4}",
{
"topic": new_topic_name,
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"send_notification_to_new_thread": "false",
},
)
self.assert_json_success(result)
# Check message count in old topic and/or stream.
messages = get_topic_messages(user_profile, old_stream, old_topic_name)
self.assert_length(messages, 0)
# Check message count in new topic and/or stream.
messages = get_topic_messages(user_profile, new_stream, new_topic_name)
self.assert_length(messages, 5)
def test_move_message_to_stream(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_lt) = self.prepare_move_topics(
"iago",
"test move stream",
"new stream",
"test",
# Set the user's translation language to German to test that
# it is overridden by the realm's default language.
"de",
)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"send_notification_to_old_thread": "true",
},
HTTP_ACCEPT_LANGUAGE="de",
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 1)
self.assertEqual(
messages[0].content,
f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 4)
self.assertEqual(
messages[3].content,
f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_to_preexisting_topic(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_lt) = self.prepare_move_topics(
"iago",
"test move stream",
"new stream",
"test",
# Set the user's translation language to German to test that
# it is overridden by the realm's default language.
"de",
)
self.send_stream_message(
sender=self.example_user("iago"),
stream_name="new stream",
topic_name="test",
content="Always here",
)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"send_notification_to_old_thread": "true",
},
HTTP_ACCEPT_LANGUAGE="de",
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 1)
self.assertEqual(
messages[0].content,
f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 5)
self.assertEqual(
messages[4].content,
f"3 messages were moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_realm_admin_cant_move_to_another_realm(self) -> None:
user_profile = self.example_user("iago")
self.assertEqual(user_profile.role, UserProfile.ROLE_REALM_ADMINISTRATOR)
self.login("iago")
lear_realm = get_realm("lear")
new_stream = self.make_stream("new", lear_realm)
msg_id = self.send_stream_message(user_profile, "Verona", topic_name="test123")
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
},
)
self.assert_json_error(result, "Invalid channel ID")
def test_move_message_realm_admin_cant_move_to_private_stream_without_subscription(
self,
) -> None:
user_profile = self.example_user("iago")
self.assertEqual(user_profile.role, UserProfile.ROLE_REALM_ADMINISTRATOR)
self.login("iago")
new_stream = self.make_stream("new", invite_only=True)
msg_id = self.send_stream_message(user_profile, "Verona", topic_name="test123")
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
},
)
self.assert_json_error(result, "Invalid channel ID")
def test_move_message_realm_admin_cant_move_from_private_stream_without_subscription(
self,
) -> None:
user_profile = self.example_user("iago")
self.assertEqual(user_profile.role, UserProfile.ROLE_REALM_ADMINISTRATOR)
self.login("iago")
self.make_stream("privatestream", invite_only=True)
self.subscribe(user_profile, "privatestream")
msg_id = self.send_stream_message(user_profile, "privatestream", topic_name="test123")
self.unsubscribe(user_profile, "privatestream")
verona = get_stream("Verona", user_profile.realm)
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": verona.id,
"propagate_mode": "change_all",
},
)
self.assert_json_error(
result,
"Invalid message(s)",
)
def test_move_message_from_private_stream_message_access_checks(
self,
) -> None:
hamlet = self.example_user("hamlet")
user_profile = self.example_user("iago")
self.assertEqual(user_profile.role, UserProfile.ROLE_REALM_ADMINISTRATOR)
self.login("iago")
private_stream = self.make_stream(
"privatestream", invite_only=True, history_public_to_subscribers=False
)
self.subscribe(hamlet, "privatestream")
original_msg_id = self.send_stream_message(hamlet, "privatestream", topic_name="test123")
self.subscribe(user_profile, "privatestream")
new_msg_id = self.send_stream_message(user_profile, "privatestream", topic_name="test123")
# Now we unsub and hamlet sends a new message (we won't have access to it even after re-subbing!)
self.unsubscribe(user_profile, "privatestream")
new_inaccessible_msg_id = self.send_stream_message(
hamlet, "privatestream", topic_name="test123"
)
# Re-subscribe and send another message:
self.subscribe(user_profile, "privatestream")
newest_msg_id = self.send_stream_message(
user_profile, "privatestream", topic_name="test123"
)
verona = get_stream("Verona", user_profile.realm)
result = self.client_patch(
"/json/messages/" + str(new_msg_id),
{
"stream_id": verona.id,
"propagate_mode": "change_all",
},
)
self.assert_json_success(result)
self.assertEqual(Message.objects.get(id=new_msg_id).recipient_id, verona.recipient_id)
self.assertEqual(Message.objects.get(id=newest_msg_id).recipient_id, verona.recipient_id)
# The original message and the new, inaccessible message weren't moved,
# because user_profile doesn't have access to them.
self.assertEqual(
Message.objects.get(id=original_msg_id).recipient_id, private_stream.recipient_id
)
self.assertEqual(
Message.objects.get(id=new_inaccessible_msg_id).recipient_id,
private_stream.recipient_id,
)
def test_move_message_to_stream_change_later(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
result = self.client_patch(
f"/json/messages/{msg_id_later}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_later",
"send_notification_to_old_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 2)
self.assertEqual(messages[0].id, msg_id)
self.assertEqual(
messages[1].content,
f"2 messages were moved from this topic to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 3)
self.assertEqual(messages[0].id, msg_id_later)
self.assertEqual(
messages[2].content,
f"2 messages were moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_to_preexisting_topic_change_later(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
self.send_stream_message(
sender=self.example_user("iago"),
stream_name="new stream",
topic_name="test",
content="Always here",
)
result = self.client_patch(
f"/json/messages/{msg_id_later}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_later",
"send_notification_to_old_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 2)
self.assertEqual(messages[0].id, msg_id)
self.assertEqual(
messages[1].content,
f"2 messages were moved from this topic to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 4)
self.assertEqual(messages[0].id, msg_id_later)
self.assertEqual(
messages[3].content,
f"2 messages were moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_to_stream_change_later_all_moved(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_later",
"send_notification_to_old_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 1)
self.assertEqual(
messages[0].content,
f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 4)
self.assertEqual(messages[0].id, msg_id)
self.assertEqual(
messages[3].content,
f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_to_preexisting_topic_change_later_all_moved(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
self.send_stream_message(
sender=self.example_user("iago"),
stream_name="new stream",
topic_name="test",
content="Always here",
)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_later",
"send_notification_to_old_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 1)
self.assertEqual(
messages[0].content,
f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 5)
self.assertEqual(messages[0].id, msg_id)
self.assertEqual(
messages[4].content,
f"3 messages were moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_to_stream_change_one(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
result = self.client_patch(
"/json/messages/" + str(msg_id_later),
{
"stream_id": new_stream.id,
"propagate_mode": "change_one",
"send_notification_to_old_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 3)
self.assertEqual(messages[0].id, msg_id)
self.assertEqual(
messages[2].content,
f"A message was moved from this topic to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
message = {
"id": msg_id_later,
"stream_id": new_stream.id,
"display_recipient": new_stream.name,
"topic": "test",
}
moved_message_link = near_stream_message_url(messages[1].realm, message)
self.assert_length(messages, 2)
self.assertEqual(messages[0].id, msg_id_later)
self.assertEqual(
messages[1].content,
f"[A message]({moved_message_link}) was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_to_preexisting_topic_change_one(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
self.send_stream_message(
sender=self.example_user("iago"),
stream_name="new stream",
topic_name="test",
content="Always here",
)
result = self.client_patch(
"/json/messages/" + str(msg_id_later),
{
"stream_id": new_stream.id,
"propagate_mode": "change_one",
"send_notification_to_old_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 3)
self.assertEqual(messages[0].id, msg_id)
self.assertEqual(
messages[2].content,
f"A message was moved from this topic to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
message = {
"id": msg_id_later,
"stream_id": new_stream.id,
"display_recipient": new_stream.name,
"topic": "test",
}
moved_message_link = near_stream_message_url(messages[2].realm, message)
self.assert_length(messages, 3)
self.assertEqual(messages[0].id, msg_id_later)
self.assertEqual(
messages[2].content,
f"[A message]({moved_message_link}) was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_to_stream_change_all(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
result = self.client_patch(
"/json/messages/" + str(msg_id_later),
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"send_notification_to_old_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 1)
self.assertEqual(
messages[0].content,
f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 4)
self.assertEqual(messages[0].id, msg_id)
self.assertEqual(
messages[3].content,
f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_to_preexisting_topic_change_all(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
self.send_stream_message(
sender=self.example_user("iago"),
stream_name="new stream",
topic_name="test",
content="Always here",
)
result = self.client_patch(
"/json/messages/" + str(msg_id_later),
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"send_notification_to_old_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 1)
self.assertEqual(
messages[0].content,
f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 5)
self.assertEqual(messages[0].id, msg_id)
self.assertEqual(
messages[4].content,
f"3 messages were moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_move_message_between_streams_policy_setting(self) -> None:
othello = self.example_user("othello")
cordelia = self.example_user("cordelia")
realm = othello.realm
def check_move_message_according_to_permission(
username: str, expect_fail: bool = False
) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
username, "old_stream", "new_stream", "test"
)
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
},
)
if expect_fail:
self.assert_json_error(result, "You don't have permission to move this message")
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 3)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 0)
else:
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 0)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 4)
administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
full_members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.FULL_MEMBERS, realm=realm, is_system_group=True
)
members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
)
moderators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
)
nobody_system_group = NamedUserGroup.objects.get(
name=SystemGroups.NOBODY, realm=realm, is_system_group=True
)
# Check sending messages when nobody is allowed to move messages.
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
nobody_system_group,
acting_user=None,
)
check_move_message_according_to_permission("desdemona", expect_fail=True)
check_move_message_according_to_permission("iago", expect_fail=True)
# Check sending messages when only administrators are allowed.
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
administrators_system_group,
acting_user=None,
)
check_move_message_according_to_permission("shiva", expect_fail=True)
check_move_message_according_to_permission("iago")
# Check sending messages when only moderators are allowed.
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
moderators_system_group,
acting_user=None,
)
check_move_message_according_to_permission("cordelia", expect_fail=True)
check_move_message_according_to_permission("shiva")
# Check sending messages when full members are allowed.
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
full_members_system_group,
acting_user=None,
)
do_set_realm_property(othello.realm, "waiting_period_threshold", 100000, acting_user=None)
check_move_message_according_to_permission("othello", expect_fail=True)
do_set_realm_property(realm, "waiting_period_threshold", 0, acting_user=None)
check_move_message_according_to_permission("cordelia")
# Check sending messages when members are allowed.
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
members_system_group,
acting_user=None,
)
check_move_message_according_to_permission("polonius", expect_fail=True)
check_move_message_according_to_permission("cordelia")
# Test for checking setting for non-system user group.
user_group = check_add_user_group(
realm, "new_group", [othello, cordelia], acting_user=othello
)
do_change_realm_permission_group_setting(
realm, "can_move_messages_between_channels_group", user_group, acting_user=None
)
# Othello and Cordelia are in the allowed user group, so can move messages.
check_move_message_according_to_permission("othello")
check_move_message_according_to_permission("cordelia")
# Iago is not in the allowed user group, so cannot move messages.
check_move_message_according_to_permission("iago", expect_fail=True)
# Test for checking the setting for anonymous user group.
anonymous_user_group = self.create_or_update_anonymous_group_for_setting(
[othello],
[administrators_system_group],
)
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
anonymous_user_group,
acting_user=None,
)
# Othello is the direct member of the anonymous user group, so can move messages.
check_move_message_according_to_permission("othello")
# Iago is in the `administrators_system_group` subgroup, so can move messages.
check_move_message_according_to_permission("iago")
# Shiva is not in the anonymous user group, so cannot move messages.
check_move_message_according_to_permission("shiva", expect_fail=True)
def test_move_message_to_stream_time_limit(self) -> None:
shiva = self.example_user("shiva")
iago = self.example_user("iago")
cordelia = self.example_user("cordelia")
realm = cordelia.realm
test_stream_1 = self.make_stream("test_stream_1")
test_stream_2 = self.make_stream("test_stream_2")
self.subscribe(shiva, test_stream_1.name)
self.subscribe(iago, test_stream_1.name)
self.subscribe(cordelia, test_stream_1.name)
self.subscribe(shiva, test_stream_2.name)
self.subscribe(iago, test_stream_2.name)
self.subscribe(cordelia, test_stream_2.name)
msg_id = self.send_stream_message(
cordelia, test_stream_1.name, topic_name="test", content="First"
)
self.send_stream_message(cordelia, test_stream_1.name, topic_name="test", content="Second")
self.send_stream_message(cordelia, test_stream_1.name, topic_name="test", content="third")
members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
members_system_group,
acting_user=None,
)
def check_move_message_to_stream(
user: UserProfile,
old_stream: Stream,
new_stream: Stream,
*,
expect_error_message: str | None = None,
) -> None:
self.login_user(user)
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"send_notification_to_new_thread": orjson.dumps(False).decode(),
},
)
if expect_error_message is not None:
self.assert_json_error(result, expect_error_message)
messages = get_topic_messages(user, old_stream, "test")
self.assert_length(messages, 3)
messages = get_topic_messages(user, new_stream, "test")
self.assert_length(messages, 0)
else:
self.assert_json_success(result)
messages = get_topic_messages(user, old_stream, "test")
self.assert_length(messages, 0)
messages = get_topic_messages(user, new_stream, "test")
self.assert_length(messages, 3)
# non-admin and non-moderator users cannot move messages sent > 1 week ago
# including sender of the message.
message = Message.objects.get(id=msg_id)
message.date_sent -= timedelta(seconds=604900)
message.save()
check_move_message_to_stream(
cordelia,
test_stream_1,
test_stream_2,
expect_error_message="The time limit for editing this message's channel has passed",
)
# admins and moderators can move messages irrespective of time limit.
check_move_message_to_stream(shiva, test_stream_1, test_stream_2, expect_error_message=None)
check_move_message_to_stream(iago, test_stream_2, test_stream_1, expect_error_message=None)
# set the topic edit limit to two weeks
do_set_realm_property(
cordelia.realm,
"move_messages_between_streams_limit_seconds",
604800 * 2,
acting_user=None,
)
check_move_message_to_stream(
cordelia, test_stream_1, test_stream_2, expect_error_message=None
)
def test_move_message_to_stream_based_on_stream_post_policy(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"othello", "old_stream_1", "new_stream_1", "test"
)
realm = user_profile.realm
members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
members_system_group,
acting_user=None,
)
def check_move_message_to_stream(role: int, error_msg: str | None = None) -> None:
do_change_user_role(user_profile, role, acting_user=None)
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
},
)
if error_msg is not None:
self.assert_json_error(result, error_msg)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 3)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 0)
else:
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 0)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 4)
# Check when stream_post_policy is STREAM_POST_POLICY_ADMINS.
do_change_stream_post_policy(
new_stream, Stream.STREAM_POST_POLICY_ADMINS, acting_user=user_profile
)
error_msg = "Only organization administrators can send to this channel."
check_move_message_to_stream(UserProfile.ROLE_MODERATOR, error_msg)
check_move_message_to_stream(UserProfile.ROLE_REALM_ADMINISTRATOR)
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"othello", "old_stream_2", "new_stream_2", "test"
)
# Check when stream_post_policy is STREAM_POST_POLICY_MODERATORS.
do_change_stream_post_policy(
new_stream, Stream.STREAM_POST_POLICY_MODERATORS, acting_user=user_profile
)
error_msg = "Only organization administrators and moderators can send to this channel."
check_move_message_to_stream(UserProfile.ROLE_MEMBER, error_msg)
check_move_message_to_stream(UserProfile.ROLE_MODERATOR)
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"othello", "old_stream_3", "new_stream_3", "test"
)
# Check when stream_post_policy is STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS.
do_change_stream_post_policy(
new_stream, Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS, acting_user=user_profile
)
error_msg = "New members cannot send to this channel."
do_set_realm_property(
user_profile.realm, "waiting_period_threshold", 100000, acting_user=None
)
check_move_message_to_stream(UserProfile.ROLE_MEMBER, error_msg)
do_set_realm_property(user_profile.realm, "waiting_period_threshold", 0, acting_user=None)
check_move_message_to_stream(UserProfile.ROLE_MEMBER)
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"othello", "old_stream_4", "new_stream_4", "test"
)
# Check when stream_post_policy is STREAM_POST_POLICY_EVERYONE.
# In this case also, guest is not allowed as we do not allow guest to move
# messages between streams in any case, so stream_post_policy of new stream does
# not matter.
do_change_stream_post_policy(
new_stream, Stream.STREAM_POST_POLICY_EVERYONE, acting_user=user_profile
)
do_set_realm_property(
user_profile.realm, "waiting_period_threshold", 100000, acting_user=None
)
check_move_message_to_stream(
UserProfile.ROLE_GUEST, "You don't have permission to move this message"
)
check_move_message_to_stream(UserProfile.ROLE_MEMBER)
def test_move_message_to_stream_with_topic_editing_not_allowed(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"othello", "old_stream_1", "new_stream_1", "test"
)
realm = user_profile.realm
administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_topics_group",
administrators_system_group,
acting_user=None,
)
self.login("cordelia")
members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_channels_group",
members_system_group,
acting_user=None,
)
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"topic": "new topic",
},
)
self.assert_json_error(result, "You don't have permission to edit this message")
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 0)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 4)
def test_move_message_to_stream_and_topic(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
with self.assert_database_query_count(55), self.assert_memcached_count(14):
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"propagate_mode": "change_all",
"send_notification_to_old_thread": "true",
"stream_id": new_stream.id,
"topic": "new topic",
},
)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 1)
self.assertEqual(
messages[0].content,
f"This topic was moved to #**new stream>new topic** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "new topic")
self.assert_length(messages, 4)
self.assertEqual(
messages[3].content,
f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
self.assert_json_success(result)
def test_move_many_messages_to_stream_and_topic(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "first origin stream", "first destination stream", "first topic"
)
with queries_captured() as queries:
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"propagate_mode": "change_all",
"send_notification_to_old_thread": "true",
"stream_id": new_stream.id,
"topic": "first topic",
},
)
self.assert_json_success(result)
# Adding more messages should not increase the number of
# queries
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"iago", "second origin stream", "second destination stream", "second topic"
)
for i in range(1, 5):
self.send_stream_message(
user_profile,
"second origin stream",
topic_name="second topic",
content=f"Extra message {i}",
)
with self.assert_database_query_count(len(queries)):
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"propagate_mode": "change_all",
"send_notification_to_old_thread": "true",
"stream_id": new_stream.id,
"topic": "second topic",
},
)
self.assert_json_success(result)
def test_inaccessible_msg_after_stream_change(self) -> None:
"""Simulates the case where message is moved to a stream where user is not a subscribed"""
(user_profile, old_stream, new_stream, msg_id, msg_id_lt) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
# Iago is set up, above, to be sub'd to both streams
iago = self.example_user("iago")
# These are only sub'd to the old (public) stream
guest_user = self.example_user("polonius")
non_guest_user = self.example_user("hamlet")
self.subscribe(guest_user, old_stream.name)
self.subscribe(non_guest_user, old_stream.name)
msg_id_to_test_acesss = self.send_stream_message(
user_profile, old_stream.name, topic_name="test", content="fourth"
)
def check_user_access(
user: UserProfile,
*,
has_user_message: bool,
has_access: bool,
stream: Stream | None = None,
is_subscribed: bool | None = None,
) -> None:
self.assertEqual(
UserMessage.objects.filter(
message_id=msg_id_to_test_acesss, user_profile_id=user.id
).exists(),
has_user_message,
)
self.assertEqual(
has_message_access(
user,
Message.objects.get(id=msg_id_to_test_acesss),
has_user_message=lambda: has_user_message,
stream=stream,
is_subscribed=is_subscribed,
),
has_access,
)
check_user_access(iago, has_user_message=True, has_access=True)
check_user_access(guest_user, has_user_message=True, has_access=True, stream=old_stream)
check_user_access(non_guest_user, has_user_message=True, has_access=True)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"topic": "new topic",
},
)
self.assert_json_success(result)
check_user_access(iago, has_user_message=True, has_access=True)
check_user_access(guest_user, has_user_message=False, has_access=False)
check_user_access(non_guest_user, has_user_message=False, has_access=True)
# If the guest user were subscribed to the new stream,
# they'd have access; has_message_access does not validate
# the is_subscribed parameter.
check_user_access(
guest_user,
has_user_message=False,
has_access=True,
is_subscribed=True,
stream=new_stream,
)
check_user_access(guest_user, has_user_message=False, has_access=False, stream=new_stream)
with self.assertRaises(AssertionError):
# Raises assertion if you pass an invalid stream.
check_user_access(
guest_user, has_user_message=False, has_access=False, stream=old_stream
)
def test_no_notify_move_message_to_stream(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_lt) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"send_notification_to_old_thread": "false",
"send_notification_to_new_thread": "false",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 0)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 3)
def test_notify_new_thread_move_message_to_stream(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_lt) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"send_notification_to_old_thread": "false",
"send_notification_to_new_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 0)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 4)
self.assertEqual(
messages[3].content,
f"This topic was moved here from #**test move stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_notify_old_thread_move_message_to_stream(self) -> None:
(user_profile, old_stream, new_stream, msg_id, msg_id_lt) = self.prepare_move_topics(
"iago", "test move stream", "new stream", "test"
)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
"send_notification_to_old_thread": "true",
"send_notification_to_new_thread": "false",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, old_stream, "test")
self.assert_length(messages, 1)
self.assertEqual(
messages[0].content,
f"This topic was moved to #**new stream>test** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, new_stream, "test")
self.assert_length(messages, 3)
def test_notify_new_topics_after_message_move(self) -> None:
user_profile = self.example_user("iago")
self.login("iago")
stream = self.make_stream("public stream")
self.subscribe(user_profile, stream.name)
msg_id = self.send_stream_message(
user_profile, stream.name, topic_name="test", content="First"
)
self.send_stream_message(user_profile, stream.name, topic_name="test", content="Second")
self.send_stream_message(user_profile, stream.name, topic_name="test", content="Third")
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"topic": "edited",
"propagate_mode": "change_one",
"send_notification_to_old_thread": "false",
"send_notification_to_new_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, stream, "test")
self.assert_length(messages, 2)
self.assertEqual(messages[0].content, "Second")
self.assertEqual(messages[1].content, "Third")
messages = get_topic_messages(user_profile, stream, "edited")
message = {
"id": msg_id,
"stream_id": stream.id,
"display_recipient": stream.name,
"topic": "edited",
}
moved_message_link = near_stream_message_url(messages[1].realm, message)
self.assert_length(messages, 2)
self.assertEqual(messages[0].content, "First")
self.assertEqual(
messages[1].content,
f"[A message]({moved_message_link}) was moved here from #**public stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_notify_both_topics_after_message_move(self) -> None:
user_profile = self.example_user("iago")
self.login("iago")
stream = self.make_stream("public stream")
self.subscribe(user_profile, stream.name)
msg_id = self.send_stream_message(
user_profile, stream.name, topic_name="test", content="First"
)
self.send_stream_message(user_profile, stream.name, topic_name="test", content="Second")
self.send_stream_message(user_profile, stream.name, topic_name="test", content="Third")
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"topic": "edited",
"propagate_mode": "change_one",
"send_notification_to_old_thread": "true",
"send_notification_to_new_thread": "true",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, stream, "test")
self.assert_length(messages, 3)
self.assertEqual(messages[0].content, "Second")
self.assertEqual(messages[1].content, "Third")
self.assertEqual(
messages[2].content,
f"A message was moved from this topic to #**public stream>edited** by @_**Iago|{user_profile.id}**.",
)
messages = get_topic_messages(user_profile, stream, "edited")
message = {
"id": msg_id,
"stream_id": stream.id,
"display_recipient": stream.name,
"topic": "edited",
}
moved_message_link = near_stream_message_url(messages[0].realm, message)
self.assert_length(messages, 2)
self.assertEqual(messages[0].content, "First")
self.assertEqual(
messages[1].content,
f"[A message]({moved_message_link}) was moved here from #**public stream>test** by @_**Iago|{user_profile.id}**.",
)
def test_notify_resolve_topic_and_move_stream(self) -> None:
(
user_profile,
first_stream,
second_stream,
msg_id,
msg_id_later,
) = self.prepare_move_topics("iago", "first stream", "second stream", "test")
# 'prepare_move_topics' sends 3 messages in the first_stream
messages = get_topic_messages(user_profile, first_stream, "test")
self.assert_length(messages, 3)
# Test resolving a topic (test -> ✔ test) while changing stream (first_stream -> second_stream)
new_topic_name = "✔ test"
new_stream = second_stream
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": new_stream.id,
"topic": new_topic_name,
"propagate_mode": "change_all",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, new_stream, new_topic_name)
self.assert_length(messages, 4)
self.assertEqual(
messages[3].content,
f"This topic was moved here from #**{first_stream.name}>test** by @_**{user_profile.full_name}|{user_profile.id}**.",
)
# Test unresolving a topic (✔ test -> test) while changing stream (second_stream -> first_stream)
new_topic_name = "test"
new_stream = first_stream
result = self.client_patch(
"/json/messages/" + str(msg_id),
{
"stream_id": new_stream.id,
"topic": new_topic_name,
"propagate_mode": "change_all",
},
)
self.assert_json_success(result)
messages = get_topic_messages(user_profile, new_stream, new_topic_name)
self.assert_length(messages, 5)
self.assertEqual(
messages[4].content,
f"This topic was moved here from #**{second_stream.name}>✔ test** by @_**{user_profile.full_name}|{user_profile.id}**.",
)
def parameterized_test_move_message_involving_private_stream(
self,
from_invite_only: bool,
history_public_to_subscribers: bool,
user_messages_created: bool,
to_invite_only: bool = True,
propagate_mode: str = "change_all",
) -> None:
admin_user = self.example_user("iago")
user_losing_access = self.example_user("cordelia")
user_gaining_access = self.example_user("hamlet")
self.login("iago")
old_stream = self.make_stream("test move stream", invite_only=from_invite_only)
new_stream = self.make_stream(
"new stream",
invite_only=to_invite_only,
history_public_to_subscribers=history_public_to_subscribers,
)
self.subscribe(admin_user, old_stream.name)
self.subscribe(user_losing_access, old_stream.name)
self.subscribe(admin_user, new_stream.name)
self.subscribe(user_gaining_access, new_stream.name)
msg_id = self.send_stream_message(
admin_user, old_stream.name, topic_name="test", content="First"
)
self.send_stream_message(admin_user, old_stream.name, topic_name="test", content="Second")
self.assert_length(get_topic_messages(admin_user, old_stream, "test"), 2)
self.assert_length(get_topic_messages(admin_user, new_stream, "test"), 0)
self.assert_has_usermessage(user_losing_access.id, msg_id)
self.assert_lacks_usermessage(user_gaining_access.id, msg_id)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": propagate_mode,
},
)
self.assert_json_success(result)
# We gain one more message than we moved because of a notification-bot message.
if propagate_mode == "change_one":
self.assert_length(get_topic_messages(admin_user, old_stream, "test"), 1)
self.assert_length(get_topic_messages(admin_user, new_stream, "test"), 2)
else:
self.assert_length(get_topic_messages(admin_user, old_stream, "test"), 0)
self.assert_length(get_topic_messages(admin_user, new_stream, "test"), 3)
self.assert_lacks_usermessage(user_losing_access.id, msg_id)
# When the history is shared, UserMessage is not created for the user but the user
# can see the message.
if user_messages_created:
self.assert_has_usermessage(user_gaining_access.id, msg_id)
else:
self.assert_lacks_usermessage(user_gaining_access.id, msg_id)
def test_move_message_from_public_to_private_stream_not_shared_history(self) -> None:
self.parameterized_test_move_message_involving_private_stream(
from_invite_only=False,
history_public_to_subscribers=False,
user_messages_created=True,
)
def test_move_message_from_public_to_private_stream_shared_history(self) -> None:
self.parameterized_test_move_message_involving_private_stream(
from_invite_only=False,
history_public_to_subscribers=True,
user_messages_created=False,
)
def test_move_one_message_from_public_to_private_stream_not_shared_history(self) -> None:
self.parameterized_test_move_message_involving_private_stream(
from_invite_only=False,
history_public_to_subscribers=False,
user_messages_created=True,
propagate_mode="change_one",
)
def test_move_one_message_from_public_to_private_stream_shared_history(self) -> None:
self.parameterized_test_move_message_involving_private_stream(
from_invite_only=False,
history_public_to_subscribers=True,
user_messages_created=False,
propagate_mode="change_one",
)
def test_move_message_from_private_to_private_stream_not_shared_history(self) -> None:
self.parameterized_test_move_message_involving_private_stream(
from_invite_only=True,
history_public_to_subscribers=False,
user_messages_created=True,
)
def test_move_message_from_private_to_private_stream_shared_history(self) -> None:
self.parameterized_test_move_message_involving_private_stream(
from_invite_only=True,
history_public_to_subscribers=True,
user_messages_created=False,
)
def test_move_message_from_private_to_public(self) -> None:
self.parameterized_test_move_message_involving_private_stream(
from_invite_only=True,
history_public_to_subscribers=True,
user_messages_created=False,
to_invite_only=False,
)
def test_move_message_from_private_to_private_with_old_member(self) -> None:
admin_user = self.example_user("iago")
user_losing_access = self.example_user("cordelia")
self.login("iago")
old_stream = self.make_stream("test move stream", invite_only=True)
new_stream = self.make_stream("new stream", invite_only=True)
self.subscribe(admin_user, old_stream.name)
self.subscribe(user_losing_access, old_stream.name)
self.subscribe(admin_user, new_stream.name)
msg_id = self.send_stream_message(
admin_user, old_stream.name, topic_name="test", content="First"
)
self.assert_has_usermessage(user_losing_access.id, msg_id)
self.assertEqual(
has_message_access(
user_losing_access,
Message.objects.get(id=msg_id),
has_user_message=lambda: True,
stream=old_stream,
is_subscribed=True,
),
True,
)
# Unsubscribe the user_losing_access; they will keep their
# UserMessage row, but lose access to the message; their
# Subscription row remains, but is inactive.
self.unsubscribe(user_losing_access, old_stream.name)
self.assert_has_usermessage(user_losing_access.id, msg_id)
self.assertEqual(
has_message_access(
user_losing_access,
Message.objects.get(id=msg_id),
has_user_message=lambda: True,
stream=old_stream,
is_subscribed=False,
),
False,
)
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
},
)
self.assert_json_success(result)
# They should no longer have a UserMessage row, so we preserve
# the invariant that users without subscriptions never have
# UserMessage rows -- and definitely do not have access.
self.assert_lacks_usermessage(user_losing_access.id, msg_id)
self.assertEqual(
has_message_access(
user_losing_access,
Message.objects.get(id=msg_id),
has_user_message=lambda: True,
stream=new_stream,
is_subscribed=False,
),
False,
)
def test_move_message_to_private_hidden_history_with_old_member(self) -> None:
admin_user = self.example_user("iago")
user = self.example_user("cordelia")
self.login("iago")
old_stream = self.make_stream("test move stream", invite_only=True)
new_stream = self.make_stream(
"new stream", invite_only=True, history_public_to_subscribers=False
)
self.subscribe(admin_user, old_stream.name)
self.subscribe(user, old_stream.name)
self.subscribe(admin_user, new_stream.name)
self.subscribe(user, new_stream.name)
# Cordelia is subscribed to both streams when this first
# message is sent
first_msg_id = self.send_stream_message(
admin_user, old_stream.name, topic_name="test", content="First"
)
self.assert_has_usermessage(user.id, first_msg_id)
self.assertEqual(
has_message_access(
user,
Message.objects.get(id=first_msg_id),
has_user_message=lambda: True,
stream=old_stream,
is_subscribed=True,
),
True,
)
# Unsubscribe the user; they will keep their UserMessage row,
# but lose access to the message; their Subscription row
# remains, but is inactive.
self.unsubscribe(user, old_stream.name)
self.assert_has_usermessage(user.id, first_msg_id)
self.assertEqual(
has_message_access(
user,
Message.objects.get(id=first_msg_id),
has_user_message=lambda: True,
stream=old_stream,
is_subscribed=False,
),
False,
)
# The user is no longer subscribed, so does not have a
# UserMessage row, or access
second_msg_id = self.send_stream_message(
admin_user, old_stream.name, topic_name="test", content="Second"
)
self.assert_lacks_usermessage(user.id, second_msg_id)
self.assertEqual(
has_message_access(
user,
Message.objects.get(id=second_msg_id),
has_user_message=lambda: False,
stream=old_stream,
is_subscribed=False,
),
False,
)
# Move both messages
result = self.client_patch(
f"/json/messages/{first_msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
},
)
self.assert_json_success(result)
# They should have a UserMessage row for both messages, and
# now have access to both -- being in the stream when the
# message is moved in is always sufficient to grant access.
self.assert_has_usermessage(user.id, first_msg_id)
self.assert_has_usermessage(user.id, second_msg_id)
self.assertEqual(
has_message_access(
user,
Message.objects.get(id=first_msg_id),
has_user_message=lambda: True,
stream=new_stream,
is_subscribed=True,
),
True,
)
self.assertEqual(
has_message_access(
user,
Message.objects.get(id=second_msg_id),
has_user_message=lambda: True,
stream=new_stream,
is_subscribed=True,
),
True,
)
def test_move_message_to_private_hidden_history_with_new_member(self) -> None:
admin_user = self.example_user("iago")
user = self.example_user("cordelia")
self.login("iago")
old_stream = self.make_stream(
"test move stream", invite_only=True, history_public_to_subscribers=False
)
new_stream = self.make_stream(
"new stream", invite_only=True, history_public_to_subscribers=False
)
self.subscribe(admin_user, old_stream.name)
self.subscribe(admin_user, new_stream.name)
# Cordelia is subscribed to neither stream when this message is sent
msg_id = self.send_stream_message(
admin_user, old_stream.name, topic_name="test", content="First"
)
self.assert_lacks_usermessage(user.id, msg_id)
self.assertEqual(
has_message_access(
user,
Message.objects.get(id=msg_id),
has_user_message=lambda: False,
stream=old_stream,
is_subscribed=False,
),
False,
)
# Subscribe to both streams. Because the streams do not have
# shared history, Cordelia does not get a UserMessage row, or
# access.
self.subscribe(user, old_stream.name)
self.subscribe(user, new_stream.name)
self.assert_lacks_usermessage(user.id, msg_id)
self.assertEqual(
has_message_access(
user,
Message.objects.get(id=msg_id),
has_user_message=lambda: False,
stream=old_stream,
is_subscribed=False,
),
False,
)
# Move the message to the other private-history stream
result = self.client_patch(
f"/json/messages/{msg_id}",
{
"stream_id": new_stream.id,
"propagate_mode": "change_all",
},
)
self.assert_json_success(result)
# They should now have a UserMessage row, and now have access
# -- being in the stream when the message is moved in is
# always sufficient to grant access.
self.assert_has_usermessage(user.id, msg_id)
self.assertEqual(
has_message_access(
user,
Message.objects.get(id=msg_id),
has_user_message=lambda: True,
stream=new_stream,
is_subscribed=True,
),
True,
)