diff --git a/connect_ext_ppr/services/cbc_hub.py b/connect_ext_ppr/services/cbc_hub.py index 70d1ff3..61a2c0a 100644 --- a/connect_ext_ppr/services/cbc_hub.py +++ b/connect_ext_ppr/services/cbc_hub.py @@ -1,6 +1,8 @@ import base64 from functools import cached_property from io import FileIO +import re +from typing import Dict from connect_ext_ppr.client import CBCClient from connect_ext_ppr.client.exception import ClientError @@ -11,6 +13,7 @@ class CBCService: PLM_TYPE = 'http://com.odin.platform/inhouse-products/application' SUBSCRIPTION_TYPE = 'http://parallels.com/aps/types/pa/subscription' + ADAPTER_TYPE = 'http://connect.cloudblue.com/aps-openapi-adapter/app' def __init__(self, hub_credential: HubCredential, verify_certificate: bool = False): self.hub_credential = hub_credential @@ -61,6 +64,10 @@ def plm_service(self): def subscription_service(self): return self.client(self.SUBSCRIPTION_TYPE) + @cached_property + def adapter_service(self): + return self.client(self.ADAPTER_TYPE) + def get_product_details(self, product_id: str): return self.plm_service.appDetails[product_id].get( fulfillmentSystem='connect', @@ -93,3 +100,25 @@ def parse_ppr(self, file: FileIO): 'excelConfig': base64_content, }, ) + + def apply_ppr(self, parsed_ppr: Dict): + headers = self.plm_service.action( + name='applyConfig', + payload=parsed_ppr, + output='headers', + ) + + task_info = headers['APS-Info'] if 'APS-Info' in headers.keys() else None + + if task_info: + tracking_ids = re.findall( + r'[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}', + task_info, + ) + + return tracking_ids[0] if tracking_ids else None + + def search_task_logs_by_name(self, partial_name: str): + return self.adapter_service.getTaskLog.get( + task_name=f'%{partial_name}%', + ) diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 6fe9f5e..efc4e1e 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -13,19 +13,19 @@ def test_client_get( cbc_client, cbc_endpoint, - flat_catalog_type_object, + service, ): - object_id = flat_catalog_type_object['aps']['id'] + object_id = service['aps']['id'] responses.add( method='GET', url=f'{cbc_endpoint}/aps/2/resources/{object_id}', - json=flat_catalog_type_object, + json=service, ) obj = cbc_client.get(object_id) - TestCase().assertDictEqual(obj, flat_catalog_type_object) + TestCase().assertDictEqual(obj, service) @responses.activate diff --git a/tests/client/test_collection.py b/tests/client/test_collection.py index 4854b36..11d162d 100644 --- a/tests/client/test_collection.py +++ b/tests/client/test_collection.py @@ -25,17 +25,17 @@ def test_collection_wrong_collection_value_type(cbc_client): def test_collection_get( cbc_endpoint, cbc_client, - flat_catalog_type_objects, + services, ): responses.add( method='GET', url=f'{cbc_endpoint}/flat-catalog', - json=flat_catalog_type_objects, + json=services, ) objs = cbc_client.flat_catalog.get() - TestCase().assertListEqual(objs, flat_catalog_type_objects) + TestCase().assertListEqual(objs, services) def test_sub_collection(cbc_endpoint, cbc_client): @@ -74,31 +74,31 @@ def test_collection_resource_wrong_type(cbc_client): def test_collection_get_with_identifier( cbc_endpoint, cbc_client, - flat_catalog_type_object, + service, ): - object_id = flat_catalog_type_object['aps']['id'] + object_id = service['aps']['id'] responses.add( method='GET', url=f'{cbc_endpoint}/flat-catalog/wizard/{object_id}', - json=flat_catalog_type_object, + json=service, ) obj = cbc_client.flat_catalog.wizard[object_id].get() - TestCase().assertDictEqual(obj, flat_catalog_type_object) + TestCase().assertDictEqual(obj, service) @responses.activate def test_collection_action( cbc_endpoint, cbc_client, - flat_catalog_type_object, + service, ): responses.add( method='POST', url=f'{cbc_endpoint}/flat-catalog/wizard/upload', - json=flat_catalog_type_object, + json=service, ) obj = cbc_client.flat_catalog.wizard.action( @@ -106,7 +106,7 @@ def test_collection_action( payload={}, ) - TestCase().assertDictEqual(obj, flat_catalog_type_object) + TestCase().assertDictEqual(obj, service) def test_collection_action_with_both_payload_and_file(cbc_client): diff --git a/tests/client/test_resource.py b/tests/client/test_resource.py index 66466d2..41d6185 100644 --- a/tests/client/test_resource.py +++ b/tests/client/test_resource.py @@ -14,29 +14,29 @@ def test_resource(cbc_endpoint, cbc_client): def test_resource_get( cbc_endpoint, cbc_client, - flat_catalog_type_object, + service, ): responses.add( method='GET', url=f'{cbc_endpoint}/test-collection/identifier', - json=flat_catalog_type_object, + json=service, ) obj = cbc_client.test_collection['identifier'].get() - TestCase().assertDictEqual(obj, flat_catalog_type_object) + TestCase().assertDictEqual(obj, service) @responses.activate def test_resource_action_with_payload( cbc_endpoint, cbc_client, - flat_catalog_type_object, + service, ): responses.add( method='POST', url=f'{cbc_endpoint}/test-collection/identifier/upload', - json=flat_catalog_type_object, + json=service, ) obj = cbc_client.test_collection['identifier'].action( @@ -44,14 +44,14 @@ def test_resource_action_with_payload( payload={}, ) - TestCase().assertDictEqual(obj, flat_catalog_type_object) + TestCase().assertDictEqual(obj, service) @responses.activate def test_resource_action_not_json_response( cbc_endpoint, cbc_client, - flat_catalog_type_object, + service, ): responses.add( method='POST', @@ -71,12 +71,12 @@ def test_resource_action_not_json_response( def test_resource_action_with_extra_headers( cbc_endpoint, cbc_client, - flat_catalog_type_object, + service, ): responses.add( method='POST', url=f'{cbc_endpoint}/test-collection/identifier/upload', - json=flat_catalog_type_object, + json=service, ) obj = cbc_client.test_collection['identifier'].action( @@ -85,19 +85,19 @@ def test_resource_action_with_extra_headers( headers={'Content-Type': 'application/json'}, ) - TestCase().assertDictEqual(obj, flat_catalog_type_object) + TestCase().assertDictEqual(obj, service) @responses.activate def test_resource_action_with_file( cbc_endpoint, cbc_client, - flat_catalog_type_object, + service, ): responses.add( method='POST', url=f'{cbc_endpoint}/test-collection/identifier/upload', - json=flat_catalog_type_object, + json=service, ) obj = cbc_client.test_collection['identifier'].action( @@ -105,19 +105,19 @@ def test_resource_action_with_file( file=io.StringIO("some initial text data"), ) - TestCase().assertDictEqual(obj, flat_catalog_type_object) + TestCase().assertDictEqual(obj, service) @responses.activate def test_resource_action_with_headers_output( cbc_endpoint, cbc_client, - flat_catalog_type_object, + service, ): responses.add( method='POST', url=f'{cbc_endpoint}/test-collection/identifier/upload', - json=flat_catalog_type_object, + json=service, ) obj = cbc_client.test_collection['identifier'].action( diff --git a/tests/client/test_service.py b/tests/client/test_service.py index 5f50073..c309ce0 100644 --- a/tests/client/test_service.py +++ b/tests/client/test_service.py @@ -11,16 +11,16 @@ def test_service_discovery( cbc_endpoint, cbc_client, flat_catalog_type, - flat_catalog_type_objects, + services, ): responses.add( method='GET', url=f'{cbc_endpoint}/aps/2/resources/?implementing({flat_catalog_type})', - json=flat_catalog_type_objects, + json=services, ) services = cbc_client(flat_catalog_type).get() - TestCase().assertListEqual(services, flat_catalog_type_objects) + TestCase().assertListEqual(services, services) @responses.activate @@ -76,13 +76,13 @@ def test_service_discovery_collection_with_underscore( cbc_endpoint, cbc_client, flat_catalog_type, - flat_catalog_type_objects, + services, ): - service_id = flat_catalog_type_objects[0]['aps']['id'] + service_id = services[0]['aps']['id'] responses.add( method='GET', url=f'{cbc_endpoint}/aps/2/resources/?implementing({flat_catalog_type})', - json=flat_catalog_type_objects, + json=services, ) collection = cbc_client(flat_catalog_type).flat_catalog @@ -95,13 +95,13 @@ def test_service_discovery_collection( cbc_endpoint, cbc_client, flat_catalog_type, - flat_catalog_type_objects, + services, ): - service_id = flat_catalog_type_objects[0]['aps']['id'] + service_id = services[0]['aps']['id'] responses.add( method='GET', url=f'{cbc_endpoint}/aps/2/resources/?implementing({flat_catalog_type})', - json=flat_catalog_type_objects, + json=services, ) service = cbc_client(flat_catalog_type) @@ -122,12 +122,12 @@ def test_service_discovery_collection_wrong_collection_value_type( cbc_endpoint, cbc_client, flat_catalog_type, - flat_catalog_type_objects, + services, ): responses.add( method='GET', url=f'{cbc_endpoint}/aps/2/resources/?implementing({flat_catalog_type})', - json=flat_catalog_type_objects, + json=services, ) with pytest.raises(TypeError): @@ -139,12 +139,12 @@ def test_service_discovery_collection_blank_collection_value( cbc_endpoint, cbc_client, flat_catalog_type, - flat_catalog_type_objects, + services, ): responses.add( method='GET', url=f'{cbc_endpoint}/aps/2/resources/?implementing({flat_catalog_type})', - json=flat_catalog_type_objects, + json=services, ) with pytest.raises(ValueError): diff --git a/tests/conftest.py b/tests/conftest.py index 36b1ab4..768ff9a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -416,25 +416,6 @@ def cbc_client( ) -@pytest.fixture -def flat_catalog_type_object(): - return { - 'refreshStatsUUID': '83dc93d4-2722-4cb9-b581-9a26aeeb0fe0', - 'aps': { - 'modified': '2023-06-30T22:28:16Z', - 'id': '3e123a60-b055-45d1-b838-b35d34405927', - 'type': 'http://ingrammicro.com/pa/flat-catalog/1.4', - 'status': 'aps:ready', - 'revision': 4, - }, - } - - -@pytest.fixture -def flat_catalog_type_objects(flat_catalog_type_object): - return [flat_catalog_type_object] - - @pytest.fixture def flat_catalog_type(): return 'http://ingrammicro.com/pa/flat-catalog' @@ -699,13 +680,12 @@ def subscriptions(): @pytest.fixture -def plm_service(): +def service(): return { 'aps': { 'modified': '2023-07-13T07:01:38Z', 'id': '4b4b65ec-149a-4a8c-9897-dc32f2e9e379', - 'type': 'http://com.odin.platform/inhouse-products/application/1.0', 'status': 'aps:ready', 'revision': 3, }, @@ -713,8 +693,8 @@ def plm_service(): @pytest.fixture -def plm_services(plm_service): - return [plm_service] +def services(service): + return [service] @pytest.fixture @@ -725,3 +705,22 @@ def sample_ppr_file(): @pytest.fixture def parse_ppr_success_response(): return json.load(open('./tests/fixtures/parse_ppr_success_response.json')) + + +@pytest.fixture +def task_logs_response(): + return [ + { + 'actionOutput': '', + 'location': 'SCREF:JMSQueue:0', + 'method': 'APSAsyncOperations', + 'mutex': 'PL:8', + 'name': "Execute operation '/applyConfig'(eebc63a9-ed15-4a7f-83d5-bb2d262c7650)" + ' on resource application(4b4b65ec-149a-4a8c-9897-dc32f2e9e379)', + 'orderId': 0, + 'startedAt': 1689262873, + 'status': 's', + 'subscription_id': 0, + 'task_id': 106, + }, + ] diff --git a/tests/services/test_cbc_hub.py b/tests/services/test_cbc_hub.py index 73d0a1a..78ad061 100644 --- a/tests/services/test_cbc_hub.py +++ b/tests/services/test_cbc_hub.py @@ -11,17 +11,22 @@ def __mock_common_services( cbc_endpoint, aps_controller_details, - plm_services, + services, ): responses.add( method='GET', url=f'{cbc_endpoint}/aps', json=aps_controller_details, ) + # responses library treats following urls as same + # /aps/2/resources/ + # /aps/2/resources/?implementing(abc) + # /aps/2/resources/?implementing(xyz) + # That means only one service implementation is enough for all services for identification responses.add( method='GET', - url=f'{cbc_endpoint}/aps/2/resources/?implementing({CBCService.PLM_TYPE})', - json=plm_services, + url=f'{cbc_endpoint}/aps/2/resources/', + json=services, ) @@ -29,17 +34,17 @@ def __mock_common_services( def test_get_product_details_positive( hub_credentials, cbc_endpoint, - plm_services, + services, aps_controller_details, product_details, ): product_id = 'PRD-000-000-000' - service_id = plm_services[0]['aps']['id'] + service_id = services[0]['aps']['id'] __mock_common_services( cbc_endpoint, aps_controller_details, - plm_services, + services, ) responses.add( @@ -60,16 +65,16 @@ def test_get_product_details_not_found( hub_credentials, cbc_endpoint, aps_controller_details, - plm_services, + services, get_product_details_not_found_response, ): product_id = 'PRD-000-000-000' - service_id = plm_services[0]['aps']['id'] + service_id = services[0]['aps']['id'] __mock_common_services( cbc_endpoint, aps_controller_details, - plm_services, + services, ) responses.add( @@ -90,16 +95,16 @@ def test_install_product_positive( hub_credentials, cbc_endpoint, aps_controller_details, - plm_services, + services, subscriptions, ): product_id = 'PRD-000-000-000' - service_id = plm_services[0]['aps']['id'] + service_id = services[0]['aps']['id'] __mock_common_services( cbc_endpoint, aps_controller_details, - plm_services, + services, ) responses.add( method='GET', @@ -125,17 +130,17 @@ def test_install_product_not_found( hub_credentials, cbc_endpoint, aps_controller_details, - plm_services, + services, subscriptions, import_product_not_found_response, ): product_id = 'PRD-000-000-000' - service_id = plm_services[0]['aps']['id'] + service_id = services[0]['aps']['id'] __mock_common_services( cbc_endpoint, aps_controller_details, - plm_services, + services, ) responses.add( method='GET', @@ -161,17 +166,17 @@ def test_update_product_positive( hub_credentials, cbc_endpoint, aps_controller_details, - plm_services, + services, subscriptions, update_product_response, ): product_id = 'PRD-000-000-000' - service_id = plm_services[0]['aps']['id'] + service_id = services[0]['aps']['id'] __mock_common_services( cbc_endpoint, aps_controller_details, - plm_services, + services, ) responses.add( method='GET', @@ -197,18 +202,18 @@ def test_update_product_positive( def test_update_product_negative_product_not_installed( hub_credentials, cbc_endpoint, - plm_services, + services, aps_controller_details, subscriptions, product_not_installed_response, ): product_id = 'PRD-000-000-000' - service_id = plm_services[0]['aps']['id'] + service_id = services[0]['aps']['id'] __mock_common_services( cbc_endpoint, aps_controller_details, - plm_services, + services, ) responses.add( method='GET', @@ -289,16 +294,16 @@ def test_parse_ppr_positive( hub_credentials, cbc_endpoint, aps_controller_details, - plm_services, + services, sample_ppr_file, parse_ppr_success_response, ): - service_id = plm_services[0]['aps']['id'] + service_id = services[0]['aps']['id'] __mock_common_services( cbc_endpoint, aps_controller_details, - plm_services, + services, ) responses.add( method='POST', @@ -311,3 +316,123 @@ def test_parse_ppr_positive( response = cbc_service.parse_ppr(sample_ppr_file) TestCase().assertDictEqual(response, parse_ppr_success_response) + + +@responses.activate +def test_apply_ppr_positive( + hub_credentials, + cbc_endpoint, + aps_controller_details, + services, + parse_ppr_success_response, +): + service_id = services[0]['aps']['id'] + + __mock_common_services( + cbc_endpoint, + aps_controller_details, + services, + ) + responses.add( + method='POST', + url=f'{cbc_endpoint}/aps/2/resources/' + f'{service_id}/applyConfig', + status=202, + headers={ + 'APS-Info': 'Importing configuration for request b09b2497-484c-4b1c-92a6-73a0443193ac', + }, + ) + + cbc_service = CBCService(hub_credentials) + tracking_id = cbc_service.apply_ppr(parse_ppr_success_response) + + assert tracking_id == 'b09b2497-484c-4b1c-92a6-73a0443193ac' + + +@responses.activate +def test_apply_ppr_negative_no_tracking_provided( + hub_credentials, + cbc_endpoint, + aps_controller_details, + services, + parse_ppr_success_response, +): + service_id = services[0]['aps']['id'] + + __mock_common_services( + cbc_endpoint, + aps_controller_details, + services, + ) + responses.add( + method='POST', + url=f'{cbc_endpoint}/aps/2/resources/' + f'{service_id}/applyConfig', + status=202, + ) + + cbc_service = CBCService(hub_credentials) + tracking_id = cbc_service.apply_ppr(parse_ppr_success_response) + + assert not tracking_id + + +@responses.activate +def test_apply_ppr_negative_tracking_id_not_present_in_header( + hub_credentials, + cbc_endpoint, + aps_controller_details, + services, + parse_ppr_success_response, +): + service_id = services[0]['aps']['id'] + + __mock_common_services( + cbc_endpoint, + aps_controller_details, + services, + ) + responses.add( + method='POST', + url=f'{cbc_endpoint}/aps/2/resources/' + f'{service_id}/applyConfig', + status=202, + headers={ + 'APS-Info': 'Importing configuration for request.', + }, + ) + + cbc_service = CBCService(hub_credentials) + tracking_id = cbc_service.apply_ppr(parse_ppr_success_response) + + assert not tracking_id + + +@responses.activate +def test_search_task_logs_by_name_positive( + hub_credentials, + cbc_endpoint, + aps_controller_details, + services, + task_logs_response, +): + __mock_common_services( + cbc_endpoint, + aps_controller_details, + services, + ) + + service_id = services[0]['aps']['id'] + tracking_id = 'b09b2497-484c-4b1c-92a6-73a0443193ac' + + responses.add( + method='GET', + url=f'{cbc_endpoint}/aps/2/resources/{service_id}/getTaskLog?task_name=%25{tracking_id}%25', + json=task_logs_response, + ) + + cbc_service = CBCService(hub_credentials) + + task_logs = cbc_service.search_task_logs_by_name(tracking_id) + + TestCase().assertListEqual(task_logs, task_logs_response)