test_api.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. from unittest import mock
  2. from django.urls import reverse
  3. from django.utils import timezone
  4. from django.utils.dateparse import parse_datetime
  5. from freezegun import freeze_time
  6. from model_bakery import baker
  7. from apps.uptime.models import Monitor, MonitorCheck
  8. from glitchtip.test_utils.test_case import GlitchTestCase
  9. class UptimeAPITestCase(GlitchTestCase):
  10. @classmethod
  11. def setUpTestData(cls):
  12. cls.create_user()
  13. cls.list_url = reverse(
  14. "api:list_monitors",
  15. args=[cls.organization.slug],
  16. )
  17. def setUp(self):
  18. self.client.force_login(self.user)
  19. @mock.patch("apps.uptime.tasks.perform_checks.run")
  20. def test_list(self, mocked):
  21. monitor = baker.make(
  22. "uptime.Monitor", organization=self.organization, url="http://example.com"
  23. )
  24. baker.make(
  25. "uptime.MonitorCheck",
  26. monitor=monitor,
  27. is_up=False,
  28. start_check="2021-09-19T15:39:31Z",
  29. )
  30. baker.make(
  31. "uptime.MonitorCheck",
  32. monitor=monitor,
  33. is_up=True,
  34. is_change=True,
  35. start_check="2021-09-19T15:40:31Z",
  36. )
  37. res = self.client.get(self.list_url)
  38. self.assertContains(res, monitor.name)
  39. data = res.json()
  40. self.assertEqual(data[0]["isUp"], True)
  41. self.assertEqual(data[0]["lastChange"], "2021-09-19T15:40:31Z")
  42. @mock.patch("apps.uptime.tasks.perform_checks.run")
  43. def test_list_aggregation(self, _):
  44. """Test up and down event aggregations"""
  45. monitor = baker.make(
  46. "uptime.Monitor", organization=self.organization, url="http://example.com"
  47. )
  48. start_time = timezone.now()
  49. # Make 100 events, 50 up and then 50 up and down every minute
  50. for i in range(99):
  51. is_up = i % 2
  52. if i < 50:
  53. is_up = True
  54. current_time = start_time + timezone.timedelta(minutes=i)
  55. with freeze_time(current_time):
  56. baker.make(
  57. "uptime.MonitorCheck",
  58. monitor=monitor,
  59. is_up=is_up,
  60. start_check=current_time,
  61. )
  62. with freeze_time(current_time):
  63. res = self.client.get(self.list_url)
  64. self.assertEqual(len(res.json()[0]["checks"]), 60)
  65. @mock.patch("apps.uptime.tasks.perform_checks.run")
  66. def test_create_http_monitor(self, mocked):
  67. data = {
  68. "monitorType": "Ping",
  69. "name": "Test",
  70. "url": "https://www.google.com",
  71. "expectedStatus": 200,
  72. "expectedBody": "",
  73. "interval": 60,
  74. "project": self.project.pk,
  75. "timeout": 25,
  76. }
  77. res = self.client.post(self.list_url, data, content_type="application/json")
  78. self.assertEqual(res.status_code, 201)
  79. monitor = Monitor.objects.all().first()
  80. self.assertEqual(monitor.name, data["name"])
  81. self.assertEqual(monitor.timeout, data["timeout"])
  82. self.assertEqual(monitor.organization, self.organization)
  83. self.assertEqual(monitor.project, self.project)
  84. mocked.assert_called_once()
  85. @mock.patch("apps.uptime.tasks.perform_checks.run")
  86. def test_create_port_monitor(self, mocked):
  87. """Port monitor URLs should be converted to domain:port format, with protocol removed"""
  88. data = {
  89. "monitorType": "TCP Port",
  90. "name": "Test",
  91. "url": "http://example.com:80",
  92. "expectedStatus": None,
  93. "expectedBody": "",
  94. "timeout": None,
  95. "interval": 60,
  96. }
  97. res = self.client.post(self.list_url, data, content_type="application/json")
  98. self.assertEqual(res.status_code, 201)
  99. monitor = Monitor.objects.all().first()
  100. self.assertEqual(monitor.url, "example.com:80")
  101. mocked.assert_called_once()
  102. def test_create_port_monitor_validation(self):
  103. """Port monitor URLs should be converted to domain:port format, with protocol removed"""
  104. data = {
  105. "monitorType": "TCP Port",
  106. "name": "Test",
  107. "url": "example:80:",
  108. "expectedStatus": None,
  109. "expectedBody": "",
  110. "timeout": None,
  111. "interval": 60,
  112. }
  113. res = self.client.post(self.list_url, data, content_type="application/json")
  114. self.assertEqual(res.status_code, 422)
  115. def test_create_invalid(self):
  116. data = {
  117. "monitorType": "Ping",
  118. "name": "Test",
  119. "url": "foo:80:",
  120. "interval": 60,
  121. "expectedStatus": 200,
  122. "expectedBody": "",
  123. "timeout": None,
  124. "project": self.project.pk,
  125. }
  126. res = self.client.post(self.list_url, data, content_type="application/json")
  127. self.assertEqual(res.status_code, 422)
  128. data = {
  129. "monitorType": "Ping",
  130. "name": "Test",
  131. "url": "https://www.google.com",
  132. "expectedStatus": 200,
  133. "expectedBody": "",
  134. "interval": 60,
  135. "project": self.project.pk,
  136. "timeout": 999,
  137. }
  138. res = self.client.post(self.list_url, data, content_type="application/json")
  139. self.assertEqual(res.status_code, 422)
  140. @mock.patch("apps.uptime.tasks.perform_checks.run")
  141. def test_create_expected_status(self, mocked):
  142. data = {
  143. "monitorType": "Ping",
  144. "name": "Test",
  145. "url": "http://example.com",
  146. "expectedStatus": None,
  147. "expectedBody": "",
  148. "timeout": None,
  149. "interval": 60,
  150. "project": self.project.pk,
  151. }
  152. res = self.client.post(self.list_url, data, content_type="application/json")
  153. mocked.assert_called_once()
  154. self.assertEqual(res.status_code, 201)
  155. self.assertTrue(Monitor.objects.filter(expected_status=None).exists())
  156. @mock.patch("apps.uptime.tasks.perform_checks.run")
  157. def test_monitor_retrieve(self, _):
  158. """Test monitor details endpoint. Unlike the list view,
  159. checks here should include response time for the frontend graph"""
  160. environment = baker.make(
  161. "environments.Environment", organization=self.organization
  162. )
  163. monitor = baker.make(
  164. "uptime.Monitor",
  165. organization=self.organization,
  166. url="http://example.com",
  167. monitor_type="Heartbeat",
  168. environment=environment,
  169. )
  170. now = timezone.now()
  171. baker.make(
  172. "uptime.MonitorCheck",
  173. monitor=monitor,
  174. is_up=False,
  175. is_change=True,
  176. start_check="2021-09-19T15:39:31Z",
  177. )
  178. baker.make(
  179. "uptime.MonitorCheck",
  180. monitor=monitor,
  181. is_up=True,
  182. is_change=True,
  183. start_check=now,
  184. )
  185. url = reverse("api:get_monitor", args=[self.organization.slug, monitor.pk])
  186. res = self.client.get(url)
  187. data = res.json()
  188. self.assertEqual(data["isUp"], True)
  189. self.assertEqual(parse_datetime(data["lastChange"]), now)
  190. self.assertEqual(data["environment"], environment.pk)
  191. self.assertIn("responseTime", data["checks"][0])
  192. @mock.patch("apps.uptime.tasks.perform_checks.run")
  193. def test_monitor_checks_list(self, _):
  194. monitor = baker.make(
  195. "uptime.Monitor",
  196. organization=self.organization,
  197. url="http://example.com",
  198. )
  199. baker.make(
  200. "uptime.MonitorCheck",
  201. monitor=monitor,
  202. is_up=False,
  203. start_check="2021-09-19T15:39:31Z",
  204. )
  205. url = reverse(
  206. "api:list_monitor_checks", args=[self.organization.slug, monitor.pk]
  207. )
  208. res = self.client.get(url)
  209. self.assertContains(res, "2021-09-19T15:39:31Z")
  210. @mock.patch("apps.uptime.tasks.perform_checks.run")
  211. def test_monitor_update(self, _):
  212. monitor = baker.make(
  213. "uptime.Monitor",
  214. organization=self.organization,
  215. url="http://example.com",
  216. interval="60",
  217. monitor_type="Ping",
  218. expected_status=None,
  219. )
  220. url = reverse("api:update_monitor", args=[self.organization.slug, monitor.pk])
  221. data = {
  222. "name": monitor.name,
  223. "url": "https://differentexample.com",
  224. "monitorType": "Ping",
  225. "interval": 60,
  226. "expectedBody": "",
  227. "expected_status": None,
  228. "timeout": 20,
  229. "project": self.project.id,
  230. }
  231. res = self.client.put(url, data, content_type="application/json")
  232. self.assertEqual(res.status_code, 200)
  233. self.assertEqual(res.json()["project"], self.project.id)
  234. self.assertEqual(res.json()["url"], "https://differentexample.com")
  235. data = {
  236. "name": monitor.name,
  237. "url": "https://differentexample.com",
  238. "monitorType": "GET",
  239. "interval": 60,
  240. "expectedBody": "test",
  241. "expected_status": None,
  242. "timeout": 20,
  243. "project": self.project.id,
  244. }
  245. res = self.client.put(url, data, content_type="application/json")
  246. self.assertEqual(res.status_code, 422)
  247. data = {
  248. "name": monitor.name,
  249. "url": "https://differentexample.com",
  250. "monitorType": "GET",
  251. "interval": 60,
  252. "expectedBody": "",
  253. "expected_status": 422,
  254. "timeout": None,
  255. "project": self.project.id,
  256. }
  257. res = self.client.put(url, data, content_type="application/json")
  258. self.assertEqual(res.status_code, 200)
  259. self.assertEqual(res.json()["monitorType"], "GET")
  260. self.assertEqual(res.json()["expectedBody"], "")
  261. self.assertEqual(res.json()["timeout"], None)
  262. def test_monitor_delete(self):
  263. monitor = baker.make(
  264. "uptime.Monitor",
  265. organization=self.organization,
  266. url="http://example.com",
  267. interval="60",
  268. monitor_type="Ping",
  269. expected_status=None,
  270. )
  271. baker.make(
  272. "uptime.MonitorCheck",
  273. monitor=monitor,
  274. is_up=False,
  275. start_check="2021-09-19T15:39:31Z",
  276. )
  277. url = reverse("api:delete_monitor", args=[self.organization.slug, monitor.pk])
  278. res = self.client.delete(url)
  279. self.assertEqual(res.status_code, 204)
  280. self.assertEqual(Monitor.objects.count(), 0)
  281. self.assertEqual(MonitorCheck.objects.count(), 0)
  282. another_org = baker.make(
  283. "organizations_ext.Organization"
  284. )
  285. another_monitor = baker.make(
  286. "uptime.Monitor",
  287. organization=another_org,
  288. url="http://example.com",
  289. interval="60",
  290. monitor_type="Ping",
  291. expected_status=None,
  292. )
  293. url = reverse("api:delete_monitor", args=[another_org.slug, another_monitor.pk])
  294. res = self.client.delete(url)
  295. self.assertEqual(res.status_code, 404)
  296. @mock.patch("apps.uptime.tasks.perform_checks.run")
  297. def test_list_isolation(self, _):
  298. """Users should only access monitors in their organization"""
  299. user2 = baker.make("users.user")
  300. org2 = baker.make("organizations_ext.Organization")
  301. org2.add_user(user2)
  302. monitor1 = baker.make(
  303. "uptime.Monitor", url="http://example.com", organization=self.organization
  304. )
  305. monitor2 = baker.make(
  306. "uptime.Monitor", url="http://example.com", organization=org2
  307. )
  308. res = self.client.get(self.list_url)
  309. self.assertContains(res, monitor1.name)
  310. self.assertNotContains(res, monitor2.name)
  311. def test_create_isolation(self):
  312. """Users should only make monitors in their organization"""
  313. org2 = baker.make("organizations_ext.Organization")
  314. url = reverse("api:list_monitors", args=[org2.slug])
  315. data = {
  316. "monitorType": "Ping",
  317. "name": "Test",
  318. "url": "https://www.google.com",
  319. "expectedStatus": 200,
  320. "interval": 60,
  321. "project": self.project.pk,
  322. }
  323. res = self.client.post(url, data)
  324. self.assertEqual(res.status_code, 400)