# What this PR does _tldr_; we had a lengthy discussion about this [here](https://raintank-corp.slack.com/archives/C04JCU51NF8/p1701893410542629?thread_ts=1701690117.016909&cid=C04JCU51NF8). `firebase.messaging.UnregisteredError` errors occur because of events outside of our control and retrying will never fix them, therefore we should simply skip retrying in this case. We retry these fairly often ([logs](https://ops.grafana-ops.net/explore?schemaVersion=1&panes=%7B%22iWZ%22:%7B%22datasource%22:%22000000193%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%23%20%7Bcluster%3D~%5C%22prod-%28eu-west-0%7Cus-central-0%29%5C%22,%20namespace%3D%5C%22amixr-prod%5C%22%7D%20%7C%3D%20%5C%22task_name%3Dapps.webhooks.tasks.trigger_webhook.execute_webhook%5C%22%20%7C%3D%20%5C%22retry%5C%22%5Cn%7Bcluster%3D~%5C%22prod-%28eu-west-0%7Cus-central-0%29%5C%22,%20namespace%3D%5C%22amixr-prod%5C%22%7D%20%7C%3D%20%5C%22apps.mobile_app.fcm_relay.fcm_relay_async%5C%22%20%7C%3D%20%5C%22UnregisteredError%5C%22%22,%22queryType%22:%22range%22,%22datasource%22:%7B%22type%22:%22loki%22,%22uid%22:%22000000193%22%7D,%22editorMode%22:%22code%22%7D%5D,%22range%22:%7B%22from%22:%22now-7d%22,%22to%22:%22now%22%7D%7D%7D&orgId=1)) which eats up unnecessary celery worker resources. Related to https://github.com/grafana/oncall-private/issues/1820 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required)
210 lines
6.8 KiB
Python
210 lines
6.8 KiB
Python
from unittest.mock import Mock, patch
|
|
|
|
import firebase_admin.messaging
|
|
import pytest
|
|
from firebase_admin.exceptions import FirebaseError
|
|
from requests import HTTPError
|
|
|
|
from apps.mobile_app import utils
|
|
from apps.mobile_app.models import FCMDevice
|
|
from apps.mobile_app.utils import add_stack_slug_to_message_title
|
|
from apps.oss_installation.models import CloudConnector
|
|
|
|
MOBILE_APP_BACKEND_ID = 5
|
|
CLOUD_LICENSE_NAME = "Cloud"
|
|
OPEN_SOURCE_LICENSE_NAME = "OpenSource"
|
|
|
|
|
|
@patch.object(FCMDevice, "send_message", return_value="ok")
|
|
@pytest.mark.django_db
|
|
def test_send_push_notification_cloud(
|
|
mock_send_message,
|
|
settings,
|
|
make_organization_and_user,
|
|
):
|
|
# create a user and connect a mobile device
|
|
_, user = make_organization_and_user()
|
|
device = FCMDevice.objects.create(user=user, registration_id="test_device_id")
|
|
mock_message = {"foo": "bar"}
|
|
|
|
# check FCM is contacted directly when using the cloud license
|
|
settings.LICENSE = CLOUD_LICENSE_NAME
|
|
settings.IS_OPEN_SOURCE = False
|
|
|
|
succeeded = utils.send_push_notification(device, mock_message)
|
|
assert succeeded
|
|
mock_send_message.assert_called_once_with(mock_message)
|
|
|
|
|
|
@patch.object(FCMDevice, "send_message")
|
|
@pytest.mark.django_db
|
|
def test_send_push_notification_cloud_firebase_error(
|
|
mock_send_message,
|
|
settings,
|
|
make_organization_and_user,
|
|
):
|
|
mock_send_message.return_value = FirebaseError(code="test_error_code", message="test_error_message")
|
|
|
|
# create a user and connect a mobile device
|
|
_, user = make_organization_and_user()
|
|
device = FCMDevice.objects.create(user=user, registration_id="test_device_id")
|
|
mock_message = {"foo": "bar"}
|
|
|
|
# check FCM is contacted directly when using the cloud license
|
|
settings.LICENSE = CLOUD_LICENSE_NAME
|
|
settings.IS_OPEN_SOURCE = False
|
|
|
|
with pytest.raises(FirebaseError):
|
|
utils.send_push_notification(device, mock_message)
|
|
|
|
mock_send_message.assert_called_once_with(mock_message)
|
|
|
|
|
|
@patch.object(FCMDevice, "send_message")
|
|
@pytest.mark.parametrize(
|
|
"ExceptionClass,exception_kwargs",
|
|
[
|
|
(firebase_admin.messaging.UnregisteredError, {"message": "test_error_message"}),
|
|
],
|
|
)
|
|
@pytest.mark.django_db
|
|
def test_send_push_notification_cloud_ignores_certain_errors(
|
|
mock_send_message,
|
|
settings,
|
|
make_organization_and_user,
|
|
ExceptionClass,
|
|
exception_kwargs,
|
|
):
|
|
mock_send_message.return_value = ExceptionClass(**exception_kwargs)
|
|
|
|
# create a user and connect a mobile device
|
|
_, user = make_organization_and_user()
|
|
device = FCMDevice.objects.create(user=user, registration_id="test_device_id")
|
|
mock_message = {"foo": "bar"}
|
|
|
|
# check FCM is contacted directly when using the cloud license
|
|
settings.LICENSE = CLOUD_LICENSE_NAME
|
|
settings.IS_OPEN_SOURCE = False
|
|
|
|
try:
|
|
utils.send_push_notification(device, mock_message)
|
|
except Exception:
|
|
pytest.fail(f"send_push_notification should not raise an exception for {ExceptionClass.__name__} errors")
|
|
|
|
mock_send_message.assert_called_once_with(mock_message)
|
|
|
|
|
|
@patch("apps.mobile_app.utils._send_push_notification_to_fcm_relay", return_value="ok")
|
|
@pytest.mark.django_db
|
|
def test_send_push_notification_oss(
|
|
mock_send_push_notification_to_fcm_relay,
|
|
settings,
|
|
make_organization_and_user,
|
|
):
|
|
settings.LICENSE = OPEN_SOURCE_LICENSE_NAME
|
|
|
|
mock_error_cb = Mock()
|
|
|
|
# create cloud connection
|
|
CloudConnector.objects.create(cloud_url="test")
|
|
|
|
# create a user and connect a mobile device
|
|
_, user = make_organization_and_user()
|
|
device = FCMDevice.objects.create(user=user, registration_id="test_device_id")
|
|
mock_message = {"foo": "bar"}
|
|
|
|
succeeded = utils.send_push_notification(device, mock_message, mock_error_cb)
|
|
assert succeeded
|
|
mock_error_cb.assert_not_called()
|
|
mock_send_push_notification_to_fcm_relay.assert_called_once_with(mock_message)
|
|
|
|
|
|
@patch("apps.mobile_app.utils._send_push_notification_to_fcm_relay")
|
|
@pytest.mark.django_db
|
|
def test_send_push_notification_oss_no_cloud_connector(
|
|
mock_send_push_notification_to_fcm_relay,
|
|
settings,
|
|
make_organization_and_user,
|
|
):
|
|
settings.LICENSE = OPEN_SOURCE_LICENSE_NAME
|
|
|
|
mock_error_cb = Mock()
|
|
|
|
# create a user and connect a mobile device
|
|
_, user = make_organization_and_user()
|
|
device = FCMDevice.objects.create(user=user, registration_id="test_device_id")
|
|
mock_message = {"foo": "bar"}
|
|
|
|
succeeded = utils.send_push_notification(device, mock_message, mock_error_cb)
|
|
|
|
assert not succeeded
|
|
mock_error_cb.assert_called_once_with()
|
|
mock_send_push_notification_to_fcm_relay.assert_not_called()
|
|
|
|
|
|
@patch("apps.mobile_app.utils._send_push_notification_to_fcm_relay")
|
|
@pytest.mark.django_db
|
|
def test_send_push_notification_oss_fcm_relay_returns_client_error(
|
|
mock_send_push_notification_to_fcm_relay,
|
|
settings,
|
|
make_organization_and_user,
|
|
):
|
|
settings.LICENSE = OPEN_SOURCE_LICENSE_NAME
|
|
|
|
class MockResponse:
|
|
status_code = 400
|
|
|
|
mock_error_cb = Mock()
|
|
mock_send_push_notification_to_fcm_relay.side_effect = HTTPError(response=MockResponse)
|
|
|
|
# create cloud connection
|
|
CloudConnector.objects.create(cloud_url="test")
|
|
|
|
# create a user and connect a mobile device
|
|
_, user = make_organization_and_user()
|
|
device = FCMDevice.objects.create(user=user, registration_id="test_device_id")
|
|
mock_message = {"foo": "bar"}
|
|
|
|
succeeded = utils.send_push_notification(device, mock_message, mock_error_cb)
|
|
assert not succeeded
|
|
mock_send_push_notification_to_fcm_relay.assert_called_once_with(mock_message)
|
|
|
|
|
|
@patch("apps.mobile_app.utils._send_push_notification_to_fcm_relay")
|
|
@pytest.mark.django_db
|
|
def test_send_push_notification_oss_fcm_relay_returns_server_error(
|
|
mock_send_push_notification_to_fcm_relay,
|
|
settings,
|
|
make_organization_and_user,
|
|
):
|
|
settings.LICENSE = OPEN_SOURCE_LICENSE_NAME
|
|
|
|
class MockResponse:
|
|
status_code = 500
|
|
|
|
mock_error_cb = Mock()
|
|
mock_send_push_notification_to_fcm_relay.side_effect = HTTPError(response=MockResponse)
|
|
|
|
# create cloud connection
|
|
CloudConnector.objects.create(cloud_url="test")
|
|
|
|
# create a user and connect a mobile device
|
|
_, user = make_organization_and_user()
|
|
device = FCMDevice.objects.create(user=user, registration_id="test_device_id")
|
|
mock_message = {"foo": "bar"}
|
|
|
|
with pytest.raises(HTTPError):
|
|
utils.send_push_notification(device, mock_message, mock_error_cb)
|
|
|
|
mock_error_cb.assert_not_called()
|
|
mock_send_push_notification_to_fcm_relay.assert_called_once_with(mock_message)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_add_stack_slug_to_message_title(make_organization):
|
|
test_stack_slug = "my-org"
|
|
organization = make_organization(stack_slug=test_stack_slug)
|
|
some_message_title = "Test title"
|
|
expected_result = "[my-org] Test title"
|
|
result = add_stack_slug_to_message_title(some_message_title, organization)
|
|
assert result == expected_result
|