From b8117c1b124d7f4ef13f8263ad72d5e4cea69012 Mon Sep 17 00:00:00 2001 From: Jonathan Rios Date: Tue, 25 Jul 2023 13:36:46 +0200 Subject: [PATCH] LITE-28160 Regenerate ppr when product version change --- connect_ext_ppr/events.py | 9 +++- connect_ext_ppr/service.py | 50 +++++++++++++-------- tests/conftest.py | 2 +- tests/test_events.py | 89 +++++++++++++++++++++++++++++++++++++- tests/test_service.py | 83 ++++++++++++++++++++++++----------- 5 files changed, 183 insertions(+), 50 deletions(-) diff --git a/connect_ext_ppr/events.py b/connect_ext_ppr/events.py index 9593a86..1fc5d8e 100644 --- a/connect_ext_ppr/events.py +++ b/connect_ext_ppr/events.py @@ -12,7 +12,7 @@ from connect.eaas.core.responses import BackgroundResponse from sqlalchemy.exc import DBAPIError -from connect_ext_ppr.service import add_deployments, update_product +from connect_ext_ppr.service import add_deployments, process_ppr_from_product_update from connect_ext_ppr.utils import get_all_info, get_marketplaces, get_products @@ -74,7 +74,12 @@ def handle_product_changed(self, request): db, create a new one. ''' self.logger.info(f"Product {request['id']} changed.") - update_product(request, self.config, self.logger) + try: + process_ppr_from_product_update( + request, self.config, self.context, self.installation_client, self.logger, + ) + except (ClientError, DBAPIError): + return BackgroundResponse.reschedule() return BackgroundResponse.done() @event( diff --git a/connect_ext_ppr/service.py b/connect_ext_ppr/service.py index 8731a68..9c35416 100644 --- a/connect_ext_ppr/service.py +++ b/connect_ext_ppr/service.py @@ -11,7 +11,7 @@ from connect_ext_ppr.models.file import File from connect_ext_ppr.models.ppr import PPRVersion from connect_ext_ppr.models.replicas import Product -from connect_ext_ppr.schemas import FileSchema +from connect_ext_ppr.schemas import FileSchema, PPRCreateSchema from connect_ext_ppr.utils import ( create_ppr_to_media, get_base_workbook, @@ -85,18 +85,27 @@ def add_deployments(installation, listings, config, logger): logger.info(f"The following Deployments have been created: {dep_ids}.") -def update_product(data, config, logger): - product_id = data['id'] +def process_ppr_from_product_update(data, config, context, client, logger): with get_db_ctx_manager(config) as db: - q = db.query(Product).filter_by(id=product_id) + q = db.query(Deployment).filter_by(product_id=data['id']) if db.query(q.exists()).scalar(): - logger.info(f"Updating product: {product_id}.") - product = q.first() - product.name = data['name'] - product.logo = data.get('icon') - product.version = data['version'] - db.add(product) - db.commit() + deployment = q.first() + product = deployment.product + older_version = product.version + update_product(data, db, product, logger) + if older_version < data['version']: + ppr = PPRCreateSchema() + logger.info(f"Product version changed: {older_version} -> {data['version']}.") + create_ppr(ppr, context, deployment, db, client, logger) + + +def update_product(data, db, product, logger): + logger.info(f"Updating product: {product.id}.") + product.name = data['name'] + product.logo = data.get('icon') + product.version = data['version'] + db.add(product) + db.commit() def get_ppr_new_version(db, deployment): @@ -116,6 +125,7 @@ def create_ppr(ppr, context, deployment, db, client, logger): file_data = ppr.file new_version = get_ppr_new_version(db, deployment) config_kwargs = {} + config_json = {} status = PPRVersion.STATUS.ready if not file_data: active_configuration = ( @@ -125,11 +135,11 @@ def create_ppr(ppr, context, deployment, db, client, logger): state=Configuration.STATE.active, ).one_or_none() ) - if not active_configuration: - raise ExtensionHttpError.EXT_006( - format_kwargs={'deployment_id': deployment.id}, + if active_configuration: + config_kwargs.update({'configuration': active_configuration.id}) + config_json = get_configuration_from_media( + client, deployment.account_id, deployment.id, active_configuration.file, ) - config_kwargs.update({'configuration': active_configuration.id}) previous_ppr = ( db.query(PPRVersion) .filter_by( @@ -140,9 +150,6 @@ def create_ppr(ppr, context, deployment, db, client, logger): .first() ) data = None - config_json = get_configuration_from_media( - client, deployment.account_id, deployment.id, active_configuration.file, - ) product_info = ( f"(product_id={deployment.product_id}, " f"product_version={deployment.product.version})" @@ -158,7 +165,7 @@ def create_ppr(ppr, context, deployment, db, client, logger): else: logger.info( f"Start creation of PPR version {product_info}" - f" based on previous product information.", + f" based on product information.", ) items = list(get_product_items(client, deployment.product.id)) @@ -223,6 +230,11 @@ def create_ppr(ppr, context, deployment, db, client, logger): ) db.set_verbose(new_ppr) db.commit() + + logger.info( + f"New PPR version created: (id={new_ppr.id}, version={new_ppr.version}" + f", product_version={new_ppr.product_version}, file={new_ppr.file}).", + ) return new_ppr except DBAPIError as ex: diff --git a/tests/conftest.py b/tests/conftest.py index c514941..87e294c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -125,7 +125,7 @@ def _build_deployment( vendor_id='VA-000-000', hub_id='HB-0000-0000', ): - product = product_factory(product_id) + product = product_factory(id=product_id, owner_id=vendor_id) product_id = product.id dep = Deployment( diff --git a/tests/test_events.py b/tests/test_events.py index 023fbd8..f917c22 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -3,11 +3,14 @@ # Copyright (c) 2023, Ingram Micro # All rights reserved. # +import json + from connect.client import ClientError from connect.client.rql import R import pytest from connect_ext_ppr.events import ConnectExtensionXvsEventsApplication +from connect_ext_ppr.models.ppr import PPRVersion from connect_ext_ppr.models.replicas import Product @@ -109,14 +112,17 @@ def test_handle_product_changed( logger, installation, product, - product_factory, + common_context, + deployment_factory, ): config = {} - product_obj = product_factory(id=product['id'], owner_id='VA-123-123') + deployment = deployment_factory(product_id=product['id'], vendor_id='VA-123-123') + product_obj = deployment.product ext = ConnectExtensionXvsEventsApplication( connect_client, logger, config, installation=installation, installation_client=connect_client, + context=common_context, ) result = ext.handle_product_changed(product) assert result.status == 'success' @@ -133,6 +139,7 @@ def test_ignore_product_changed( logger, installation, product, + common_context, ): config = {} @@ -140,6 +147,7 @@ def test_ignore_product_changed( connect_client, logger, config, installation=installation, installation_client=connect_client, + context=common_context, ) result = ext.handle_product_changed(product) assert result.status == 'success' @@ -147,6 +155,83 @@ def test_ignore_product_changed( assert not dbsession.query(q.exists()).scalar() +def test_handle_ppr_creation_from_product_update( + product, + item_response, + media_response, + dbsession, + logger, + installation, + common_context, + connect_client, + deployment_factory, + configuration_factory, + configuration_json, + client_mocker_factory, + file_factory, +): + config = {} + product_version = product['version'] = 4 + dep = deployment_factory(product_id=product['id']) + config_file = file_factory( + id='MFL-YYY', + mime_type='application/json', + ) + configuration_factory(file=config_file.id, deployment=dep.id) + client_mocker = client_mocker_factory(base_url=connect_client.endpoint) + client_mocker.ns('media').ns('folders').ns('accounts').collection( + f'{dep.account_id}/{dep.id}/configurations/files', + )[config_file.id].get( + return_value=configuration_json, + ) + + client_mocker.products[dep.product_id].items.all().mock( + return_value=[item_response], + ) + + client_mocker.ns('media').ns('folders').ns('accounts').collection( + f'{dep.account_id}/{dep.id}/pprs/files', + ).create( + return_value=json.dumps(media_response), + ) + + ext = ConnectExtensionXvsEventsApplication( + connect_client, logger, config, + installation=installation, + installation_client=connect_client, + context=common_context, + ) + qs = dbsession.query(PPRVersion).filter_by(deployment=dep.id, product_version=product_version) + result = ext.handle_product_changed(product) + assert result.status == 'success' + assert qs.count() == 1 + assert qs.first().file == media_response['id'] + + +def test_reschedule_product_change( + product, + dbsession, + logger, + installation, + common_context, + connect_client, + mocker, +): + config = {} + ext = ConnectExtensionXvsEventsApplication( + connect_client, logger, config, + installation=installation, + installation_client=connect_client, + context=common_context, + ) + mocker.patch( + 'connect_ext_ppr.events.process_ppr_from_product_update', + side_effect=ClientError, + ) + result = ext.handle_product_changed(product) + assert result.status == 'reschedule' + + @pytest.mark.parametrize( 'status', ('installed', 'uninstalled'), diff --git a/tests/test_service.py b/tests/test_service.py index e12cee8..18584e0 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -133,7 +133,7 @@ def test_create_ppr_base_on_user_uploaded_file_with_errors( assert new_ppr.status == 'failed' -def test_create_ppr_base_on_another_ppr_version( +def test_create_ppr_base_on_another_ppr_version_w_config( dbsession, common_context, connect_client, logger, deployment_factory, client_mocker_factory, file_factory, bytes_ppr_workbook_factory, ppr_version_factory, configuration_json, item_response, configuration_factory, media_response, @@ -148,7 +148,7 @@ def test_create_ppr_base_on_another_ppr_version( mime_type='application/json', ) ppr_version = ppr_version_factory(file=ppr_file.id, status='ready', deployment=deployment) - configuration_factory(file=config_file.id, deployment=deployment.id) + conf = configuration_factory(file=config_file.id, deployment=deployment.id) ppr_data = PPRCreateSchema(file=None) client_mocker = client_mocker_factory(base_url=connect_client.endpoint) @@ -190,35 +190,33 @@ def test_create_ppr_base_on_another_ppr_version( file_name = dbsession.query(File.name).filter_by(id='MFL-ZZZ').limit(1).scalar() assert new_ppr.file == media_response['id'] assert new_ppr.version == ppr_version.version + 1 + assert new_ppr.configuration == conf.id assert new_ppr.status == 'ready' assert file_name.startswith(f"PPR_{deployment.product_id}_v{new_ppr.version}_") assert file_name.endswith(".xlsx") -def test_create_ppr_wo_ppr_version( - dbsession, common_context, connect_client, logger, - client_mocker_factory, file_factory, ppr_version_factory, deployment_factory, - configuration_json, item_response, configuration_factory, media_response, +def test_create_ppr_base_on_another_ppr_version_wo_config( + dbsession, common_context, connect_client, logger, deployment_factory, + client_mocker_factory, file_factory, bytes_ppr_workbook_factory, ppr_version_factory, + item_response, media_response, ): deployment = deployment_factory() ppr_file = file_factory( id='MFL-XXX', mime_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ) - config_file = file_factory( - id='MFL-YYY', - mime_type='application/json', - ) - ppr_version = ppr_version_factory(file=ppr_file.id, deployment=deployment) - configuration_factory(file=config_file.id, deployment=deployment.id) + ppr_version = ppr_version_factory(file=ppr_file.id, status='ready', deployment=deployment) ppr_data = PPRCreateSchema(file=None) client_mocker = client_mocker_factory(base_url=connect_client.endpoint) + bytes_ppr_workbook = bytes_ppr_workbook_factory() + client_mocker.ns('media').ns('folders').ns('accounts').collection( - f'{deployment.account_id}/{deployment.id}/configurations/files', - )[config_file.id].get( - return_value=configuration_json, + f'{deployment.account_id}/{deployment.id}/pprs/files', + )[ppr_file.id].get( + return_value=bytes_ppr_workbook, ) client_mocker.products[deployment.product_id].items.all().mock( @@ -241,33 +239,66 @@ def test_create_ppr_wo_ppr_version( 'created': ['PRD-000-000-000-00001'], }, } + file_name = dbsession.query(File.name).filter_by(id='MFL-ZZZ').limit(1).scalar() assert new_ppr.file == media_response['id'] assert new_ppr.version == ppr_version.version + 1 + assert new_ppr.configuration is None assert new_ppr.status == 'ready' + assert file_name.startswith(f"PPR_{deployment.product_id}_v{new_ppr.version}_") + assert file_name.endswith(".xlsx") -def test_no_active_configuration_available( +def test_create_ppr_wo_ppr_version_w_config( dbsession, common_context, connect_client, logger, - file_factory, ppr_version_factory, deployment_factory, + client_mocker_factory, file_factory, ppr_version_factory, deployment_factory, + configuration_json, item_response, configuration_factory, media_response, ): deployment = deployment_factory() ppr_file = file_factory( id='MFL-XXX', mime_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ) - ppr_version_factory(file=ppr_file.id, deployment=deployment) + config_file = file_factory( + id='MFL-YYY', + mime_type='application/json', + ) + ppr_version = ppr_version_factory(file=ppr_file.id, deployment=deployment) + conf = configuration_factory(file=config_file.id, deployment=deployment.id) ppr_data = PPRCreateSchema(file=None) - with pytest.raises(ClientError) as ex: - create_ppr( - ppr_data, common_context, deployment, - dbsession, connect_client, logger, - ) - assert ex.value.message == ( - f"Can not autogenerate a new PPR for deployment {deployment.id}:" - f" There must be one `active` configuration file." + client_mocker = client_mocker_factory(base_url=connect_client.endpoint) + + client_mocker.ns('media').ns('folders').ns('accounts').collection( + f'{deployment.account_id}/{deployment.id}/configurations/files', + )[config_file.id].get( + return_value=configuration_json, + ) + + client_mocker.products[deployment.product_id].items.all().mock( + return_value=[item_response], + ) + + media_response['id'] = 'MFL-ZZZ' + client_mocker.ns('media').ns('folders').ns('accounts').collection( + f'{deployment.account_id}/{deployment.id}/pprs/files', + ).create( + return_value=json.dumps(media_response), ) + new_ppr = create_ppr(ppr_data, common_context, deployment, dbsession, connect_client, logger) + + assert new_ppr.id + assert new_ppr.summary == { + 'ResourceCategories': {}, + 'Resources': { + 'created': ['PRD-000-000-000-00001'], + }, + } + assert new_ppr.file == media_response['id'] + assert new_ppr.configuration == conf.id + assert new_ppr.version == ppr_version.version + 1 + assert new_ppr.status == 'ready' + def test_create_ppr_db_error( dbsession, common_context, deployment_factory, connect_client, logger,