mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 03:53:50 +00:00 
			
		
		
		
	Previously, we enqueued events to "missedmessage_mobile_notifications" even for users who had no registered push devices. 'handle_push_notification' later used to perform the check & skip if there were no registered devices. This commit avoids putting such events into the "missedmessage_mobile_notifications" queue at all. By doing so, we reduce unnecessary churn.
		
			
				
	
	
		
			735 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			735 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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 PushDevice, 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, content: str) -> None:
 | |
|         url = "/json/messages/" + str(message_id)
 | |
| 
 | |
|         request = dict(
 | |
|             content=content,
 | |
|         )
 | |
| 
 | |
|         with mock.patch("zerver.tornado.event_queue.maybe_enqueue_notifications") as m:
 | |
|             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, content: str, expect_short_circuit: bool = False
 | |
|     ) -> 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)
 | |
| 
 | |
|         request = dict(
 | |
|             content=content,
 | |
|         )
 | |
| 
 | |
|         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)
 | |
|         PushDevice.objects.all().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)
 |