diff --git a/zerver/actions/message_edit.py b/zerver/actions/message_edit.py index a534ace4b9..16eb3f6248 100644 --- a/zerver/actions/message_edit.py +++ b/zerver/actions/message_edit.py @@ -570,6 +570,7 @@ def update_message_content( event["prior_mention_user_ids"] = list(prior_mention_user_ids) event["presence_idle_user_ids"] = filter_presence_idle_user_ids(info.active_user_ids) event["all_bot_user_ids"] = list(info.all_bot_user_ids) + event["push_device_registered_user_ids"] = list(info.push_device_registered_user_ids) if rendering_result.mentions_stream_wildcard: event["stream_wildcard_mention_user_ids"] = list(info.stream_wildcard_mention_user_ids) event["stream_wildcard_mention_in_followed_topic_user_ids"] = list( diff --git a/zerver/actions/message_send.py b/zerver/actions/message_send.py index b54e826079..53ed6dbf50 100644 --- a/zerver/actions/message_send.py +++ b/zerver/actions/message_send.py @@ -5,17 +5,18 @@ from collections.abc import Set as AbstractSet from dataclasses import dataclass from datetime import timedelta from email.headerregistry import Address -from typing import Any, TypedDict +from typing import Any, TypedDict, cast import orjson from django.conf import settings from django.core.exceptions import ValidationError from django.db import IntegrityError, transaction -from django.db.models import F, Q, QuerySet +from django.db.models import Exists, F, OuterRef, Q, QuerySet from django.utils.html import escape from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ from django.utils.translation import override as override_language +from django_stubs_ext import WithAnnotations from zerver.actions.uploads import do_claim_attachments from zerver.actions.user_topics import ( @@ -99,6 +100,8 @@ from zerver.lib.widget import do_widget_post_save_actions from zerver.models import ( Client, Message, + PushDevice, + PushDeviceToken, Realm, Recipient, Stream, @@ -208,6 +211,7 @@ class RecipientInfoResult: all_bot_user_ids: set[int] topic_participant_user_ids: set[int] sender_muted_stream: bool | None + push_device_registered_user_ids: set[int] class ActiveUserDict(TypedDict): @@ -218,6 +222,11 @@ class ActiveUserDict(TypedDict): long_term_idle: bool is_bot: bool bot_type: int | None + has_push_device_registered: bool + + +class UserProfileAnnotations(TypedDict): + has_push_device_registered: bool @dataclass @@ -400,16 +409,40 @@ def get_recipient_info( user_ids = message_to_user_id_set | possibly_mentioned_user_ids if user_ids: - query: QuerySet[UserProfile, ActiveUserDict] = UserProfile.objects.filter( - is_active=True - ).values( - "id", - "enable_online_push_notifications", - "enable_offline_email_notifications", - "enable_offline_push_notifications", - "is_bot", - "bot_type", - "long_term_idle", + query: QuerySet[WithAnnotations[UserProfile, UserProfileAnnotations], ActiveUserDict] = ( + # We use 'cast' because django-stubs says: + # Currently, the mypy plugin can recognize that specific names were passed to `QuerySet.annotate` + # and include them in the type, but does not record the types of these attributes. + # + # The knowledge of the specific annotated fields is not yet used in creating more specific types + # for `QuerySet`'s `values` method, however knowledge that the model was annotated _is_ used to + # create a broader type result type for `values`, and to allow `filter`ing on any field. + # + # ref: https://github.com/typeddjango/django-stubs/blob/de8e0b4dd6588129f0e02f601a71486f42799533/README.md?plain=1#L263 + cast( + "QuerySet[WithAnnotations[UserProfile, UserProfileAnnotations], ActiveUserDict]", + UserProfile.objects.filter(is_active=True) + .annotate( + # Uses index "zerver_pushdevice_user_bouncer_device_id_idx" + # and "zerver_pushdevicetoken_user_id_015e5dc1". + has_push_device_registered=Exists( + PushDevice.objects.filter( + user_id=OuterRef("id"), bouncer_device_id__isnull=False + ) + ) + | Exists(PushDeviceToken.objects.filter(user_id=OuterRef("id"))) + ) + .values( + "id", + "enable_online_push_notifications", + "enable_offline_email_notifications", + "enable_offline_push_notifications", + "is_bot", + "bot_type", + "long_term_idle", + "has_push_device_registered", + ), + ) ) # query_for_ids is fast highly optimized for large queries, and we @@ -487,6 +520,9 @@ def get_recipient_info( # where we determine notifiability of the message for users. all_bot_user_ids = {row["id"] for row in rows if row["is_bot"]} + # Users who have at least one push device registered to receive push notifications. + push_device_registered_user_ids = get_ids_for(lambda r: r["has_push_device_registered"]) + return RecipientInfoResult( active_user_ids=active_user_ids, online_push_user_ids=online_push_user_ids, @@ -508,6 +544,7 @@ def get_recipient_info( all_bot_user_ids=all_bot_user_ids, topic_participant_user_ids=topic_participant_user_ids, sender_muted_stream=sender_muted_stream, + push_device_registered_user_ids=push_device_registered_user_ids, ) @@ -712,6 +749,7 @@ def build_message_send_dict( default_bot_user_ids=info.default_bot_user_ids, service_bot_tuples=info.service_bot_tuples, all_bot_user_ids=info.all_bot_user_ids, + push_device_registered_user_ids=info.push_device_registered_user_ids, topic_wildcard_mention_user_ids=topic_wildcard_mention_user_ids, stream_wildcard_mention_user_ids=stream_wildcard_mention_user_ids, topic_wildcard_mention_in_followed_topic_user_ids=topic_wildcard_mention_in_followed_topic_user_ids, @@ -1124,6 +1162,7 @@ def do_send_messages( stream_wildcard_mention_in_followed_topic_user_ids=send_request.stream_wildcard_mention_in_followed_topic_user_ids, muted_sender_user_ids=send_request.muted_sender_user_ids, all_bot_user_ids=send_request.all_bot_user_ids, + push_device_registered_user_ids=send_request.push_device_registered_user_ids, ) for user_id in send_request.active_user_ids ] @@ -1167,6 +1206,7 @@ def do_send_messages( ), muted_sender_user_ids=list(send_request.muted_sender_user_ids), all_bot_user_ids=list(send_request.all_bot_user_ids), + push_device_registered_user_ids=list(send_request.push_device_registered_user_ids), disable_external_notifications=send_request.disable_external_notifications, realm_host=send_request.realm.host, ) diff --git a/zerver/lib/message.py b/zerver/lib/message.py index 124bf08fa5..f865d3ca92 100644 --- a/zerver/lib/message.py +++ b/zerver/lib/message.py @@ -149,6 +149,7 @@ class SendMessageRequest: default_bot_user_ids: set[int] service_bot_tuples: list[tuple[int, int]] all_bot_user_ids: set[int] + push_device_registered_user_ids: set[int] # IDs of topic participants who should be notified of topic wildcard mention. # The 'user_allows_notifications_in_StreamTopic' with 'wildcard_mentions_notify' # setting ON should return True. diff --git a/zerver/lib/notification_data.py b/zerver/lib/notification_data.py index 8ae5be7537..0e66305125 100644 --- a/zerver/lib/notification_data.py +++ b/zerver/lib/notification_data.py @@ -71,6 +71,7 @@ class UserMessageNotificationsData: stream_wildcard_mention_in_followed_topic_user_ids: set[int], muted_sender_user_ids: set[int], all_bot_user_ids: set[int], + push_device_registered_user_ids: set[int], ) -> "UserMessageNotificationsData": if user_id in all_bot_user_ids: # Don't send any notifications to bots @@ -126,30 +127,46 @@ class UserMessageNotificationsData: and "stream_wildcard_mentioned" in flags ) - dm_push_notify = user_id not in dm_mention_push_disabled_user_ids and private_message + push_device_registered = user_id in push_device_registered_user_ids + dm_push_notify = ( + push_device_registered + and user_id not in dm_mention_push_disabled_user_ids + and private_message + ) mention_push_notify = ( - user_id not in dm_mention_push_disabled_user_ids and "mentioned" in flags + push_device_registered + and user_id not in dm_mention_push_disabled_user_ids + and "mentioned" in flags ) topic_wildcard_mention_push_notify = ( - user_id in topic_wildcard_mention_user_ids + push_device_registered + and user_id in topic_wildcard_mention_user_ids and user_id not in dm_mention_push_disabled_user_ids and "topic_wildcard_mentioned" in flags ) stream_wildcard_mention_push_notify = ( - user_id in stream_wildcard_mention_user_ids + push_device_registered + and user_id in stream_wildcard_mention_user_ids and user_id not in dm_mention_push_disabled_user_ids and "stream_wildcard_mentioned" in flags ) topic_wildcard_mention_in_followed_topic_push_notify = ( - user_id in topic_wildcard_mention_in_followed_topic_user_ids + push_device_registered + and user_id in topic_wildcard_mention_in_followed_topic_user_ids and user_id not in dm_mention_push_disabled_user_ids and "topic_wildcard_mentioned" in flags ) stream_wildcard_mention_in_followed_topic_push_notify = ( - user_id in stream_wildcard_mention_in_followed_topic_user_ids + push_device_registered + and user_id in stream_wildcard_mention_in_followed_topic_user_ids and user_id not in dm_mention_push_disabled_user_ids and "stream_wildcard_mentioned" in flags ) + online_push_enabled = push_device_registered and user_id in online_push_user_ids + stream_push_notify = push_device_registered and user_id in stream_push_user_ids + followed_topic_push_notify = ( + push_device_registered and user_id in followed_topic_push_user_ids + ) return cls( user_id=user_id, dm_email_notify=dm_email_notify, @@ -160,10 +177,10 @@ class UserMessageNotificationsData: mention_push_notify=mention_push_notify, topic_wildcard_mention_push_notify=topic_wildcard_mention_push_notify, stream_wildcard_mention_push_notify=stream_wildcard_mention_push_notify, - online_push_enabled=user_id in online_push_user_ids, - stream_push_notify=user_id in stream_push_user_ids, + online_push_enabled=online_push_enabled, + stream_push_notify=stream_push_notify, stream_email_notify=user_id in stream_email_user_ids, - followed_topic_push_notify=user_id in followed_topic_push_user_ids, + followed_topic_push_notify=followed_topic_push_notify, followed_topic_email_notify=user_id in followed_topic_email_user_ids, topic_wildcard_mention_in_followed_topic_push_notify=topic_wildcard_mention_in_followed_topic_push_notify, topic_wildcard_mention_in_followed_topic_email_notify=topic_wildcard_mention_in_followed_topic_email_notify, diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index 31f193ccb4..b3fa23860d 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -1917,6 +1917,23 @@ Output: realm, licenses, licenses_at_next_renewal, CustomerPlan.BILLING_SCHEDULE_MONTHLY ) + def register_push_device(self, user_profile_id: int) -> None: + PushDevice.objects.create( + user_id=user_profile_id, + push_account_id=10, + bouncer_device_id=1, + token_kind=PushDevice.TokenKind.FCM, + push_public_key="n4WTVqj8KH6u0vScRycR4TqRaHhFeJ0POvMb8LCu8iI=", + ) + + def register_push_device_token(self, user_profile_id: int) -> None: + PushDeviceToken.objects.create( + user_id=user_profile_id, + kind=PushDeviceToken.APNS, + token="test-token", + ios_app_id="com.zulip.flutter", + ) + def create_user_notifications_data_object( self, *, user_id: int, **kwargs: Any ) -> UserMessageNotificationsData: diff --git a/zerver/tests/test_event_queue.py b/zerver/tests/test_event_queue.py index c856a46e08..deb708c21d 100644 --- a/zerver/tests/test_event_queue.py +++ b/zerver/tests/test_event_queue.py @@ -16,7 +16,7 @@ from zerver.actions.user_topics import do_set_user_topic_visibility_policy from zerver.lib.cache import cache_delete, get_muting_users_cache_key from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import HostRequestMock, dummy_handler, mock_queue_publish -from zerver.models import Recipient, Subscription, UserProfile, UserTopic +from zerver.models import PushDevice, Recipient, Subscription, UserProfile, UserTopic from zerver.models.streams import get_stream from zerver.tornado.event_queue import ( ClientDescriptor, @@ -194,6 +194,7 @@ class MissedMessageHookTest(ZulipTestCase): do_change_user_setting( self.user_profile, "enable_online_push_notifications", False, acting_user=None ) + self.register_push_device(self.user_profile.id) self.iago = self.example_user("iago") self.client_descriptor = self.allocate_event_queue(self.user_profile) self.assertTrue(self.client_descriptor.event_queue.empty()) @@ -306,6 +307,28 @@ class MissedMessageHookTest(ZulipTestCase): already_notified={"email_notified": True, "push_notified": False}, ) + def test_no_push_device_registered(self) -> None: + # When `enable_offline_push_notifications` is `true` but no push device registered, + # push notifications should not be sent. + do_change_user_setting( + self.user_profile, "enable_offline_push_notifications", True, acting_user=None + ) + PushDevice.objects.all().delete() + msg_id = self.send_personal_message(self.iago, self.user_profile) + with mock.patch("zerver.tornado.event_queue.maybe_enqueue_notifications") as mock_enqueue: + missedmessage_hook(self.user_profile.id, self.client_descriptor, True) + mock_enqueue.assert_called_once() + args_dict = mock_enqueue.call_args_list[0][1] + + self.assert_maybe_enqueue_notifications_call_args( + args_dict=args_dict, + message_id=msg_id, + user_id=self.user_profile.id, + dm_email_notify=True, + dm_push_notify=False, + already_notified={"email_notified": True, "push_notified": False}, + ) + def test_topic_wildcard_mention(self) -> None: # By default, topic wildcard mentions should send notifications, just like regular mentions self.send_stream_message(self.user_profile, "Denmark") diff --git a/zerver/tests/test_handle_push_notification.py b/zerver/tests/test_handle_push_notification.py index 598bf3d8e9..aa60397ce8 100644 --- a/zerver/tests/test_handle_push_notification.py +++ b/zerver/tests/test_handle_push_notification.py @@ -875,6 +875,7 @@ class HandlePushNotificationTest(PushNotificationTestCase): othello = self.example_user("othello") cordelia = self.example_user("cordelia") zulip_realm = get_realm("zulip") + self.register_push_device_token(self.user_profile.id) # user groups having upto 'MAX_GROUP_SIZE_FOR_MENTION_REACTIVATION' # members are small user groups. diff --git a/zerver/tests/test_message_edit_notifications.py b/zerver/tests/test_message_edit_notifications.py index 661e0d0be8..a65bb13237 100644 --- a/zerver/tests/test_message_edit_notifications.py +++ b/zerver/tests/test_message_edit_notifications.py @@ -3,19 +3,26 @@ 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 Subscription, UserPresence, UserTopic +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) @@ -223,6 +230,37 @@ class EditMessageSideEffectsTest(ZulipTestCase): 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: @@ -632,6 +670,7 @@ class EditMessageSideEffectsTest(ZulipTestCase): 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) @@ -677,6 +716,7 @@ class EditMessageSideEffectsTest(ZulipTestCase): 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) diff --git a/zerver/tests/test_message_flags.py b/zerver/tests/test_message_flags.py index 5b73c06cd0..e1fe588b63 100644 --- a/zerver/tests/test_message_flags.py +++ b/zerver/tests/test_message_flags.py @@ -172,18 +172,22 @@ class UnreadCountTests(ZulipTestCase): @override def setUp(self) -> None: super().setUp() - with mock.patch( - "zerver.lib.push_notifications.push_notifications_configured", return_value=True - ) as mock_push_notifications_configured: + hamlet = self.example_user("hamlet") + self.register_push_device(hamlet.id) + with ( + mock.patch( + "zerver.lib.push_notifications.send_push_notifications" + ) as mock_send_push_notifications, + mock.patch( + "zerver.lib.push_notifications.push_notifications_configured", return_value=True + ) as mock_push_notifications_configured, + ): self.unread_msg_ids = [ - self.send_personal_message( - self.example_user("iago"), self.example_user("hamlet"), "hello" - ), - self.send_personal_message( - self.example_user("iago"), self.example_user("hamlet"), "hello2" - ), + self.send_personal_message(self.example_user("iago"), hamlet, "hello"), + self.send_personal_message(self.example_user("iago"), hamlet, "hello2"), ] mock_push_notifications_configured.assert_called() + mock_send_push_notifications.assert_called() # Sending a new message results in unread UserMessages being created # for users other than sender. @@ -858,11 +862,13 @@ class PushNotificationMarkReadFlowsTest(ZulipTestCase): .values_list("message_id", flat=True) ) + @mock.patch("zerver.lib.push_notifications.send_push_notifications") @mock.patch("zerver.lib.push_notifications.push_notifications_configured", return_value=True) def test_track_active_mobile_push_notifications( - self, mock_push_notifications: mock.MagicMock + self, + mock_push_notifications: mock.MagicMock, + mock_send_push_notifications: mock.MagicMock, ) -> None: - mock_push_notifications.return_value = True self.login("hamlet") user_profile = self.example_user("hamlet") cordelia = self.example_user("cordelia") @@ -870,6 +876,7 @@ class PushNotificationMarkReadFlowsTest(ZulipTestCase): self.subscribe(cordelia, "test_stream") second_stream = self.subscribe(user_profile, "second_stream") self.subscribe(cordelia, "second_stream") + self.register_push_device(user_profile.id) property_name = "push_notifications" result = self.api_post( @@ -942,6 +949,7 @@ class PushNotificationMarkReadFlowsTest(ZulipTestCase): result = self.client_post("/json/mark_all_as_read", {}) self.assertEqual(self.get_mobile_push_notification_ids(user_profile), []) mock_push_notifications.assert_called() + mock_send_push_notifications.assert_called() class MarkAllAsReadEndpointTest(ZulipTestCase): diff --git a/zerver/tests/test_notification_data.py b/zerver/tests/test_notification_data.py index a36f72bb27..42be24cc65 100644 --- a/zerver/tests/test_notification_data.py +++ b/zerver/tests/test_notification_data.py @@ -366,6 +366,7 @@ class TestNotificationData(ZulipTestCase): followed_topic_push_user_ids=set(), topic_wildcard_mention_in_followed_topic_user_ids=set(), stream_wildcard_mention_in_followed_topic_user_ids=set(), + push_device_registered_user_ids=set(), ) self.assertEqual(user_data.is_notifiable(acting_user_id=1000, idle=True), notifiable) @@ -474,3 +475,39 @@ class TestNotificationData(ZulipTestCase): cordelia.id: hamlet_and_cordelia.id, }, ) + + def test_push_notifiability_when_no_push_device_registered(self) -> None: + user_data = UserMessageNotificationsData.from_user_id_sets( + user_id=9, + flags=[ + "mentioned", + "topic_wildcard_mentioned", + "stream_wildcard_mentioned", + "topic_wildcard_mentioned", + "stream_wildcard_mentioned", + ], + private_message=False, + disable_external_notifications=False, + online_push_user_ids={9}, + dm_mention_email_disabled_user_ids=set(), + dm_mention_push_disabled_user_ids=set(), + all_bot_user_ids=set(), + muted_sender_user_ids=set(), + stream_email_user_ids=set(), + stream_push_user_ids={9}, + topic_wildcard_mention_user_ids={9}, + stream_wildcard_mention_user_ids={9}, + followed_topic_email_user_ids=set(), + followed_topic_push_user_ids={9}, + topic_wildcard_mention_in_followed_topic_user_ids={9}, + stream_wildcard_mention_in_followed_topic_user_ids={9}, + push_device_registered_user_ids=set(), + ) + + self.assertFalse(user_data.online_push_enabled) + self.assertFalse(user_data.stream_push_notify) + self.assertFalse(user_data.topic_wildcard_mention_push_notify) + self.assertFalse(user_data.stream_wildcard_mention_push_notify) + self.assertFalse(user_data.followed_topic_push_notify) + self.assertFalse(user_data.topic_wildcard_mention_in_followed_topic_push_notify) + self.assertFalse(user_data.stream_wildcard_mention_in_followed_topic_push_notify) diff --git a/zerver/tests/test_push_notifications.py b/zerver/tests/test_push_notifications.py index ba6f3c4d60..ec997396b0 100644 --- a/zerver/tests/test_push_notifications.py +++ b/zerver/tests/test_push_notifications.py @@ -1408,9 +1408,13 @@ class TestAPNs(PushNotificationTestCase): logger.output, ) + @mock.patch("zerver.lib.push_notifications.send_push_notifications_legacy") @mock.patch("zerver.lib.push_notifications.push_notifications_configured", return_value=True) - def test_apns_badge_count(self, mock_push_notifications: mock.MagicMock) -> None: + def test_apns_badge_count( + self, mock_push_notifications: mock.MagicMock, mock_send_push_notifications: mock.MagicMock + ) -> None: user_profile = self.example_user("othello") + self.register_push_device_token(user_profile.id) # Test APNs badge count for personal messages. message_ids = [ self.send_personal_message(self.sender, user_profile, "Content of message") @@ -1439,6 +1443,7 @@ class TestAPNs(PushNotificationTestCase): self.assertEqual(get_apns_badge_count_future(user_profile), num_messages - i - 1) mock_push_notifications.assert_called() + mock_send_push_notifications.assert_called() class TestGetAPNsPayload(PushNotificationTestCase): @@ -1516,11 +1521,15 @@ class TestGetAPNsPayload(PushNotificationTestCase): } self.assertDictEqual(payload, expected) + @mock.patch("zerver.lib.push_notifications.send_push_notifications_legacy") @mock.patch("zerver.lib.push_notifications.push_notifications_configured", return_value=True) def test_get_message_payload_apns_group_direct_message( - self, mock_push_notifications: mock.MagicMock + self, + mock_push_notifications: mock.MagicMock, + mock_send_push_notifications: mock.MagicMock, ) -> None: user_profile = self.example_user("othello") + self.register_push_device_token(user_profile.id) message_id = self.send_group_direct_message( self.sender, [self.example_user("othello"), self.example_user("cordelia")] ) @@ -1558,6 +1567,7 @@ class TestGetAPNsPayload(PushNotificationTestCase): } self.assertDictEqual(payload, expected) mock_push_notifications.assert_called() + mock_send_push_notifications.assert_called() def _test_get_message_payload_apns_stream_message( self, trigger: str, empty_string_topic: bool = False diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index 17f5481462..754bda7e9d 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -2348,6 +2348,7 @@ class RecipientInfoTest(ZulipTestCase): all_bot_user_ids=set(), topic_participant_user_ids=set(), sender_muted_stream=False, + push_device_registered_user_ids=set(), ) self.assertEqual(info, expected_info) diff --git a/zerver/tornado/event_queue.py b/zerver/tornado/event_queue.py index da8b431b84..8c5479170f 100644 --- a/zerver/tornado/event_queue.py +++ b/zerver/tornado/event_queue.py @@ -1122,6 +1122,7 @@ def process_message_event( ) muted_sender_user_ids = set(event_template.get("muted_sender_user_ids", [])) all_bot_user_ids = set(event_template.get("all_bot_user_ids", [])) + push_device_registered_user_ids = set(event_template.get("push_device_registered_user_ids", [])) disable_external_notifications = event_template.get("disable_external_notifications", False) user_ids_without_access_to_sender = set( event_template.get("user_ids_without_access_to_sender", []) @@ -1192,6 +1193,7 @@ def process_message_event( stream_wildcard_mention_in_followed_topic_user_ids=stream_wildcard_mention_in_followed_topic_user_ids, muted_sender_user_ids=muted_sender_user_ids, all_bot_user_ids=all_bot_user_ids, + push_device_registered_user_ids=push_device_registered_user_ids, ) # Calling asdict would be slow, as it does a deep copy; pull @@ -1401,6 +1403,7 @@ def process_message_update_event( ) muted_sender_user_ids = set(event_template.pop("muted_sender_user_ids", [])) all_bot_user_ids = set(event_template.pop("all_bot_user_ids", [])) + push_device_registered_user_ids = set(event_template.pop("push_device_registered_user_ids", [])) disable_external_notifications = event_template.pop("disable_external_notifications", False) online_push_user_ids = set(event_template.pop("online_push_user_ids", [])) stream_name = event_template.get("stream_name") @@ -1445,6 +1448,7 @@ def process_message_update_event( stream_wildcard_mention_in_followed_topic_user_ids=stream_wildcard_mention_in_followed_topic_user_ids, muted_sender_user_ids=muted_sender_user_ids, all_bot_user_ids=all_bot_user_ids, + push_device_registered_user_ids=push_device_registered_user_ids, ) maybe_enqueue_notifications_for_message_update(