123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- 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 IntegrityError, router, transaction
- from django.utils import timezone
- from freezegun import freeze_time
- 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 import TestCase
- 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()
- # 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)
- 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"]
- try:
- create_method(*args, **kwargs)
- except IntegrityError:
- raise
- else:
- 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 = "test@test.com"
- trigger = AlertRuleTriggerAction(
- target_type=AlertRuleTriggerAction.TargetType.SPECIFIC.value, target_identifier=email
- )
- assert trigger.target == email
- class AlertRuleTriggerActionActivateBaseTest:
- method = None
- 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()
|