# What this PR does Changes UserNotificationPolicyLogRecord to success when slack_prevent_posting is set as the user has already been notified in slack or another method in their personal notification preferences. These entries have also been filtered out of the alert group history timeline as they were causing confusion to users thinking notifications failed when in fact they had already been sent. ## Which issue(s) this PR closes https://github.com/grafana/support-escalations/issues/13236 ## 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] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
675 lines
29 KiB
Python
675 lines
29 KiB
Python
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from django.utils import timezone
|
|
from telegram.error import RetryAfter
|
|
|
|
from apps.alerts.models import AlertGroup
|
|
from apps.alerts.paging import direct_paging
|
|
from apps.alerts.tasks.notify_user import notify_user_task, perform_notification, send_bundled_notification
|
|
from apps.api.permissions import LegacyAccessControlRole
|
|
from apps.base.models.user_notification_policy import UserNotificationPolicy
|
|
from apps.base.models.user_notification_policy_log_record import UserNotificationPolicyLogRecord
|
|
from apps.slack.models import SlackMessage
|
|
from apps.telegram.models import TelegramToUserConnector
|
|
|
|
NOTIFICATION_UNAUTHORIZED_MSG = "notification is not allowed for user"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_custom_backend_call(
|
|
make_organization,
|
|
make_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
):
|
|
organization = make_organization()
|
|
user_1 = make_user(organization=organization)
|
|
user_notification_policy = make_user_notification_policy(
|
|
user=user_1,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.TESTONLY,
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
log_record = make_user_notification_policy_log_record(
|
|
author=user_1,
|
|
alert_group=alert_group,
|
|
notification_policy=user_notification_policy,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
|
)
|
|
|
|
with patch("apps.base.tests.messaging_backend.TestOnlyBackend.notify_user") as mock_notify_user:
|
|
perform_notification(log_record.pk, False)
|
|
|
|
mock_notify_user.assert_called_once_with(user_1, alert_group, user_notification_policy)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_custom_backend_error(
|
|
make_organization,
|
|
make_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
):
|
|
organization = make_organization()
|
|
user_1 = make_user(organization=organization)
|
|
user_notification_policy = make_user_notification_policy(
|
|
user=user_1,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.TESTONLY,
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
log_record = make_user_notification_policy_log_record(
|
|
author=user_1,
|
|
alert_group=alert_group,
|
|
notification_policy=user_notification_policy,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
|
)
|
|
|
|
with patch("apps.alerts.tasks.notify_user.get_messaging_backend_from_id") as mock_get_backend:
|
|
mock_get_backend.return_value = None
|
|
perform_notification(log_record.pk, False)
|
|
|
|
error_log_record = UserNotificationPolicyLogRecord.objects.last()
|
|
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
|
assert error_log_record.reason == "Messaging backend not available"
|
|
assert (
|
|
error_log_record.notification_error_code
|
|
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_MESSAGING_BACKEND_ERROR
|
|
)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"author_set,notification_policy_set",
|
|
[
|
|
(False, True),
|
|
(True, False),
|
|
],
|
|
)
|
|
def test_notify_user_missing_data_errors(
|
|
make_organization,
|
|
make_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
author_set,
|
|
notification_policy_set,
|
|
):
|
|
organization = make_organization()
|
|
user_1 = make_user(organization=organization)
|
|
user_notification_policy = make_user_notification_policy(
|
|
user=user_1,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SMS,
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
log_record = make_user_notification_policy_log_record(
|
|
author=user_1 if author_set else None,
|
|
alert_group=alert_group,
|
|
notification_policy=user_notification_policy if notification_policy_set else None,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
|
)
|
|
|
|
with patch("apps.alerts.tasks.notify_user.get_messaging_backend_from_id") as mock_get_backend:
|
|
mock_get_backend.return_value = None
|
|
perform_notification(log_record.pk, False)
|
|
|
|
error_log_record = UserNotificationPolicyLogRecord.objects.last()
|
|
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
|
assert error_log_record.reason == "Expected data is missing"
|
|
assert error_log_record.notification_error_code is None
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_notify_user_perform_notification_error_if_viewer(
|
|
make_organization,
|
|
make_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
):
|
|
organization = make_organization()
|
|
user_1 = make_user(
|
|
organization=organization, role=LegacyAccessControlRole.VIEWER, _verified_phone_number="1234567890"
|
|
)
|
|
user_notification_policy = make_user_notification_policy(
|
|
user=user_1,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SMS,
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
log_record = make_user_notification_policy_log_record(
|
|
author=user_1,
|
|
alert_group=alert_group,
|
|
notification_policy=user_notification_policy,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
|
)
|
|
|
|
perform_notification(log_record.pk, False)
|
|
|
|
error_log_record = UserNotificationPolicyLogRecord.objects.last()
|
|
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
|
assert error_log_record.reason == NOTIFICATION_UNAUTHORIZED_MSG
|
|
assert error_log_record.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORBIDDEN
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_notify_user_error_if_viewer(
|
|
make_organization,
|
|
make_user,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
):
|
|
organization = make_organization()
|
|
user_1 = make_user(
|
|
organization=organization, role=LegacyAccessControlRole.VIEWER, _verified_phone_number="1234567890"
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
|
|
notify_user_task(user_1.pk, alert_group.pk)
|
|
|
|
error_log_record = UserNotificationPolicyLogRecord.objects.last()
|
|
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
|
assert error_log_record.reason == NOTIFICATION_UNAUTHORIZED_MSG
|
|
assert error_log_record.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORBIDDEN
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_notify_user_perform_notification_skip_if_resolved(
|
|
make_organization,
|
|
make_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
):
|
|
organization = make_organization()
|
|
user_1 = make_user(organization=organization, _verified_phone_number="1234567890")
|
|
user_notification_policy = make_user_notification_policy(
|
|
user=user_1,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SMS,
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel, resolved=True)
|
|
log_record = make_user_notification_policy_log_record(
|
|
author=user_1,
|
|
alert_group=alert_group,
|
|
notification_policy=user_notification_policy,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
|
)
|
|
|
|
perform_notification(log_record.pk, False)
|
|
|
|
error_log_record = UserNotificationPolicyLogRecord.objects.last()
|
|
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
|
assert error_log_record.reason == "Skipped notification because alert group is resolved"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"reason_to_skip_escalation,error_code",
|
|
[
|
|
(AlertGroup.RATE_LIMITED, UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK_RATELIMIT),
|
|
(AlertGroup.CHANNEL_ARCHIVED, UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK_CHANNEL_IS_ARCHIVED),
|
|
(AlertGroup.ACCOUNT_INACTIVE, UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK_TOKEN_ERROR),
|
|
(AlertGroup.RESTRICTED_ACTION, UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK),
|
|
(AlertGroup.NO_REASON, None),
|
|
],
|
|
)
|
|
def test_perform_notification_reason_to_skip_escalation_in_slack(
|
|
reason_to_skip_escalation,
|
|
error_code,
|
|
make_organization,
|
|
make_slack_team_identity,
|
|
make_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
make_slack_message,
|
|
):
|
|
organization = make_organization()
|
|
slack_team_identity = make_slack_team_identity()
|
|
organization.slack_team_identity = slack_team_identity
|
|
organization.save()
|
|
user = make_user(organization=organization)
|
|
user_notification_policy = make_user_notification_policy(
|
|
user=user,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SLACK,
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
alert_group.reason_to_skip_escalation = reason_to_skip_escalation
|
|
alert_group.save()
|
|
log_record = make_user_notification_policy_log_record(
|
|
author=user,
|
|
alert_group=alert_group,
|
|
notification_policy=user_notification_policy,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
|
)
|
|
if not error_code:
|
|
make_slack_message(alert_group=alert_group, channel_id="test_channel_id", slack_id="test_slack_id")
|
|
with patch.object(SlackMessage, "send_slack_notification") as mocked_send_slack_notification:
|
|
perform_notification(log_record.pk, False)
|
|
last_log_record = UserNotificationPolicyLogRecord.objects.last()
|
|
|
|
if error_code:
|
|
log_reason = f"Skipped escalation in Slack, reason: '{alert_group.get_reason_to_skip_escalation_display()}'"
|
|
mocked_send_slack_notification.assert_not_called()
|
|
assert last_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
|
assert last_log_record.reason == log_reason
|
|
assert last_log_record.notification_error_code == error_code
|
|
else:
|
|
mocked_send_slack_notification.assert_called()
|
|
assert last_log_record.type != UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_perform_notification_slack_prevent_posting(
|
|
make_organization,
|
|
make_slack_team_identity,
|
|
make_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
make_slack_message,
|
|
):
|
|
organization = make_organization()
|
|
slack_team_identity = make_slack_team_identity()
|
|
organization.slack_team_identity = slack_team_identity
|
|
organization.save()
|
|
user = make_user(organization=organization)
|
|
user_notification_policy = make_user_notification_policy(
|
|
user=user,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SLACK,
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
log_record = make_user_notification_policy_log_record(
|
|
author=user,
|
|
alert_group=alert_group,
|
|
notification_policy=user_notification_policy,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
|
slack_prevent_posting=True,
|
|
)
|
|
make_slack_message(alert_group=alert_group, channel_id="test_channel_id", slack_id="test_slack_id")
|
|
|
|
with patch.object(SlackMessage, "send_slack_notification") as mocked_send_slack_notification:
|
|
perform_notification(log_record.pk, False)
|
|
|
|
mocked_send_slack_notification.assert_not_called()
|
|
last_log_record = UserNotificationPolicyLogRecord.objects.last()
|
|
assert last_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_SUCCESS
|
|
assert last_log_record.reason == "Prevented from posting in Slack"
|
|
assert (
|
|
last_log_record.notification_error_code
|
|
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_POSTING_TO_SLACK_IS_DISABLED
|
|
)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_perform_notification_missing_user_notification_policy_log_record(caplog):
|
|
invalid_pk = 12345
|
|
perform_notification(invalid_pk, False)
|
|
|
|
assert (
|
|
f"perform_notification: log_record {invalid_pk} doesn't exist. Skipping remainder of task. "
|
|
"The alert group associated with this log record may have been deleted."
|
|
) in caplog.text
|
|
assert f"perform_notification: found record for {invalid_pk}" not in caplog.text
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_perform_notification_telegram_retryafter_error(
|
|
make_organization_and_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
):
|
|
organization, user = make_organization_and_user()
|
|
user_notification_policy = make_user_notification_policy(
|
|
user=user,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.TELEGRAM,
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
log_record = make_user_notification_policy_log_record(
|
|
author=user,
|
|
alert_group=alert_group,
|
|
notification_policy=user_notification_policy,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
|
)
|
|
countdown = 15
|
|
exc = RetryAfter(countdown)
|
|
with patch.object(TelegramToUserConnector, "notify_user", side_effect=exc) as mock_notify_user:
|
|
with patch.object(perform_notification, "apply_async") as mock_apply_async:
|
|
perform_notification(log_record.pk, False)
|
|
|
|
mock_notify_user.assert_called_once_with(user, alert_group, user_notification_policy)
|
|
# task is rescheduled using the countdown value from the exception
|
|
mock_apply_async.assert_called_once_with((log_record.pk, False), countdown=countdown)
|
|
assert alert_group.personal_log_records.last() == log_record
|
|
|
|
# but if the log was too old, skip and create a failed log record
|
|
log_record.created_at = timezone.now() - timezone.timedelta(minutes=90)
|
|
log_record.save()
|
|
with patch.object(TelegramToUserConnector, "notify_user", side_effect=exc) as mock_notify_user:
|
|
with patch.object(perform_notification, "apply_async") as mock_apply_async:
|
|
perform_notification(log_record.pk, False)
|
|
mock_notify_user.assert_called_once_with(user, alert_group, user_notification_policy)
|
|
assert not mock_apply_async.called
|
|
last_log_record = UserNotificationPolicyLogRecord.objects.last()
|
|
assert last_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
|
assert last_log_record.reason == "Telegram rate limit exceeded"
|
|
assert (
|
|
last_log_record.notification_error_code
|
|
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_TELEGRAM_RATELIMIT
|
|
)
|
|
|
|
|
|
@patch("apps.base.models.UserNotificationPolicy.get_default_fallback_policy")
|
|
@patch("apps.base.tests.messaging_backend.TestOnlyBackend.notify_user")
|
|
@pytest.mark.django_db
|
|
def test_perform_notification_use_default_notification_policy_fallback(
|
|
mock_notify_user,
|
|
mock_get_default_fallback_policy,
|
|
make_organization,
|
|
make_user,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
):
|
|
organization = make_organization()
|
|
user = make_user(organization=organization)
|
|
fallback_notification_policy = UserNotificationPolicy(
|
|
user=user,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.TESTONLY,
|
|
important=False,
|
|
order=0,
|
|
)
|
|
|
|
mock_get_default_fallback_policy.return_value = fallback_notification_policy
|
|
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
log_record = make_user_notification_policy_log_record(
|
|
author=user,
|
|
alert_group=alert_group,
|
|
notification_policy=None,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
|
)
|
|
|
|
perform_notification(log_record.pk, True)
|
|
|
|
mock_notify_user.assert_called_once_with(user, alert_group, fallback_notification_policy)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_notify_user_task_notification_bundle_is_enabled(
|
|
make_organization_and_user,
|
|
make_user_for_organization,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
settings,
|
|
):
|
|
settings.FEATURE_NOTIFICATION_BUNDLE_ENABLED = True
|
|
organization, user_1 = make_organization_and_user()
|
|
user_2 = make_user_for_organization(organization)
|
|
make_user_notification_policy(
|
|
user=user_1,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SMS, # channel is in NOTIFICATION_CHANNELS_TO_BUNDLE
|
|
)
|
|
make_user_notification_policy(
|
|
user=user_1,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SMS,
|
|
important=True,
|
|
)
|
|
make_user_notification_policy(
|
|
user=user_2,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SLACK, # channel is not in NOTIFICATION_CHANNELS_TO_BUNDLE
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group_1 = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
alert_group_2 = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
assert not user_1.notification_bundles.exists()
|
|
# send 1st notification to user_1, check notification_bundle was created
|
|
# without scheduling send_bundled_notification task
|
|
notify_user_task(user_1.id, alert_group_1.id)
|
|
assert user_1.notification_bundles.count() == 1
|
|
notification_bundle = user_1.notification_bundles.first()
|
|
assert notification_bundle.notification_task_id is None
|
|
assert not notification_bundle.notifications.exists()
|
|
# send 2nd notification to user_1, check bundled notification was attached to notification_bundle
|
|
# and send_bundled_notification was scheduled
|
|
notify_user_task(user_1.id, alert_group_2.id)
|
|
notification_bundle.refresh_from_db()
|
|
assert notification_bundle.notifications.count() == 1
|
|
assert notification_bundle.notification_task_id is not None
|
|
# send important notification to user_1, check new notification_bundle was created
|
|
notify_user_task(user_1.id, alert_group_1.id, important=True)
|
|
assert user_1.notification_bundles.count() == 2
|
|
important_notification_bundle = user_1.notification_bundles.get(important=True)
|
|
assert important_notification_bundle.notification_task_id is None
|
|
assert not important_notification_bundle.notifications.exists()
|
|
# send notification to user_2 (notification channel is not in NOTIFICATION_CHANNELS_TO_BUNDLE),
|
|
# check notification_bundle was not created
|
|
notify_user_task(user_2.id, alert_group_1.id)
|
|
assert not user_2.notification_bundles.exists()
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_notify_user_task_notification_bundle_is_not_enabled(
|
|
make_organization_and_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
settings,
|
|
):
|
|
settings.FEATURE_NOTIFICATION_BUNDLE_ENABLED = False
|
|
organization, user = make_organization_and_user()
|
|
make_user_notification_policy(
|
|
user=user,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SMS, # channel is in NOTIFICATION_CHANNELS_TO_BUNDLE
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
|
|
# send notification, check notification_bundle was not created
|
|
notify_user_task(user.id, alert_group.id)
|
|
assert not user.notification_bundles.exists()
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_send_bundle_notification(
|
|
make_organization_and_user,
|
|
make_user_notification_policy,
|
|
make_user_notification_bundle,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
settings,
|
|
caplog,
|
|
):
|
|
settings.FEATURE_NOTIFICATION_BUNDLE_ENABLED = True
|
|
organization, user = make_organization_and_user()
|
|
notification_policy = make_user_notification_policy(
|
|
user=user,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SMS, # channel is in NOTIFICATION_CHANNELS_TO_BUNDLE
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group_1 = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
alert_group_2 = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
alert_group_3 = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
notification_bundle = make_user_notification_bundle(
|
|
user, UserNotificationPolicy.NotificationChannel.SMS, notification_task_id="test_task_id", eta=timezone.now()
|
|
)
|
|
notification_bundle.append_notification(alert_group_1, notification_policy)
|
|
notification_bundle.append_notification(alert_group_2, notification_policy)
|
|
notification_bundle.append_notification(alert_group_3, notification_policy)
|
|
assert notification_bundle.notifications.filter(bundle_uuid__isnull=True).count() == 3
|
|
alert_group_3.resolve()
|
|
with patch("apps.alerts.tasks.notify_user.compare_escalations", return_value=True):
|
|
# send notification for 2 active alert groups
|
|
send_bundled_notification(notification_bundle.id)
|
|
assert f"alert_group {alert_group_3.id} is not active, skip notification" in caplog.text
|
|
assert "perform bundled notification for alert groups with ids:" in caplog.text
|
|
# check bundle_uuid was set, notification for resolved alert group was deleted
|
|
assert notification_bundle.notifications.filter(bundle_uuid__isnull=True).count() == 0
|
|
assert notification_bundle.notifications.all().count() == 2
|
|
assert not notification_bundle.notifications.filter(alert_group=alert_group_3).exists()
|
|
|
|
# send notification for 1 active alert group
|
|
notification_bundle.notifications.update(bundle_uuid=None)
|
|
alert_group_2.resolve()
|
|
send_bundled_notification(notification_bundle.id)
|
|
assert f"alert_group {alert_group_2.id} is not active, skip notification" in caplog.text
|
|
assert (
|
|
f"there is only one alert group in bundled notification, perform regular notification. "
|
|
f"alert_group {alert_group_1.id}"
|
|
) in caplog.text
|
|
# check bundle_uuid was set
|
|
assert notification_bundle.notifications.filter(bundle_uuid__isnull=True).count() == 0
|
|
assert notification_bundle.notifications.all().count() == 1
|
|
# cleanup notifications
|
|
notification_bundle.notifications.all().delete()
|
|
|
|
# send notification for 0 active alert group
|
|
notification_bundle.append_notification(alert_group_1, notification_policy)
|
|
alert_group_1.resolve()
|
|
send_bundled_notification(notification_bundle.id)
|
|
assert f"alert_group {alert_group_1.id} is not active, skip notification" in caplog.text
|
|
assert f"no alert groups to notify about or notification is not allowed for user {user.id}" in caplog.text
|
|
# check all notifications were deleted
|
|
assert notification_bundle.notifications.all().count() == 0
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_send_bundle_notification_task_id_mismatch(
|
|
make_organization_and_user,
|
|
make_user_notification_bundle,
|
|
settings,
|
|
caplog,
|
|
):
|
|
settings.FEATURE_NOTIFICATION_BUNDLE_ENABLED = True
|
|
organization, user = make_organization_and_user()
|
|
notification_bundle = make_user_notification_bundle(
|
|
user, UserNotificationPolicy.NotificationChannel.SMS, notification_task_id="test_task_id", eta=timezone.now()
|
|
)
|
|
send_bundled_notification(notification_bundle.id)
|
|
assert (
|
|
f"send_bundled_notification: notification_task_id mismatch. "
|
|
f"Duplication or non-active notification triggered. "
|
|
f"Active: {notification_bundle.notification_task_id}"
|
|
) in caplog.text
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_notify_user_task_notification_bundle_eta_is_outdated(
|
|
make_organization_and_user,
|
|
make_user_for_organization,
|
|
make_user_notification_policy,
|
|
make_user_notification_bundle,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
settings,
|
|
):
|
|
settings.FEATURE_NOTIFICATION_BUNDLE_ENABLED = True
|
|
organization, user = make_organization_and_user()
|
|
notification_policy = make_user_notification_policy(
|
|
user=user,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.SMS, # channel is in NOTIFICATION_CHANNELS_TO_BUNDLE
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group_1 = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
alert_group_2 = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
now = timezone.now()
|
|
outdated_eta = now - timezone.timedelta(minutes=5)
|
|
test_task_id = "test_task_id"
|
|
notification_bundle = make_user_notification_bundle(
|
|
user,
|
|
UserNotificationPolicy.NotificationChannel.SMS,
|
|
eta=outdated_eta,
|
|
notification_task_id=test_task_id,
|
|
last_notified_at=now,
|
|
)
|
|
notification_bundle.append_notification(alert_group_1, notification_policy)
|
|
assert not notification_bundle.eta_is_valid()
|
|
assert notification_bundle.notifications.count() == 1
|
|
|
|
# call notify_user_task and check that new notification task for notification_bundle was scheduled
|
|
notify_user_task(user.id, alert_group_2.id)
|
|
notification_bundle.refresh_from_db()
|
|
assert notification_bundle.eta_is_valid()
|
|
assert notification_bundle.notification_task_id != test_task_id
|
|
assert notification_bundle.last_notified_at == now
|
|
assert notification_bundle.notifications.count() == 2
|
|
|
|
|
|
@patch.object(perform_notification, "apply_async")
|
|
@pytest.mark.django_db
|
|
def test_notify_user_task_direct_paging_acknowledged(
|
|
mock_perform_notification_apply_async,
|
|
make_organization,
|
|
make_user,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
make_user_notification_policy,
|
|
django_capture_on_commit_callbacks,
|
|
):
|
|
organization = make_organization()
|
|
from_user = make_user(organization=organization)
|
|
user1 = make_user(organization=organization)
|
|
user2 = make_user(organization=organization)
|
|
make_user_notification_policy(
|
|
user=user1,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.TESTONLY,
|
|
)
|
|
make_user_notification_policy(
|
|
user=user2,
|
|
step=UserNotificationPolicy.Step.NOTIFY,
|
|
notify_by=UserNotificationPolicy.NotificationChannel.TESTONLY,
|
|
)
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
|
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
|
|
|
direct_paging(organization, from_user, "Test", alert_group=alert_group, users=[(user1, False), (user2, False)])
|
|
alert_group.acknowledge_by_user_or_backsync(user1)
|
|
|
|
# no notification should be sent for user1 because they have acknowledged the alert group
|
|
with django_capture_on_commit_callbacks(execute=True):
|
|
notify_user_task(user1.pk, alert_group.pk, notify_even_acknowledged=True)
|
|
|
|
mock_perform_notification_apply_async.assert_not_called()
|
|
|
|
# user2 should receive a notification because they have not acknowledged the alert group
|
|
with django_capture_on_commit_callbacks(execute=True):
|
|
notify_user_task(user2.pk, alert_group.pk, notify_even_acknowledged=True)
|
|
|
|
mock_perform_notification_apply_async.assert_called_once()
|