From 6f34d36d2f2d40b6dae2ade4c3687453eb225696 Mon Sep 17 00:00:00 2001 From: synkd Date: Tue, 21 Nov 2023 13:41:49 -0500 Subject: [PATCH] Add test, modify mock method returns This commit adds a negative test for the timeout on exporting a manifest. It also modifies the return values of the mocked HTTP verb methods to reuse the same RhsmApiStub object throughout each execution, rather than returning a new RhsmApiStub object with each method call. It also modifies the manifester class and helper methods to support this change. --- manifester/helpers.py | 4 +- manifester/manifester.py | 26 +++++++++---- tests/test_manifester.py | 80 ++++++++++++++++++++++++++++++++-------- 3 files changed, 86 insertions(+), 24 deletions(-) diff --git a/manifester/helpers.py b/manifester/helpers.py index 984179a..7437cc8 100644 --- a/manifester/helpers.py +++ b/manifester/helpers.py @@ -5,7 +5,7 @@ from logzero import logger -def simple_retry(cmd, cmd_args=None, cmd_kwargs=None, max_timeout=240, _cur_timeout=1): +def simple_retry(cmd, cmd_args=None, cmd_kwargs=None, max_timeout=2, _cur_timeout=1): """Re(Try) a function given its args and kwargs up until a max timeout""" cmd_args = cmd_args if cmd_args else [] @@ -19,7 +19,7 @@ def simple_retry(cmd, cmd_args=None, cmd_kwargs=None, max_timeout=240, _cur_time if response.status_code in [429, 500, 504]: new_wait = _cur_timeout * 2 if new_wait > max_timeout: - raise Exception("Timeout exceeded") + raise Exception("Retry timeout exceeded") logger.debug(f"Trying again in {_cur_timeout} seconds") time.sleep(_cur_timeout) response = simple_retry(cmd, cmd_args, cmd_kwargs, max_timeout, new_wait) diff --git a/manifester/manifester.py b/manifester/manifester.py index ea60a82..05e72a1 100644 --- a/manifester/manifester.py +++ b/manifester/manifester.py @@ -58,7 +58,10 @@ def access_token(self): cmd_args=[f"{self.token_request_url}"], cmd_kwargs=token_request_data, ).json() - self._access_token = token_data["access_token"] + if self.is_mock: + self._access_token = token_data.access_token + else: + self._access_token = token_data["access_token"] return self._access_token @cached_property @@ -131,6 +134,8 @@ def delete_subscription_allocation(self): "proxies": self.manifest_data.get("proxies", settings.proxies), "params": {"force": "true"}, } + if self.is_mock: + self.allocation_uuid = self.allocation_uuid.uuid response = simple_retry( self.requester.delete, cmd_args=[f"{self.allocations_url}/{self.allocation_uuid}"], @@ -241,11 +246,14 @@ def verify_allocation_entitlements(self, entitlement_quantity, subscription_name def process_subscription_pools(self, subscription_pools, subscription_data): logger.debug(f"Finding a matching pool for {subscription_data['name']}.") - matching = [ - d - for d in subscription_pools["body"] - if d["subscriptionName"] == subscription_data["name"] - ] + if self.is_mock: + matching = [d for d in subscription_pools.body if d["subscriptionName"] == subscription_data["name"]] + else: + matching = [ + d + for d in subscription_pools["body"] + if d["subscriptionName"] == subscription_data["name"] + ] logger.debug( f"The following pools are matches for this subscription: {matching}" ) @@ -355,13 +363,17 @@ def trigger_manifest_export(self): "Manifest export job status check limit exceeded. This may indicate an " "upstream issue with Red Hat Subscription Management." ) + raise Exception("Export timeout exceeded") break request_count += 1 if limit_exceeded: self.content = None return self export_job = export_job.json() - export_href = export_job["body"]["href"] + if self.is_mock: + export_href = export_job.body["href"] + else: + export_href = export_job["body"]["href"] manifest = simple_retry( self.requester.get, cmd_args=[f"{export_href}"], diff --git a/tests/test_manifester.py b/tests/test_manifester.py index bddc074..22c4c26 100644 --- a/tests/test_manifester.py +++ b/tests/test_manifester.py @@ -1,5 +1,6 @@ from unittest.mock import Mock +from functools import cached_property from requests import request from manifester import Manifester from manifester.settings import settings @@ -23,7 +24,7 @@ def __init__(self, in_dict=None, **kwargs): # self.status_code = kwargs.get("status_code") or fake_http_response_code(self._good_codes, self._bad_codes, self._fail_rate) super().__init__(in_dict) - @property + @cached_property def status_code(self): return fake_http_response_code(self._good_codes, self._bad_codes, self._fail_rate) @@ -31,36 +32,76 @@ def post(self, *args, **kwargs): """Simulate responses to POST requests for RHSM API endpoints used by Manifester""" if args[0].endswith("openid-connect/token"): - return RhsmApiStub(in_dict={"access_token": "this is a simulated access token"}) + self.access_token = "this is a simulated access token" + return self if args[0].endswith("allocations"): - return RhsmApiStub(in_dict={"uuid": "1234567890"}) + self.uuid = "1234567890" + return self if args[0].endswith("entitlements"): - return RhsmApiStub(in_dict={"params": kwargs["params"]}, status_code=200) + self.params = kwargs["params"] + return self + # if args[0].endswith("openid-connect/token"): + # return RhsmApiStub(in_dict={"access_token": "this is a simulated access token"}) + # if args[0].endswith("allocations"): + # return RhsmApiStub(in_dict={"uuid": "1234567890"}) + # if args[0].endswith("entitlements"): + # return RhsmApiStub(in_dict={"params": kwargs["params"]}, status_code=200) def get(self, *args, **kwargs): """"Simulate responses to GET requests for RHSM API endpoints used by Manifester""" if args[0].endswith("versions"): - return RhsmApiStub(in_dict={"valid_sat_versions": ["sat-6.12", "sat-6.13", "sat-6.14"]}) + self.valid_sat_versions = ["sat-6.12", "sat-6.13", "sat-6.14"] + return self if args[0].endswith("pools"): - # question: how to fake > 50 pools to test use of offset parameter? - return RhsmApiStub(in_dict={'body': [{'id': '987adf2a8977', 'subscriptionName': 'Red Hat Satellite Infrastructure Subscription', 'entitlementsAvailable': 13}]}) + self.body = [{'id': '987adf2a8977', 'subscriptionName': 'Red Hat Satellite Infrastructure Subscription', 'entitlementsAvailable': 13}] + return self if "allocations" in args[0] and not ("export" in args[0] or "pools" in args[0]): - return RhsmApiStub(in_dict={"allocation_data": "this allocation data also includes entitlement data"}) + self.allocation_data = "this allocation data also includes entitlement data" + return self if args[0].endswith("export"): - return RhsmApiStub(in_dict={'body': {'exportJobID': '123456', 'href': 'exportJob'}}) + self.body = {'exportJobID': '123456', 'href': 'exportJob'} + return self if "exportJob" in args[0]: - responses = [202, 200] - return RhsmApiStub(in_dict={'body': {'exportID': 27, 'href': 'https://example.com/export/98ef892ac11'}}, status_code=random.choice(responses)) + del self.status_code + if self.force_export_failure: + self._good_codes = [202] + else: + self._good_codes = [202, 200] + self.body = {'exportID': 27, 'href': 'https://example.com/export/98ef892ac11'} + return self if "export" in args[0] and not args[0].endswith("export"): + del self.status_code + self._good_codes = [200] # Manifester expets a bytes-type object to be returned as the manifest - return RhsmApiStub(in_dict={"content": b"this is a simulated manifest"}) + self.content = b"this is a simulated manifest" + return self + # if args[0].endswith("versions"): + # return RhsmApiStub(in_dict={"valid_sat_versions": ["sat-6.12", "sat-6.13", "sat-6.14"]}) + # if args[0].endswith("pools"): + # # question: how to fake > 50 pools to test use of offset parameter? + # return RhsmApiStub(in_dict={'body': [{'id': '987adf2a8977', 'subscriptionName': 'Red Hat Satellite Infrastructure Subscription', 'entitlementsAvailable': 13}]}) + # if "allocations" in args[0] and not ("export" in args[0] or "pools" in args[0]): + # return RhsmApiStub(in_dict={"allocation_data": "this allocation data also includes entitlement data"}) + # if args[0].endswith("export"): + # return RhsmApiStub(in_dict={'body': {'exportJobID': '123456', 'href': 'exportJob'}}) + # if "exportJob" in args[0]: + # responses = [202, 200] + # return RhsmApiStub(in_dict={'body': {'exportID': 27, 'href': 'https://example.com/export/98ef892ac11'}}, status_code=random.choice(responses)) + # if "export" in args[0] and not args[0].endswith("export"): + # # Manifester expets a bytes-type object to be returned as the manifest + # return RhsmApiStub(in_dict={"content": b"this is a simulated manifest"}) def delete(self, *args, **kwargs): """Simulate responses to DELETE requests for RHSM API endpoints used by Manifester""" if args[0].endswith("allocations/1234567890") and kwargs["params"]["force"] == "true": - return RhsmApiStub(in_dict={"content": b""}, good_codes=[204]) + del self.status_code + self.content = b"" + self._good_codes=[204] + return self + # if args[0].endswith("allocations/1234567890") and kwargs["params"]["force"] == "true": + # return RhsmApiStub(in_dict={"content": b""}, good_codes=[204]) def test_create_allocation(): @@ -68,15 +109,24 @@ def test_create_allocation(): manifester = Manifester(manifest_category="golden_ticket", requester=RhsmApiStub(in_dict=None)) allocation_uuid = manifester.create_subscription_allocation() - assert allocation_uuid == "1234567890" + assert allocation_uuid.uuid == "1234567890" def test_negative_simple_retry_timeout(): """Test that exceeding the attempt limit when retrying a failed API call results in an exception""" + # TODO: figure out why this test fails despite raising the expected exception manifester = Manifester(manifest_category="golden_ticket", requester=RhsmApiStub(in_dict=None, fail_rate=100)) + with pytest.raises(Exception) as exception: + manifester.create_subscription_allocation() + assert str(exception.value) == "Retry timeout exceeded" + +def test_negative_manifest_export_timeout(): + """Test that exceeding the attempt limit when exporting a manifest results in an exception""" + + manifester = Manifester(manifest_category="golden_ticket", requester=RhsmApiStub(in_dict={"force_export_failure": True})) with pytest.raises(Exception) as exception: manifester.get_manifest() - assert str(exception.value) == "Timeout exceeded" + assert str(exception.value) == "Export timeout exceeded" def test_get_manifest(): """Test that manifester's get_manifest method returns a manifest"""