snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
784
zerver/tests/test_message_edit_notifications.py
Normal file
784
zerver/tests/test_message_edit_notifications.py
Normal file
@@ -0,0 +1,784 @@
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
from unittest import mock
|
||||
|
||||
from django.utils.timezone import now as timezone_now
|
||||
from typing_extensions import override
|
||||
|
||||
from zerver.actions.user_settings import do_change_user_setting
|
||||
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
|
||||
from zerver.lib.push_notifications import get_apns_badge_count, get_apns_badge_count_future
|
||||
from zerver.lib.test_classes import ZulipTestCase
|
||||
from zerver.lib.test_helpers import mock_queue_publish
|
||||
from zerver.models import Device, Subscription, UserPresence, UserTopic
|
||||
from zerver.models.scheduled_jobs import NotificationTriggers
|
||||
from zerver.models.streams import get_stream
|
||||
from zerver.tornado.event_queue import maybe_enqueue_notifications
|
||||
|
||||
|
||||
class EditMessageSideEffectsTest(ZulipTestCase):
|
||||
@override
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
cordelia = self.example_user("cordelia")
|
||||
self.register_push_device(cordelia.id)
|
||||
|
||||
def _assert_update_does_not_notify_anybody(self, message_id: int, **request: str | int) -> None:
|
||||
url = "/json/messages/" + str(message_id)
|
||||
|
||||
with (
|
||||
mock.patch("zerver.tornado.event_queue.maybe_enqueue_notifications") as m,
|
||||
self.captureOnCommitCallbacks(execute=True),
|
||||
):
|
||||
result = self.client_patch(url, request)
|
||||
|
||||
self.assert_json_success(result)
|
||||
self.assertFalse(m.called)
|
||||
|
||||
def test_updates_with_pm_mention(self) -> None:
|
||||
hamlet = self.example_user("hamlet")
|
||||
cordelia = self.example_user("cordelia")
|
||||
|
||||
self.login_user(hamlet)
|
||||
|
||||
message_id = self.send_personal_message(
|
||||
hamlet,
|
||||
cordelia,
|
||||
content="no mention",
|
||||
)
|
||||
|
||||
self._assert_update_does_not_notify_anybody(
|
||||
message_id=message_id,
|
||||
content="now we mention @**Cordelia, Lear's daughter**",
|
||||
)
|
||||
|
||||
def _login_and_send_original_stream_message(
|
||||
self, content: str, enable_online_push_notifications: bool = False
|
||||
) -> int:
|
||||
"""
|
||||
Note our conventions here:
|
||||
|
||||
Hamlet is our logged in user (and sender).
|
||||
Cordelia is the receiver we care about.
|
||||
Scotland is the stream we send messages to.
|
||||
"""
|
||||
hamlet = self.example_user("hamlet")
|
||||
cordelia = self.example_user("cordelia")
|
||||
|
||||
cordelia.enable_online_push_notifications = enable_online_push_notifications
|
||||
cordelia.save()
|
||||
|
||||
self.login_user(hamlet)
|
||||
self.subscribe(hamlet, "Scotland")
|
||||
self.subscribe(cordelia, "Scotland")
|
||||
|
||||
message_id = self.send_stream_message(
|
||||
hamlet,
|
||||
"Scotland",
|
||||
content=content,
|
||||
)
|
||||
|
||||
return message_id
|
||||
|
||||
def _get_queued_data_for_message_update(
|
||||
self, *, message_id: int, expect_short_circuit: bool = False, **request: str
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
This function updates a message with a post to
|
||||
/json/messages/(message_id).
|
||||
|
||||
By using mocks, we are able to capture two pieces of data:
|
||||
|
||||
enqueue_kwargs: These are the arguments passed in to
|
||||
maybe_enqueue_notifications.
|
||||
|
||||
queue_messages: These are the messages that
|
||||
maybe_enqueue_notifications actually
|
||||
puts on the queue.
|
||||
|
||||
Using this helper allows you to construct a test that goes
|
||||
pretty deep into the missed-messages codepath, without actually
|
||||
queuing the final messages.
|
||||
"""
|
||||
url = "/json/messages/" + str(message_id)
|
||||
|
||||
with (
|
||||
mock.patch("zerver.tornado.event_queue.maybe_enqueue_notifications") as m,
|
||||
self.captureOnCommitCallbacks(execute=True),
|
||||
):
|
||||
result = self.client_patch(url, request)
|
||||
|
||||
cordelia = self.example_user("cordelia")
|
||||
cordelia_calls = [
|
||||
call_args
|
||||
for call_args in m.call_args_list
|
||||
if call_args[1]["user_notifications_data"].user_id == cordelia.id
|
||||
]
|
||||
|
||||
if expect_short_circuit:
|
||||
self.assert_length(cordelia_calls, 0)
|
||||
return {}
|
||||
|
||||
# Normally we expect maybe_enqueue_notifications to be
|
||||
# called for Cordelia, so continue on.
|
||||
self.assert_length(cordelia_calls, 1)
|
||||
enqueue_kwargs = cordelia_calls[0][1]
|
||||
|
||||
queue_messages = []
|
||||
|
||||
def fake_publish(queue_name: str, event: Mapping[str, Any] | str, *args: Any) -> None:
|
||||
queue_messages.append(
|
||||
dict(
|
||||
queue_name=queue_name,
|
||||
event=event,
|
||||
)
|
||||
)
|
||||
|
||||
with mock_queue_publish(
|
||||
"zerver.tornado.event_queue.queue_json_publish_rollback_unsafe",
|
||||
side_effect=fake_publish,
|
||||
) as m:
|
||||
maybe_enqueue_notifications(**enqueue_kwargs)
|
||||
|
||||
self.assert_json_success(result)
|
||||
|
||||
return dict(
|
||||
enqueue_kwargs=enqueue_kwargs,
|
||||
queue_messages=queue_messages,
|
||||
)
|
||||
|
||||
def _send_and_update_message(
|
||||
self,
|
||||
original_content: str,
|
||||
updated_content: str,
|
||||
enable_online_push_notifications: bool = False,
|
||||
expect_short_circuit: bool = False,
|
||||
connected_to_zulip: bool = False,
|
||||
present_on_web: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
message_id = self._login_and_send_original_stream_message(
|
||||
content=original_content,
|
||||
enable_online_push_notifications=enable_online_push_notifications,
|
||||
)
|
||||
|
||||
if present_on_web:
|
||||
self._make_cordelia_present_on_web()
|
||||
|
||||
if connected_to_zulip:
|
||||
with self._cordelia_connected_to_zulip():
|
||||
info = self._get_queued_data_for_message_update(
|
||||
message_id=message_id,
|
||||
content=updated_content,
|
||||
expect_short_circuit=expect_short_circuit,
|
||||
)
|
||||
else:
|
||||
info = self._get_queued_data_for_message_update(
|
||||
message_id=message_id,
|
||||
content=updated_content,
|
||||
expect_short_circuit=expect_short_circuit,
|
||||
)
|
||||
|
||||
return dict(
|
||||
message_id=message_id,
|
||||
info=info,
|
||||
)
|
||||
|
||||
def test_updates_with_stream_mention(self) -> None:
|
||||
original_content = "no mention"
|
||||
updated_content = "now we mention @**Cordelia, Lear's daughter**"
|
||||
notification_message_data = self._send_and_update_message(original_content, updated_content)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
mention_email_notify=True,
|
||||
mention_push_notify=True,
|
||||
already_notified={},
|
||||
)
|
||||
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
queue_messages = info["queue_messages"]
|
||||
|
||||
self.assert_length(queue_messages, 2)
|
||||
|
||||
self.assertEqual(queue_messages[0]["queue_name"], "missedmessage_mobile_notifications")
|
||||
mobile_event = queue_messages[0]["event"]
|
||||
|
||||
self.assertEqual(mobile_event["user_profile_id"], cordelia.id)
|
||||
self.assertEqual(mobile_event["trigger"], NotificationTriggers.MENTION)
|
||||
|
||||
self.assertEqual(queue_messages[1]["queue_name"], "missedmessage_emails")
|
||||
email_event = queue_messages[1]["event"]
|
||||
|
||||
self.assertEqual(email_event["user_profile_id"], cordelia.id)
|
||||
self.assertEqual(email_event["trigger"], NotificationTriggers.MENTION)
|
||||
|
||||
def test_second_mention_is_ignored(self) -> None:
|
||||
original_content = "hello @**Cordelia, Lear's daughter**"
|
||||
updated_content = "re-mention @**Cordelia, Lear's daughter**"
|
||||
self._send_and_update_message(original_content, updated_content, expect_short_circuit=True)
|
||||
|
||||
def test_no_push_device_registered(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
|
||||
# We're interested to test the push notification behaviour when
|
||||
# the user has no push device registered. Disable email notification.
|
||||
do_change_user_setting(
|
||||
cordelia, "enable_offline_email_notifications", False, acting_user=None
|
||||
)
|
||||
self.assertTrue(cordelia.enable_offline_push_notifications)
|
||||
Device.objects.filter(push_token_id__isnull=False).delete()
|
||||
|
||||
original_content = "no mention"
|
||||
updated_content = "now we mention @**Cordelia, Lear's daughter**"
|
||||
notification_message_data = self._send_and_update_message(original_content, updated_content)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
mention_email_notify=False,
|
||||
mention_push_notify=False,
|
||||
already_notified={},
|
||||
)
|
||||
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
self.assert_length(info["queue_messages"], 0)
|
||||
|
||||
def _turn_on_stream_push_for_cordelia(self) -> None:
|
||||
"""
|
||||
conventions:
|
||||
Cordelia is the message receiver we care about.
|
||||
Scotland is our stream.
|
||||
"""
|
||||
cordelia = self.example_user("cordelia")
|
||||
stream = self.subscribe(cordelia, "Scotland")
|
||||
recipient = stream.recipient
|
||||
cordelia_subscription = Subscription.objects.get(
|
||||
user_profile_id=cordelia.id,
|
||||
recipient=recipient,
|
||||
)
|
||||
cordelia_subscription.push_notifications = True
|
||||
cordelia_subscription.save()
|
||||
|
||||
def test_updates_with_stream_push_notify(self) -> None:
|
||||
self._turn_on_stream_push_for_cordelia()
|
||||
|
||||
# Even though Cordelia configured this stream for pushes,
|
||||
# we short-circuit the logic, assuming the original message
|
||||
# also did a push.
|
||||
original_content = "no mention"
|
||||
updated_content = "nothing special about updated message"
|
||||
self._send_and_update_message(original_content, updated_content, expect_short_circuit=True)
|
||||
|
||||
def _cordelia_connected_to_zulip(self) -> Any:
|
||||
"""
|
||||
Right now the easiest way to make Cordelia look
|
||||
connected to Zulip is to mock the function below.
|
||||
|
||||
This is a bit blunt, as it affects other users too,
|
||||
but we only really look at Cordelia's data, anyway.
|
||||
"""
|
||||
return mock.patch(
|
||||
"zerver.tornado.event_queue.receiver_is_off_zulip",
|
||||
return_value=False,
|
||||
)
|
||||
|
||||
def test_stream_push_notify_for_sorta_present_user(self) -> None:
|
||||
self._turn_on_stream_push_for_cordelia()
|
||||
|
||||
# Simulate Cordelia still has an actively polling client, but
|
||||
# the lack of presence info should still mark her as offline.
|
||||
#
|
||||
# Despite Cordelia being offline, we still short circuit
|
||||
# offline notifications due to the her stream push setting.
|
||||
original_content = "no mention"
|
||||
updated_content = "nothing special about updated message"
|
||||
self._send_and_update_message(
|
||||
original_content, updated_content, expect_short_circuit=True, connected_to_zulip=True
|
||||
)
|
||||
|
||||
def _make_cordelia_present_on_web(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
now = timezone_now()
|
||||
UserPresence.objects.create(
|
||||
user_profile_id=cordelia.id,
|
||||
realm_id=cordelia.realm_id,
|
||||
last_connected_time=now,
|
||||
last_active_time=now,
|
||||
)
|
||||
|
||||
def test_stream_push_notify_for_fully_present_user(self) -> None:
|
||||
self._turn_on_stream_push_for_cordelia()
|
||||
|
||||
# Simulate Cordelia is FULLY present, not just in term of
|
||||
# browser activity, but also in terms of her client descriptors.
|
||||
original_content = "no mention"
|
||||
updated_content = "nothing special about updated message"
|
||||
self._send_and_update_message(
|
||||
original_content,
|
||||
updated_content,
|
||||
expect_short_circuit=True,
|
||||
connected_to_zulip=True,
|
||||
present_on_web=True,
|
||||
)
|
||||
|
||||
def test_online_push_enabled_for_fully_present_mentioned_user(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
|
||||
# Simulate Cordelia is FULLY present, not just in term of
|
||||
# browser activity, but also in terms of her client descriptors.
|
||||
original_content = "no mention"
|
||||
updated_content = "newly mention @**Cordelia, Lear's daughter**"
|
||||
notification_message_data = self._send_and_update_message(
|
||||
original_content,
|
||||
updated_content,
|
||||
enable_online_push_notifications=True,
|
||||
connected_to_zulip=True,
|
||||
present_on_web=True,
|
||||
)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
mention_push_notify=True,
|
||||
mention_email_notify=True,
|
||||
online_push_enabled=True,
|
||||
idle=False,
|
||||
already_notified={},
|
||||
)
|
||||
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
queue_messages = info["queue_messages"]
|
||||
|
||||
self.assert_length(queue_messages, 1)
|
||||
|
||||
def test_online_push_enabled_for_fully_present_boring_user(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
|
||||
# Simulate Cordelia is FULLY present, not just in term of
|
||||
# browser activity, but also in terms of her client descriptors.
|
||||
original_content = "no mention"
|
||||
updated_content = "nothing special about updated message"
|
||||
notification_message_data = self._send_and_update_message(
|
||||
original_content,
|
||||
updated_content,
|
||||
enable_online_push_notifications=True,
|
||||
connected_to_zulip=True,
|
||||
present_on_web=True,
|
||||
)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
online_push_enabled=True,
|
||||
idle=False,
|
||||
already_notified={},
|
||||
)
|
||||
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
queue_messages = info["queue_messages"]
|
||||
|
||||
# Cordelia being present and having `enable_online_push_notifications`
|
||||
# does not mean we'll send her notifications for messages which she
|
||||
# wouldn't otherwise have received notifications for.
|
||||
self.assert_length(queue_messages, 0)
|
||||
|
||||
def test_updates_with_stream_mention_of_sorta_present_user(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
|
||||
# We will simulate that the user still has an active client,
|
||||
# but they don't have UserPresence rows, so we will still
|
||||
# send offline notifications.
|
||||
original_content = "no mention"
|
||||
updated_content = "now we mention @**Cordelia, Lear's daughter**"
|
||||
notification_message_data = self._send_and_update_message(
|
||||
original_content,
|
||||
updated_content,
|
||||
connected_to_zulip=True,
|
||||
)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
message_id=message_id,
|
||||
acting_user_id=self.example_user("hamlet").id,
|
||||
mention_email_notify=True,
|
||||
mention_push_notify=True,
|
||||
already_notified={},
|
||||
)
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
# She will get messages enqueued. (Other tests drill down on the
|
||||
# actual content of these messages.)
|
||||
self.assert_length(info["queue_messages"], 2)
|
||||
|
||||
def test_updates_with_topic_wildcard_mention_in_followed_topic(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
self.subscribe(cordelia, "Scotland")
|
||||
|
||||
do_change_user_setting(
|
||||
cordelia, "enable_followed_topic_email_notifications", False, acting_user=None
|
||||
)
|
||||
do_change_user_setting(
|
||||
cordelia, "enable_followed_topic_push_notifications", False, acting_user=None
|
||||
)
|
||||
do_change_user_setting(cordelia, "wildcard_mentions_notify", False, acting_user=None)
|
||||
do_set_user_topic_visibility_policy(
|
||||
user_profile=cordelia,
|
||||
stream=get_stream("Scotland", cordelia.realm),
|
||||
topic_name="test",
|
||||
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
|
||||
)
|
||||
|
||||
# Only users who either sent or reacted to messages in the topic
|
||||
# are considered for @topic mention notifications.
|
||||
self.send_stream_message(cordelia, "Scotland")
|
||||
|
||||
# We will simulate that the user still has an active client,
|
||||
# but they don't have UserPresence rows, so we will still
|
||||
# send offline notifications.
|
||||
original_content = "no mention"
|
||||
updated_content = "now we mention @**topic**"
|
||||
notification_message_data = self._send_and_update_message(
|
||||
original_content,
|
||||
updated_content,
|
||||
connected_to_zulip=True,
|
||||
)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
topic_wildcard_mention_in_followed_topic_email_notify=True,
|
||||
topic_wildcard_mention_in_followed_topic_push_notify=True,
|
||||
already_notified={},
|
||||
)
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
# messages will get enqueued.
|
||||
self.assert_length(info["queue_messages"], 2)
|
||||
|
||||
def test_updates_with_stream_wildcard_mention_in_followed_topic(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
self.subscribe(cordelia, "Scotland")
|
||||
|
||||
do_change_user_setting(
|
||||
cordelia, "enable_followed_topic_email_notifications", False, acting_user=None
|
||||
)
|
||||
do_change_user_setting(
|
||||
cordelia, "enable_followed_topic_push_notifications", False, acting_user=None
|
||||
)
|
||||
do_change_user_setting(cordelia, "wildcard_mentions_notify", False, acting_user=None)
|
||||
do_set_user_topic_visibility_policy(
|
||||
user_profile=cordelia,
|
||||
stream=get_stream("Scotland", cordelia.realm),
|
||||
topic_name="test",
|
||||
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
|
||||
)
|
||||
|
||||
# We will simulate that the user still has an active client,
|
||||
# but they don't have UserPresence rows, so we will still
|
||||
# send offline notifications.
|
||||
original_content = "no mention"
|
||||
updated_content = "now we mention @**all**"
|
||||
notification_message_data = self._send_and_update_message(
|
||||
original_content,
|
||||
updated_content,
|
||||
connected_to_zulip=True,
|
||||
)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
stream_wildcard_mention_in_followed_topic_email_notify=True,
|
||||
stream_wildcard_mention_in_followed_topic_push_notify=True,
|
||||
already_notified={},
|
||||
)
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
# messages will get enqueued.
|
||||
self.assert_length(info["queue_messages"], 2)
|
||||
|
||||
def test_updates_with_topic_wildcard_mention(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
|
||||
# Only users who either sent or reacted to messages in the topic
|
||||
# are considered for @topic mention notifications.
|
||||
self.subscribe(cordelia, "Scotland")
|
||||
self.send_stream_message(cordelia, "Scotland")
|
||||
|
||||
# We will simulate that the user still has an active client,
|
||||
# but they don't have UserPresence rows, so we will still
|
||||
# send offline notifications.
|
||||
original_content = "no mention"
|
||||
updated_content = "now we mention @**topic**"
|
||||
notification_message_data = self._send_and_update_message(
|
||||
original_content,
|
||||
updated_content,
|
||||
connected_to_zulip=True,
|
||||
)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
topic_wildcard_mention_email_notify=True,
|
||||
topic_wildcard_mention_push_notify=True,
|
||||
already_notified={},
|
||||
)
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
# messages will get enqueued.
|
||||
self.assert_length(info["queue_messages"], 2)
|
||||
|
||||
def test_updates_with_stream_wildcard_mention(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
|
||||
# We will simulate that the user still has an active client,
|
||||
# but they don't have UserPresence rows, so we will still
|
||||
# send offline notifications.
|
||||
original_content = "no mention"
|
||||
updated_content = "now we mention @**all**"
|
||||
notification_message_data = self._send_and_update_message(
|
||||
original_content,
|
||||
updated_content,
|
||||
connected_to_zulip=True,
|
||||
)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
stream_wildcard_mention_email_notify=True,
|
||||
stream_wildcard_mention_push_notify=True,
|
||||
already_notified={},
|
||||
)
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
# She will get messages enqueued.
|
||||
self.assert_length(info["queue_messages"], 2)
|
||||
|
||||
def test_updates_with_upgrade_wildcard_mention(self) -> None:
|
||||
# If there was a previous wildcard mention delivered to the
|
||||
# user (because wildcard_mention_notify=True), we don't notify
|
||||
original_content = "Mention @**all**"
|
||||
updated_content = "now we mention @**Cordelia, Lear's daughter**"
|
||||
self._send_and_update_message(
|
||||
original_content, updated_content, expect_short_circuit=True, connected_to_zulip=True
|
||||
)
|
||||
|
||||
def test_updates_with_upgrade_wildcard_mention_disabled(self) -> None:
|
||||
# If the user has disabled notifications for wildcard
|
||||
# mentions, they won't have been notified at first, which
|
||||
# means they should be notified when the message is edited to
|
||||
# contain a wildcard mention.
|
||||
#
|
||||
# This is a bug that we're not equipped to fix right now.
|
||||
cordelia = self.example_user("cordelia")
|
||||
cordelia.wildcard_mentions_notify = False
|
||||
cordelia.save()
|
||||
|
||||
original_content = "Mention @**all**"
|
||||
updated_content = "now we mention @**Cordelia, Lear's daughter**"
|
||||
self._send_and_update_message(
|
||||
original_content, updated_content, expect_short_circuit=True, connected_to_zulip=True
|
||||
)
|
||||
|
||||
def test_updates_with_stream_mention_of_fully_present_user(self) -> None:
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
|
||||
# Simulate Cordelia is FULLY present, not just in term of
|
||||
# browser activity, but also in terms of her client descriptors.
|
||||
original_content = "no mention"
|
||||
updated_content = "now we mention @**Cordelia, Lear's daughter**"
|
||||
notification_message_data = self._send_and_update_message(
|
||||
original_content,
|
||||
updated_content,
|
||||
connected_to_zulip=True,
|
||||
present_on_web=True,
|
||||
)
|
||||
|
||||
message_id = notification_message_data["message_id"]
|
||||
info = notification_message_data["info"]
|
||||
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
mention_email_notify=True,
|
||||
mention_push_notify=True,
|
||||
idle=False,
|
||||
already_notified={},
|
||||
)
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
# Because Cordelia is FULLY present, we don't need to send any offline
|
||||
# push notifications or message notification emails.
|
||||
self.assert_length(info["queue_messages"], 0)
|
||||
|
||||
@mock.patch("zerver.lib.push_notifications.push_notifications_configured", return_value=True)
|
||||
def test_clear_notification_when_mention_removed(
|
||||
self, mock_push_notifications: mock.MagicMock
|
||||
) -> None:
|
||||
mentioned_user = self.example_user("iago")
|
||||
self.register_push_device(mentioned_user.id)
|
||||
self.assertEqual(get_apns_badge_count(mentioned_user), 0)
|
||||
self.assertEqual(get_apns_badge_count_future(mentioned_user), 0)
|
||||
|
||||
message_id = self._login_and_send_original_stream_message(
|
||||
content="@**Iago**",
|
||||
)
|
||||
|
||||
self.assertEqual(get_apns_badge_count(mentioned_user), 0)
|
||||
self.assertEqual(get_apns_badge_count_future(mentioned_user), 1)
|
||||
|
||||
self._get_queued_data_for_message_update(message_id=message_id, content="Removed mention")
|
||||
|
||||
self.assertEqual(get_apns_badge_count(mentioned_user), 0)
|
||||
self.assertEqual(get_apns_badge_count_future(mentioned_user), 0)
|
||||
|
||||
@mock.patch("zerver.lib.push_notifications.push_notifications_configured", return_value=True)
|
||||
def test_clear_notification_when_group_mention_removed(
|
||||
self, mock_push_notifications: mock.MagicMock
|
||||
) -> None:
|
||||
group_mentioned_user = self.example_user("cordelia")
|
||||
self.assertEqual(get_apns_badge_count(group_mentioned_user), 0)
|
||||
self.assertEqual(get_apns_badge_count_future(group_mentioned_user), 0)
|
||||
message_id = self._login_and_send_original_stream_message(
|
||||
content="Hello @*hamletcharacters*",
|
||||
)
|
||||
|
||||
self.assertEqual(get_apns_badge_count(group_mentioned_user), 0)
|
||||
self.assertEqual(get_apns_badge_count_future(group_mentioned_user), 1)
|
||||
|
||||
self._get_queued_data_for_message_update(
|
||||
message_id=message_id,
|
||||
content="Removed group mention",
|
||||
expect_short_circuit=True,
|
||||
)
|
||||
|
||||
self.assertEqual(get_apns_badge_count(group_mentioned_user), 0)
|
||||
self.assertEqual(get_apns_badge_count_future(group_mentioned_user), 0)
|
||||
|
||||
@mock.patch("zerver.lib.push_notifications.push_notifications_configured", return_value=True)
|
||||
def test_not_clear_notification_when_mention_removed_but_stream_notified(
|
||||
self, mock_push_notifications: mock.MagicMock
|
||||
) -> None:
|
||||
mentioned_user = self.example_user("iago")
|
||||
mentioned_user.enable_stream_push_notifications = True
|
||||
mentioned_user.save()
|
||||
self.register_push_device(mentioned_user.id)
|
||||
|
||||
self.assertEqual(get_apns_badge_count(mentioned_user), 0)
|
||||
self.assertEqual(get_apns_badge_count_future(mentioned_user), 0)
|
||||
|
||||
message_id = self._login_and_send_original_stream_message(
|
||||
content="@**Iago**",
|
||||
)
|
||||
|
||||
self.assertEqual(get_apns_badge_count(mentioned_user), 0)
|
||||
self.assertEqual(get_apns_badge_count_future(mentioned_user), 1)
|
||||
|
||||
self._get_queued_data_for_message_update(message_id=message_id, content="Removed mention")
|
||||
|
||||
self.assertEqual(get_apns_badge_count(mentioned_user), 0)
|
||||
self.assertEqual(get_apns_badge_count_future(mentioned_user), 1)
|
||||
|
||||
def _send_notifiable_message_and_login_editor(self) -> int:
|
||||
message_id = self._login_and_send_original_stream_message(
|
||||
content="hello @**Cordelia, Lear's daughter**",
|
||||
)
|
||||
|
||||
iago = self.example_user("iago")
|
||||
self.subscribe(iago, "Scotland")
|
||||
self.login_user(iago)
|
||||
return message_id
|
||||
|
||||
# The next three tests cover notifications for topic and channel
|
||||
# edits. It seems obvious that such edits cannot notify anyone,
|
||||
# but historically we had such bugs - see #39888.
|
||||
def test_topic_edit_does_not_notify_anybody(self) -> None:
|
||||
message_id = self._send_notifiable_message_and_login_editor()
|
||||
|
||||
self._assert_update_does_not_notify_anybody(
|
||||
message_id=message_id,
|
||||
topic="new topic",
|
||||
)
|
||||
|
||||
def test_channel_edit_does_not_notify_anybody(self) -> None:
|
||||
message_id = self._send_notifiable_message_and_login_editor()
|
||||
|
||||
self._assert_update_does_not_notify_anybody(
|
||||
message_id=message_id,
|
||||
stream_id=self.get_stream_id("Denmark"),
|
||||
)
|
||||
|
||||
def test_content_edit_that_also_moves_topic_notifies(self) -> None:
|
||||
message_id = self._login_and_send_original_stream_message(content="no mention")
|
||||
|
||||
info = self._get_queued_data_for_message_update(
|
||||
message_id=message_id,
|
||||
content="now we mention @**Cordelia, Lear's daughter**",
|
||||
topic="new topic",
|
||||
)
|
||||
|
||||
cordelia = self.example_user("cordelia")
|
||||
hamlet = self.example_user("hamlet")
|
||||
expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters(
|
||||
user_id=cordelia.id,
|
||||
acting_user_id=hamlet.id,
|
||||
message_id=message_id,
|
||||
mention_email_notify=True,
|
||||
mention_push_notify=True,
|
||||
already_notified={},
|
||||
)
|
||||
self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs)
|
||||
|
||||
queue_names = [queue_message["queue_name"] for queue_message in info["queue_messages"]]
|
||||
self.assertEqual(
|
||||
queue_names, ["missedmessage_mobile_notifications", "missedmessage_emails"]
|
||||
)
|
||||
Reference in New Issue
Block a user