tests.py 11 KB

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