From d88f99a712454adbef7798b2415fc957263fa648 Mon Sep 17 00:00:00 2001
From: James Kirsop
Date: Tue, 25 Feb 2020 12:48:54 +1100
Subject: [PATCH 1/3] Changes to prototype this for testing with real data
---
hc/api/tests/test_update_check.py | 5 -----
hc/api/urls.py | 1 +
hc/api/views.py | 21 +++++++++++++++++----
3 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/hc/api/tests/test_update_check.py b/hc/api/tests/test_update_check.py
index a46eec39..7c9225b6 100644
--- a/hc/api/tests/test_update_check.py
+++ b/hc/api/tests/test_update_check.py
@@ -72,11 +72,6 @@ class UpdateCheckTestCase(BaseTestCase):
check = Check.objects.get()
self.assertEqual(check.channel_set.count(), 0)
- def test_it_requires_post(self):
- url = "/api/v1/checks/%s" % self.check.code
- r = self.client.get(url, HTTP_X_API_KEY="X" * 32)
- self.assertEqual(r.status_code, 405)
-
def test_it_handles_invalid_uuid(self):
r = self.post("not-an-uuid", {"api_key": "X" * 32})
self.assertEqual(r.status_code, 404)
diff --git a/hc/api/urls.py b/hc/api/urls.py
index 5ff1b5f3..f4e5a3ef 100644
--- a/hc/api/urls.py
+++ b/hc/api/urls.py
@@ -22,6 +22,7 @@ urlpatterns = [
path("ping//fail", views.ping, {"action": "fail"}, name="hc-fail"),
path("ping//start", views.ping, {"action": "start"}, name="hc-start"),
path("api/v1/checks/", views.checks),
+ path("api/v1/checks/", views.single, name="hc-api-single"),
path("api/v1/checks/", views.update, name="hc-api-update"),
path("api/v1/checks//pause", views.pause, name="hc-api-pause"),
path("api/v1/notifications//bounce", views.bounce, name="hc-api-bounce"),
diff --git a/hc/api/views.py b/hc/api/views.py
index bc675d57..a8ec08f1 100644
--- a/hc/api/views.py
+++ b/hc/api/views.py
@@ -19,6 +19,7 @@ from hc.api import schemas
from hc.api.decorators import authorize, authorize_read, cors, validate_json
from hc.api.models import Check, Notification, Channel
from hc.lib.badges import check_signature, get_badge_svg
+from hc.lib.jsonschema import ValidationError, validate
class BadChannelException(Exception):
@@ -178,18 +179,19 @@ def channels(request):
@csrf_exempt
-@cors("POST", "DELETE")
-@validate_json(schemas.check)
+@cors("POST", "DELETE", "GET")
+@validate_json()
@authorize
-def update(request, code):
+def single(request, code):
check = get_object_or_404(Check, code=code)
if check.project != request.project:
return HttpResponseForbidden()
if request.method == "POST":
try:
+ validate(request.json, schemas.check)
_update(check, request.json)
- except BadChannelException as e:
+ except (BadChannelException,ValidationError) as e:
return JsonResponse({"error": str(e)}, status=400)
return JsonResponse(check.to_dict())
@@ -199,10 +201,21 @@ def update(request, code):
check.delete()
return JsonResponse(response)
+ elif request.method == "GET":
+ return JsonResponse(check.to_dict())
+
# Otherwise, method not allowed
return HttpResponse(status=405)
+@csrf_exempt
+@cors("POST", "DELETE")
+@validate_json(schemas.check)
+@authorize
+def update(request, code):
+ single(request, code)
+
+
@cors("POST")
@csrf_exempt
@validate_json()
From 6373db8aa1d57f94f6aa02b6c9c43db622bb062e Mon Sep 17 00:00:00 2001
From: James Kirsop
Date: Tue, 25 Feb 2020 12:48:54 +1100
Subject: [PATCH 2/3] Changes to prototype this for testing with real data
---
hc/api/tests/test_update_check.py | 5 -----
hc/api/urls.py | 1 +
hc/api/views.py | 21 +++++++++++++++++----
3 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/hc/api/tests/test_update_check.py b/hc/api/tests/test_update_check.py
index a46eec39..7c9225b6 100644
--- a/hc/api/tests/test_update_check.py
+++ b/hc/api/tests/test_update_check.py
@@ -72,11 +72,6 @@ class UpdateCheckTestCase(BaseTestCase):
check = Check.objects.get()
self.assertEqual(check.channel_set.count(), 0)
- def test_it_requires_post(self):
- url = "/api/v1/checks/%s" % self.check.code
- r = self.client.get(url, HTTP_X_API_KEY="X" * 32)
- self.assertEqual(r.status_code, 405)
-
def test_it_handles_invalid_uuid(self):
r = self.post("not-an-uuid", {"api_key": "X" * 32})
self.assertEqual(r.status_code, 404)
diff --git a/hc/api/urls.py b/hc/api/urls.py
index 5ff1b5f3..f4e5a3ef 100644
--- a/hc/api/urls.py
+++ b/hc/api/urls.py
@@ -22,6 +22,7 @@ urlpatterns = [
path("ping//fail", views.ping, {"action": "fail"}, name="hc-fail"),
path("ping//start", views.ping, {"action": "start"}, name="hc-start"),
path("api/v1/checks/", views.checks),
+ path("api/v1/checks/", views.single, name="hc-api-single"),
path("api/v1/checks/", views.update, name="hc-api-update"),
path("api/v1/checks//pause", views.pause, name="hc-api-pause"),
path("api/v1/notifications//bounce", views.bounce, name="hc-api-bounce"),
diff --git a/hc/api/views.py b/hc/api/views.py
index bc675d57..a8ec08f1 100644
--- a/hc/api/views.py
+++ b/hc/api/views.py
@@ -19,6 +19,7 @@ from hc.api import schemas
from hc.api.decorators import authorize, authorize_read, cors, validate_json
from hc.api.models import Check, Notification, Channel
from hc.lib.badges import check_signature, get_badge_svg
+from hc.lib.jsonschema import ValidationError, validate
class BadChannelException(Exception):
@@ -178,18 +179,19 @@ def channels(request):
@csrf_exempt
-@cors("POST", "DELETE")
-@validate_json(schemas.check)
+@cors("POST", "DELETE", "GET")
+@validate_json()
@authorize
-def update(request, code):
+def single(request, code):
check = get_object_or_404(Check, code=code)
if check.project != request.project:
return HttpResponseForbidden()
if request.method == "POST":
try:
+ validate(request.json, schemas.check)
_update(check, request.json)
- except BadChannelException as e:
+ except (BadChannelException,ValidationError) as e:
return JsonResponse({"error": str(e)}, status=400)
return JsonResponse(check.to_dict())
@@ -199,10 +201,21 @@ def update(request, code):
check.delete()
return JsonResponse(response)
+ elif request.method == "GET":
+ return JsonResponse(check.to_dict())
+
# Otherwise, method not allowed
return HttpResponse(status=405)
+@csrf_exempt
+@cors("POST", "DELETE")
+@validate_json(schemas.check)
+@authorize
+def update(request, code):
+ single(request, code)
+
+
@cors("POST")
@csrf_exempt
@validate_json()
From 456a80f1fa7a91871fe770bd549a112b92bb6c73 Mon Sep 17 00:00:00 2001
From: James Kirsop
Date: Mon, 23 Mar 2020 11:37:32 +1100
Subject: [PATCH 3/3] Adding tests and docs
---
hc/api/models.py | 2 +-
hc/api/tests/test_get_check.py | 55 ++++++++++++++++++++++++++++++++++
hc/api/urls.py | 1 -
templates/docs/api.html | 38 +++++++++++++++++++++++
templates/docs/api.md | 40 +++++++++++++++++++++++++
5 files changed, 134 insertions(+), 2 deletions(-)
create mode 100644 hc/api/tests/test_get_check.py
diff --git a/hc/api/models.py b/hc/api/models.py
index 3f980b2c..dcc8105a 100644
--- a/hc/api/models.py
+++ b/hc/api/models.py
@@ -224,7 +224,7 @@ class Check(models.Model):
if readonly:
result["unique_key"] = self.unique_key
else:
- update_rel_url = reverse("hc-api-update", args=[self.code])
+ update_rel_url = reverse("hc-api-single", args=[self.code])
pause_rel_url = reverse("hc-api-pause", args=[self.code])
result["ping_url"] = self.url()
diff --git a/hc/api/tests/test_get_check.py b/hc/api/tests/test_get_check.py
new file mode 100644
index 00000000..3b0c30a8
--- /dev/null
+++ b/hc/api/tests/test_get_check.py
@@ -0,0 +1,55 @@
+from datetime import timedelta as td
+import uuid
+
+from django.utils.timezone import now
+from hc.api.models import Channel, Check
+from hc.test import BaseTestCase
+
+
+class GetCheckTestCase(BaseTestCase):
+ def setUp(self):
+ super(GetCheckTestCase, self).setUp()
+
+ self.now = now().replace(microsecond=0)
+
+ self.a1 = Check(project=self.project, name="Alice 1")
+ self.a1.timeout = td(seconds=3600)
+ self.a1.grace = td(seconds=900)
+ self.a1.n_pings = 0
+ self.a1.status = "new"
+ self.a1.tags = "a1-tag a1-additional-tag"
+ self.a1.desc = "This is description"
+ self.a1.save()
+
+ self.c1 = Channel.objects.create(project=self.project)
+ self.a1.channel_set.add(self.c1)
+
+ def get(self, code):
+ url = "/api/v1/checks/%s" % code
+ return self.client.get(url, HTTP_X_API_KEY="X" * 32)
+
+ def test_it_works(self):
+ r = self.get(self.a1.code)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r["Access-Control-Allow-Origin"], "*")
+
+ doc = r.json()
+ self.assertEqual(len(doc), 13)
+
+ self.assertEqual(doc["timeout"], 3600)
+ self.assertEqual(doc["grace"], 900)
+ self.assertEqual(doc["ping_url"], self.a1.url())
+ self.assertEqual(doc["last_ping"], None)
+ self.assertEqual(doc["n_pings"], 0)
+ self.assertEqual(doc["status"], "new")
+ self.assertEqual(doc["channels"], str(self.c1.code))
+ self.assertEqual(doc["desc"], "This is description")
+
+ def test_it_handles_invalid_uuid(self):
+ r = self.get("not-an-uuid")
+ self.assertEqual(r.status_code, 404)
+
+ def test_it_handles_missing_check(self):
+ made_up_code = "07c2f548-9850-4b27-af5d-6c9dc157ec02"
+ r = self.get(made_up_code)
+ self.assertEqual(r.status_code, 404)
\ No newline at end of file
diff --git a/hc/api/urls.py b/hc/api/urls.py
index f4e5a3ef..28050a25 100644
--- a/hc/api/urls.py
+++ b/hc/api/urls.py
@@ -23,7 +23,6 @@ urlpatterns = [
path("ping//start", views.ping, {"action": "start"}, name="hc-start"),
path("api/v1/checks/", views.checks),
path("api/v1/checks/", views.single, name="hc-api-single"),
- path("api/v1/checks/", views.update, name="hc-api-update"),
path("api/v1/checks//pause", views.pause, name="hc-api-pause"),
path("api/v1/notifications//bounce", views.bounce, name="hc-api-bounce"),
path("api/v1/channels/", views.channels),
diff --git a/templates/docs/api.html b/templates/docs/api.html
index 9f0801a6..c896f2f3 100644
--- a/templates/docs/api.html
+++ b/templates/docs/api.html
@@ -15,6 +15,10 @@ checks in user's account.
GET SITE_ROOT/api/v1/checks/ |
+Create a single check |
+GET SITE_ROOT/api/v1/checks/<uuid> |
+
+
Create a new check |
POST SITE_ROOT/api/v1/checks/ |
@@ -174,6 +178,40 @@ is added. This identifier is stable across API calls. Example:
+Get a single Check
+GET SITE_ROOT/api/v1/checks/<uuid>
+Returns a JSON object containing information information from a single check.
+Response Codes
+
+- 200 OK
+- The request succeeded.
+- 401 Unauthorized
+- The API key is either missing or invalid.
+
+Example Request
+curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/checks/<uuid>
+
+
+
+Example Response
+{
+ "channels": "4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc",
+ "desc": "Longer free-form description goes here",
+ "grace": 900,
+ "last_ping": "2017-01-04T13:24:39.903464+00:00",
+ "n_pings": 1,
+ "name": "Api test 1",
+ "next_ping": "2017-01-04T14:24:39.903464+00:00",
+ "pause_url": "SITE_ROOT/api/v1/checks/662ebe36-ecab-48db-afe3-e20029cb71e6/pause",
+ "ping_url": "PING_ENDPOINT662ebe36-ecab-48db-afe3-e20029cb71e6",
+ "status": "up",
+ "tags": "foo",
+ "timeout": 3600,
+ "update_url": "SITE_ROOT/api/v1/checks/662ebe36-ecab-48db-afe3-e20029cb71e6"
+}
+
+
+
Create a Check
POST SITE_ROOT/api/v1/checks/
Creates a new check and returns its ping URL.
diff --git a/templates/docs/api.md b/templates/docs/api.md
index f23d69b6..d04bac9e 100644
--- a/templates/docs/api.md
+++ b/templates/docs/api.md
@@ -8,6 +8,7 @@ checks in user's account.
Endpoint Name | Endpoint Address
------------------------------------------------------|-------
[Get a list of existing checks](#list-checks) | `GET SITE_ROOT/api/v1/checks/`
+[Create a single check](#get-check) | `GET SITE_ROOT/api/v1/checks/`
[Create a new check](#create-check) | `POST SITE_ROOT/api/v1/checks/`
[Update an existing check](#update-check) | `POST SITE_ROOT/api/v1/checks/`
[Pause monitoring of a check](#pause-check) | `POST SITE_ROOT/api/v1/checks//pause`
@@ -157,6 +158,45 @@ is added. This identifier is stable across API calls. Example:
}
```
+## Get a single Check {: #get-check .rule }
+`GET SITE_ROOT/api/v1/checks/`
+
+Returns a JSON object containing information information from a single check.
+
+### Response Codes
+
+200 OK
+: The request succeeded.
+
+401 Unauthorized
+: The API key is either missing or invalid.
+
+### Example Request
+
+```bash
+curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/checks/
+```
+
+### Example Response
+
+```json
+{
+ "channels": "4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc",
+ "desc": "Longer free-form description goes here",
+ "grace": 900,
+ "last_ping": "2017-01-04T13:24:39.903464+00:00",
+ "n_pings": 1,
+ "name": "Api test 1",
+ "next_ping": "2017-01-04T14:24:39.903464+00:00",
+ "pause_url": "SITE_ROOT/api/v1/checks/662ebe36-ecab-48db-afe3-e20029cb71e6/pause",
+ "ping_url": "PING_ENDPOINT662ebe36-ecab-48db-afe3-e20029cb71e6",
+ "status": "up",
+ "tags": "foo",
+ "timeout": 3600,
+ "update_url": "SITE_ROOT/api/v1/checks/662ebe36-ecab-48db-afe3-e20029cb71e6"
+}
+```
+
## Create a Check {: #create-check .rule }
`POST SITE_ROOT/api/v1/checks/`