From 7a9c6b359dc3979739a83b357a15615b04996181 Mon Sep 17 00:00:00 2001 From: Greg Land Date: Thu, 25 Jul 2024 12:54:18 -0400 Subject: [PATCH] Added api/ready command * Added api/ready * Added tests for api/ready * Added fix for BaseApiClient.do_get. 503 status codes were getting turned ApltyAPIExceptions. The new api/ready uses 503 as its status code when aptly is not ready. --- aptly_api/base.py | 2 +- aptly_api/parts/misc.py | 22 ++++++++++++++++++++++ aptly_api/tests/test_client.py | 6 ++++++ aptly_api/tests/test_misc.py | 23 +++++++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/aptly_api/base.py b/aptly_api/base.py index 41cb8eb..e4097c8 100644 --- a/aptly_api/base.py +++ b/aptly_api/base.py @@ -90,7 +90,7 @@ def do_get(self, urlpath: str, params: Optional[Dict[str, str]] = None) -> reque resp = requests.get(self._make_url(urlpath), params=params, verify=self.ssl_verify, cert=self.ssl_cert, auth=self.http_auth, timeout=self.timeout) - if resp.status_code < 200 or resp.status_code >= 300: + if resp.status_code < 200 or resp.status_code >= 300 and resp.status_code != 503: raise AptlyAPIException(self._error_from_response(resp), status_code=resp.status_code) return resp diff --git a/aptly_api/parts/misc.py b/aptly_api/parts/misc.py index a1f21b1..e718be9 100644 --- a/aptly_api/parts/misc.py +++ b/aptly_api/parts/misc.py @@ -5,6 +5,8 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. from typing import cast +import requests + from aptly_api.base import AptlyAPIException, BaseAPIClient @@ -18,3 +20,23 @@ def version(self) -> str: return cast(str, resp.json()["Version"]) else: raise AptlyAPIException("Aptly server didn't return a valid response object:\n%s" % resp.text) + + def _do_get_clear_404(self) -> requests.Response: + try: + return self.do_get("api/ready") + except AptlyAPIException as error: + # This is needed to hide the exception masking the 404 error + if error.status_code == 404: + raise NotImplementedError("The Ready API is not yet supported") from error + # 503 is needed by api/ready for returning its unready condition + if error.status_code not in {200, 503}: + raise AptlyAPIException("Aptly server returned an unexpected status_code " + str(error.status_code)) from error + raise + + def ready(self) -> str: + resp = self._do_get_clear_404() + + if "Status" not in resp.json(): + raise AptlyAPIException("Aptly server didn't return a valid response object:\n%s" % resp.text) + + return cast(str, resp.json()["Status"]) diff --git a/aptly_api/tests/test_client.py b/aptly_api/tests/test_client.py index 016f0bc..d48c55a 100644 --- a/aptly_api/tests/test_client.py +++ b/aptly_api/tests/test_client.py @@ -95,6 +95,12 @@ def test_error_get(self, *, rmock: requests_mock.Mocker) -> None: with self.assertRaises(AptlyAPIException): self.client.files.do_get("mock://test/api") + @requests_mock.Mocker(kw='rmock') + def test_error_get_api_ready_503_code(self, *, rmock: requests_mock.Mocker) -> None: + rmock.register_uri("GET", "mock://test/api/ready", status_code=503, text='{"Status":"Aptly is unavailable"}') + self.assertEqual(self.client.files.do_get("mock://test/api/ready").status_code, 503) + self.assertEqual(self.client.files.do_get("mock://test/api/ready").json()["Status"], "Aptly is unavailable") + @requests_mock.Mocker(kw='rmock') def test_error_post(self, *, rmock: requests_mock.Mocker) -> None: rmock.register_uri("POST", "mock://test/api", status_code=400, text='[{"error": "error", "meta": "meta"}]', diff --git a/aptly_api/tests/test_misc.py b/aptly_api/tests/test_misc.py index 18040a2..abf2609 100644 --- a/aptly_api/tests/test_misc.py +++ b/aptly_api/tests/test_misc.py @@ -30,3 +30,26 @@ def test_version_error(self, *, rmock: requests_mock.Mocker) -> None: rmock.get("http://test/api/version", text='{"droenk": "blah"}') with self.assertRaises(AptlyAPIException): self.mapi.version() + + def test_ready(self, *, rmock: requests_mock.Mocker) -> None: + rmock.get("http://test/api/ready", text='{"Status":"Aptly is ready"}') + self.assertEqual(self.mapi.ready(), "Aptly is ready") + + def test_ready_not_ready(self, *, rmock: requests_mock.Mocker) -> None: + rmock.register_uri("GET", "http://test/api/ready", status_code=503, text='{"Status":"Aptly is unavailable"}') + self.assertEqual(self.mapi.ready(), "Aptly is unavailable") + + def test_ready_error(self, *, rmock: requests_mock.Mocker) -> None: + rmock.get("http://test/api/ready", text='{"droenk": "blah"}') + with self.assertRaises(AptlyAPIException): + self.mapi.ready() + + def test_ready_error_bad_status_code(self, *, rmock: requests_mock.Mocker) -> None: + rmock.register_uri("GET", "http://test/api/ready", status_code=999, text='{"Status":"Aptly is ready"}') + with self.assertRaises(AptlyAPIException): + self.mapi.ready() + + def test_ready_aptly_to_old(self, *, rmock: requests_mock.Mocker) -> None: + rmock.register_uri("GET", "http://test/api/ready", status_code=404, text="Not Found") + with self.assertRaises(NotImplementedError): + self.mapi.ready()