Files
2024-02-15 17:01:50 +00:00

556 lines
21 KiB
Python

import unittest
from datetime import timedelta
from unittest import mock
from unittest.mock import Mock, patch
import pytest
from django.core.cache import cache
from django.db import router, transaction
from django.utils import timezone
from sentry.db.models.manager import BaseManager
from sentry.incidents.logic import delete_alert_rule, update_alert_rule
from sentry.incidents.models import (
AlertRule,
AlertRuleActivity,
AlertRuleActivityType,
AlertRuleStatus,
AlertRuleTrigger,
AlertRuleTriggerAction,
Incident,
IncidentStatus,
IncidentTrigger,
IncidentType,
TriggerStatus,
)
from sentry.services.hybrid_cloud.user.service import user_service
from sentry.testutils.cases import TestCase
from sentry.testutils.helpers.datetime import freeze_time
from sentry.testutils.silo import region_silo_test
@region_silo_test
class FetchForOrganizationTest(TestCase):
def test_empty(self):
incidents = Incident.objects.fetch_for_organization(self.organization, [self.project])
assert [] == list(incidents)
self.create_project()
def test_simple(self):
incident = self.create_incident()
assert [incident] == list(
Incident.objects.fetch_for_organization(self.organization, [self.project])
)
def test_invalid_project(self):
project = self.create_project()
incident = self.create_incident(projects=[project])
assert [] == list(
Incident.objects.fetch_for_organization(self.organization, [self.project])
)
assert [incident] == list(
Incident.objects.fetch_for_organization(self.organization, [project])
)
def test_multi_project(self):
project = self.create_project()
incident = self.create_incident(projects=[project, self.project])
assert [incident] == list(
Incident.objects.fetch_for_organization(self.organization, [self.project])
)
assert [incident] == list(
Incident.objects.fetch_for_organization(self.organization, [project])
)
assert [incident] == list(
Incident.objects.fetch_for_organization(self.organization, [self.project, project])
)
class IncidentGetForSubscriptionTest(TestCase):
def test(self):
alert_rule = self.create_alert_rule()
subscription = alert_rule.snuba_query.subscriptions.get()
# First test fetching from database
assert cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % subscription.id) is None
assert AlertRule.objects.get_for_subscription(subscription) == alert_rule
# Now test fetching from cache
assert cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % subscription.id) == alert_rule
assert AlertRule.objects.get_for_subscription(subscription) == alert_rule
class IncidentClearSubscriptionCacheTest(TestCase):
def setUp(self):
self.alert_rule = self.create_alert_rule()
self.subscription = self.alert_rule.snuba_query.subscriptions.get()
def test_updated_subscription(self):
AlertRule.objects.get_for_subscription(self.subscription)
assert (
cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id)
== self.alert_rule
)
self.subscription.save()
assert cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id) is None
def test_deleted_subscription(self):
AlertRule.objects.get_for_subscription(self.subscription)
assert (
cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id)
== self.alert_rule
)
subscription_id = self.subscription.id
self.subscription.delete()
assert cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id) is None
# Add the subscription id back in so we don't use `None` in the lookup check.
self.subscription.id = subscription_id
with pytest.raises(AlertRule.DoesNotExist):
AlertRule.objects.get_for_subscription(self.subscription)
def test_deleted_alert_rule(self):
AlertRule.objects.get_for_subscription(self.subscription)
assert (
cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id)
== self.alert_rule
)
delete_alert_rule(self.alert_rule)
assert cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id) is None
with pytest.raises(AlertRule.DoesNotExist):
AlertRule.objects.get_for_subscription(self.subscription)
class AlertRuleTriggerClearCacheTest(TestCase):
def setUp(self):
self.alert_rule = self.create_alert_rule()
self.trigger = self.create_alert_rule_trigger(self.alert_rule)
def test_updated_alert_rule(self):
AlertRuleTrigger.objects.get_for_alert_rule(self.alert_rule)
assert cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id)) == [
self.trigger
]
self.alert_rule.save()
assert (
cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id))
) is None
def test_deleted_alert_rule(self):
AlertRuleTrigger.objects.get_for_alert_rule(self.alert_rule)
assert cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id)) == [
self.trigger
]
alert_rule_id = self.alert_rule.id
self.alert_rule.delete()
assert (cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(alert_rule_id))) is None
def test_updated_alert_rule_trigger(self):
AlertRuleTrigger.objects.get_for_alert_rule(self.alert_rule)
assert cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id)) == [
self.trigger
]
self.trigger.save()
assert (
cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id))
) is None
def test_deleted_alert_rule_trigger(self):
AlertRuleTrigger.objects.get_for_alert_rule(self.alert_rule)
assert cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id)) == [
self.trigger
]
self.trigger.delete()
assert (
cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id))
) is None
class ActiveIncidentClearCacheTest(TestCase):
def setUp(self):
self.alert_rule = self.create_alert_rule()
self.trigger = self.create_alert_rule_trigger(self.alert_rule)
def test_negative_cache(self):
assert (
cache.get(
Incident.objects._build_active_incident_cache_key(
self.alert_rule.id, self.project.id
)
)
is None
)
Incident.objects.get_active_incident(self.alert_rule, self.project)
assert (
cache.get(
Incident.objects._build_active_incident_cache_key(
self.alert_rule.id, self.project.id
)
)
is False
)
self.create_incident(status=IncidentStatus.CLOSED.value)
self.alert_rule.save()
assert (
cache.get(
Incident.objects._build_active_incident_cache_key(
self.alert_rule.id, self.project.id
)
)
) is False
def test_cache(self):
assert (
cache.get(
Incident.objects._build_active_incident_cache_key(
self.alert_rule.id, self.project.id
)
)
is None
)
active_incident = self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
Incident.objects.get_active_incident(self.alert_rule, self.project)
assert (
cache.get(
Incident.objects._build_active_incident_cache_key(
self.alert_rule.id, self.project.id
)
)
== active_incident
)
active_incident = self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
assert (
cache.get(
Incident.objects._build_active_incident_cache_key(
self.alert_rule.id, self.project.id
)
)
is None
)
Incident.objects.get_active_incident(self.alert_rule, self.project)
assert (
cache.get(
Incident.objects._build_active_incident_cache_key(
self.alert_rule.id, self.project.id
)
)
== active_incident
)
class IncidentTriggerClearCacheTest(TestCase):
def setUp(self):
self.alert_rule = self.create_alert_rule()
self.trigger = self.create_alert_rule_trigger(self.alert_rule)
self.incident = self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
def test_deleted_incident(self):
incident_trigger = IncidentTrigger.objects.create(
incident=self.incident,
alert_rule_trigger=self.trigger,
status=TriggerStatus.ACTIVE.value,
)
IncidentTrigger.objects.get_for_incident(self.incident)
assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) == [
incident_trigger
]
self.incident.delete()
assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) is None
def test_updated_incident_trigger(self):
IncidentTrigger.objects.get_for_incident(self.incident)
assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) == []
incident_trigger = IncidentTrigger.objects.create(
incident=self.incident,
alert_rule_trigger=self.trigger,
status=TriggerStatus.ACTIVE.value,
)
IncidentTrigger.objects.get_for_incident(self.incident)
assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) == [
incident_trigger
]
def test_deleted_incident_trigger(self):
incident_trigger = IncidentTrigger.objects.create(
incident=self.incident,
alert_rule_trigger=self.trigger,
status=TriggerStatus.ACTIVE.value,
)
IncidentTrigger.objects.get_for_incident(self.incident)
assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) == [
incident_trigger
]
self.trigger.delete()
assert (cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id))) is None
class IncidentCreationTest(TestCase):
def test_simple(self):
title = "hello"
alert_rule = self.create_alert_rule()
incident = Incident.objects.create(
self.organization,
title=title,
type=IncidentType.ALERT_TRIGGERED.value,
alert_rule=alert_rule,
)
assert incident.identifier == 1
assert incident.title == title
# Check identifier correctly increments
incident = Incident.objects.create(
self.organization,
title=title,
type=IncidentType.ALERT_TRIGGERED.value,
alert_rule=alert_rule,
)
assert incident.identifier == 2
def test_identifier_conflict(self):
create_method = BaseManager.create
call_count = [0]
alert_rule = self.create_alert_rule()
def mock_base_create(*args, **kwargs):
if not call_count[0]:
call_count[0] += 1
# This incident will take the identifier we already fetched and
# use it, which will cause the database to throw an integrity
# error.
with transaction.atomic(router.db_for_write(Incident)):
incident = Incident.objects.create(
self.organization,
status=IncidentStatus.OPEN.value,
title="Conflicting Incident",
type=IncidentType.ALERT_TRIGGERED.value,
alert_rule=alert_rule,
)
assert incident.identifier == kwargs["identifier"]
create_method(*args, **kwargs)
self.fail("Expected an integrity error")
else:
call_count[0] += 1
return create_method(*args, **kwargs)
self.organization
with patch.object(BaseManager, "create", new=mock_base_create):
incident = Incident.objects.create(
self.organization,
alert_rule=alert_rule,
status=IncidentStatus.OPEN.value,
title="hi",
type=IncidentType.ALERT_TRIGGERED.value,
)
# We should have 3 calls - one for initial create, one for conflict,
# then the final one for the retry we get due to the conflict
assert call_count[0] == 3
# Ideally this would be 2, but because we create the conflicting
# row inside of a transaction it ends up rolled back. We just want
# to verify that it was created successfully.
assert incident.identifier == 1
@freeze_time()
class IncidentDurationTest(unittest.TestCase):
def test(self):
incident = Incident(date_started=timezone.now() - timedelta(minutes=5))
assert incident.duration == timedelta(minutes=5)
incident.date_closed = incident.date_started + timedelta(minutes=2)
assert incident.duration == timedelta(minutes=2)
class IncidentAlertRuleRelationTest(TestCase):
def test(self):
self.alert_rule = self.create_alert_rule()
self.trigger = self.create_alert_rule_trigger(self.alert_rule)
self.incident = self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
assert self.incident.alert_rule.id == self.alert_rule.id
all_alert_rules = list(AlertRule.objects.all())
assert self.alert_rule in all_alert_rules
self.alert_rule.status = AlertRuleStatus.SNAPSHOT.value
self.alert_rule.save()
all_alert_rules = list(AlertRule.objects.all())
assert self.alert_rule not in all_alert_rules
assert self.incident.alert_rule.id == self.alert_rule.id
@freeze_time()
class IncidentCurrentEndDateTest(unittest.TestCase):
def test(self):
incident = Incident()
assert incident.current_end_date == timezone.now()
incident.date_closed = timezone.now() - timedelta(minutes=10)
assert incident.current_end_date == timezone.now() - timedelta(minutes=10)
@region_silo_test
class AlertRuleFetchForOrganizationTest(TestCase):
def test_empty(self):
alert_rule = AlertRule.objects.fetch_for_organization(self.organization)
assert [] == list(alert_rule)
def test_simple(self):
alert_rule = self.create_alert_rule()
assert [alert_rule] == list(AlertRule.objects.fetch_for_organization(self.organization))
def test_with_projects(self):
project = self.create_project()
alert_rule = self.create_alert_rule(projects=[project])
assert [] == list(
AlertRule.objects.fetch_for_organization(self.organization, [self.project])
)
assert [alert_rule] == list(
AlertRule.objects.fetch_for_organization(self.organization, [project])
)
def test_multi_project(self):
project = self.create_project()
alert_rule1 = self.create_alert_rule(projects=[project, self.project])
alert_rule2 = self.create_alert_rule(projects=[project])
assert [alert_rule1] == list(
AlertRule.objects.fetch_for_organization(self.organization, [self.project])
)
assert {alert_rule1, alert_rule2} == set(
AlertRule.objects.fetch_for_organization(self.organization, [project])
)
class AlertRuleTriggerActionTargetTest(TestCase):
def test_user(self):
trigger = AlertRuleTriggerAction(
target_type=AlertRuleTriggerAction.TargetType.USER.value,
target_identifier=str(self.user.id),
)
assert trigger.target == user_service.get_user(user_id=self.user.id)
def test_invalid_user(self):
trigger = AlertRuleTriggerAction(
target_type=AlertRuleTriggerAction.TargetType.USER.value, target_identifier="10000000"
)
assert trigger.target is None
def test_team(self):
trigger = AlertRuleTriggerAction(
target_type=AlertRuleTriggerAction.TargetType.TEAM.value,
target_identifier=str(self.team.id),
)
assert trigger.target == self.team
def test_invalid_team(self):
trigger = AlertRuleTriggerAction(
target_type=AlertRuleTriggerAction.TargetType.TEAM.value, target_identifier="10000000"
)
assert trigger.target is None
def test_specific(self):
email = "[email protected]"
trigger = AlertRuleTriggerAction(
target_type=AlertRuleTriggerAction.TargetType.SPECIFIC.value, target_identifier=email
)
assert trigger.target == email
class AlertRuleTriggerActionActivateBaseTest:
method: str
def setUp(self):
self.old_handlers = AlertRuleTriggerAction._type_registrations
AlertRuleTriggerAction._type_registrations = {}
def tearDown(self):
AlertRuleTriggerAction._type_registrations = self.old_handlers
def test_no_handler(self):
trigger = AlertRuleTriggerAction(type=AlertRuleTriggerAction.Type.EMAIL.value)
assert trigger.fire(Mock(), Mock(), Mock(), 123, IncidentStatus.CRITICAL) is None
def test_handler(self):
mock_handler = Mock()
mock_method = getattr(mock_handler.return_value, self.method)
mock_method.return_value = "test"
type = AlertRuleTriggerAction.Type.EMAIL
AlertRuleTriggerAction.register_type("something", type, [])(mock_handler)
trigger = AlertRuleTriggerAction(type=type.value)
assert (
getattr(trigger, self.method)(Mock(), Mock(), Mock(), 123, IncidentStatus.CRITICAL)
== mock_method.return_value
)
class AlertRuleTriggerActionFireTest(AlertRuleTriggerActionActivateBaseTest, unittest.TestCase):
method = "fire"
class AlertRuleTriggerActionResolveTest(AlertRuleTriggerActionActivateBaseTest, unittest.TestCase):
method = "resolve"
class AlertRuleTriggerActionActivateTest(TestCase):
@pytest.fixture(autouse=True)
def _setup_metric_patch(self):
with mock.patch("sentry.incidents.models.metrics") as self.metrics:
yield
def setUp(self):
self.old_handlers = AlertRuleTriggerAction._type_registrations
AlertRuleTriggerAction._type_registrations = {}
def tearDown(self):
AlertRuleTriggerAction._type_registrations = self.old_handlers
def test_unhandled(self):
trigger = AlertRuleTriggerAction(type=AlertRuleTriggerAction.Type.EMAIL.value)
trigger.build_handler(Mock(), Mock(), Mock())
self.metrics.incr.assert_called_once_with("alert_rule_trigger.unhandled_type.0")
def test_handled(self):
mock_handler = Mock()
type = AlertRuleTriggerAction.Type.EMAIL
AlertRuleTriggerAction.register_type("something", type, [])(mock_handler)
trigger = AlertRuleTriggerAction(type=AlertRuleTriggerAction.Type.EMAIL.value)
incident = Mock()
project = Mock()
trigger.build_handler(trigger, incident, project)
mock_handler.assert_called_once_with(trigger, incident, project)
assert not self.metrics.incr.called
class AlertRuleActivityTest(TestCase):
def test_simple(self):
assert AlertRuleActivity.objects.all().count() == 0
self.alert_rule = self.create_alert_rule()
assert AlertRuleActivity.objects.filter(
alert_rule=self.alert_rule, type=AlertRuleActivityType.CREATED.value
).exists()
def test_delete(self):
assert AlertRuleActivity.objects.all().count() == 0
self.alert_rule = self.create_alert_rule()
self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
delete_alert_rule(self.alert_rule)
assert AlertRuleActivity.objects.filter(
alert_rule=self.alert_rule, type=AlertRuleActivityType.DELETED.value
).exists()
def test_update(self):
assert AlertRuleActivity.objects.all().count() == 0
self.alert_rule = self.create_alert_rule()
self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
update_alert_rule(self.alert_rule, name="updated_name")
assert AlertRuleActivity.objects.filter(
previous_alert_rule=self.alert_rule, type=AlertRuleActivityType.SNAPSHOT.value
).exists()
assert AlertRuleActivity.objects.filter(
alert_rule=self.alert_rule, type=AlertRuleActivityType.UPDATED.value
).exists()