test_models.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. import unittest
  2. from datetime import timedelta
  3. from unittest import mock
  4. from unittest.mock import Mock, patch
  5. import pytest
  6. from django.core.cache import cache
  7. from django.db import IntegrityError, router, transaction
  8. from django.utils import timezone
  9. from freezegun import freeze_time
  10. from sentry.db.models.manager import BaseManager
  11. from sentry.incidents.logic import delete_alert_rule, update_alert_rule
  12. from sentry.incidents.models import (
  13. AlertRule,
  14. AlertRuleActivity,
  15. AlertRuleActivityType,
  16. AlertRuleStatus,
  17. AlertRuleTrigger,
  18. AlertRuleTriggerAction,
  19. Incident,
  20. IncidentStatus,
  21. IncidentTrigger,
  22. IncidentType,
  23. TriggerStatus,
  24. )
  25. from sentry.services.hybrid_cloud.user.service import user_service
  26. from sentry.testutils import TestCase
  27. from sentry.testutils.silo import region_silo_test
  28. @region_silo_test
  29. class FetchForOrganizationTest(TestCase):
  30. def test_empty(self):
  31. incidents = Incident.objects.fetch_for_organization(self.organization, [self.project])
  32. assert [] == list(incidents)
  33. self.create_project()
  34. def test_simple(self):
  35. incident = self.create_incident()
  36. assert [incident] == list(
  37. Incident.objects.fetch_for_organization(self.organization, [self.project])
  38. )
  39. def test_invalid_project(self):
  40. project = self.create_project()
  41. incident = self.create_incident(projects=[project])
  42. assert [] == list(
  43. Incident.objects.fetch_for_organization(self.organization, [self.project])
  44. )
  45. assert [incident] == list(
  46. Incident.objects.fetch_for_organization(self.organization, [project])
  47. )
  48. def test_multi_project(self):
  49. project = self.create_project()
  50. incident = self.create_incident(projects=[project, self.project])
  51. assert [incident] == list(
  52. Incident.objects.fetch_for_organization(self.organization, [self.project])
  53. )
  54. assert [incident] == list(
  55. Incident.objects.fetch_for_organization(self.organization, [project])
  56. )
  57. assert [incident] == list(
  58. Incident.objects.fetch_for_organization(self.organization, [self.project, project])
  59. )
  60. class IncidentGetForSubscriptionTest(TestCase):
  61. def test(self):
  62. alert_rule = self.create_alert_rule()
  63. subscription = alert_rule.snuba_query.subscriptions.get()
  64. # First test fetching from database
  65. assert cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % subscription.id) is None
  66. assert AlertRule.objects.get_for_subscription(subscription) == alert_rule
  67. # Now test fetching from cache
  68. assert cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % subscription.id) == alert_rule
  69. assert AlertRule.objects.get_for_subscription(subscription) == alert_rule
  70. class IncidentClearSubscriptionCacheTest(TestCase):
  71. def setUp(self):
  72. self.alert_rule = self.create_alert_rule()
  73. self.subscription = self.alert_rule.snuba_query.subscriptions.get()
  74. def test_updated_subscription(self):
  75. AlertRule.objects.get_for_subscription(self.subscription)
  76. assert (
  77. cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id)
  78. == self.alert_rule
  79. )
  80. self.subscription.save()
  81. assert cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id) is None
  82. def test_deleted_subscription(self):
  83. AlertRule.objects.get_for_subscription(self.subscription)
  84. assert (
  85. cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id)
  86. == self.alert_rule
  87. )
  88. subscription_id = self.subscription.id
  89. self.subscription.delete()
  90. # Add the subscription id back in so we don't use `None` in the lookup check.
  91. self.subscription.id = subscription_id
  92. with pytest.raises(AlertRule.DoesNotExist):
  93. AlertRule.objects.get_for_subscription(self.subscription)
  94. def test_deleted_alert_rule(self):
  95. AlertRule.objects.get_for_subscription(self.subscription)
  96. assert (
  97. cache.get(AlertRule.objects.CACHE_SUBSCRIPTION_KEY % self.subscription.id)
  98. == self.alert_rule
  99. )
  100. delete_alert_rule(self.alert_rule)
  101. with pytest.raises(AlertRule.DoesNotExist):
  102. AlertRule.objects.get_for_subscription(self.subscription)
  103. class AlertRuleTriggerClearCacheTest(TestCase):
  104. def setUp(self):
  105. self.alert_rule = self.create_alert_rule()
  106. self.trigger = self.create_alert_rule_trigger(self.alert_rule)
  107. def test_updated_alert_rule(self):
  108. AlertRuleTrigger.objects.get_for_alert_rule(self.alert_rule)
  109. assert cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id)) == [
  110. self.trigger
  111. ]
  112. self.alert_rule.save()
  113. assert (
  114. cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id))
  115. ) is None
  116. def test_deleted_alert_rule(self):
  117. AlertRuleTrigger.objects.get_for_alert_rule(self.alert_rule)
  118. assert cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id)) == [
  119. self.trigger
  120. ]
  121. alert_rule_id = self.alert_rule.id
  122. self.alert_rule.delete()
  123. assert (cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(alert_rule_id))) is None
  124. def test_updated_alert_rule_trigger(self):
  125. AlertRuleTrigger.objects.get_for_alert_rule(self.alert_rule)
  126. assert cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id)) == [
  127. self.trigger
  128. ]
  129. self.trigger.save()
  130. assert (
  131. cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id))
  132. ) is None
  133. def test_deleted_alert_rule_trigger(self):
  134. AlertRuleTrigger.objects.get_for_alert_rule(self.alert_rule)
  135. assert cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id)) == [
  136. self.trigger
  137. ]
  138. self.trigger.delete()
  139. assert (
  140. cache.get(AlertRuleTrigger.objects._build_trigger_cache_key(self.alert_rule.id))
  141. ) is None
  142. class ActiveIncidentClearCacheTest(TestCase):
  143. def setUp(self):
  144. self.alert_rule = self.create_alert_rule()
  145. self.trigger = self.create_alert_rule_trigger(self.alert_rule)
  146. def test_negative_cache(self):
  147. assert (
  148. cache.get(
  149. Incident.objects._build_active_incident_cache_key(
  150. self.alert_rule.id, self.project.id
  151. )
  152. )
  153. is None
  154. )
  155. Incident.objects.get_active_incident(self.alert_rule, self.project)
  156. assert (
  157. cache.get(
  158. Incident.objects._build_active_incident_cache_key(
  159. self.alert_rule.id, self.project.id
  160. )
  161. )
  162. is False
  163. )
  164. self.create_incident(status=IncidentStatus.CLOSED.value)
  165. self.alert_rule.save()
  166. assert (
  167. cache.get(
  168. Incident.objects._build_active_incident_cache_key(
  169. self.alert_rule.id, self.project.id
  170. )
  171. )
  172. ) is False
  173. def test_cache(self):
  174. assert (
  175. cache.get(
  176. Incident.objects._build_active_incident_cache_key(
  177. self.alert_rule.id, self.project.id
  178. )
  179. )
  180. is None
  181. )
  182. active_incident = self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
  183. Incident.objects.get_active_incident(self.alert_rule, self.project)
  184. assert (
  185. cache.get(
  186. Incident.objects._build_active_incident_cache_key(
  187. self.alert_rule.id, self.project.id
  188. )
  189. )
  190. == active_incident
  191. )
  192. active_incident = self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
  193. assert (
  194. cache.get(
  195. Incident.objects._build_active_incident_cache_key(
  196. self.alert_rule.id, self.project.id
  197. )
  198. )
  199. is None
  200. )
  201. Incident.objects.get_active_incident(self.alert_rule, self.project)
  202. assert (
  203. cache.get(
  204. Incident.objects._build_active_incident_cache_key(
  205. self.alert_rule.id, self.project.id
  206. )
  207. )
  208. == active_incident
  209. )
  210. class IncidentTriggerClearCacheTest(TestCase):
  211. def setUp(self):
  212. self.alert_rule = self.create_alert_rule()
  213. self.trigger = self.create_alert_rule_trigger(self.alert_rule)
  214. self.incident = self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
  215. def test_deleted_incident(self):
  216. incident_trigger = IncidentTrigger.objects.create(
  217. incident=self.incident,
  218. alert_rule_trigger=self.trigger,
  219. status=TriggerStatus.ACTIVE.value,
  220. )
  221. IncidentTrigger.objects.get_for_incident(self.incident)
  222. assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) == [
  223. incident_trigger
  224. ]
  225. self.incident.delete()
  226. assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) is None
  227. def test_updated_incident_trigger(self):
  228. IncidentTrigger.objects.get_for_incident(self.incident)
  229. assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) == []
  230. incident_trigger = IncidentTrigger.objects.create(
  231. incident=self.incident,
  232. alert_rule_trigger=self.trigger,
  233. status=TriggerStatus.ACTIVE.value,
  234. )
  235. IncidentTrigger.objects.get_for_incident(self.incident)
  236. assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) == [
  237. incident_trigger
  238. ]
  239. def test_deleted_incident_trigger(self):
  240. incident_trigger = IncidentTrigger.objects.create(
  241. incident=self.incident,
  242. alert_rule_trigger=self.trigger,
  243. status=TriggerStatus.ACTIVE.value,
  244. )
  245. IncidentTrigger.objects.get_for_incident(self.incident)
  246. assert cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id)) == [
  247. incident_trigger
  248. ]
  249. self.trigger.delete()
  250. assert (cache.get(IncidentTrigger.objects._build_cache_key(self.incident.id))) is None
  251. class IncidentCreationTest(TestCase):
  252. def test_simple(self):
  253. title = "hello"
  254. alert_rule = self.create_alert_rule()
  255. incident = Incident.objects.create(
  256. self.organization,
  257. title=title,
  258. type=IncidentType.ALERT_TRIGGERED.value,
  259. alert_rule=alert_rule,
  260. )
  261. assert incident.identifier == 1
  262. assert incident.title == title
  263. # Check identifier correctly increments
  264. incident = Incident.objects.create(
  265. self.organization,
  266. title=title,
  267. type=IncidentType.ALERT_TRIGGERED.value,
  268. alert_rule=alert_rule,
  269. )
  270. assert incident.identifier == 2
  271. def test_identifier_conflict(self):
  272. create_method = BaseManager.create
  273. call_count = [0]
  274. alert_rule = self.create_alert_rule()
  275. def mock_base_create(*args, **kwargs):
  276. if not call_count[0]:
  277. call_count[0] += 1
  278. # This incident will take the identifier we already fetched and
  279. # use it, which will cause the database to throw an integrity
  280. # error.
  281. with transaction.atomic(router.db_for_write(Incident)):
  282. incident = Incident.objects.create(
  283. self.organization,
  284. status=IncidentStatus.OPEN.value,
  285. title="Conflicting Incident",
  286. type=IncidentType.ALERT_TRIGGERED.value,
  287. alert_rule=alert_rule,
  288. )
  289. assert incident.identifier == kwargs["identifier"]
  290. try:
  291. create_method(*args, **kwargs)
  292. except IntegrityError:
  293. raise
  294. else:
  295. self.fail("Expected an integrity error")
  296. else:
  297. call_count[0] += 1
  298. return create_method(*args, **kwargs)
  299. self.organization
  300. with patch.object(BaseManager, "create", new=mock_base_create):
  301. incident = Incident.objects.create(
  302. self.organization,
  303. alert_rule=alert_rule,
  304. status=IncidentStatus.OPEN.value,
  305. title="hi",
  306. type=IncidentType.ALERT_TRIGGERED.value,
  307. )
  308. # We should have 3 calls - one for initial create, one for conflict,
  309. # then the final one for the retry we get due to the conflict
  310. assert call_count[0] == 3
  311. # Ideally this would be 2, but because we create the conflicting
  312. # row inside of a transaction it ends up rolled back. We just want
  313. # to verify that it was created successfully.
  314. assert incident.identifier == 1
  315. @freeze_time()
  316. class IncidentDurationTest(unittest.TestCase):
  317. def test(self):
  318. incident = Incident(date_started=timezone.now() - timedelta(minutes=5))
  319. assert incident.duration == timedelta(minutes=5)
  320. incident.date_closed = incident.date_started + timedelta(minutes=2)
  321. assert incident.duration == timedelta(minutes=2)
  322. class IncidentAlertRuleRelationTest(TestCase):
  323. def test(self):
  324. self.alert_rule = self.create_alert_rule()
  325. self.trigger = self.create_alert_rule_trigger(self.alert_rule)
  326. self.incident = self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
  327. assert self.incident.alert_rule.id == self.alert_rule.id
  328. all_alert_rules = list(AlertRule.objects.all())
  329. assert self.alert_rule in all_alert_rules
  330. self.alert_rule.status = AlertRuleStatus.SNAPSHOT.value
  331. self.alert_rule.save()
  332. all_alert_rules = list(AlertRule.objects.all())
  333. assert self.alert_rule not in all_alert_rules
  334. assert self.incident.alert_rule.id == self.alert_rule.id
  335. @freeze_time()
  336. class IncidentCurrentEndDateTest(unittest.TestCase):
  337. def test(self):
  338. incident = Incident()
  339. assert incident.current_end_date == timezone.now()
  340. incident.date_closed = timezone.now() - timedelta(minutes=10)
  341. assert incident.current_end_date == timezone.now() - timedelta(minutes=10)
  342. @region_silo_test
  343. class AlertRuleFetchForOrganizationTest(TestCase):
  344. def test_empty(self):
  345. alert_rule = AlertRule.objects.fetch_for_organization(self.organization)
  346. assert [] == list(alert_rule)
  347. def test_simple(self):
  348. alert_rule = self.create_alert_rule()
  349. assert [alert_rule] == list(AlertRule.objects.fetch_for_organization(self.organization))
  350. def test_with_projects(self):
  351. project = self.create_project()
  352. alert_rule = self.create_alert_rule(projects=[project])
  353. assert [] == list(
  354. AlertRule.objects.fetch_for_organization(self.organization, [self.project])
  355. )
  356. assert [alert_rule] == list(
  357. AlertRule.objects.fetch_for_organization(self.organization, [project])
  358. )
  359. def test_multi_project(self):
  360. project = self.create_project()
  361. alert_rule1 = self.create_alert_rule(projects=[project, self.project])
  362. alert_rule2 = self.create_alert_rule(projects=[project])
  363. assert [alert_rule1] == list(
  364. AlertRule.objects.fetch_for_organization(self.organization, [self.project])
  365. )
  366. assert {alert_rule1, alert_rule2} == set(
  367. AlertRule.objects.fetch_for_organization(self.organization, [project])
  368. )
  369. class AlertRuleTriggerActionTargetTest(TestCase):
  370. def test_user(self):
  371. trigger = AlertRuleTriggerAction(
  372. target_type=AlertRuleTriggerAction.TargetType.USER.value,
  373. target_identifier=str(self.user.id),
  374. )
  375. assert trigger.target == user_service.get_user(user_id=self.user.id)
  376. def test_invalid_user(self):
  377. trigger = AlertRuleTriggerAction(
  378. target_type=AlertRuleTriggerAction.TargetType.USER.value, target_identifier="10000000"
  379. )
  380. assert trigger.target is None
  381. def test_team(self):
  382. trigger = AlertRuleTriggerAction(
  383. target_type=AlertRuleTriggerAction.TargetType.TEAM.value,
  384. target_identifier=str(self.team.id),
  385. )
  386. assert trigger.target == self.team
  387. def test_invalid_team(self):
  388. trigger = AlertRuleTriggerAction(
  389. target_type=AlertRuleTriggerAction.TargetType.TEAM.value, target_identifier="10000000"
  390. )
  391. assert trigger.target is None
  392. def test_specific(self):
  393. email = "test@test.com"
  394. trigger = AlertRuleTriggerAction(
  395. target_type=AlertRuleTriggerAction.TargetType.SPECIFIC.value, target_identifier=email
  396. )
  397. assert trigger.target == email
  398. class AlertRuleTriggerActionActivateBaseTest:
  399. method = None
  400. def setUp(self):
  401. self.old_handlers = AlertRuleTriggerAction._type_registrations
  402. AlertRuleTriggerAction._type_registrations = {}
  403. def tearDown(self):
  404. AlertRuleTriggerAction._type_registrations = self.old_handlers
  405. def test_no_handler(self):
  406. trigger = AlertRuleTriggerAction(type=AlertRuleTriggerAction.Type.EMAIL.value)
  407. assert trigger.fire(Mock(), Mock(), Mock(), 123, IncidentStatus.CRITICAL) is None
  408. def test_handler(self):
  409. mock_handler = Mock()
  410. mock_method = getattr(mock_handler.return_value, self.method)
  411. mock_method.return_value = "test"
  412. type = AlertRuleTriggerAction.Type.EMAIL
  413. AlertRuleTriggerAction.register_type("something", type, [])(mock_handler)
  414. trigger = AlertRuleTriggerAction(type=type.value)
  415. assert (
  416. getattr(trigger, self.method)(Mock(), Mock(), Mock(), 123, IncidentStatus.CRITICAL)
  417. == mock_method.return_value
  418. )
  419. class AlertRuleTriggerActionFireTest(AlertRuleTriggerActionActivateBaseTest, unittest.TestCase):
  420. method = "fire"
  421. class AlertRuleTriggerActionResolveTest(AlertRuleTriggerActionActivateBaseTest, unittest.TestCase):
  422. method = "resolve"
  423. class AlertRuleTriggerActionActivateTest(TestCase):
  424. @pytest.fixture(autouse=True)
  425. def _setup_metric_patch(self):
  426. with mock.patch("sentry.incidents.models.metrics") as self.metrics:
  427. yield
  428. def setUp(self):
  429. self.old_handlers = AlertRuleTriggerAction._type_registrations
  430. AlertRuleTriggerAction._type_registrations = {}
  431. def tearDown(self):
  432. AlertRuleTriggerAction._type_registrations = self.old_handlers
  433. def test_unhandled(self):
  434. trigger = AlertRuleTriggerAction(type=AlertRuleTriggerAction.Type.EMAIL.value)
  435. trigger.build_handler(Mock(), Mock(), Mock())
  436. self.metrics.incr.assert_called_once_with("alert_rule_trigger.unhandled_type.0")
  437. def test_handled(self):
  438. mock_handler = Mock()
  439. type = AlertRuleTriggerAction.Type.EMAIL
  440. AlertRuleTriggerAction.register_type("something", type, [])(mock_handler)
  441. trigger = AlertRuleTriggerAction(type=AlertRuleTriggerAction.Type.EMAIL.value)
  442. incident = Mock()
  443. project = Mock()
  444. trigger.build_handler(trigger, incident, project)
  445. mock_handler.assert_called_once_with(trigger, incident, project)
  446. assert not self.metrics.incr.called
  447. class AlertRuleActivityTest(TestCase):
  448. def test_simple(self):
  449. assert AlertRuleActivity.objects.all().count() == 0
  450. self.alert_rule = self.create_alert_rule()
  451. assert AlertRuleActivity.objects.filter(
  452. alert_rule=self.alert_rule, type=AlertRuleActivityType.CREATED.value
  453. ).exists()
  454. def test_delete(self):
  455. assert AlertRuleActivity.objects.all().count() == 0
  456. self.alert_rule = self.create_alert_rule()
  457. self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
  458. delete_alert_rule(self.alert_rule)
  459. assert AlertRuleActivity.objects.filter(
  460. alert_rule=self.alert_rule, type=AlertRuleActivityType.DELETED.value
  461. ).exists()
  462. def test_update(self):
  463. assert AlertRuleActivity.objects.all().count() == 0
  464. self.alert_rule = self.create_alert_rule()
  465. self.create_incident(alert_rule=self.alert_rule, projects=[self.project])
  466. update_alert_rule(self.alert_rule, name="updated_name")
  467. assert AlertRuleActivity.objects.filter(
  468. previous_alert_rule=self.alert_rule, type=AlertRuleActivityType.SNAPSHOT.value
  469. ).exists()
  470. assert AlertRuleActivity.objects.filter(
  471. alert_rule=self.alert_rule, type=AlertRuleActivityType.UPDATED.value
  472. ).exists()