diff --git a/CHANGELOG.md b/CHANGELOG.md index 626cfb5..414185a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +- ✨ — Add new check types: body-like, headers-like and json-like (#58) + ## 0.3.0 Date: 2024-09-02 diff --git a/argos/checks/checks.py b/argos/checks/checks.py index 15c038f..d1fc530 100644 --- a/argos/checks/checks.py +++ b/argos/checks/checks.py @@ -1,6 +1,7 @@ """Define the available checks""" import json +import re from datetime import datetime from jsonpointer import resolve_pointer, JsonPointerException @@ -110,6 +111,36 @@ class HTTPHeadersHave(BaseCheck): ) +class HTTPHeadersLike(BaseCheck): + """Checks that response headers contains the expected headers and that the values + matches the provided regexes""" + + config = "headers-like" + expected_cls = ExpectedStringValue + + async def run(self) -> dict: + # XXX Get the method from the task + task = self.task + response = await self.http_client.request( + method="get", url=task.url, timeout=60 + ) + + status = True + for header, value in json.loads(self.expected).items(): + if header not in response.headers: + status = False + break + if not re.search(rf"{value}", response.headers[header]): + status = False + break + + return self.response( + status=status, + expected=self.expected, + retrieved=json.dumps(dict(response.headers)), + ) + + class HTTPBodyContains(BaseCheck): """Checks that the HTTP body contains the expected string.""" @@ -123,6 +154,22 @@ class HTTPBodyContains(BaseCheck): return self.response(status=self.expected in response.text) +class HTTPBodyLike(BaseCheck): + """Checks that the HTTP body matches the provided regex.""" + + config = "body-like" + expected_cls = ExpectedStringValue + + async def run(self) -> dict: + response = await self.http_client.request( + method="get", url=self.task.url, timeout=60 + ) + if re.search(rf"{self.expected}", response.text): + return self.response(status=True) + + return self.response(status=False) + + class HTTPJsonContains(BaseCheck): """Checks that JSON response contains the expected structure (without checking the value)""" @@ -187,6 +234,40 @@ class HTTPJsonHas(BaseCheck): ) +class HTTPJsonLike(BaseCheck): + """Checks that JSON response contains the expected structure and that the values + matches the provided regexes""" + + config = "json-like" + expected_cls = ExpectedStringValue + + async def run(self) -> dict: + # XXX Get the method from the task + task = self.task + response = await self.http_client.request( + method="get", url=task.url, timeout=60 + ) + + obj = response.json() + + status = True + for pointer, exp_value in json.loads(self.expected).items(): + try: + value = resolve_pointer(obj, pointer) + if not re.search(rf"{exp_value:}", value): + status = False + break + except JsonPointerException: + status = False + break + + return self.response( + status=status, + expected=self.expected, + retrieved=json.dumps(obj), + ) + + class HTTPJsonIs(BaseCheck): """Checks that JSON response is the exact expected JSON object""" diff --git a/argos/config-example.yaml b/argos/config-example.yaml index b5558ee..a84cb89 100644 --- a/argos/config-example.yaml +++ b/argos/config-example.yaml @@ -84,6 +84,8 @@ websites: - status-is: 200 # Check that the response contains this string - body-contains: '
' + # Check that the response matches this regex + - body-like: MyPads .* accounts # Check that the SSL certificate is no older than ssl.thresholds - ssl-certificate-expiration: "on-check" # Check that the response contains this headers @@ -104,6 +106,12 @@ websites: - headers-have: content-encoding: "gzip" content-type: "text/html" + # Checks that response headers contains the expected headers and + # that the values matches the provided regexes + # You have to double the escape character \ + - headers-like: + content-encoding: "gzip|utf" + content-type: "text/(html|css)" - path: "/my-stats.json" checks: # Check that JSON response contains the expected structure @@ -116,6 +124,12 @@ websites: - json-has: /maintenance: false /productname: "Nextcloud" + # Check that JSON response contains the expected structure and + # that the values matches the provided regexes + # You have to double the escape character \ + - json-like: + /productname: ".*cloud" + /versionstring: "29\\..*" # Check that JSON response is the exact expected JSON object # The order of the items in the object does not matter. - json-is: '{"foo": "bar", "baz": 42}' diff --git a/docs/checks.md b/docs/checks.md index 0b62ad9..06fddd6 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -11,11 +11,14 @@ These checks are the most basic ones. They simply check that the response from t | `status-is` | Check that the returned status code matches what you expect. | `status-is: "200"` | | `status-in` | Check that the returned status code is in the list of codes you expect. |status-in:
- 200
- 302
|
| `body-contains` | Check that the returned body contains a given string. | `body-contains: "Hello world"` |
+| `body-like` | Check that the returned body matches a given regex. | `body-like: "Hel+o w.*"` |
| `headers-contain` | Check that the response contains the expected headers. | headers-contain:
- "content-encoding"
- "content-type"
|
| `headers-have` | Check that the response contains the expected headers with the expected value. | headers-have:
content-encoding: "gzip"
content-type: "text/html"
|
+| `headers-like` | Check that response headers contains the expected headers and that the values matches the provided regexes. | headers-like:
content-encoding: "gzip\|utf"
content-type: "text/(html\|css)"
|
| `json-contains` | Check that JSON response contains the expected structure. | json-contains:
- /foo/bar/0
- /timestamp
|
| `json-has` | Check that JSON response contains the expected structure and values. | json-has:
/maintenance: false
/productname: "Nextcloud"
|
-| `json-is` | Check that JSON response is the exact expected JSON object | `json-is: '{"foo": "bar", "baz": 42}'`|
+| `json-like` | Check that JSON response contains the expected structure and that the values matches the provided regexes. | json-like:
/productname: ".\*cloud"
/versionstring: "29\\\\..\*"
|
+| `json-is` | Check that JSON response is the exact expected JSON object. | `json-is: '{"foo": "bar", "baz": 42}'`|
```{code-block} yaml
---
@@ -27,6 +30,7 @@ caption: argos-config.yaml
checks:
- status-is: 200
- body-contains: "Hello world"
+ - body-like: "Hel+o w.*"
- headers-contain:
- "content-encoding"
- "content-type"
@@ -39,6 +43,11 @@ caption: argos-config.yaml
- headers-have:
content-encoding: "gzip"
content-type: "text/html"
+ # It’s VERY important to respect the 4 spaces indentation here!
+ # You have to double the escape character \
+ - headers-like:
+ content-encoding: "gzip|utf"
+ content-type: "text/(html|css)"
- json-contains:
- /foo/bar/0
- /timestamp
@@ -46,6 +55,11 @@ caption: argos-config.yaml
- json-has:
/maintenance: false
/productname: "Nextcloud"
+ # It’s VERY important to respect the 4 spaces indentation here!
+ # You have to double the escape character \
+ - json-like:
+ /productname: ".*cloud"
+ /versionstring: "29\\..*"
- json-is: '{"foo": "bar", "baz": 42}'
```