You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

323 lines
12 KiB

  1. from datetime import timedelta as td
  2. import uuid
  3. from django.utils.timezone import now
  4. from hc.api.models import Channel, Check
  5. from hc.test import BaseTestCase
  6. class UpdateCheckTestCase(BaseTestCase):
  7. def setUp(self):
  8. super().setUp()
  9. self.check = Check.objects.create(project=self.project)
  10. def post(self, code, data):
  11. url = "/api/v1/checks/%s" % code
  12. return self.csrf_client.post(url, data, content_type="application/json")
  13. def test_it_works(self):
  14. self.check.last_ping = now()
  15. self.check.status = "up"
  16. self.check.save()
  17. r = self.post(
  18. self.check.code,
  19. {
  20. "api_key": "X" * 32,
  21. "name": "Foo",
  22. "tags": "bar,baz",
  23. "desc": "My description",
  24. "timeout": 3600,
  25. "grace": 60,
  26. },
  27. )
  28. self.assertEqual(r.status_code, 200)
  29. self.assertEqual(r["Access-Control-Allow-Origin"], "*")
  30. doc = r.json()
  31. assert "ping_url" in doc
  32. self.assertEqual(doc["name"], "Foo")
  33. self.assertEqual(doc["slug"], "foo")
  34. self.assertEqual(doc["tags"], "bar,baz")
  35. self.assertEqual(doc["desc"], "My description")
  36. self.assertEqual(doc["n_pings"], 0)
  37. self.assertTrue("schedule" not in doc)
  38. self.assertTrue("tz" not in doc)
  39. self.assertEqual(Check.objects.count(), 1)
  40. self.check.refresh_from_db()
  41. self.assertEqual(self.check.name, "Foo")
  42. self.assertEqual(self.check.tags, "bar,baz")
  43. self.assertEqual(self.check.timeout.total_seconds(), 3600)
  44. self.assertEqual(self.check.grace.total_seconds(), 60)
  45. # alert_after should be updated too
  46. expected_aa = self.check.last_ping + td(seconds=3600 + 60)
  47. self.assertEqual(self.check.alert_after, expected_aa)
  48. def test_it_handles_options(self):
  49. r = self.client.options("/api/v1/checks/%s" % self.check.code)
  50. self.assertEqual(r.status_code, 204)
  51. self.assertIn("POST", r["Access-Control-Allow-Methods"])
  52. def test_it_unassigns_channels(self):
  53. Channel.objects.create(project=self.project)
  54. self.check.assign_all_channels()
  55. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": ""})
  56. self.assertEqual(r.status_code, 200)
  57. check = Check.objects.get()
  58. self.assertEqual(check.channel_set.count(), 0)
  59. def test_it_handles_invalid_uuid(self):
  60. r = self.post("not-an-uuid", {"api_key": "X" * 32})
  61. self.assertEqual(r.status_code, 404)
  62. def test_it_handles_missing_check(self):
  63. made_up_code = "07c2f548-9850-4b27-af5d-6c9dc157ec02"
  64. r = self.post(made_up_code, {"api_key": "X" * 32})
  65. self.assertEqual(r.status_code, 404)
  66. def test_it_validates_ownership(self):
  67. check = Check.objects.create(project=self.bobs_project, status="up")
  68. r = self.post(check.code, {"api_key": "X" * 32})
  69. self.assertEqual(r.status_code, 403)
  70. def test_it_updates_cron_to_simple(self):
  71. self.check.kind = "cron"
  72. self.check.schedule = "5 * * * *"
  73. self.check.save()
  74. r = self.post(self.check.code, {"api_key": "X" * 32, "timeout": 3600})
  75. self.assertEqual(r.status_code, 200)
  76. self.check.refresh_from_db()
  77. self.assertEqual(self.check.kind, "simple")
  78. def test_it_sets_single_channel(self):
  79. channel = Channel.objects.create(project=self.project)
  80. # Create another channel so we can test that only the first one
  81. # gets assigned:
  82. Channel.objects.create(project=self.project)
  83. r = self.post(
  84. self.check.code, {"api_key": "X" * 32, "channels": str(channel.code)}
  85. )
  86. self.assertEqual(r.status_code, 200)
  87. self.check.refresh_from_db()
  88. self.assertEqual(self.check.channel_set.count(), 1)
  89. self.assertEqual(self.check.channel_set.first().code, channel.code)
  90. def test_it_sets_the_channel_only_once(self):
  91. channel = Channel.objects.create(project=self.project)
  92. duplicates = "%s,%s" % (channel.code, channel.code)
  93. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": duplicates})
  94. self.assertEqual(r.status_code, 200)
  95. self.check.refresh_from_db()
  96. self.assertEqual(self.check.channel_set.count(), 1)
  97. def test_it_sets_channel_by_name(self):
  98. channel = Channel.objects.create(project=self.project, name="alerts")
  99. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": "alerts"})
  100. self.assertEqual(r.status_code, 200)
  101. self.check.refresh_from_db()
  102. self.assertEqual(self.check.channel_set.count(), 1)
  103. self.assertEqual(self.check.channel_set.first().code, channel.code)
  104. def test_it_sets_channel_by_name_formatted_as_uuid(self):
  105. name = "102eaa82-a274-4b15-a499-c1bb6bbcd7b6"
  106. channel = Channel.objects.create(project=self.project, name=name)
  107. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": name})
  108. self.assertEqual(r.status_code, 200)
  109. self.check.refresh_from_db()
  110. self.assertEqual(self.check.channel_set.count(), 1)
  111. self.assertEqual(self.check.channel_set.first().code, channel.code)
  112. def test_it_handles_comma_separated_channel_codes(self):
  113. c1 = Channel.objects.create(project=self.project)
  114. c2 = Channel.objects.create(project=self.project)
  115. r = self.post(
  116. self.check.code,
  117. {"api_key": "X" * 32, "channels": "%s,%s" % (c1.code, c2.code)},
  118. )
  119. self.assertEqual(r.status_code, 200)
  120. self.check.refresh_from_db()
  121. self.assertEqual(self.check.channel_set.count(), 2)
  122. def test_it_handles_asterix(self):
  123. Channel.objects.create(project=self.project)
  124. Channel.objects.create(project=self.project)
  125. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": "*"})
  126. self.assertEqual(r.status_code, 200)
  127. self.check.refresh_from_db()
  128. self.assertEqual(self.check.channel_set.count(), 2)
  129. def test_it_ignores_channels_if_channels_key_missing(self):
  130. Channel.objects.create(project=self.project)
  131. self.check.assign_all_channels()
  132. r = self.post(self.check.code, {"api_key": "X" * 32})
  133. self.assertEqual(r.status_code, 200)
  134. check = Check.objects.get()
  135. self.assertEqual(check.channel_set.count(), 1)
  136. def test_it_rejects_bad_channel_code(self):
  137. payload = {"api_key": "X" * 32, "channels": "abc", "name": "New Name"}
  138. r = self.post(self.check.code, payload,)
  139. self.assertEqual(r.status_code, 400)
  140. self.assertEqual(r.json()["error"], "invalid channel identifier: abc")
  141. # The name should be unchanged
  142. self.check.refresh_from_db()
  143. self.assertEqual(self.check.name, "")
  144. def test_it_rejects_missing_channel(self):
  145. code = str(uuid.uuid4())
  146. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": code})
  147. self.assertEqual(r.status_code, 400)
  148. self.assertEqual(r.json()["error"], "invalid channel identifier: " + code)
  149. self.check.refresh_from_db()
  150. self.assertEqual(self.check.channel_set.count(), 0)
  151. def test_it_rejects_channel_from_another_project(self):
  152. charlies_channel = Channel.objects.create(project=self.charlies_project)
  153. code = str(charlies_channel.code)
  154. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": code})
  155. self.assertEqual(r.status_code, 400)
  156. self.assertEqual(r.json()["error"], "invalid channel identifier: " + code)
  157. self.check.refresh_from_db()
  158. self.assertEqual(self.check.channel_set.count(), 0)
  159. def test_it_handles_channel_lookup_by_name_with_no_results(self):
  160. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": "foo"})
  161. self.assertEqual(r.status_code, 400)
  162. self.assertEqual(r.json()["error"], "invalid channel identifier: foo")
  163. self.check.refresh_from_db()
  164. self.assertEqual(self.check.channel_set.count(), 0)
  165. def test_it_handles_channel_lookup_by_name_with_multiple_results(self):
  166. Channel.objects.create(project=self.project, name="foo")
  167. Channel.objects.create(project=self.project, name="foo")
  168. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": "foo"})
  169. self.assertEqual(r.status_code, 400)
  170. self.assertEqual(r.json()["error"], "non-unique channel identifier: foo")
  171. self.check.refresh_from_db()
  172. self.assertEqual(self.check.channel_set.count(), 0)
  173. def test_it_rejects_multiple_empty_channel_names(self):
  174. Channel.objects.create(project=self.project, name="")
  175. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": ","})
  176. self.assertEqual(r.status_code, 400)
  177. self.assertEqual(r.json()["error"], "empty channel identifier")
  178. self.check.refresh_from_db()
  179. self.assertEqual(self.check.channel_set.count(), 0)
  180. def test_it_rejects_non_string_channels_key(self):
  181. r = self.post(self.check.code, {"api_key": "X" * 32, "channels": None})
  182. self.assertEqual(r.status_code, 400)
  183. def test_it_rejects_non_string_desc(self):
  184. r = self.post(self.check.code, {"api_key": "X" * 32, "desc": 123})
  185. self.assertEqual(r.status_code, 400)
  186. def test_it_validates_cron_expression(self):
  187. self.check.kind = "cron"
  188. self.check.schedule = "5 * * * *"
  189. self.check.save()
  190. samples = ["* invalid *", "1,2 61 * * *", "0 0 31 2 *"]
  191. for sample in samples:
  192. r = self.post(self.check.code, {"api_key": "X" * 32, "schedule": sample})
  193. self.assertEqual(r.status_code, 400, "Did not reject '%s'" % sample)
  194. # Schedule should be unchanged
  195. self.check.refresh_from_db()
  196. self.assertEqual(self.check.schedule, "5 * * * *")
  197. def test_it_rejects_readonly_key(self):
  198. self.project.api_key_readonly = "R" * 32
  199. self.project.save()
  200. r = self.post(self.check.code, {"api_key": "R" * 32, "name": "Foo"})
  201. self.assertEqual(r.status_code, 401)
  202. def test_it_sets_manual_resume_to_true(self):
  203. r = self.post(self.check.code, {"api_key": "X" * 32, "manual_resume": True})
  204. self.assertEqual(r.status_code, 200)
  205. self.check.refresh_from_db()
  206. self.assertTrue(self.check.manual_resume)
  207. def test_it_sets_manual_resume_to_false(self):
  208. self.check.manual_resume = True
  209. self.check.save()
  210. r = self.post(self.check.code, {"api_key": "X" * 32, "manual_resume": False})
  211. self.assertEqual(r.status_code, 200)
  212. self.check.refresh_from_db()
  213. self.assertFalse(self.check.manual_resume)
  214. def test_it_sets_methods(self):
  215. r = self.post(self.check.code, {"api_key": "X" * 32, "methods": "POST"})
  216. self.assertEqual(r.status_code, 200)
  217. self.check.refresh_from_db()
  218. self.assertEqual(self.check.methods, "POST")
  219. def test_it_clears_methods(self):
  220. self.check.methods = "POST"
  221. self.check.save()
  222. # Client supplies an empty string: we should save it
  223. r = self.post(self.check.code, {"api_key": "X" * 32, "methods": ""})
  224. self.assertEqual(r.status_code, 200)
  225. self.check.refresh_from_db()
  226. self.assertEqual(self.check.methods, "")
  227. def test_it_leaves_methods_unchanged(self):
  228. self.check.methods = "POST"
  229. self.check.save()
  230. # Client omits the methods key: we should leave it unchanged
  231. r = self.post(self.check.code, {"api_key": "X" * 32})
  232. self.assertEqual(r.status_code, 200)
  233. self.check.refresh_from_db()
  234. self.assertEqual(self.check.methods, "POST")
  235. def test_it_rejects_bad_methods_value(self):
  236. r = self.post(self.check.code, {"api_key": "X" * 32, "methods": "bad-value"})
  237. self.assertEqual(r.status_code, 400)