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.

282 lines
9.7 KiB

  1. from datetime import timedelta as td
  2. import json
  3. from hc.api.models import Channel, Check
  4. from hc.test import BaseTestCase
  5. class CreateCheckTestCase(BaseTestCase):
  6. URL = "/api/v1/checks/"
  7. def post(self, data, expect_fragment=None):
  8. if "api_key" not in data:
  9. data["api_key"] = "X" * 32
  10. r = self.csrf_client.post(self.URL, data, content_type="application/json")
  11. if expect_fragment:
  12. self.assertEqual(r.status_code, 400)
  13. self.assertIn(expect_fragment, r.json()["error"])
  14. return r
  15. def test_it_works(self):
  16. r = self.post({"name": "Foo", "tags": "bar,baz", "timeout": 3600, "grace": 60})
  17. self.assertEqual(r.status_code, 201)
  18. self.assertEqual(r["Access-Control-Allow-Origin"], "*")
  19. doc = r.json()
  20. assert "ping_url" in doc
  21. self.assertEqual(doc["name"], "Foo")
  22. self.assertEqual(doc["slug"], "foo")
  23. self.assertEqual(doc["tags"], "bar,baz")
  24. self.assertEqual(doc["last_ping"], None)
  25. self.assertEqual(doc["n_pings"], 0)
  26. self.assertEqual(doc["methods"], "")
  27. self.assertTrue("schedule" not in doc)
  28. self.assertTrue("tz" not in doc)
  29. check = Check.objects.get()
  30. self.assertEqual(check.name, "Foo")
  31. self.assertEqual(check.slug, "foo")
  32. self.assertEqual(check.tags, "bar,baz")
  33. self.assertEqual(check.methods, "")
  34. self.assertEqual(check.timeout.total_seconds(), 3600)
  35. self.assertEqual(check.grace.total_seconds(), 60)
  36. self.assertEqual(check.project, self.project)
  37. def test_it_handles_options(self):
  38. r = self.client.options(self.URL)
  39. self.assertEqual(r.status_code, 204)
  40. self.assertIn("POST", r["Access-Control-Allow-Methods"])
  41. def test_30_days_works(self):
  42. r = self.post({"name": "Foo", "timeout": 2592000, "grace": 2592000})
  43. self.assertEqual(r.status_code, 201)
  44. check = Check.objects.get()
  45. self.assertEqual(check.timeout.total_seconds(), 2592000)
  46. self.assertEqual(check.grace.total_seconds(), 2592000)
  47. def test_it_accepts_api_key_in_header(self):
  48. payload = json.dumps({"name": "Foo"})
  49. r = self.client.post(
  50. self.URL, payload, content_type="application/json", HTTP_X_API_KEY="X" * 32
  51. )
  52. self.assertEqual(r.status_code, 201)
  53. def test_it_assigns_channels(self):
  54. channel = Channel.objects.create(project=self.project)
  55. r = self.post({"channels": "*"})
  56. self.assertEqual(r.status_code, 201)
  57. check = Check.objects.get()
  58. self.assertEqual(check.channel_set.get(), channel)
  59. def test_it_sets_channel_by_name(self):
  60. channel = Channel.objects.create(project=self.project, name="alerts")
  61. r = self.post({"channels": "alerts"})
  62. self.assertEqual(r.status_code, 201)
  63. check = Check.objects.get()
  64. assigned_channel = check.channel_set.get()
  65. self.assertEqual(assigned_channel, channel)
  66. def test_it_sets_channel_by_name_formatted_as_uuid(self):
  67. name = "102eaa82-a274-4b15-a499-c1bb6bbcd7b6"
  68. channel = Channel.objects.create(project=self.project, name=name)
  69. r = self.post({"channels": name})
  70. self.assertEqual(r.status_code, 201)
  71. check = Check.objects.get()
  72. assigned_channel = check.channel_set.get()
  73. self.assertEqual(assigned_channel, channel)
  74. def test_it_handles_channel_lookup_by_name_with_no_results(self):
  75. r = self.post({"channels": "abc"})
  76. self.assertEqual(r.status_code, 400)
  77. self.assertEqual(r.json()["error"], "invalid channel identifier: abc")
  78. # The check should not have been saved
  79. self.assertFalse(Check.objects.exists())
  80. def test_it_handles_channel_lookup_by_name_with_multiple_results(self):
  81. Channel.objects.create(project=self.project, name="foo")
  82. Channel.objects.create(project=self.project, name="foo")
  83. r = self.post({"channels": "foo"})
  84. self.assertEqual(r.status_code, 400)
  85. self.assertEqual(r.json()["error"], "non-unique channel identifier: foo")
  86. # The check should not have been saved
  87. self.assertFalse(Check.objects.exists())
  88. def test_it_rejects_multiple_empty_channel_names(self):
  89. Channel.objects.create(project=self.project, name="")
  90. r = self.post({"channels": ","})
  91. self.assertEqual(r.status_code, 400)
  92. self.assertEqual(r.json()["error"], "empty channel identifier")
  93. # The check should not have been saved
  94. self.assertFalse(Check.objects.exists())
  95. def test_it_supports_unique_name(self):
  96. check = Check.objects.create(project=self.project, name="Foo")
  97. r = self.post({"name": "Foo", "tags": "bar", "unique": ["name"]})
  98. # Expect 200 instead of 201
  99. self.assertEqual(r.status_code, 200)
  100. # And there should be only one check in the database:
  101. self.assertEqual(Check.objects.count(), 1)
  102. # The tags field should have a value now:
  103. check.refresh_from_db()
  104. self.assertEqual(check.tags, "bar")
  105. def test_it_supports_unique_tags(self):
  106. Check.objects.create(project=self.project, tags="foo")
  107. r = self.post({"tags": "foo", "unique": ["tags"]})
  108. # Expect 200 instead of 201
  109. self.assertEqual(r.status_code, 200)
  110. # And there should be only one check in the database:
  111. self.assertEqual(Check.objects.count(), 1)
  112. def test_it_supports_unique_timeout(self):
  113. Check.objects.create(project=self.project, timeout=td(seconds=123))
  114. r = self.post({"timeout": 123, "unique": ["timeout"]})
  115. # Expect 200 instead of 201
  116. self.assertEqual(r.status_code, 200)
  117. # And there should be only one check in the database:
  118. self.assertEqual(Check.objects.count(), 1)
  119. def test_it_supports_unique_grace(self):
  120. Check.objects.create(project=self.project, grace=td(seconds=123))
  121. r = self.post({"grace": 123, "unique": ["grace"]})
  122. # Expect 200 instead of 201
  123. self.assertEqual(r.status_code, 200)
  124. # And there should be only one check in the database:
  125. self.assertEqual(Check.objects.count(), 1)
  126. def test_it_handles_missing_request_body(self):
  127. r = self.client.post(self.URL, content_type="application/json")
  128. self.assertEqual(r.status_code, 401)
  129. self.assertEqual(r.json()["error"], "missing api key")
  130. def test_it_handles_invalid_json(self):
  131. r = self.client.post(
  132. self.URL, "this is not json", content_type="application/json"
  133. )
  134. self.assertEqual(r.status_code, 400)
  135. self.assertEqual(r.json()["error"], "could not parse request body")
  136. def test_it_rejects_wrong_api_key(self):
  137. r = self.post({"api_key": "Y" * 32})
  138. self.assertEqual(r.status_code, 401)
  139. def test_it_rejects_small_timeout(self):
  140. self.post({"timeout": 0}, expect_fragment="timeout is too small")
  141. def test_it_rejects_large_timeout(self):
  142. self.post({"timeout": 2592001}, expect_fragment="timeout is too large")
  143. def test_it_rejects_non_number_timeout(self):
  144. self.post({"timeout": "oops"}, expect_fragment="timeout is not a number")
  145. def test_it_rejects_non_string_name(self):
  146. self.post({"name": False}, expect_fragment="name is not a string")
  147. def test_it_rejects_long_name(self):
  148. self.post({"name": "01234567890" * 20}, expect_fragment="name is too long")
  149. def test_unique_accepts_only_specific_values(self):
  150. self.post(
  151. {"name": "Foo", "unique": ["status"]}, expect_fragment="unexpected value",
  152. )
  153. def test_it_rejects_bad_unique_values(self):
  154. self.post(
  155. {"name": "Foo", "unique": "not a list"}, expect_fragment="not an array",
  156. )
  157. def test_it_supports_cron_syntax(self):
  158. r = self.post({"schedule": "5 * * * *", "tz": "Europe/Riga", "grace": 60})
  159. self.assertEqual(r.status_code, 201)
  160. doc = r.json()
  161. self.assertEqual(doc["schedule"], "5 * * * *")
  162. self.assertEqual(doc["tz"], "Europe/Riga")
  163. self.assertEqual(doc["grace"], 60)
  164. self.assertTrue("timeout" not in doc)
  165. def test_it_validates_cron_expression(self):
  166. r = self.post({"schedule": "bad-expression", "tz": "Europe/Riga", "grace": 60})
  167. self.assertEqual(r.status_code, 400)
  168. def test_it_validates_timezone(self):
  169. r = self.post({"schedule": "* * * * *", "tz": "not-a-timezone", "grace": 60})
  170. self.assertEqual(r.status_code, 400)
  171. def test_it_sets_default_timeout(self):
  172. r = self.post({})
  173. self.assertEqual(r.status_code, 201)
  174. doc = r.json()
  175. self.assertEqual(doc["timeout"], 86400)
  176. def test_it_obeys_check_limit(self):
  177. self.profile.check_limit = 0
  178. self.profile.save()
  179. r = self.post({})
  180. self.assertEqual(r.status_code, 403)
  181. def test_it_rejects_readonly_key(self):
  182. self.project.api_key_readonly = "R" * 32
  183. self.project.save()
  184. r = self.post({"api_key": "R" * 32, "name": "Foo"})
  185. self.assertEqual(r.status_code, 401)
  186. def test_it_sets_manual_resume(self):
  187. r = self.post({"manual_resume": True})
  188. self.assertEqual(r.status_code, 201)
  189. check = Check.objects.get()
  190. self.assertTrue(check.manual_resume)
  191. def test_it_rejects_non_boolean_manual_resume(self):
  192. r = self.post({"manual_resume": "surprise"})
  193. self.assertEqual(r.status_code, 400)
  194. def test_it_sets_methods(self):
  195. r = self.post({"methods": "POST"})
  196. self.assertEqual(r.status_code, 201)
  197. check = Check.objects.get()
  198. self.assertEqual(check.methods, "POST")
  199. def test_it_rejects_bad_methods_value(self):
  200. r = self.post({"methods": "bad-value"})
  201. self.assertEqual(r.status_code, 400)