From ec5472cd6dabbd4e08905bca4bc36ebcdc509474 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Mon, 27 Mar 2023 14:36:51 +0100 Subject: [PATCH] Support notification priority in FCM relay (#1630) # What this PR does Adds support for notification priority in FCM relay + add some tests on FCM relay. ## Which issue(s) this PR fixes Related to https://github.com/grafana/oncall/issues/1550 and https://github.com/grafana/oncall/pull/1612. ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated No changelog update since notification priority is "Unreleased": https://github.com/grafana/oncall/blob/a2caeae3c7d15c81e72c70108e32617d68cf1a7b/CHANGELOG.md#unreleased --- engine/apps/mobile_app/fcm_relay.py | 24 ++++++++--- .../apps/mobile_app/tests/test_fcm_relay.py | 42 ++++++++++++++++++- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/engine/apps/mobile_app/fcm_relay.py b/engine/apps/mobile_app/fcm_relay.py index 3cbc5e64..a8a47201 100644 --- a/engine/apps/mobile_app/fcm_relay.py +++ b/engine/apps/mobile_app/fcm_relay.py @@ -4,7 +4,7 @@ from celery.utils.log import get_task_logger from django.conf import settings from fcm_django.models import FCMDevice from firebase_admin.exceptions import FirebaseError -from firebase_admin.messaging import APNSConfig, APNSPayload, Aps, ApsAlert, CriticalSound, Message +from firebase_admin.messaging import AndroidConfig, APNSConfig, APNSPayload, Aps, ApsAlert, CriticalSound, Message from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -42,18 +42,19 @@ class FCMRelayView(APIView): token = request.data["token"] data = request.data["data"] apns = request.data["apns"] + android = request.data.get("android") # optional except KeyError: return Response(status=status.HTTP_400_BAD_REQUEST) - fcm_relay_async.delay(token=token, data=data, apns=apns) + fcm_relay_async.delay(token=token, data=data, apns=apns, android=android) return Response(status=status.HTTP_200_OK) @shared_dedicated_queue_retry_task( autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else 5 ) -def fcm_relay_async(token, data, apns): - message = Message(token=token, data=data, apns=deserialize_apns(apns)) +def fcm_relay_async(token, data, apns, android=None): + message = _get_message_from_request_data(token, data, apns, android) # https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream response = FCMDevice(registration_id=token).send_message(message) @@ -63,7 +64,17 @@ def fcm_relay_async(token, data, apns): raise response -def deserialize_apns(apns): +def _get_message_from_request_data(token, data, apns, android): + """ + Create Message object from JSON payload from OSS instance. + """ + + return Message( + token=token, data=data, apns=_deserialize_apns(apns), android=AndroidConfig(**android) if android else None + ) + + +def _deserialize_apns(apns): """ Create APNSConfig object from JSON payload from OSS instance. """ @@ -95,5 +106,6 @@ def deserialize_apns(apns): sound=sound, custom_data=aps, ) - ) + ), + headers=apns.get("headers"), ) diff --git a/engine/apps/mobile_app/tests/test_fcm_relay.py b/engine/apps/mobile_app/tests/test_fcm_relay.py index 7cbfd930..47634617 100644 --- a/engine/apps/mobile_app/tests/test_fcm_relay.py +++ b/engine/apps/mobile_app/tests/test_fcm_relay.py @@ -1,3 +1,4 @@ +import json from unittest.mock import patch import pytest @@ -7,7 +8,8 @@ from firebase_admin.exceptions import FirebaseError from rest_framework import status from rest_framework.test import APIClient -from apps.mobile_app.fcm_relay import FCMRelayThrottler, fcm_relay_async +from apps.mobile_app.fcm_relay import FCMRelayThrottler, _get_message_from_request_data, fcm_relay_async +from apps.mobile_app.tasks import _get_fcm_message @pytest.mark.django_db @@ -88,3 +90,41 @@ def test_fcm_relay_async_retry(): ): with pytest.raises(FirebaseError): fcm_relay_async(token="test_token", data={}, apns={}) + + +def test_get_message_from_request_data(): + token = "test_token" + data = {"test_data_key": "test_data_value"} + apns = {"headers": {"apns-priority": "10"}, "payload": {"aps": {"thread-id": "test_thread_id"}}} + android = {"priority": "high"} + message = _get_message_from_request_data(token, data, apns, android) + + assert message.token == "test_token" + assert message.data == {"test_data_key": "test_data_value"} + assert message.apns.headers == {"apns-priority": "10"} + assert message.apns.payload.aps.thread_id == "test_thread_id" + assert message.android.priority == "high" + + +@pytest.mark.django_db +def test_fcm_relay_serialize_deserialize( + make_organization_and_user, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user = make_organization_and_user() + device = FCMDevice.objects.create(user=user, registration_id="test_device_id") + + alert_receive_channel = make_alert_receive_channel(organization=organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group=alert_group, raw_request_data={}) + + # Imitate sending a message to the FCM relay endpoint + original_message = _get_fcm_message(alert_group, user, device.registration_id, critical=False) + request_data = json.loads(str(original_message)) + + # Imitate receiving a message from the FCM relay endpoint + relayed_message = _get_message_from_request_data( + request_data["token"], request_data["data"], request_data["apns"], request_data["android"] + ) + + # Check that the message is the same after serialization and deserialization + assert json.loads(str(original_message)) == json.loads(str(relayed_message))