oncall-engine/engine/apps/user_management/tests/test_organization.py
Vadim Stepanov 3c00345f54
Fix Grafana teams sync (#1652)
# What this PR does
Sometimes plugin sync fails with the following exception:
```
Cannot delete or update a parent row: a foreign key constraint fails (`schedules_oncallschedule`, CONSTRAINT `alerts_oncallschedul_team_id_4e633f4b_fk_user_mana` FOREIGN KEY (`team_id`) REFERENCES `user_management_team` (`id`))'
```

How to reproduce:
1. Create a new Grafana team
2. Create two schedules with different types (e.g. ICal and Web) and
assign both schedules to the new team
3. Delete the team in Grafana
4. Trigger plugin sync, the sync will fail with the exception above

This happens because the `OnCallSchedule` Django model is a polymorphic
model and there's a [known
bug](https://github.com/django-polymorphic/django-polymorphic/issues/229)
in `django-polymorphic` with deleting related objects when using
`SET_NULL` and `CASCADE`. This PR adds non-polymorphic versions of
`SET_NULL` and `CASCADE` to use in schedule FKs as per this
[comment](https://github.com/django-polymorphic/django-polymorphic/issues/229#issuecomment-398434412).

This also applies to two other schedule FKs: `organization` and
`user_group`, which are not working properly as well.

## 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)
2023-03-28 18:26:24 +00:00

203 lines
7.5 KiB
Python

import pytest
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from django.utils import timezone
from rest_framework.test import APIClient
from apps.alerts.models import AlertGroupLogRecord, AlertReceiveChannel, EscalationPolicy
from apps.base.models import UserNotificationPolicy, UserNotificationPolicyLogRecord
from apps.schedules.models import OnCallScheduleICal, OnCallScheduleWeb
from apps.telegram.models import TelegramMessage
from apps.twilioapp.constants import TwilioCallStatuses, TwilioMessageStatuses
from apps.user_management.models import Organization
@pytest.mark.django_db
def test_organization_soft_delete(
make_organization_and_user_with_token,
make_alert_receive_channel,
):
organization, _, token = make_organization_and_user_with_token()
alert_receive_channel = make_alert_receive_channel(
organization=organization, integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER
)
org_id = organization.id
organization.delete()
deleted_organization = Organization.objects_with_deleted.get(id=org_id)
# check if org soft-deleted
assert deleted_organization.deleted_at is not None
# check if public api responds with 404
client = APIClient()
url = reverse("api-public:integrations-list")
response = client.get(url, format="json", HTTP_AUTHORIZATION=f"{token}")
assert response.status_code == 404
# check if alert receiver view responds with 403
url = reverse("integrations:alertmanager", kwargs={"alert_channel_key": alert_receive_channel.token})
data = {"a": "b"}
response = client.post(url, data, format="json")
assert response.status_code == 403
@pytest.mark.django_db
def test_organization_hard_delete(
make_organization,
make_user,
make_team,
make_slack_team_identity,
make_slack_user_identity,
make_slack_message,
make_slack_action_record,
make_schedule,
make_custom_action,
make_alert_receive_channel,
make_escalation_chain,
make_escalation_policy,
make_channel_filter,
make_user_notification_policy,
make_telegram_user_connector,
make_telegram_channel,
make_telegram_verification_code,
make_telegram_channel_verification_code,
make_telegram_message,
make_alert,
make_alert_group,
make_alert_group_log_record,
make_user_notification_policy_log_record,
make_sms,
make_phone_call,
make_token_for_organization,
make_public_api_token,
make_invitation,
make_resolution_note,
make_resolution_note_slack_message,
):
slack_team_identity = make_slack_team_identity()
organization = make_organization(slack_team_identity=slack_team_identity)
slack_user_identity_1 = make_slack_user_identity(slack_team_identity=slack_team_identity, slack_id="USER_1")
slack_user_identity_2 = make_slack_user_identity(slack_team_identity=slack_team_identity, slack_id="USER_2")
user_1 = make_user(organization=organization, slack_user_identity=slack_user_identity_1)
user_2 = make_user(organization=organization, slack_user_identity=slack_user_identity_2)
user_notification_policy = make_user_notification_policy(
user=user_1, step=UserNotificationPolicy.Step.WAIT, wait_delay=timezone.timedelta(minutes=15), important=False
)
team = make_team(organization=organization)
team.users.add(user_1)
# Creating different types of schedules to check that deletion works well with PolymorphicModel
schedule_web = make_schedule(organization=organization, schedule_class=OnCallScheduleWeb)
schedule_ical = make_schedule(organization=organization, schedule_class=OnCallScheduleICal)
custom_action = make_custom_action(organization=organization)
escalation_chain = make_escalation_chain(organization=organization)
escalation_policy = make_escalation_policy(
escalation_chain=escalation_chain,
escalation_policy_step=EscalationPolicy.STEP_WAIT,
wait_delay=EscalationPolicy.ONE_MINUTE,
last_notified_user=user_1,
)
escalation_policy.notify_to_users_queue.set([user_1, user_2])
alert_receive_channel = make_alert_receive_channel(organization=organization, author=user_1)
channel_filter = make_channel_filter(alert_receive_channel, is_default=True, escalation_chain=escalation_chain)
alert_group = make_alert_group(
alert_receive_channel=alert_receive_channel,
acknowledged_by_user=user_1,
silenced_by_user=user_2,
wiped_by=user_2,
)
alert = make_alert(alert_group=alert_group, raw_request_data={})
alert_group.resolved_by_alert = alert
alert_group.save(update_fields=["resolved_by_alert"])
user_notification_policy_log_record = make_user_notification_policy_log_record(
author=user_1,
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
notification_policy=user_notification_policy,
notification_step=user_notification_policy.step,
notification_channel=user_notification_policy.notify_by,
alert_group=alert_group,
)
sms = make_sms(
receiver=user_1, status=TwilioMessageStatuses.SENT, represents_alert=alert, represents_alert_group=alert_group
)
phone_call = make_phone_call(
receiver=user_1, status=TwilioCallStatuses.COMPLETED, represents_alert=alert, represents_alert_group=alert_group
)
telegram_user_connector = make_telegram_user_connector(user=user_1)
telegram_channel = make_telegram_channel(organization=organization)
telegram_verification_code = make_telegram_verification_code(user=user_1)
telegram_channel_verification_code = make_telegram_channel_verification_code(
organization=organization, author=user_1
)
telegram_message = make_telegram_message(alert_group=alert_group, message_type=TelegramMessage.ALERT_GROUP_MESSAGE)
slack_message = make_slack_message(alert_group=alert_group)
slack_action_record = make_slack_action_record(organization=organization, user=user_1)
plugin_token, _ = make_token_for_organization(organization)
public_api_token, _ = make_public_api_token(user_1, organization)
invitation = make_invitation(alert_group=alert_group, author=user_1, invitee=user_2)
alert_group_log_record = make_alert_group_log_record(
alert_group=alert_group, author=user_1, type=AlertGroupLogRecord.TYPE_ACK, invitation=invitation
)
resolution_note_slack_message = make_resolution_note_slack_message(
alert_group=alert_group, user=user_1, added_by_user=user_2
)
resolution_note = make_resolution_note(
alert_group=alert_group, author=user_1, resolution_note_slack_message=resolution_note_slack_message
)
cascading_objects = [
user_1,
user_2,
team,
user_notification_policy,
schedule_web,
schedule_ical,
custom_action,
escalation_chain,
escalation_policy,
alert_receive_channel,
channel_filter,
alert_group,
alert,
alert_group_log_record,
user_notification_policy_log_record,
phone_call,
sms,
telegram_message,
telegram_user_connector,
telegram_channel,
telegram_verification_code,
telegram_channel_verification_code,
slack_message,
slack_action_record,
plugin_token,
public_api_token,
invitation,
resolution_note,
resolution_note_slack_message,
]
organization.hard_delete()
for obj in cascading_objects:
with pytest.raises(ObjectDoesNotExist):
obj.refresh_from_db()