mirror of https://github.com/zulip/zulip.git
message: Don't allow moving messages that have passed the time limit.
We previously allowed moving messages that have passed the time limit using "change_all" value for "propagate_mode" parameter. This commit changes the behavior to not allow moving messages (both stream and topic edit) that have passed the time limit for non-admin and non-moderator users.
This commit is contained in:
parent
440f9e397a
commit
bd7f728796
|
@ -24,6 +24,10 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
* [`PATCH /messages/{message_id}`](/api/update-message): Topic editing
|
* [`PATCH /messages/{message_id}`](/api/update-message): Topic editing
|
||||||
restrictions now apply to messages without a topic as well.
|
restrictions now apply to messages without a topic as well.
|
||||||
|
* [`PATCH /messages/{message_id}`](/api/update-message): The endpoint
|
||||||
|
now returns an error when users, other than organization administrators
|
||||||
|
and moderators, try to move messages that have passed the time limit
|
||||||
|
using `change_all` value for `propagate_mode` parameter.
|
||||||
|
|
||||||
**Feature level 171**:
|
**Feature level 171**:
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 171
|
API_FEATURE_LEVEL = 172
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||||
# only when going from an old version of the code to a newer version. Bump
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -21,7 +21,7 @@ from zerver.actions.message_send import (
|
||||||
)
|
)
|
||||||
from zerver.actions.uploads import check_attachment_reference_change
|
from zerver.actions.uploads import check_attachment_reference_change
|
||||||
from zerver.actions.user_topics import bulk_do_set_user_topic_visibility_policy
|
from zerver.actions.user_topics import bulk_do_set_user_topic_visibility_policy
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import JsonableError, MessageMoveError
|
||||||
from zerver.lib.markdown import MessageRenderingResult, topic_links
|
from zerver.lib.markdown import MessageRenderingResult, topic_links
|
||||||
from zerver.lib.markdown import version as markdown_version
|
from zerver.lib.markdown import version as markdown_version
|
||||||
from zerver.lib.mention import MentionBackend, MentionData, silent_mention_syntax_for_user
|
from zerver.lib.mention import MentionBackend, MentionData, silent_mention_syntax_for_user
|
||||||
|
@ -36,7 +36,11 @@ from zerver.lib.message import (
|
||||||
from zerver.lib.queue import queue_json_publish
|
from zerver.lib.queue import queue_json_publish
|
||||||
from zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id
|
from zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id
|
||||||
from zerver.lib.stream_topic import StreamTopicTarget
|
from zerver.lib.stream_topic import StreamTopicTarget
|
||||||
from zerver.lib.streams import access_stream_by_id, check_stream_access_based_on_stream_post_policy
|
from zerver.lib.streams import (
|
||||||
|
access_stream_by_id,
|
||||||
|
can_access_stream_history,
|
||||||
|
check_stream_access_based_on_stream_post_policy,
|
||||||
|
)
|
||||||
from zerver.lib.string_validation import check_stream_topic
|
from zerver.lib.string_validation import check_stream_topic
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.topic import (
|
from zerver.lib.topic import (
|
||||||
|
@ -941,6 +945,98 @@ def do_update_message(
|
||||||
return len(changed_messages)
|
return len(changed_messages)
|
||||||
|
|
||||||
|
|
||||||
|
def check_time_limit_for_change_all_propagate_mode(
|
||||||
|
message: Message,
|
||||||
|
user_profile: UserProfile,
|
||||||
|
topic_name: Optional[str] = None,
|
||||||
|
stream_id: Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
|
realm = user_profile.realm
|
||||||
|
message_move_limit_buffer = 20
|
||||||
|
|
||||||
|
topic_edit_deadline_seconds = None
|
||||||
|
if topic_name is not None and realm.move_messages_within_stream_limit_seconds is not None:
|
||||||
|
# We set topic_edit_deadline_seconds only if topic is actually
|
||||||
|
# changed and there is some time limit to edit topic.
|
||||||
|
topic_edit_deadline_seconds = (
|
||||||
|
realm.move_messages_within_stream_limit_seconds + message_move_limit_buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
stream_edit_deadline_seconds = None
|
||||||
|
if stream_id is not None and realm.move_messages_between_streams_limit_seconds is not None:
|
||||||
|
# We set stream_edit_deadline_seconds only if stream is
|
||||||
|
# actually changed and there is some time limit to edit
|
||||||
|
# stream.
|
||||||
|
stream_edit_deadline_seconds = (
|
||||||
|
realm.move_messages_between_streams_limit_seconds + message_move_limit_buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate whichever of the applicable topic and stream moving
|
||||||
|
# limits is stricter, and use that.
|
||||||
|
if topic_edit_deadline_seconds is not None and stream_edit_deadline_seconds is not None:
|
||||||
|
# When both stream and topic are changed, we consider the
|
||||||
|
# minimum of the two limits to make sure that we raise the
|
||||||
|
# error even when user cannot change one of topic or stream.
|
||||||
|
message_move_deadline_seconds = min(
|
||||||
|
topic_edit_deadline_seconds, stream_edit_deadline_seconds
|
||||||
|
)
|
||||||
|
elif topic_edit_deadline_seconds is not None:
|
||||||
|
message_move_deadline_seconds = topic_edit_deadline_seconds
|
||||||
|
elif stream_edit_deadline_seconds is not None:
|
||||||
|
message_move_deadline_seconds = stream_edit_deadline_seconds
|
||||||
|
else:
|
||||||
|
# There is no applicable time limit for this move request, so
|
||||||
|
# approve it.
|
||||||
|
return
|
||||||
|
|
||||||
|
stream = get_stream_by_id_in_realm(message.recipient.type_id, realm)
|
||||||
|
|
||||||
|
if not can_access_stream_history(user_profile, stream):
|
||||||
|
# If the user doesn't have full access to the stream's
|
||||||
|
# history, check if the user can move the entire portion that
|
||||||
|
# they do have access to.
|
||||||
|
accessible_messages_in_topic = UserMessage.objects.filter(
|
||||||
|
user_profile=user_profile,
|
||||||
|
message__recipient_id=message.recipient_id,
|
||||||
|
message__subject__iexact=message.topic_name(),
|
||||||
|
).values_list("message_id", flat=True)
|
||||||
|
messages_allowed_to_move: List[int] = list(
|
||||||
|
Message.objects.filter(
|
||||||
|
id__in=accessible_messages_in_topic,
|
||||||
|
date_sent__gt=timezone_now()
|
||||||
|
- datetime.timedelta(seconds=message_move_deadline_seconds),
|
||||||
|
)
|
||||||
|
.order_by("date_sent")
|
||||||
|
.values_list("id", flat=True)
|
||||||
|
)
|
||||||
|
total_messages_requested_to_move = len(accessible_messages_in_topic)
|
||||||
|
else:
|
||||||
|
all_messages_in_topic = (
|
||||||
|
messages_for_topic(message.recipient_id, message.topic_name())
|
||||||
|
.order_by("id")
|
||||||
|
.values_list("id", "date_sent")
|
||||||
|
)
|
||||||
|
oldest_allowed_message_date = timezone_now() - datetime.timedelta(
|
||||||
|
seconds=message_move_deadline_seconds
|
||||||
|
)
|
||||||
|
messages_allowed_to_move = [
|
||||||
|
message[0]
|
||||||
|
for message in all_messages_in_topic
|
||||||
|
if message[1] > oldest_allowed_message_date
|
||||||
|
]
|
||||||
|
total_messages_requested_to_move = len(all_messages_in_topic)
|
||||||
|
|
||||||
|
if total_messages_requested_to_move == len(messages_allowed_to_move):
|
||||||
|
# We return if all messages are allowed to move.
|
||||||
|
return
|
||||||
|
|
||||||
|
raise MessageMoveError(
|
||||||
|
first_message_id_allowed_to_move=messages_allowed_to_move[0],
|
||||||
|
total_messages_in_topic=total_messages_requested_to_move,
|
||||||
|
total_messages_allowed_to_move=len(messages_allowed_to_move),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_update_message(
|
def check_update_message(
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
message_id: int,
|
message_id: int,
|
||||||
|
@ -1075,6 +1171,14 @@ def check_update_message(
|
||||||
_("The time limit for editing this message's stream has passed")
|
_("The time limit for editing this message's stream has passed")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
propagate_mode == "change_all"
|
||||||
|
and not user_profile.is_realm_admin
|
||||||
|
and not user_profile.is_moderator
|
||||||
|
and (topic_name is not None or stream_id is not None)
|
||||||
|
):
|
||||||
|
check_time_limit_for_change_all_propagate_mode(message, user_profile, topic_name, stream_id)
|
||||||
|
|
||||||
number_changed = do_update_message(
|
number_changed = do_update_message(
|
||||||
user_profile,
|
user_profile,
|
||||||
message,
|
message,
|
||||||
|
|
|
@ -39,6 +39,7 @@ class ErrorCode(Enum):
|
||||||
AUTHENTICATION_FAILED = auto()
|
AUTHENTICATION_FAILED = auto()
|
||||||
UNAUTHORIZED = auto()
|
UNAUTHORIZED = auto()
|
||||||
REQUEST_TIMEOUT = auto()
|
REQUEST_TIMEOUT = auto()
|
||||||
|
MOVE_MESSAGES_TIME_LIMIT_EXCEEDED = auto()
|
||||||
|
|
||||||
|
|
||||||
class JsonableError(Exception):
|
class JsonableError(Exception):
|
||||||
|
@ -474,3 +475,28 @@ class ValidationFailureError(JsonableError):
|
||||||
def __init__(self, error: ValidationError) -> None:
|
def __init__(self, error: ValidationError) -> None:
|
||||||
super().__init__(error.messages[0])
|
super().__init__(error.messages[0])
|
||||||
self.errors = error.message_dict
|
self.errors = error.message_dict
|
||||||
|
|
||||||
|
|
||||||
|
class MessageMoveError(JsonableError):
|
||||||
|
code = ErrorCode.MOVE_MESSAGES_TIME_LIMIT_EXCEEDED
|
||||||
|
data_fields = [
|
||||||
|
"first_message_id_allowed_to_move",
|
||||||
|
"total_messages_in_topic",
|
||||||
|
"total_messages_allowed_to_move",
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
first_message_id_allowed_to_move: int,
|
||||||
|
total_messages_in_topic: int,
|
||||||
|
total_messages_allowed_to_move: int,
|
||||||
|
) -> None:
|
||||||
|
self.first_message_id_allowed_to_move = first_message_id_allowed_to_move
|
||||||
|
self.total_messages_in_topic = total_messages_in_topic
|
||||||
|
self.total_messages_allowed_to_move = total_messages_allowed_to_move
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def msg_format() -> str:
|
||||||
|
return _(
|
||||||
|
"You only have permission to move the {total_messages_allowed_to_move}/{total_messages_in_topic} most recent messages in this topic."
|
||||||
|
)
|
||||||
|
|
|
@ -6525,6 +6525,12 @@ paths:
|
||||||
permissions. Now topic editing restrictions apply to messages without a
|
permissions. Now topic editing restrictions apply to messages without a
|
||||||
topic as well.
|
topic as well.
|
||||||
|
|
||||||
|
Before Zulip 7.0 (feature level 172), users were allowed to move messages
|
||||||
|
that had passed the time limit by using `change_all` value for `propagate_mode`
|
||||||
|
parameter. Now, the server returns an error if users, other than organization
|
||||||
|
administrators and moderators, try to move messages that have passed the time
|
||||||
|
limit.
|
||||||
|
|
||||||
[config-message-editing]: /help/restrict-message-editing-and-deletion
|
[config-message-editing]: /help/restrict-message-editing-and-deletion
|
||||||
x-curl-examples-parameters:
|
x-curl-examples-parameters:
|
||||||
oneOf:
|
oneOf:
|
||||||
|
@ -6627,7 +6633,8 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
allOf:
|
oneOf:
|
||||||
|
- allOf:
|
||||||
- $ref: "#/components/schemas/CodedError"
|
- $ref: "#/components/schemas/CodedError"
|
||||||
- properties:
|
- properties:
|
||||||
msg:
|
msg:
|
||||||
|
@ -6646,6 +6653,59 @@ paths:
|
||||||
description: |
|
description: |
|
||||||
A typical JSON response for when one doesn't have the permission to
|
A typical JSON response for when one doesn't have the permission to
|
||||||
edit a particular message:
|
edit a particular message:
|
||||||
|
- allOf:
|
||||||
|
- $ref: "#/components/schemas/CodedError"
|
||||||
|
- example:
|
||||||
|
{
|
||||||
|
"code": "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED",
|
||||||
|
"first_message_id_allowed_to_move": 123,
|
||||||
|
"msg": "You only have permission to move the 2/5 most recent messages in this topic.",
|
||||||
|
"result": "error",
|
||||||
|
"total_messages_allowed_to_move": 2,
|
||||||
|
"total_messages_in_topic": 5,
|
||||||
|
}
|
||||||
|
description: |
|
||||||
|
A special failed JSON response for when the user has permission to move
|
||||||
|
the target message, but asked to move all messages in a topic
|
||||||
|
(`change_all`) and the user does not have permission to move the entire
|
||||||
|
topic.
|
||||||
|
|
||||||
|
This happens when the topic contains some messages that are older than an
|
||||||
|
applicable time limit for the requested topic move
|
||||||
|
(`move_messages_within_stream_limit_seconds` and/or
|
||||||
|
`move_messages_between_streams_limit_seconds`).
|
||||||
|
|
||||||
|
This response contains data on which portion of this topic the user has
|
||||||
|
permission to move; `first_message_id_allowed_to_move` is the oldest
|
||||||
|
message ID in this topic that the user has permission to move; the user
|
||||||
|
has permission to move the most recent `total_messages_allowed_to_move`
|
||||||
|
messages, but `total_messages_in_topic` exist in the topic.
|
||||||
|
|
||||||
|
A client is recommended to either just present the error to the user, or
|
||||||
|
if `first_message_id_allowed_to_move` is not `null`, prompt the user with
|
||||||
|
this information. Clients should support this error code with
|
||||||
|
`first_message_id_allowed_to_move=null` for forwards compatibility
|
||||||
|
with future server versions that may use this error code in other
|
||||||
|
propagation modes where it is possible that the user does not have
|
||||||
|
permission to move any messages, or where the server did not calculate the
|
||||||
|
other fields.
|
||||||
|
|
||||||
|
If clients choose to present a prompt for this error code, they should
|
||||||
|
recommend the option of cancelling and (manually) asking a moderator to
|
||||||
|
move the entire topic, since that's often a better experience than
|
||||||
|
partially moving a topic. This API supports a client that wishes to allow
|
||||||
|
the user to repeat the request with a `change_later` propagation mode and
|
||||||
|
a target message ID of `first_message_id_allowed_to_move`, if the user
|
||||||
|
desires to move only the portion of the topic that they can.
|
||||||
|
|
||||||
|
Note that in a stream with protected history, the Zulip security model
|
||||||
|
requires that the above calculations only include messages the acting user
|
||||||
|
has access to. So in the rare case of a user attempting to move a topic
|
||||||
|
that started before the user joined a private stream with protected
|
||||||
|
history, this API endpoint might move only portion of a topic that they
|
||||||
|
have access to, without this error or any confirmation dialog.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 7.0 (feature level 172).
|
||||||
delete:
|
delete:
|
||||||
operationId: delete-message
|
operationId: delete-message
|
||||||
summary: Delete a message
|
summary: Delete a message
|
||||||
|
|
|
@ -1354,9 +1354,9 @@ class EditMessageTest(EditMessageTestCase):
|
||||||
users_to_be_notified_via_muted_topics_event.append(user_topic.user_profile_id)
|
users_to_be_notified_via_muted_topics_event.append(user_topic.user_profile_id)
|
||||||
|
|
||||||
change_all_topic_name = "Topic 1 edited"
|
change_all_topic_name = "Topic 1 edited"
|
||||||
# This code path adds 17 (10 + 4/visibility_policy + 1/user + 1) to
|
# This code path adds 19 (12 + 4/visibility_policy + 1/user + 1) to
|
||||||
# the number of database queries for moving a topic.
|
# the number of database queries for moving a topic.
|
||||||
with self.assert_database_query_count(17):
|
with self.assert_database_query_count(19):
|
||||||
check_update_message(
|
check_update_message(
|
||||||
user_profile=hamlet,
|
user_profile=hamlet,
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
|
@ -1647,7 +1647,7 @@ class EditMessageTest(EditMessageTestCase):
|
||||||
users_to_be_notified_via_muted_topics_event.append(user_topic.user_profile_id)
|
users_to_be_notified_via_muted_topics_event.append(user_topic.user_profile_id)
|
||||||
|
|
||||||
change_all_topic_name = "Topic 1 edited"
|
change_all_topic_name = "Topic 1 edited"
|
||||||
with self.assert_database_query_count(22):
|
with self.assert_database_query_count(24):
|
||||||
check_update_message(
|
check_update_message(
|
||||||
user_profile=hamlet,
|
user_profile=hamlet,
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
|
@ -1966,6 +1966,215 @@ class EditMessageTest(EditMessageTestCase):
|
||||||
self.check_topic(id3, topic_name="topiC1")
|
self.check_topic(id3, topic_name="topiC1")
|
||||||
self.check_topic(id4, topic_name="edited")
|
self.check_topic(id4, topic_name="edited")
|
||||||
|
|
||||||
|
def test_change_all_propagate_mode_for_moving_old_messages(self) -> None:
|
||||||
|
user_profile = self.example_user("hamlet")
|
||||||
|
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")
|
||||||
|
|
||||||
|
do_set_realm_property(
|
||||||
|
user_profile.realm,
|
||||||
|
"move_messages_between_streams_policy",
|
||||||
|
Realm.POLICY_MEMBERS_ONLY,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
message = Message.objects.get(id=id1)
|
||||||
|
message.date_sent = message.date_sent - datetime.timedelta(days=10)
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
message = Message.objects.get(id=id2)
|
||||||
|
message.date_sent = message.date_sent - datetime.timedelta(days=8)
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
message = Message.objects.get(id=id3)
|
||||||
|
message.date_sent = message.date_sent - datetime.timedelta(days=5)
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
verona = get_stream("Verona", user_profile.realm)
|
||||||
|
denmark = get_stream("Denmark", user_profile.realm)
|
||||||
|
old_topic = "topic1"
|
||||||
|
old_stream = denmark
|
||||||
|
|
||||||
|
def test_moving_all_topic_messages(
|
||||||
|
new_topic: Optional[str] = None, new_stream: Optional[Stream] = None
|
||||||
|
) -> None:
|
||||||
|
self.login("hamlet")
|
||||||
|
params_dict: Dict[str, Union[str, int]] = {
|
||||||
|
"propagate_mode": "change_all",
|
||||||
|
"send_notification_to_new_thread": "false",
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_topic is not None:
|
||||||
|
params_dict["topic"] = new_topic
|
||||||
|
else:
|
||||||
|
new_topic = old_topic
|
||||||
|
|
||||||
|
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)
|
||||||
|
self.assert_length(messages, 5)
|
||||||
|
|
||||||
|
# Check message count in new topic and/or stream.
|
||||||
|
messages = get_topic_messages(user_profile, new_stream, new_topic)
|
||||||
|
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)
|
||||||
|
self.assert_length(messages, 2)
|
||||||
|
|
||||||
|
# Check message count in new topic and/or stream.
|
||||||
|
messages = get_topic_messages(user_profile, new_stream, new_topic)
|
||||||
|
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,
|
||||||
|
"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)
|
||||||
|
self.assert_length(messages, 0)
|
||||||
|
|
||||||
|
# Check message count in new topic and/or stream.
|
||||||
|
messages = get_topic_messages(user_profile, new_stream, new_topic)
|
||||||
|
self.assert_length(messages, 5)
|
||||||
|
|
||||||
|
# Test only topic editing case.
|
||||||
|
test_moving_all_topic_messages(new_topic="topic edited")
|
||||||
|
|
||||||
|
# Move these messages to the original topic to test the next case.
|
||||||
|
self.client_patch(
|
||||||
|
f"/json/messages/{id4}",
|
||||||
|
{
|
||||||
|
"topic": old_topic,
|
||||||
|
"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="edited", new_stream=verona)
|
||||||
|
|
||||||
|
def test_change_all_propagate_mode_for_moving_from_stream_with_restricted_history(self) -> None:
|
||||||
|
self.make_stream("privatestream", invite_only=True, history_public_to_subscribers=False)
|
||||||
|
iago = self.example_user("iago")
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
self.subscribe(iago, "privatestream")
|
||||||
|
self.subscribe(cordelia, "privatestream")
|
||||||
|
id1 = self.send_stream_message(iago, "privatestream", topic_name="topic1")
|
||||||
|
id2 = self.send_stream_message(iago, "privatestream", topic_name="topic1")
|
||||||
|
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
self.subscribe(hamlet, "privatestream")
|
||||||
|
id3 = self.send_stream_message(iago, "privatestream", topic_name="topic1")
|
||||||
|
id4 = self.send_stream_message(hamlet, "privatestream", topic_name="topic1")
|
||||||
|
self.send_stream_message(hamlet, "privatestream", topic_name="topic1")
|
||||||
|
|
||||||
|
message = Message.objects.get(id=id1)
|
||||||
|
message.date_sent = message.date_sent - datetime.timedelta(days=10)
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
message = Message.objects.get(id=id2)
|
||||||
|
message.date_sent = message.date_sent - datetime.timedelta(days=9)
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
message = Message.objects.get(id=id3)
|
||||||
|
message.date_sent = message.date_sent - datetime.timedelta(days=8)
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
message = Message.objects.get(id=id4)
|
||||||
|
message.date_sent = message.date_sent - datetime.timedelta(days=6)
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
self.login("hamlet")
|
||||||
|
result = self.client_patch(
|
||||||
|
f"/json/messages/{id4}",
|
||||||
|
{
|
||||||
|
"topic": "edited",
|
||||||
|
"propagate_mode": "change_all",
|
||||||
|
"send_notification_to_new_thread": "false",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assert_json_error(
|
||||||
|
result,
|
||||||
|
"You only have permission to move the 2/3 most recent messages in this topic.",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.login("cordelia")
|
||||||
|
result = self.client_patch(
|
||||||
|
f"/json/messages/{id4}",
|
||||||
|
{
|
||||||
|
"topic": "edited",
|
||||||
|
"propagate_mode": "change_all",
|
||||||
|
"send_notification_to_new_thread": "false",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assert_json_error(
|
||||||
|
result,
|
||||||
|
"You only have permission to move the 2/5 most recent messages in this topic.",
|
||||||
|
)
|
||||||
|
|
||||||
def test_move_message_to_stream(self) -> None:
|
def test_move_message_to_stream(self) -> None:
|
||||||
(user_profile, old_stream, new_stream, msg_id, msg_id_lt) = self.prepare_move_topics(
|
(user_profile, old_stream, new_stream, msg_id, msg_id_lt) = self.prepare_move_topics(
|
||||||
"iago",
|
"iago",
|
||||||
|
|
Loading…
Reference in New Issue