mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 12:03:46 +00:00 
			
		
		
		
	Mobile push notification payloads for APNs no longer contain the `server` and `realm_id` fields, which were unused.
		
			
				
	
	
		
			1049 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1049 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from unittest import mock
 | |
| 
 | |
| import responses
 | |
| from django.conf import settings
 | |
| from django.http.response import ResponseHeaders
 | |
| from django.test import override_settings
 | |
| from requests.exceptions import ConnectionError
 | |
| from requests.models import PreparedRequest
 | |
| from typing_extensions import override
 | |
| 
 | |
| from analytics.models import RealmCount
 | |
| from zerver.actions.message_delete import do_delete_messages
 | |
| from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group
 | |
| 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 (
 | |
|     UserPushIdentityCompat,
 | |
|     handle_push_notification,
 | |
|     handle_remove_push_notification,
 | |
| )
 | |
| from zerver.lib.remote_server import PushNotificationBouncerRetryLaterError
 | |
| from zerver.lib.test_classes import PushNotificationTestCase
 | |
| from zerver.lib.test_helpers import activate_push_notification_service
 | |
| from zerver.models import PushDeviceToken, Recipient, UserMessage, UserTopic
 | |
| from zerver.models.realms import get_realm
 | |
| from zerver.models.scheduled_jobs import NotificationTriggers
 | |
| from zerver.models.streams import get_stream
 | |
| from zilencer.views import DevicesToCleanUpDict
 | |
| 
 | |
| if settings.ZILENCER_ENABLED:
 | |
|     from zilencer.models import RemotePushDeviceToken
 | |
| 
 | |
| 
 | |
| class HandlePushNotificationTest(PushNotificationTestCase):
 | |
|     DEFAULT_SUBDOMAIN = ""
 | |
| 
 | |
|     def soft_deactivate_main_user(self) -> None:
 | |
|         self.user_profile = self.example_user("hamlet")
 | |
|         self.soft_deactivate_user(self.user_profile)
 | |
| 
 | |
|     @override
 | |
|     def request_callback(self, request: PreparedRequest) -> tuple[int, ResponseHeaders, bytes]:
 | |
|         assert request.url is not None  # allow mypy to infer url is present.
 | |
|         assert settings.ZULIP_SERVICES_URL is not None
 | |
|         local_url = request.url.replace(settings.ZULIP_SERVICES_URL, "")
 | |
|         assert isinstance(request.body, bytes)
 | |
|         result = self.uuid_post(
 | |
|             self.server_uuid, local_url, request.body, content_type="application/json"
 | |
|         )
 | |
|         return (result.status_code, result.headers, result.content)
 | |
| 
 | |
|     @activate_push_notification_service()
 | |
|     @responses.activate
 | |
|     def test_end_to_end(self) -> None:
 | |
|         self.add_mock_response()
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
| 
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=self.user_profile,
 | |
|             message=message,
 | |
|         )
 | |
| 
 | |
|         missed_message = {
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
|         with (
 | |
|             self.mock_fcm() as (
 | |
|                 mock_fcm_app,
 | |
|                 mock_fcm_messaging,
 | |
|             ),
 | |
|             self.mock_apns() as (apns_context, send_notification),
 | |
|             mock.patch(
 | |
|                 "corporate.lib.stripe.RemoteRealmBillingSession.current_count_for_billed_licenses",
 | |
|                 return_value=10,
 | |
|             ),
 | |
|             self.assertLogs("zerver.lib.push_notifications", level="INFO") as pn_logger,
 | |
|             self.assertLogs("zilencer.views", level="INFO") as views_logger,
 | |
|         ):
 | |
|             apns_devices = list(
 | |
|                 RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.APNS)
 | |
|                 .order_by("id")
 | |
|                 .values_list("token", flat=True)
 | |
|             )
 | |
|             fcm_devices = list(
 | |
|                 RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.FCM)
 | |
|                 .order_by("id")
 | |
|                 .values_list("token", flat=True)
 | |
|             )
 | |
|             mock_fcm_messaging.send_each.return_value = self.make_fcm_success_response(fcm_devices)
 | |
|             send_notification.return_value.is_successful = True
 | |
|             handle_push_notification(self.user_profile.id, missed_message)
 | |
|             self.assertEqual(
 | |
|                 {
 | |
|                     (args[0][0].device_token, args[0][0].apns_topic)
 | |
|                     for args in send_notification.call_args_list
 | |
|                 },
 | |
|                 {
 | |
|                     (device.token, device.ios_app_id)
 | |
|                     for device in RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.APNS)
 | |
|                 },
 | |
|             )
 | |
|             self.assertEqual(
 | |
|                 views_logger.output,
 | |
|                 [
 | |
|                     "INFO:zilencer.views:"
 | |
|                     f"Sending mobile push notifications for remote user 6cde5f7a-1f7e-4978-9716-49f69ebfc9fe:<id:{self.user_profile.id}><uuid:{self.user_profile.uuid}>: "
 | |
|                     f"{len(fcm_devices)} via FCM devices, {len(apns_devices)} via APNs devices",
 | |
|                 ],
 | |
|             )
 | |
|             for token in apns_devices:
 | |
|                 self.assertIn(
 | |
|                     "INFO:zerver.lib.push_notifications:"
 | |
|                     f"APNs: Success sending for user <id:{self.user_profile.id}><uuid:{self.user_profile.uuid}> to device {token}",
 | |
|                     pn_logger.output,
 | |
|                 )
 | |
|             for idx, token in enumerate(fcm_devices):
 | |
|                 self.assertIn(
 | |
|                     f"INFO:zerver.lib.push_notifications:FCM: Sent message with ID: {idx} to {token}",
 | |
|                     pn_logger.output,
 | |
|                 )
 | |
| 
 | |
|             remote_realm_count = RealmCount.objects.values("property", "subgroup", "value").last()
 | |
|             self.assertEqual(
 | |
|                 remote_realm_count,
 | |
|                 dict(
 | |
|                     property="mobile_pushes_sent::day",
 | |
|                     subgroup=None,
 | |
|                     value=len(fcm_devices) + len(apns_devices),
 | |
|                 ),
 | |
|             )
 | |
| 
 | |
|             self.assertIn(
 | |
|                 "INFO:zerver.lib.push_notifications:"
 | |
|                 f"Skipping E2EE push notifications for user {self.user_profile.id} because there are no registered devices",
 | |
|                 pn_logger.output,
 | |
|             )
 | |
| 
 | |
|     @activate_push_notification_service()
 | |
|     @responses.activate
 | |
|     def test_end_to_end_failure_due_to_no_plan(self) -> None:
 | |
|         self.add_mock_response()
 | |
| 
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
| 
 | |
|         self.server.last_api_feature_level = 237
 | |
|         self.server.save()
 | |
| 
 | |
|         realm = self.user_profile.realm
 | |
|         realm.push_notifications_enabled = True
 | |
|         realm.save()
 | |
| 
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=self.user_profile,
 | |
|             message=message,
 | |
|         )
 | |
| 
 | |
|         missed_message = {
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
|         with (
 | |
|             mock.patch(
 | |
|                 "corporate.lib.stripe.RemoteRealmBillingSession.current_count_for_billed_licenses",
 | |
|                 return_value=100,
 | |
|             ) as mock_current_count,
 | |
|             self.assertLogs("zerver.lib.push_notifications", level="INFO") as pn_logger,
 | |
|             self.assertLogs("zilencer.views", level="INFO"),
 | |
|         ):
 | |
|             handle_push_notification(self.user_profile.id, missed_message)
 | |
| 
 | |
|             self.assertEqual(
 | |
|                 pn_logger.output,
 | |
|                 [
 | |
|                     f"INFO:zerver.lib.push_notifications:Sending push notifications to mobile clients for user {self.user_profile.id}",
 | |
|                     "WARNING:zerver.lib.push_notifications:Bouncer refused to send push notification: Your plan doesn't allow sending push notifications. Reason provided by the server: Push notifications access with 10+ users requires signing up for a plan. https://zulip.com/plans/",
 | |
|                     f"INFO:zerver.lib.push_notifications:Skipping E2EE push notifications for user {self.user_profile.id} because there are no registered devices",
 | |
|                 ],
 | |
|             )
 | |
|             realm.refresh_from_db()
 | |
|             self.assertEqual(realm.push_notifications_enabled, False)
 | |
|             self.assertEqual(realm.push_notifications_enabled_end_timestamp, None)
 | |
| 
 | |
|             # Now verify the flag will correctly get flipped back if the server stops
 | |
|             # rejecting our notification.
 | |
| 
 | |
|             # This will put us within the allowed number of users to use push notifications
 | |
|             # for free, so the server will accept our next request.
 | |
|             mock_current_count.return_value = 5
 | |
| 
 | |
|             new_message_id = self.send_personal_message(
 | |
|                 self.example_user("othello"), self.user_profile
 | |
|             )
 | |
|             new_missed_message = {
 | |
|                 "message_id": new_message_id,
 | |
|                 "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|             }
 | |
| 
 | |
|             handle_push_notification(self.user_profile.id, new_missed_message)
 | |
|             self.assertIn(
 | |
|                 f"Sent mobile push notifications for user {self.user_profile.id}",
 | |
|                 pn_logger.output[-2],
 | |
|             )
 | |
|             realm.refresh_from_db()
 | |
|             self.assertEqual(realm.push_notifications_enabled, True)
 | |
|             self.assertEqual(realm.push_notifications_enabled_end_timestamp, None)
 | |
| 
 | |
|     @activate_push_notification_service()
 | |
|     @responses.activate
 | |
|     def test_unregistered_client(self) -> None:
 | |
|         self.add_mock_response()
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
| 
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=self.user_profile,
 | |
|             message=message,
 | |
|         )
 | |
| 
 | |
|         missed_message = {
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
|         with (
 | |
|             self.mock_fcm() as (
 | |
|                 mock_fcm_app,
 | |
|                 mock_fcm_messaging,
 | |
|             ),
 | |
|             self.mock_apns() as (apns_context, send_notification),
 | |
|             mock.patch(
 | |
|                 "corporate.lib.stripe.RemoteRealmBillingSession.current_count_for_billed_licenses",
 | |
|                 return_value=10,
 | |
|             ),
 | |
|             self.assertLogs("zerver.lib.push_notifications", level="INFO") as pn_logger,
 | |
|             self.assertLogs("zilencer.views", level="INFO") as views_logger,
 | |
|         ):
 | |
|             apns_devices = list(
 | |
|                 RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.APNS)
 | |
|                 .order_by("id")
 | |
|                 .values_list("token", flat=True)
 | |
|             )
 | |
|             fcm_devices = list(
 | |
|                 RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.FCM)
 | |
|                 .order_by("id")
 | |
|                 .values_list("token", flat=True)
 | |
|             )
 | |
| 
 | |
|             # Reset the local registrations for the user to make them compatible
 | |
|             # with the RemotePushDeviceToken entries.
 | |
|             PushDeviceToken.objects.filter(kind=PushDeviceToken.APNS).delete()
 | |
|             [
 | |
|                 PushDeviceToken.objects.create(
 | |
|                     kind=PushDeviceToken.APNS,
 | |
|                     token=device.token,
 | |
|                     user=self.user_profile,
 | |
|                     ios_app_id=device.ios_app_id,
 | |
|                 )
 | |
|                 for device in RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.APNS)
 | |
|             ]
 | |
|             PushDeviceToken.objects.filter(kind=PushDeviceToken.FCM).delete()
 | |
|             [
 | |
|                 PushDeviceToken.objects.create(
 | |
|                     kind=PushDeviceToken.FCM,
 | |
|                     token=device.token,
 | |
|                     user=self.user_profile,
 | |
|                     ios_app_id=device.ios_app_id,
 | |
|                 )
 | |
|                 for device in RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.FCM)
 | |
|             ]
 | |
| 
 | |
|             mock_fcm_messaging.send_each.return_value = self.make_fcm_success_response(
 | |
|                 [fcm_devices[0]]
 | |
|             )
 | |
|             send_notification.return_value.is_successful = False
 | |
|             send_notification.return_value.description = "Unregistered"
 | |
| 
 | |
|             # Ensure the setup is as expected:
 | |
|             self.assertNotEqual(
 | |
|                 PushDeviceToken.objects.filter(kind=PushDeviceToken.APNS).count(), 0
 | |
|             )
 | |
|             handle_push_notification(self.user_profile.id, missed_message)
 | |
|             self.assertEqual(
 | |
|                 views_logger.output,
 | |
|                 [
 | |
|                     "INFO:zilencer.views:"
 | |
|                     f"Sending mobile push notifications for remote user 6cde5f7a-1f7e-4978-9716-49f69ebfc9fe:<id:{self.user_profile.id}><uuid:{self.user_profile.uuid}>: "
 | |
|                     f"{len(fcm_devices)} via FCM devices, {len(apns_devices)} via APNs devices",
 | |
|                 ],
 | |
|             )
 | |
|             for token in apns_devices:
 | |
|                 self.assertIn(
 | |
|                     "INFO:zerver.lib.push_notifications:"
 | |
|                     f"APNs: Removing invalid/expired token {token} (Unregistered)",
 | |
|                     pn_logger.output,
 | |
|                 )
 | |
|             self.assertIn(
 | |
|                 "INFO:zerver.lib.push_notifications:Deleting push tokens based on response from bouncer: "
 | |
|                 f"Android: [], Apple: {sorted(apns_devices)}",
 | |
|                 pn_logger.output,
 | |
|             )
 | |
|             self.assertEqual(
 | |
|                 RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.APNS).count(), 0
 | |
|             )
 | |
|             # Local registrations have also been deleted:
 | |
|             self.assertEqual(PushDeviceToken.objects.filter(kind=PushDeviceToken.APNS).count(), 0)
 | |
| 
 | |
|     @activate_push_notification_service()
 | |
|     @responses.activate
 | |
|     def test_connection_error(self) -> None:
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
| 
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=self.user_profile,
 | |
|             message=message,
 | |
|         )
 | |
| 
 | |
|         missed_message = {
 | |
|             "user_profile_id": self.user_profile.id,
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
|         assert settings.ZULIP_SERVICES_URL is not None
 | |
|         URL = settings.ZULIP_SERVICES_URL + "/api/v1/remotes/push/notify"
 | |
|         responses.add(responses.POST, URL, body=ConnectionError())
 | |
|         with self.assertRaises(PushNotificationBouncerRetryLaterError):
 | |
|             handle_push_notification(self.user_profile.id, missed_message)
 | |
| 
 | |
|     @mock.patch("zerver.lib.push_notifications.push_notifications_configured", return_value=True)
 | |
|     @override_settings(ZULIP_SERVICE_PUSH_NOTIFICATIONS=False, ZULIP_SERVICES=set())
 | |
|     def test_read_message(self, mock_push_notifications: mock.MagicMock) -> None:
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
| 
 | |
|         user_profile = self.example_user("hamlet")
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
| 
 | |
|         usermessage = UserMessage.objects.create(
 | |
|             user_profile=user_profile,
 | |
|             message=message,
 | |
|         )
 | |
| 
 | |
|         missed_message = {
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
| 
 | |
|         # If the message is unread, we should send push notifications.
 | |
|         with (
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_apple_push_notification", return_value=1
 | |
|             ) as mock_send_apple,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_android_push_notification", return_value=1
 | |
|             ) as mock_send_android,
 | |
|         ):
 | |
|             handle_push_notification(user_profile.id, missed_message)
 | |
|         mock_send_apple.assert_called_once()
 | |
|         mock_send_android.assert_called_once()
 | |
| 
 | |
|         # If the message has been read, don't send push notifications.
 | |
|         usermessage.flags.read = True
 | |
|         usermessage.save()
 | |
|         with (
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_apple_push_notification", return_value=1
 | |
|             ) as mock_send_apple,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_android_push_notification", return_value=1
 | |
|             ) as mock_send_android,
 | |
|         ):
 | |
|             handle_push_notification(user_profile.id, missed_message)
 | |
|         mock_send_apple.assert_not_called()
 | |
|         mock_send_android.assert_not_called()
 | |
| 
 | |
|     def test_deleted_message(self) -> None:
 | |
|         """Simulates the race where message is deleted before handling push notifications"""
 | |
|         user_profile = self.example_user("hamlet")
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=user_profile,
 | |
|             flags=UserMessage.flags.read,
 | |
|             message=message,
 | |
|         )
 | |
|         missed_message = {
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
|         # Now, delete the message the normal way
 | |
|         do_delete_messages(user_profile.realm, [message], acting_user=None)
 | |
| 
 | |
|         # This mock.patch() should be assertNoLogs once that feature
 | |
|         # is added to Python.
 | |
|         with (
 | |
|             mock.patch("zerver.lib.push_notifications.uses_notification_bouncer") as mock_check,
 | |
|             mock.patch("logging.error") as mock_logging_error,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.push_notifications_configured", return_value=True
 | |
|             ) as mock_push_notifications,
 | |
|         ):
 | |
|             handle_push_notification(user_profile.id, missed_message)
 | |
|             mock_push_notifications.assert_called_once()
 | |
|             # Check we didn't proceed through and didn't log anything.
 | |
|             mock_check.assert_not_called()
 | |
|             mock_logging_error.assert_not_called()
 | |
| 
 | |
|     def test_missing_message(self) -> None:
 | |
|         """Simulates the race where message is missing when handling push notifications"""
 | |
|         user_profile = self.example_user("hamlet")
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=user_profile,
 | |
|             flags=UserMessage.flags.read,
 | |
|             message=message,
 | |
|         )
 | |
|         missed_message = {
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
|         # Now delete the message forcefully, so it just doesn't exist.
 | |
|         message.delete()
 | |
| 
 | |
|         # This should log an error
 | |
|         with (
 | |
|             mock.patch("zerver.lib.push_notifications.uses_notification_bouncer") as mock_check,
 | |
|             self.assertLogs(level="INFO") as mock_logging_info,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.push_notifications_configured", return_value=True
 | |
|             ) as mock_push_notifications,
 | |
|         ):
 | |
|             handle_push_notification(user_profile.id, missed_message)
 | |
|             mock_push_notifications.assert_called_once()
 | |
|             # Check we didn't proceed through.
 | |
|             mock_check.assert_not_called()
 | |
|             self.assertEqual(
 | |
|                 mock_logging_info.output,
 | |
|                 [
 | |
|                     f"INFO:root:Unexpected message access failure handling push notifications: {user_profile.id} {missed_message['message_id']}"
 | |
|                 ],
 | |
|             )
 | |
| 
 | |
|     def test_send_notifications_to_bouncer(self) -> None:
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
| 
 | |
|         user_profile = self.user_profile
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=user_profile,
 | |
|             message=message,
 | |
|         )
 | |
| 
 | |
|         missed_message = {
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
|         with (
 | |
|             activate_push_notification_service(),
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.get_message_payload_apns",
 | |
|                 return_value={"apns": True},
 | |
|             ),
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.get_message_payload_gcm",
 | |
|                 return_value=({"gcm": True}, {}),
 | |
|             ),
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_json_to_push_bouncer",
 | |
|                 return_value=dict(
 | |
|                     total_android_devices=3,
 | |
|                     total_apple_devices=5,
 | |
|                     deleted_devices=DevicesToCleanUpDict(android_devices=[], apple_devices=[]),
 | |
|                     realm=None,
 | |
|                 ),
 | |
|             ) as mock_send,
 | |
|             self.assertLogs("zerver.lib.push_notifications", level="INFO") as mock_logging_info,
 | |
|         ):
 | |
|             handle_push_notification(user_profile.id, missed_message)
 | |
|             mock_send.assert_called_with(
 | |
|                 "POST",
 | |
|                 "push/notify",
 | |
|                 {
 | |
|                     "user_uuid": str(user_profile.uuid),
 | |
|                     "user_id": user_profile.id,
 | |
|                     "realm_uuid": str(user_profile.realm.uuid),
 | |
|                     "apns_payload": {"apns": True},
 | |
|                     "gcm_payload": {"gcm": True},
 | |
|                     "gcm_options": {},
 | |
|                     "android_devices": list(
 | |
|                         PushDeviceToken.objects.filter(user=user_profile, kind=PushDeviceToken.FCM)
 | |
|                         .order_by("id")
 | |
|                         .values_list("token", flat=True)
 | |
|                     ),
 | |
|                     "apple_devices": list(
 | |
|                         PushDeviceToken.objects.filter(user=user_profile, kind=PushDeviceToken.APNS)
 | |
|                         .order_by("id")
 | |
|                         .values_list("token", flat=True)
 | |
|                     ),
 | |
|                 },
 | |
|             )
 | |
| 
 | |
|             self.assertEqual(
 | |
|                 mock_logging_info.output,
 | |
|                 [
 | |
|                     f"INFO:zerver.lib.push_notifications:Sending push notifications to mobile clients for user {user_profile.id}",
 | |
|                     f"INFO:zerver.lib.push_notifications:Sent mobile push notifications for user {user_profile.id} through bouncer: 3 via FCM devices, 5 via APNs devices",
 | |
|                     f"INFO:zerver.lib.push_notifications:Skipping E2EE push notifications for user {self.user_profile.id} because there are no registered devices",
 | |
|                 ],
 | |
|             )
 | |
| 
 | |
|     def test_non_bouncer_push(self) -> None:
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=self.user_profile,
 | |
|             message=message,
 | |
|         )
 | |
| 
 | |
|         android_devices = list(
 | |
|             PushDeviceToken.objects.filter(user=self.user_profile, kind=PushDeviceToken.FCM)
 | |
|         )
 | |
| 
 | |
|         apple_devices = list(
 | |
|             PushDeviceToken.objects.filter(user=self.user_profile, kind=PushDeviceToken.APNS)
 | |
|         )
 | |
| 
 | |
|         missed_message = {
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
|         with (
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.get_message_payload_apns",
 | |
|                 return_value={"apns": True},
 | |
|             ),
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.get_message_payload_gcm",
 | |
|                 return_value=({"gcm": True}, {}),
 | |
|             ),
 | |
|             mock.patch(
 | |
|                 # Simulate the send...push_notification functions returning a number of successes
 | |
|                 # lesser than the number of devices, so that we can verify correct CountStat counting.
 | |
|                 "zerver.lib.push_notifications.send_apple_push_notification",
 | |
|                 return_value=len(apple_devices) - 1,
 | |
|             ) as mock_send_apple,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_android_push_notification",
 | |
|                 return_value=len(android_devices) - 1,
 | |
|             ) as mock_send_android,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.push_notifications_configured", return_value=True
 | |
|             ) as mock_push_notifications,
 | |
|         ):
 | |
|             handle_push_notification(self.user_profile.id, missed_message)
 | |
|             user_identity = UserPushIdentityCompat(user_id=self.user_profile.id)
 | |
|             mock_send_apple.assert_called_with(user_identity, apple_devices, {"apns": True})
 | |
|             mock_send_android.assert_called_with(user_identity, android_devices, {"gcm": True}, {})
 | |
|             mock_push_notifications.assert_called_once()
 | |
| 
 | |
|         remote_realm_count = RealmCount.objects.values("property", "subgroup", "value").last()
 | |
|         self.assertEqual(
 | |
|             remote_realm_count,
 | |
|             dict(
 | |
|                 property="mobile_pushes_sent::day",
 | |
|                 subgroup=None,
 | |
|                 value=len(android_devices) + len(apple_devices) - 2,
 | |
|             ),
 | |
|         )
 | |
| 
 | |
|     def test_send_remove_notifications_to_bouncer(self) -> None:
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
| 
 | |
|         user_profile = self.user_profile
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=user_profile,
 | |
|             message=message,
 | |
|             flags=UserMessage.flags.active_mobile_push_notification,
 | |
|         )
 | |
| 
 | |
|         with (
 | |
|             activate_push_notification_service(),
 | |
|             mock.patch("zerver.lib.push_notifications.send_notifications_to_bouncer") as mock_send,
 | |
|         ):
 | |
|             handle_remove_push_notification(user_profile.id, [message.id])
 | |
|             mock_send.assert_called_with(
 | |
|                 user_profile,
 | |
|                 {
 | |
|                     "badge": 0,
 | |
|                     "custom": {
 | |
|                         "zulip": {
 | |
|                             "realm_name": self.sender.realm.name,
 | |
|                             "realm_uri": "http://zulip.testserver",
 | |
|                             "realm_url": "http://zulip.testserver",
 | |
|                             "user_id": self.user_profile.id,
 | |
|                             "event": "remove",
 | |
|                             "zulip_message_ids": str(message.id),
 | |
|                         },
 | |
|                     },
 | |
|                 },
 | |
|                 {
 | |
|                     "server": "testserver",
 | |
|                     "realm_id": self.sender.realm.id,
 | |
|                     "realm_name": self.sender.realm.name,
 | |
|                     "realm_uri": "http://zulip.testserver",
 | |
|                     "realm_url": "http://zulip.testserver",
 | |
|                     "user_id": self.user_profile.id,
 | |
|                     "event": "remove",
 | |
|                     "zulip_message_ids": str(message.id),
 | |
|                     "zulip_message_id": message.id,
 | |
|                 },
 | |
|                 {"priority": "normal"},
 | |
|                 list(
 | |
|                     PushDeviceToken.objects.filter(
 | |
|                         user=user_profile, kind=PushDeviceToken.FCM
 | |
|                     ).order_by("id")
 | |
|                 ),
 | |
|                 list(
 | |
|                     PushDeviceToken.objects.filter(
 | |
|                         user=user_profile, kind=PushDeviceToken.APNS
 | |
|                     ).order_by("id")
 | |
|                 ),
 | |
|             )
 | |
|             user_message = UserMessage.objects.get(user_profile=self.user_profile, message=message)
 | |
|             self.assertEqual(user_message.flags.active_mobile_push_notification, False)
 | |
| 
 | |
|     def test_non_bouncer_push_remove(self) -> None:
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=self.user_profile,
 | |
|             message=message,
 | |
|             flags=UserMessage.flags.active_mobile_push_notification,
 | |
|         )
 | |
| 
 | |
|         android_devices = list(
 | |
|             PushDeviceToken.objects.filter(user=self.user_profile, kind=PushDeviceToken.FCM)
 | |
|         )
 | |
| 
 | |
|         apple_devices = list(
 | |
|             PushDeviceToken.objects.filter(user=self.user_profile, kind=PushDeviceToken.APNS)
 | |
|         )
 | |
| 
 | |
|         with (
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.push_notifications_configured", return_value=True
 | |
|             ) as mock_push_notifications,
 | |
|             mock.patch(
 | |
|                 # Simulate the send...push_notification functions returning a number of successes
 | |
|                 # lesser than the number of devices, so that we can verify correct CountStat counting.
 | |
|                 "zerver.lib.push_notifications.send_android_push_notification",
 | |
|                 return_value=len(apple_devices) - 1,
 | |
|             ) as mock_send_android,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_apple_push_notification",
 | |
|                 return_value=len(apple_devices) - 1,
 | |
|             ) as mock_send_apple,
 | |
|         ):
 | |
|             handle_remove_push_notification(self.user_profile.id, [message.id])
 | |
|             mock_push_notifications.assert_called_once()
 | |
|             user_identity = UserPushIdentityCompat(user_id=self.user_profile.id)
 | |
|             mock_send_android.assert_called_with(
 | |
|                 user_identity,
 | |
|                 android_devices,
 | |
|                 {
 | |
|                     "server": "testserver",
 | |
|                     "realm_id": self.sender.realm.id,
 | |
|                     "realm_name": self.sender.realm.name,
 | |
|                     "realm_uri": "http://zulip.testserver",
 | |
|                     "realm_url": "http://zulip.testserver",
 | |
|                     "user_id": self.user_profile.id,
 | |
|                     "event": "remove",
 | |
|                     "zulip_message_ids": str(message.id),
 | |
|                     "zulip_message_id": message.id,
 | |
|                 },
 | |
|                 {"priority": "normal"},
 | |
|             )
 | |
|             mock_send_apple.assert_called_with(
 | |
|                 user_identity,
 | |
|                 apple_devices,
 | |
|                 {
 | |
|                     "badge": 0,
 | |
|                     "custom": {
 | |
|                         "zulip": {
 | |
|                             "realm_name": self.sender.realm.name,
 | |
|                             "realm_uri": "http://zulip.testserver",
 | |
|                             "realm_url": "http://zulip.testserver",
 | |
|                             "user_id": self.user_profile.id,
 | |
|                             "event": "remove",
 | |
|                             "zulip_message_ids": str(message.id),
 | |
|                         }
 | |
|                     },
 | |
|                 },
 | |
|             )
 | |
|             user_message = UserMessage.objects.get(user_profile=self.user_profile, message=message)
 | |
|             self.assertEqual(user_message.flags.active_mobile_push_notification, False)
 | |
| 
 | |
|             remote_realm_count = RealmCount.objects.values("property", "subgroup", "value").last()
 | |
|             self.assertEqual(
 | |
|                 remote_realm_count,
 | |
|                 dict(
 | |
|                     property="mobile_pushes_sent::day",
 | |
|                     subgroup=None,
 | |
|                     value=len(android_devices) + len(apple_devices) - 2,
 | |
|                 ),
 | |
|             )
 | |
| 
 | |
|     def test_user_message_does_not_exist(self) -> None:
 | |
|         """This simulates a condition that should only be an error if the user is
 | |
|         not long-term idle; we fake it, though, in the sense that the user should
 | |
|         not have received the message in the first place"""
 | |
|         self.make_stream("public_stream")
 | |
|         sender = self.example_user("iago")
 | |
|         self.subscribe(sender, "public_stream")
 | |
|         message_id = self.send_stream_message(sender, "public_stream", "test")
 | |
|         missed_message = {"message_id": message_id}
 | |
|         with (
 | |
|             self.assertLogs("zerver.lib.push_notifications", level="ERROR") as logger,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.push_notifications_configured", return_value=True
 | |
|             ) as mock_push_notifications,
 | |
|         ):
 | |
|             handle_push_notification(self.user_profile.id, missed_message)
 | |
|             self.assertEqual(
 | |
|                 "ERROR:zerver.lib.push_notifications:"
 | |
|                 f"Could not find UserMessage with message_id {message_id} and user_id {self.user_profile.id}",
 | |
|                 logger.output[0],
 | |
|             )
 | |
|             mock_push_notifications.assert_called_once()
 | |
| 
 | |
|     def test_user_message_does_not_exist_remove(self) -> None:
 | |
|         """This simulates a condition that should only be an error if the user is
 | |
|         not long-term idle; we fake it, though, in the sense that the user should
 | |
|         not have received the message in the first place"""
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
|         self.make_stream("public_stream")
 | |
|         sender = self.example_user("iago")
 | |
|         self.subscribe(sender, "public_stream")
 | |
|         message_id = self.send_stream_message(sender, "public_stream", "test")
 | |
|         with (
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.push_notifications_configured", return_value=True
 | |
|             ) as mock_push_notifications,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_android_push_notification", return_value=1
 | |
|             ) as mock_send_android,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_apple_push_notification", return_value=1
 | |
|             ) as mock_send_apple,
 | |
|         ):
 | |
|             handle_remove_push_notification(self.user_profile.id, [message_id])
 | |
|             mock_push_notifications.assert_called_once()
 | |
|             mock_send_android.assert_called_once()
 | |
|             mock_send_apple.assert_called_once()
 | |
| 
 | |
|     def test_user_message_soft_deactivated(self) -> None:
 | |
|         """This simulates a condition that should only be an error if the user is
 | |
|         not long-term idle; we fake it, though, in the sense that the user should
 | |
|         not have received the message in the first place"""
 | |
|         self.setup_apns_tokens()
 | |
|         self.setup_fcm_tokens()
 | |
|         self.make_stream("public_stream")
 | |
|         sender = self.example_user("iago")
 | |
|         self.subscribe(self.user_profile, "public_stream")
 | |
|         self.subscribe(sender, "public_stream")
 | |
|         logger_string = "zulip.soft_deactivation"
 | |
|         with self.assertLogs(logger_string, level="INFO") as info_logs:
 | |
|             self.soft_deactivate_main_user()
 | |
| 
 | |
|         self.assertEqual(
 | |
|             info_logs.output,
 | |
|             [
 | |
|                 f"INFO:{logger_string}:Soft deactivated user {self.user_profile.id}",
 | |
|                 f"INFO:{logger_string}:Soft-deactivated batch of 1 users; 0 remain to process",
 | |
|             ],
 | |
|         )
 | |
|         message_id = self.send_stream_message(sender, "public_stream", "test")
 | |
|         missed_message = {
 | |
|             "message_id": message_id,
 | |
|             "trigger": NotificationTriggers.STREAM_PUSH,
 | |
|         }
 | |
| 
 | |
|         android_devices = list(
 | |
|             PushDeviceToken.objects.filter(user=self.user_profile, kind=PushDeviceToken.FCM)
 | |
|         )
 | |
| 
 | |
|         apple_devices = list(
 | |
|             PushDeviceToken.objects.filter(user=self.user_profile, kind=PushDeviceToken.APNS)
 | |
|         )
 | |
| 
 | |
|         with (
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.get_message_payload_apns",
 | |
|                 return_value={"apns": True},
 | |
|             ),
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.get_message_payload_gcm",
 | |
|                 return_value=({"gcm": True}, {}),
 | |
|             ),
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_apple_push_notification", return_value=1
 | |
|             ) as mock_send_apple,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.send_android_push_notification", return_value=1
 | |
|             ) as mock_send_android,
 | |
|             mock.patch("zerver.lib.push_notifications.logger.error") as mock_logger,
 | |
|             mock.patch(
 | |
|                 "zerver.lib.push_notifications.push_notifications_configured", return_value=True
 | |
|             ) as mock_push_notifications,
 | |
|         ):
 | |
|             handle_push_notification(self.user_profile.id, missed_message)
 | |
|             mock_logger.assert_not_called()
 | |
|             user_identity = UserPushIdentityCompat(user_id=self.user_profile.id)
 | |
|             mock_send_apple.assert_called_with(user_identity, apple_devices, {"apns": True})
 | |
|             mock_send_android.assert_called_with(user_identity, android_devices, {"gcm": True}, {})
 | |
|             mock_push_notifications.assert_called_once()
 | |
| 
 | |
|     @override_settings(MAX_GROUP_SIZE_FOR_MENTION_REACTIVATION=2)
 | |
|     @mock.patch("zerver.lib.push_notifications.push_notifications_configured", return_value=True)
 | |
|     def test_user_push_soft_reactivate_soft_deactivated_user(
 | |
|         self, mock_push_notifications: mock.MagicMock
 | |
|     ) -> None:
 | |
|         othello = self.example_user("othello")
 | |
|         cordelia = self.example_user("cordelia")
 | |
|         zulip_realm = get_realm("zulip")
 | |
| 
 | |
|         # user groups having upto 'MAX_GROUP_SIZE_FOR_MENTION_REACTIVATION'
 | |
|         # members are small user groups.
 | |
|         small_user_group = check_add_user_group(
 | |
|             zulip_realm,
 | |
|             "small_user_group",
 | |
|             [self.user_profile, othello],
 | |
|             acting_user=othello,
 | |
|         )
 | |
| 
 | |
|         large_user_group = check_add_user_group(
 | |
|             zulip_realm, "large_user_group", [self.user_profile], acting_user=othello
 | |
|         )
 | |
|         subgroup = check_add_user_group(
 | |
|             zulip_realm, "subgroup", [othello, cordelia], acting_user=othello
 | |
|         )
 | |
|         add_subgroups_to_user_group(large_user_group, [subgroup], acting_user=None)
 | |
| 
 | |
|         # Personal mention in a stream message should soft reactivate the user
 | |
|         def mention_in_stream() -> None:
 | |
|             mention = f"@**{self.user_profile.full_name}**"
 | |
|             stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | |
|             handle_push_notification(
 | |
|                 self.user_profile.id,
 | |
|                 {
 | |
|                     "message_id": stream_mentioned_message_id,
 | |
|                     "trigger": NotificationTriggers.MENTION,
 | |
|                 },
 | |
|             )
 | |
| 
 | |
|         self.soft_deactivate_main_user()
 | |
|         self.expect_soft_reactivation(self.user_profile, mention_in_stream)
 | |
| 
 | |
|         # Direct message should soft reactivate the user
 | |
|         def direct_message() -> None:
 | |
|             # Soft reactivate the user by sending a personal message
 | |
|             personal_message_id = self.send_personal_message(othello, self.user_profile, "Message")
 | |
|             handle_push_notification(
 | |
|                 self.user_profile.id,
 | |
|                 {
 | |
|                     "message_id": personal_message_id,
 | |
|                     "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|                 },
 | |
|             )
 | |
| 
 | |
|         self.soft_deactivate_main_user()
 | |
|         self.expect_soft_reactivation(self.user_profile, direct_message)
 | |
| 
 | |
|         # User FOLLOWS the topic.
 | |
|         # 'wildcard_mentions_notify' is disabled to verify the corner case when only
 | |
|         # 'enable_followed_topic_wildcard_mentions_notify' is enabled (True by default).
 | |
|         do_set_user_topic_visibility_policy(
 | |
|             self.user_profile,
 | |
|             get_stream("Denmark", self.user_profile.realm),
 | |
|             "test",
 | |
|             visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
 | |
|         )
 | |
|         do_change_user_setting(
 | |
|             self.user_profile, "wildcard_mentions_notify", False, acting_user=None
 | |
|         )
 | |
| 
 | |
|         # Topic wildcard mention in followed topic should soft reactivate the user
 | |
|         # user should be a topic participant
 | |
|         self.send_stream_message(self.user_profile, "Denmark", "topic participant")
 | |
| 
 | |
|         def send_topic_wildcard_mention() -> None:
 | |
|             mention = "@**topic**"
 | |
|             stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | |
|             handle_push_notification(
 | |
|                 self.user_profile.id,
 | |
|                 {
 | |
|                     "message_id": stream_mentioned_message_id,
 | |
|                     "trigger": NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC,
 | |
|                 },
 | |
|             )
 | |
| 
 | |
|         self.soft_deactivate_main_user()
 | |
|         self.expect_soft_reactivation(self.user_profile, send_topic_wildcard_mention)
 | |
| 
 | |
|         # Stream wildcard mention in followed topic should NOT soft reactivate the user
 | |
|         def send_stream_wildcard_mention() -> None:
 | |
|             mention = "@**all**"
 | |
|             stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | |
|             handle_push_notification(
 | |
|                 self.user_profile.id,
 | |
|                 {
 | |
|                     "message_id": stream_mentioned_message_id,
 | |
|                     "trigger": NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC,
 | |
|                 },
 | |
|             )
 | |
| 
 | |
|         self.soft_deactivate_main_user()
 | |
|         self.expect_to_stay_long_term_idle(self.user_profile, send_stream_wildcard_mention)
 | |
| 
 | |
|         # Reset
 | |
|         do_set_user_topic_visibility_policy(
 | |
|             self.user_profile,
 | |
|             get_stream("Denmark", self.user_profile.realm),
 | |
|             "test",
 | |
|             visibility_policy=UserTopic.VisibilityPolicy.INHERIT,
 | |
|         )
 | |
|         do_change_user_setting(
 | |
|             self.user_profile, "wildcard_mentions_notify", True, acting_user=None
 | |
|         )
 | |
| 
 | |
|         # Topic Wildcard mention should soft reactivate the user
 | |
|         self.expect_soft_reactivation(self.user_profile, send_topic_wildcard_mention)
 | |
| 
 | |
|         # Stream Wildcard mention should NOT soft reactivate the user
 | |
|         self.soft_deactivate_main_user()
 | |
|         self.expect_to_stay_long_term_idle(self.user_profile, send_stream_wildcard_mention)
 | |
| 
 | |
|         # Small group mention should soft reactivate the user
 | |
|         def send_small_group_mention() -> None:
 | |
|             mention = "@*small_user_group*"
 | |
|             stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | |
|             handle_push_notification(
 | |
|                 self.user_profile.id,
 | |
|                 {
 | |
|                     "message_id": stream_mentioned_message_id,
 | |
|                     "trigger": NotificationTriggers.MENTION,
 | |
|                     "mentioned_user_group_id": small_user_group.id,
 | |
|                 },
 | |
|             )
 | |
| 
 | |
|         self.soft_deactivate_main_user()
 | |
|         self.expect_soft_reactivation(self.user_profile, send_small_group_mention)
 | |
| 
 | |
|         # Large group mention should NOT soft reactivate the user
 | |
|         def send_large_group_mention() -> None:
 | |
|             mention = "@*large_user_group*"
 | |
|             stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention)
 | |
|             handle_push_notification(
 | |
|                 self.user_profile.id,
 | |
|                 {
 | |
|                     "message_id": stream_mentioned_message_id,
 | |
|                     "trigger": NotificationTriggers.MENTION,
 | |
|                     "mentioned_user_group_id": large_user_group.id,
 | |
|                 },
 | |
|             )
 | |
| 
 | |
|         self.soft_deactivate_main_user()
 | |
|         self.expect_to_stay_long_term_idle(self.user_profile, send_large_group_mention)
 | |
| 
 | |
|     @mock.patch("zerver.lib.push_notifications.logger.info")
 | |
|     @mock.patch("zerver.lib.push_notifications.push_notifications_configured", return_value=True)
 | |
|     def test_user_push_notification_already_active(
 | |
|         self, mock_push_notifications: mock.MagicMock, mock_info: mock.MagicMock
 | |
|     ) -> None:
 | |
|         user_profile = self.example_user("hamlet")
 | |
|         message = self.get_message(
 | |
|             Recipient.PERSONAL,
 | |
|             type_id=self.personal_recipient_user.id,
 | |
|             realm_id=self.personal_recipient_user.realm_id,
 | |
|         )
 | |
|         UserMessage.objects.create(
 | |
|             user_profile=user_profile,
 | |
|             flags=UserMessage.flags.active_mobile_push_notification,
 | |
|             message=message,
 | |
|         )
 | |
| 
 | |
|         missed_message = {
 | |
|             "message_id": message.id,
 | |
|             "trigger": NotificationTriggers.DIRECT_MESSAGE,
 | |
|         }
 | |
|         handle_push_notification(user_profile.id, missed_message)
 | |
|         mock_push_notifications.assert_called_once()
 | |
|         # Check we didn't proceed ahead and function returned.
 | |
|         mock_info.assert_not_called()
 |