# What this PR does - Stops writing `SlackMessage.organization` + removes references to this field. [As we discussed](https://raintank-corp.slack.com/archives/C083TU81TCH/p1733315887463279?thread_ts=1733311105.095309&cid=C083TU81TCH), we do not need this field on this model/table, `SlackMessage._slack_team_identity` is sufficient (`organization` will be dropped in a separate PR) - Adds a data migration script which: - drops orphaned `SlackMessage` records; ie. ones which, even after the [`engine/apps/slack/migrations/0007_migrate_slackmessage_channel_id.py`](https://github.com/grafana/oncall/blob/dev/engine/apps/slack/migrations/0007_migrate_slackmessage_channel_id.py) migration, still don't have a `SlackMessage.channel` id filled in (we discussed + agreed on dropping these records [here](https://raintank-corp.slack.com/archives/C083TU81TCH/p1733329914516859?thread_ts=1733311105.095309&cid=C083TU81TCH)) - fills in empty `SlackMessage.slack_team_identity` values (from `slack_message.channel.slack_team_identity`) ### Other notes On the `organization` topic. We store records in `SlackMessage` for two purposes (AFAICT), and in both cases, we have references back to the `organization`: - alert groups - `slack_message.alert_group.channel.organization` - shift swap requests - `shift_swap_request.schedule.organization` ## 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.
707 lines
29 KiB
Python
707 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_with_slack_team_identity,
|
|
make_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
make_slack_channel,
|
|
make_slack_message,
|
|
):
|
|
organization, slack_team_identity = make_organization_with_slack_team_identity()
|
|
|
|
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,
|
|
reason_to_skip_escalation=reason_to_skip_escalation,
|
|
)
|
|
|
|
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:
|
|
slack_channel = make_slack_channel(slack_team_identity=slack_team_identity)
|
|
make_slack_message(slack_channel, alert_group=alert_group)
|
|
|
|
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_with_slack_team_identity,
|
|
make_user,
|
|
make_user_notification_policy,
|
|
make_alert_receive_channel,
|
|
make_alert_group,
|
|
make_user_notification_policy_log_record,
|
|
make_slack_channel,
|
|
make_slack_message,
|
|
):
|
|
organization, slack_team_identity = make_organization_with_slack_team_identity()
|
|
|
|
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,
|
|
)
|
|
|
|
slack_channel = make_slack_channel(slack_team_identity=slack_team_identity)
|
|
make_slack_message(slack_channel, alert_group=alert_group)
|
|
|
|
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)
|
|
|
|
task_id = "test_task_id"
|
|
notification_bundle = make_user_notification_bundle(
|
|
user, UserNotificationPolicy.NotificationChannel.SMS, notification_task_id=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()
|
|
|
|
# send notification for 2 active alert groups
|
|
send_bundled_notification.apply((notification_bundle.id,), task_id=task_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)
|
|
|
|
# since we're calling send_bundled_notification several times within this test, we need to reset task_id
|
|
# because it gets set to None after the first call
|
|
notification_bundle.notification_task_id = task_id
|
|
notification_bundle.save()
|
|
|
|
alert_group_2.resolve()
|
|
|
|
send_bundled_notification.apply((notification_bundle.id,), task_id=task_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)
|
|
|
|
# since we're calling send_bundled_notification several times within this test, we need to reset task_id
|
|
# because it gets set to None after the first call
|
|
notification_bundle.notification_task_id = task_id
|
|
notification_bundle.save()
|
|
|
|
alert_group_1.resolve()
|
|
|
|
send_bundled_notification.apply((notification_bundle.id,), task_id=task_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()
|