tests.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import asyncio
  2. from datetime import timedelta
  3. from unittest import mock
  4. from aioresponses import aioresponses
  5. from django.core import mail
  6. from django.core.cache import cache
  7. from django.urls import reverse
  8. from django.utils import timezone
  9. from freezegun import freeze_time
  10. from model_bakery import baker
  11. from glitchtip.test_utils.test_case import GlitchTipTestCase
  12. from organizations_ext.models import OrganizationUserRole
  13. from projects.models import ProjectAlertStatus
  14. from ..constants import MonitorType
  15. from ..models import Monitor, MonitorCheck
  16. from ..tasks import UPTIME_COUNTER_KEY, bucket_monitors, dispatch_checks
  17. from ..utils import fetch_all
  18. from ..webhooks import send_uptime_as_webhook
  19. class UptimeTestCase(GlitchTipTestCase):
  20. @mock.patch("glitchtip.uptime.tasks.perform_checks.run")
  21. def test_dispatch_checks(self, mocked):
  22. mock.return_value = None
  23. test_url = "https://example.com"
  24. with freeze_time("2020-01-01"):
  25. mon1 = baker.make(Monitor, url=test_url, monitor_type=MonitorType.GET)
  26. mon2 = baker.make(Monitor, url=test_url, monitor_type=MonitorType.GET)
  27. baker.make(MonitorCheck, monitor=mon1)
  28. self.assertEqual(mocked.call_count, 2)
  29. cache.set(UPTIME_COUNTER_KEY, 59)
  30. with freeze_time("2020-01-02"):
  31. baker.make(MonitorCheck, monitor=mon2)
  32. dispatch_checks()
  33. self.assertEqual(mocked.call_count, 3)
  34. @aioresponses()
  35. def test_fetch_all(self, mocked):
  36. test_url = "https://example.com"
  37. mocked.get(test_url, status=200)
  38. mon1 = baker.make(Monitor, url=test_url, monitor_type=MonitorType.GET)
  39. mocked.get(test_url, status=200)
  40. monitors = list(Monitor.objects.all().values())
  41. results = asyncio.run(fetch_all(monitors))
  42. self.assertEqual(results[0]["id"], mon1.pk)
  43. @aioresponses()
  44. def test_monitor_checks_integration(self, mocked):
  45. test_url = "https://example.com"
  46. mocked.get(test_url, status=200)
  47. with freeze_time("2020-01-01"):
  48. mon = baker.make(Monitor, url=test_url, monitor_type=MonitorType.GET)
  49. self.assertEqual(mon.checks.count(), 1)
  50. mocked.get(test_url, status=200)
  51. with freeze_time("2020-01-01"):
  52. dispatch_checks()
  53. self.assertEqual(mon.checks.count(), 1)
  54. cache.set(UPTIME_COUNTER_KEY, 59)
  55. with freeze_time("2020-01-02"):
  56. with self.assertNumQueries(3):
  57. dispatch_checks()
  58. self.assertEqual(mon.checks.count(), 2)
  59. @aioresponses()
  60. def test_expected_response(self, mocked):
  61. test_url = "https://example.com"
  62. mocked.get(test_url, status=200, body="Status: OK")
  63. monitor = baker.make(
  64. Monitor,
  65. name=test_url,
  66. url=test_url,
  67. expected_body="OK",
  68. monitor_type=MonitorType.GET,
  69. )
  70. check = monitor.checks.first()
  71. self.assertTrue(check.is_up)
  72. mocked.get(test_url, status=200, body="Status: Failure")
  73. monitor = baker.make(
  74. Monitor,
  75. name=test_url,
  76. url=test_url,
  77. expected_body="OK",
  78. monitor_type=MonitorType.GET,
  79. )
  80. check = monitor.checks.first()
  81. self.assertFalse(check.is_up)
  82. self.assertEqual(check.data["payload"], "Status: Failure")
  83. @aioresponses()
  84. @mock.patch("requests.post")
  85. def test_monitor_notifications(self, mocked, mock_post):
  86. self.create_user_and_project()
  87. test_url = "https://example.com"
  88. mocked.get(test_url, status=200)
  89. with freeze_time("2020-01-01"):
  90. baker.make(
  91. Monitor,
  92. name=test_url,
  93. url=test_url,
  94. monitor_type=MonitorType.GET,
  95. project=self.project,
  96. )
  97. baker.make(
  98. "alerts.AlertRecipient",
  99. alert__uptime=True,
  100. alert__project=self.project,
  101. recipient_type="email",
  102. )
  103. baker.make(
  104. "alerts.AlertRecipient",
  105. alert__uptime=True,
  106. alert__project=self.project,
  107. recipient_type="webhook",
  108. url="https://example.com",
  109. )
  110. mocked.get(test_url, status=500)
  111. cache.set(UPTIME_COUNTER_KEY, 59)
  112. with freeze_time("2020-01-02"):
  113. dispatch_checks()
  114. self.assertEqual(len(mail.outbox), 1)
  115. self.assertIn("is down", mail.outbox[0].body)
  116. mock_post.assert_called_once()
  117. mocked.get(test_url, status=500)
  118. cache.set(UPTIME_COUNTER_KEY, 59)
  119. with freeze_time("2020-01-03"):
  120. dispatch_checks()
  121. self.assertEqual(len(mail.outbox), 1)
  122. mocked.get(test_url, status=200)
  123. cache.set(UPTIME_COUNTER_KEY, 59)
  124. with freeze_time("2020-01-04"):
  125. dispatch_checks()
  126. self.assertEqual(len(mail.outbox), 2)
  127. self.assertIn("is back up", mail.outbox[1].body)
  128. @aioresponses()
  129. @mock.patch("requests.post")
  130. def test_discord_webhook(self, mocked, mocked_post):
  131. self.create_user_and_project()
  132. test_url = "https://example.com"
  133. mocked.get(test_url, status=200)
  134. check = baker.make(
  135. "uptime.MonitorCheck",
  136. monitor__monitor_type=MonitorType.GET,
  137. monitor__url=test_url,
  138. monitor__project=self.project,
  139. )
  140. recipient = baker.make("alerts.AlertRecipient", recipient_type="discord")
  141. send_uptime_as_webhook(recipient, check.pk, True, timezone.now())
  142. mocked_post.assert_called_once()
  143. @aioresponses()
  144. def test_notification_default_scope(self, mocked):
  145. """Subscribe by default should not result in alert emails for non-team members"""
  146. self.create_user_and_project()
  147. test_url = "https://example.com"
  148. # user2 is an org member but not in a relevant team, should not receive alerts
  149. user2 = baker.make("users.user")
  150. org_user2 = self.organization.add_user(user2, OrganizationUserRole.MEMBER)
  151. team2 = baker.make("teams.Team", organization=self.organization)
  152. team2.members.add(org_user2)
  153. # user3 is in team3 which should receive alerts
  154. user3 = baker.make("users.user")
  155. org_user3 = self.organization.add_user(user3, OrganizationUserRole.MEMBER)
  156. self.team.members.add(org_user3)
  157. team3 = baker.make("teams.Team", organization=self.organization)
  158. team3.members.add(org_user3)
  159. team3.projects.add(self.project)
  160. baker.make(
  161. "alerts.AlertRecipient",
  162. alert__uptime=True,
  163. alert__project=self.project,
  164. recipient_type="email",
  165. )
  166. mocked.get(test_url, status=200)
  167. with freeze_time("2020-01-01"):
  168. baker.make(
  169. Monitor,
  170. name=test_url,
  171. url=test_url,
  172. monitor_type=MonitorType.GET,
  173. project=self.project,
  174. )
  175. mocked.get(test_url, status=500)
  176. cache.set(UPTIME_COUNTER_KEY, 59)
  177. with self.assertNumQueries(10):
  178. with freeze_time("2020-01-02"):
  179. dispatch_checks()
  180. self.assertNotIn(user2.email, mail.outbox[0].to)
  181. self.assertIn(user3.email, mail.outbox[0].to)
  182. self.assertEqual(len(mail.outbox[0].to), 2)
  183. @aioresponses()
  184. def test_user_project_alert_scope(self, mocked):
  185. """User project alert should not result in alert emails for non-team members"""
  186. self.create_user_and_project()
  187. test_url = "https://example.com"
  188. baker.make(
  189. "alerts.AlertRecipient",
  190. alert__uptime=True,
  191. alert__project=self.project,
  192. recipient_type="email",
  193. )
  194. user2 = baker.make("users.user")
  195. self.organization.add_user(user2, OrganizationUserRole.MEMBER)
  196. baker.make(
  197. "projects.UserProjectAlert",
  198. user=user2,
  199. project=self.project,
  200. status=ProjectAlertStatus.ON,
  201. )
  202. mocked.get(test_url, status=200)
  203. with freeze_time("2020-01-01"):
  204. baker.make(
  205. Monitor,
  206. name=test_url,
  207. url=test_url,
  208. monitor_type=MonitorType.GET,
  209. project=self.project,
  210. )
  211. mocked.get(test_url, status=500)
  212. cache.set(UPTIME_COUNTER_KEY, 59)
  213. with self.assertNumQueries(10):
  214. with freeze_time("2020-01-02"):
  215. dispatch_checks()
  216. self.assertNotIn(user2.email, mail.outbox[0].to)
  217. def xtest_heartbeat(self):
  218. """
  219. Cannot run due to async code, it doesn't close the DB connection
  220. Run manually with --keepdb
  221. """
  222. self.create_user_and_project()
  223. with freeze_time("2020-01-01"):
  224. monitor = baker.make(
  225. Monitor,
  226. monitor_type=MonitorType.HEARTBEAT,
  227. project=self.project,
  228. )
  229. baker.make(
  230. "alerts.AlertRecipient",
  231. alert__uptime=True,
  232. alert__project=self.project,
  233. recipient_type="email",
  234. )
  235. url = reverse(
  236. "heartbeat-check",
  237. kwargs={
  238. "organization_slug": monitor.organization.slug,
  239. "endpoint_id": monitor.endpoint_id,
  240. },
  241. )
  242. self.assertFalse(monitor.checks.exists())
  243. self.client.post(url)
  244. self.assertTrue(monitor.checks.filter(is_up=True).exists())
  245. dispatch_checks()
  246. self.assertTrue(monitor.checks.filter(is_up=True).exists())
  247. self.assertEqual(len(mail.outbox), 0)
  248. cache.set(UPTIME_COUNTER_KEY, 59)
  249. with freeze_time("2020-01-02"):
  250. dispatch_checks()
  251. self.assertEqual(len(mail.outbox), 1)
  252. cache.set(UPTIME_COUNTER_KEY, 59)
  253. with freeze_time("2020-01-03"):
  254. dispatch_checks() # Still down
  255. self.assertEqual(len(mail.outbox), 1)
  256. cache.set(UPTIME_COUNTER_KEY, 59)
  257. with freeze_time("2020-01-04"):
  258. self.client.post(url) # Back up
  259. self.assertEqual(len(mail.outbox), 2)
  260. def test_heartbeat_grace_period(self):
  261. # Don't alert users when heartbeat check has never come in
  262. self.create_user_and_project()
  263. baker.make(Monitor, monitor_type=MonitorType.HEARTBEAT, project=self.project)
  264. dispatch_checks()
  265. self.assertEqual(len(mail.outbox), 0)
  266. @mock.patch("glitchtip.uptime.tasks.perform_checks.run")
  267. def test_bucket_monitors(self, _):
  268. interval_timeouts = [
  269. [1, 10],
  270. [3, 20],
  271. [3, None],
  272. [10, 10],
  273. [2, 40],
  274. [3, 50],
  275. ]
  276. for interval, timeout in interval_timeouts:
  277. baker.make(
  278. Monitor,
  279. url="http://example.com",
  280. interval=timedelta(seconds=interval),
  281. timeout=timeout,
  282. )
  283. monitors = Monitor.objects.all()
  284. bucket_monitors(monitors, 1)
  285. @mock.patch("glitchtip.uptime.utils.asyncio.open_connection")
  286. def test_port_monitor(self, mocked):
  287. self.create_user_and_project()
  288. monitor = baker.make(
  289. Monitor,
  290. url="example.com:80",
  291. monitor_type=MonitorType.PORT,
  292. project=self.project,
  293. )
  294. mocked.assert_called_once()
  295. self.assertTrue(monitor.checks.filter(is_up=True).exists())