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":
a2caeae3c7/CHANGELOG.md (unreleased)
This commit is contained in:
Vadim Stepanov 2023-03-27 14:36:51 +01:00 committed by GitHub
parent a936c8c7fe
commit ec5472cd6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 7 deletions

View file

@ -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"),
)

View file

@ -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))