mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 16:14:02 +00:00
This commit updates code to send messages to "general chat" topic if streams used for announcements for "New user signups", "New created streams", "Moderation requests" and "Zulip updates" have topics policy set to allow only "general chat" messages.
398 lines
15 KiB
Python
398 lines
15 KiB
Python
from django.conf import settings
|
|
from typing_extensions import Any, override
|
|
|
|
from zerver.actions.realm_settings import do_set_realm_moderation_request_channel
|
|
from zerver.actions.streams import do_set_stream_property
|
|
from zerver.lib.markdown.fenced_code import get_unused_fence
|
|
from zerver.lib.mention import silent_mention_syntax_for_user
|
|
from zerver.lib.message import truncate_content
|
|
from zerver.lib.message_report import MAX_REPORT_MESSAGE_SNIPPET_LENGTH
|
|
from zerver.lib.test_classes import ZulipTestCase
|
|
from zerver.lib.topic import DB_TOPIC_NAME
|
|
from zerver.lib.topic_link_util import (
|
|
get_message_link_syntax,
|
|
will_produce_broken_stream_topic_link,
|
|
)
|
|
from zerver.models import UserProfile
|
|
from zerver.models.messages import Message
|
|
from zerver.models.recipients import get_or_create_direct_message_group
|
|
from zerver.models.streams import StreamTopicsPolicyEnum
|
|
from zerver.models.users import get_system_bot
|
|
|
|
|
|
class ReportMessageTest(ZulipTestCase):
|
|
@override
|
|
def setUp(self) -> None:
|
|
super().setUp()
|
|
self.hamlet = self.example_user("hamlet")
|
|
self.realm = self.hamlet.realm
|
|
self.reported_user = self.example_user("othello")
|
|
|
|
# Set moderation request channel
|
|
self.moderation_request_channel = self.make_stream(
|
|
"reported messages", self.realm, invite_only=True
|
|
)
|
|
do_set_realm_moderation_request_channel(
|
|
self.hamlet.realm,
|
|
self.moderation_request_channel,
|
|
self.moderation_request_channel.id,
|
|
acting_user=self.hamlet,
|
|
)
|
|
|
|
# Send a message to be reported in a public channel
|
|
self.reported_message_id = self.send_stream_message(
|
|
self.reported_user,
|
|
"Denmark",
|
|
topic_name="civillized discussions",
|
|
content="I squeeze toothpaste from the middle",
|
|
)
|
|
self.reported_message = self.get_last_message()
|
|
assert self.reported_message.id == self.reported_message_id
|
|
|
|
def report_message(
|
|
self,
|
|
user_profile: UserProfile,
|
|
msg_id: int,
|
|
report_type: str,
|
|
description: str | None = None,
|
|
) -> Any:
|
|
report_info = {"report_type": report_type}
|
|
if description:
|
|
report_info["description"] = description
|
|
return self.api_post(user_profile, f"/api/v1/messages/{msg_id}/report", report_info)
|
|
|
|
def get_submitted_moderation_requests(self) -> list[dict[str, Any]]:
|
|
notification_bot = get_system_bot(settings.NOTIFICATION_BOT, self.realm.id)
|
|
|
|
return (
|
|
Message.objects.filter(
|
|
realm_id=self.realm.id,
|
|
sender_id=notification_bot.id,
|
|
recipient=self.moderation_request_channel.recipient,
|
|
)
|
|
.order_by("-id")
|
|
.values(*["id", "content", DB_TOPIC_NAME])
|
|
)
|
|
|
|
def test_disabled_moderation_request_feature(self) -> None:
|
|
# Disable moderation request feature
|
|
do_set_realm_moderation_request_channel(
|
|
self.hamlet.realm, None, -1, acting_user=self.hamlet
|
|
)
|
|
|
|
result = self.report_message(
|
|
self.hamlet,
|
|
self.reported_message_id,
|
|
report_type="harassment",
|
|
)
|
|
self.assert_json_error(result, "Message reporting is not enabled in this organization.")
|
|
|
|
def test_channel_message_report(self) -> None:
|
|
reporting_user = self.example_user("hamlet")
|
|
report_type = "harassment"
|
|
description = "this is crime against food"
|
|
|
|
reporting_user_mention = silent_mention_syntax_for_user(reporting_user)
|
|
reported_user_mention = silent_mention_syntax_for_user(self.reported_user)
|
|
channel = self.reported_message.recipient.label()
|
|
topic_name = self.reported_message.topic_name()
|
|
message_sent_to = f"{reporting_user_mention} reported #**{channel}>{topic_name}@{self.reported_message_id}** sent by {reported_user_mention}."
|
|
expected_message = """
|
|
{message_sent_to}
|
|
- Reason: **{report_type}**
|
|
- Notes:
|
|
```quote
|
|
{description}
|
|
```
|
|
{fence} spoiler **Message sent by {reported_user}**
|
|
{reported_message}
|
|
{fence}
|
|
""".format(
|
|
report_type=report_type,
|
|
description=description,
|
|
reported_user=reported_user_mention,
|
|
message_sent_to=message_sent_to,
|
|
reported_message=self.reported_message.content,
|
|
fence=get_unused_fence(self.reported_message.content),
|
|
)
|
|
|
|
result = self.report_message(
|
|
reporting_user, self.reported_message_id, report_type, description
|
|
)
|
|
self.assert_json_success(result)
|
|
reports = self.get_submitted_moderation_requests()
|
|
assert len(reports) == 1
|
|
self.assertEqual(reports[0]["content"], expected_message.strip())
|
|
expected_report_topic = f"{self.reported_user.full_name}'s moderation requests"
|
|
self.assertEqual(reports[0][DB_TOPIC_NAME], expected_report_topic)
|
|
|
|
# User can report messages in public channels they're not subscribed
|
|
# to.
|
|
self.unsubscribe(reporting_user, "Denmark")
|
|
result = self.report_message(
|
|
reporting_user, self.reported_message_id, report_type, description
|
|
)
|
|
self.assert_json_success(result)
|
|
reports = self.get_submitted_moderation_requests()
|
|
assert len(reports) == 2
|
|
self.assertEqual(reports[0]["content"], expected_message.strip())
|
|
expected_report_topic = f"{self.reported_user.full_name}'s moderation requests"
|
|
self.assertEqual(reports[0][DB_TOPIC_NAME], expected_report_topic)
|
|
|
|
# User can't report a message in channels they're not a part of.
|
|
private_channel = self.make_stream("private channel", self.realm, invite_only=True)
|
|
self.subscribe(self.reported_user, private_channel.name, True)
|
|
self.reported_message_id = self.send_stream_message(
|
|
self.reported_user,
|
|
private_channel.name,
|
|
topic_name="private discussions",
|
|
content="foo bar",
|
|
)
|
|
private_message = self.get_last_message()
|
|
result = self.report_message(reporting_user, private_message.id, report_type, description)
|
|
self.assert_json_error(result, msg="Invalid message(s)")
|
|
|
|
def test_reported_channel_message_narrow_link(self) -> None:
|
|
reporting_user = self.example_user("hamlet")
|
|
report_type = "harassment"
|
|
description = "this is crime against food"
|
|
|
|
# Check object IDs in message link syntax is accurate.
|
|
# This channel name will generate a narrow URL for its link syntax.
|
|
obscure_channel_name = "Sw*den"
|
|
self.assertTrue(will_produce_broken_stream_topic_link(obscure_channel_name))
|
|
obscure_channel = self.make_stream(obscure_channel_name, self.realm)
|
|
self.subscribe(self.reported_user, obscure_channel.name, True)
|
|
message_id = self.send_stream_message(
|
|
self.reported_user,
|
|
obscure_channel.name,
|
|
topic_name="",
|
|
content="foo baz",
|
|
)
|
|
|
|
result = self.report_message(
|
|
reporting_user,
|
|
message_id,
|
|
report_type,
|
|
description,
|
|
)
|
|
self.assert_json_success(result)
|
|
expected_message_link_syntax = get_message_link_syntax(
|
|
obscure_channel.id,
|
|
obscure_channel.name,
|
|
"",
|
|
message_id,
|
|
)
|
|
reports = self.get_submitted_moderation_requests()
|
|
assert len(reports) == 1
|
|
self.assertIn(expected_message_link_syntax, reports[0]["content"])
|
|
|
|
def test_dm_report(self) -> None:
|
|
# Send a DM to be reported
|
|
reported_dm_id = self.send_personal_message(
|
|
self.reported_user,
|
|
self.hamlet,
|
|
content="I dip fries in ice cream",
|
|
)
|
|
reported_dm = self.get_last_message()
|
|
assert reported_dm.id == reported_dm_id
|
|
|
|
reporting_user = self.example_user("hamlet")
|
|
report_type = "harassment"
|
|
description = "this is crime against food"
|
|
reporting_user_mention = silent_mention_syntax_for_user(reporting_user)
|
|
reported_user_mention = silent_mention_syntax_for_user(self.reported_user)
|
|
|
|
message_sent_to = f"{reporting_user_mention} reported a DM sent by {reported_user_mention}."
|
|
expected_message = """
|
|
{message_sent_to}
|
|
- Reason: **{report_type}**
|
|
- Notes:
|
|
```quote
|
|
{description}
|
|
```
|
|
{fence} spoiler **Message sent by {reported_user}**
|
|
{reported_message}
|
|
{fence}
|
|
""".format(
|
|
report_type=report_type,
|
|
description=description,
|
|
reported_user=reported_user_mention,
|
|
message_sent_to=message_sent_to,
|
|
reported_message=reported_dm.content,
|
|
fence=get_unused_fence(reported_dm.content),
|
|
)
|
|
|
|
result = self.report_message(reporting_user, reported_dm_id, report_type, description)
|
|
self.assert_json_success(result)
|
|
reports = self.get_submitted_moderation_requests()
|
|
assert len(reports) == 1
|
|
self.assertEqual(reports[0]["content"], expected_message.strip())
|
|
|
|
# User can't report DM they're not a part of.
|
|
ZOE = self.example_user("ZOE")
|
|
result = self.report_message(ZOE, reported_dm_id, report_type, description)
|
|
self.assert_json_error(result, msg="Invalid message(s)")
|
|
|
|
def test_personal_message_report_using_direct_message_group(self) -> None:
|
|
direct_message_group = get_or_create_direct_message_group(
|
|
id_list=[self.hamlet.id, self.reported_user.id],
|
|
)
|
|
|
|
# Send a DM to be reported
|
|
reported_dm_id = self.send_personal_message(
|
|
self.reported_user,
|
|
self.hamlet,
|
|
content="I dip fries in ice cream",
|
|
)
|
|
reported_dm = self.get_last_message()
|
|
assert reported_dm.id == reported_dm_id
|
|
assert reported_dm.recipient == direct_message_group.recipient
|
|
|
|
reporting_user = self.hamlet
|
|
report_type = "harassment"
|
|
description = "this is crime against food"
|
|
reporting_user_mention = silent_mention_syntax_for_user(reporting_user)
|
|
reported_user_mention = silent_mention_syntax_for_user(self.reported_user)
|
|
|
|
message_sent_to = f"{reporting_user_mention} reported a DM sent by {reported_user_mention}."
|
|
expected_message = """
|
|
{message_sent_to}
|
|
- Reason: **{report_type}**
|
|
- Notes:
|
|
```quote
|
|
{description}
|
|
```
|
|
{fence} spoiler **Message sent by {reported_user}**
|
|
{reported_message}
|
|
{fence}
|
|
""".format(
|
|
report_type=report_type,
|
|
description=description,
|
|
reported_user=reported_user_mention,
|
|
message_sent_to=message_sent_to,
|
|
reported_message=reported_dm.content,
|
|
fence=get_unused_fence(reported_dm.content),
|
|
)
|
|
|
|
result = self.report_message(reporting_user, reported_dm_id, report_type, description)
|
|
self.assert_json_success(result)
|
|
reports = self.get_submitted_moderation_requests()
|
|
assert len(reports) == 1
|
|
self.assertEqual(reports[0]["content"], expected_message.strip())
|
|
|
|
# User can't report DM they're not a part of.
|
|
ZOE = self.example_user("ZOE")
|
|
result = self.report_message(ZOE, reported_dm_id, report_type, description)
|
|
self.assert_json_error(result, msg="Invalid message(s)")
|
|
|
|
def test_gdm_report(self) -> None:
|
|
# Send a group DM to be reported
|
|
reported_gdm_id = self.send_group_direct_message(
|
|
self.reported_user,
|
|
[self.hamlet, self.reported_user, self.example_user("iago")],
|
|
content="I eat cereal with water",
|
|
)
|
|
reported_gdm = self.get_last_message()
|
|
assert reported_gdm.id == reported_gdm_id
|
|
|
|
reporting_user = self.example_user("hamlet")
|
|
report_type = "harassment"
|
|
description = "Call the police please"
|
|
reporting_user_mention = silent_mention_syntax_for_user(reporting_user)
|
|
reported_user_mention = silent_mention_syntax_for_user(self.reported_user)
|
|
iago_user_mention = silent_mention_syntax_for_user(self.example_user("iago"))
|
|
gdm_user_mention = (
|
|
f"{reporting_user_mention}, {iago_user_mention}, and {reported_user_mention}"
|
|
)
|
|
|
|
message_sent_to = f"{reporting_user_mention} reported a DM sent by {reported_user_mention} to {gdm_user_mention}."
|
|
expected_message = """
|
|
{message_sent_to}
|
|
- Reason: **{report_type}**
|
|
- Notes:
|
|
```quote
|
|
{description}
|
|
```
|
|
{fence} spoiler **Message sent by {reported_user}**
|
|
{reported_message}
|
|
{fence}
|
|
""".format(
|
|
report_type=report_type,
|
|
description=description,
|
|
reported_user=reported_user_mention,
|
|
message_sent_to=message_sent_to,
|
|
reported_message=reported_gdm.content,
|
|
fence=get_unused_fence(reported_gdm.content),
|
|
)
|
|
|
|
result = self.report_message(reporting_user, reported_gdm_id, report_type, description)
|
|
self.assert_json_success(result)
|
|
reports = self.get_submitted_moderation_requests()
|
|
assert len(reports) == 1
|
|
self.assertEqual(reports[0]["content"], expected_message.strip())
|
|
|
|
# User can't report group direct messages they're not a part of.
|
|
ZOE = self.example_user("ZOE")
|
|
result = self.report_message(ZOE, reported_gdm_id, report_type, description)
|
|
self.assert_json_error(result, msg="Invalid message(s)")
|
|
|
|
def test_truncate_reported_message(self) -> None:
|
|
large_message = "." * (MAX_REPORT_MESSAGE_SNIPPET_LENGTH + 1)
|
|
reported_truncate_message_id = self.send_stream_message(
|
|
self.reported_user,
|
|
"Denmark",
|
|
topic_name="civillized discussions",
|
|
content=large_message,
|
|
)
|
|
reported_truncate_message = self.get_last_message()
|
|
assert reported_truncate_message.id == reported_truncate_message_id
|
|
|
|
reporting_user = self.example_user("hamlet")
|
|
|
|
result = self.report_message(reporting_user, reported_truncate_message_id, "spam")
|
|
self.assert_json_success(result)
|
|
|
|
reports = self.get_submitted_moderation_requests()
|
|
assert len(reports) == 1
|
|
self.assertNotIn(large_message, reports[0]["content"])
|
|
|
|
expected_truncated_message = truncate_content(
|
|
large_message, MAX_REPORT_MESSAGE_SNIPPET_LENGTH, "\n[message truncated]"
|
|
)
|
|
self.assertIn(expected_truncated_message, reports[0]["content"])
|
|
|
|
def test_other_report_type_with_no_description(self) -> None:
|
|
result = self.report_message(self.hamlet, self.reported_message_id, report_type="other")
|
|
|
|
self.assert_json_error(result, "An explanation is required.")
|
|
|
|
result = self.report_message(
|
|
self.hamlet,
|
|
self.reported_message_id,
|
|
report_type="other",
|
|
description="This is crime against food.",
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
def test_message_report_to_channel_with_topics_disabled(self) -> None:
|
|
notification_bot = get_system_bot(settings.NOTIFICATION_BOT, self.realm.id)
|
|
do_set_stream_property(
|
|
self.moderation_request_channel,
|
|
"topics_policy",
|
|
StreamTopicsPolicyEnum.empty_topic_only.value,
|
|
self.hamlet,
|
|
)
|
|
|
|
result = self.report_message(
|
|
self.hamlet,
|
|
self.reported_message_id,
|
|
report_type="harassment",
|
|
)
|
|
self.assert_json_success(result)
|
|
report_msg = self.get_last_message()
|
|
self.assertEqual(report_msg.sender_id, notification_bot.id)
|
|
self.assertEqual(report_msg.topic_name(), "")
|
|
self.assertIn("reported", report_msg.content)
|