diff --git a/Dockerfile b/Dockerfile index a57c735..50ce613 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM cloudblueconnect/connect-extension-runner:28.4 +FROM cloudblueconnect/connect-extension-runner:28.5 COPY pyproject.toml /install_temp/. COPY poetry.* /install_temp/. diff --git a/connect_ext_ppr/constants.py b/connect_ext_ppr/constants.py new file mode 100644 index 0000000..98e0d24 --- /dev/null +++ b/connect_ext_ppr/constants.py @@ -0,0 +1,360 @@ +# Common schemas +# Categories +CATEGORIES_SCHEMA = { + "type": "array", + "allOf": [ + {"contains": {"const": "Name_EN"}}, + {"contains": {"const": "Description_EN"}}, + ], + "items": { + "anyOf": [ + { + "pattern": "^(Description|Name)_[a-zA-Z]+$", + }, + { + "enum": [ + "Name_EN", + "Description_EN", + "ExpandCategory", + "DisplayInCCP", + "ParentCategory", + ], + }, + ], + }, + "uniqueItems": True, +} +# Policies +POLICIES_SCHEMA = { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ServicePlan", + "Period", + "PeriodType", + "Trial", + "Action", + "ActionPeriod", + "ActionPeriodTimezone", + "ApplicableTo", + ], + }, + "allOf": [ + {"contains": {"const": "ServicePlan"}}, + {"contains": {"const": "Period"}}, + {"contains": {"const": "PeriodType"}}, + {"contains": {"const": "Trial"}}, + {"contains": {"const": "Action"}}, + {"contains": {"const": "ActionPeriod"}}, + {"contains": {"const": "ActionPeriodTimezone"}}, + {"contains": {"const": "ApplicableTo"}}, + ], + "additionalItems": False, +} + +# Per Sheet Schema +RESOURCES_SCHEMA = { + "Resources": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "Name_EN", + "Description_EN", + "ResourceCategory", + "MPN", + "UOM", + "Measurable", + ], + }, + "allOf": [ + {"contains": {"const": "Name_EN"}}, + {"contains": {"const": "Description_EN"}}, + {"contains": {"const": "ResourceCategory"}}, + {"contains": {"const": "MPN"}}, + {"contains": {"const": "UOM"}}, + {"contains": {"const": "Measurable"}}, + ], + "additionalItems": False, + }, +} +SERVICE_PLANS_SCHEMA = { + "ServicePlans": { + "type": "array", + "allOf": [ + {"contains": {"const": "Name_EN"}}, + {"contains": {"const": "Description_EN"}}, + {"contains": {"const": "PlanCategory"}}, + {"contains": {"const": "ServiceTerms"}}, + {"contains": {"const": "BillingPeriodDuration"}}, + {"contains": {"const": "BillingPeriodType"}}, + {"contains": {"const": "PricePeriod"}}, + {"contains": {"const": "RecurringType"}}, + ], + "items": { + "anyOf": [ + { + "pattern": "^((Description|Name|OpUnit)_(?:[a-zA-Z]+(?:\\s+[a-zA-Z]+)*)" + "|(ResellerGroupName|UpgradePath|SalesCategory)_\\d+)$", + }, + { + "enum": [ + "OldName_1", + "Name_EN", + "Description_EN", + "PlanCategory", + "ServiceTerms", + "BillingPeriodDuration", + "BillingPeriodType", + "AlignBillingOrderWithStatementDay", + "NewDayOfStatement", + "AlignSalesOrderWithStatementDay", + "AllowScheduledChanges", + "BillingPeriodAlignment", + "CotermingPossibilities", + "ExpirationDateAlignedWithEndOfMonth", + "ExpirationDateAlignedWithSubscription", + "FirstBillingPeriodForFree", + "PricePeriod", + "RecurringType", + "AutoRenew", + "RenewOrderInterval", + "AutoRenewPlan", + "AutoRenewPeriod", + "AutoRenewPeriodType", + "BillingAlignmentResellerRedefineAllowed", + "WelcomeNotificationTemplate", + "ExpirationNotificationTemplate", + "ProcessByRatingEngine", + "SubscriptionStartDateAfterUpgrade", + "Published", + "VendorTimezone", + "MPN", + ], + }, + ], + }, + "uniqueItems": True, + }, +} +PLAN_SWITCH_PATHS_SCHEMA = { + "PlanSwitchPaths": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "FromPlan", + "ToPlan", + "ImmediateSwitchAllowed", + "SubsStartDateAfterSwitch", + "SubsPeriodChange", + "UpsizeAllowed", + "DownsizeAllowed", + "PartialSwitchAllowed", + "RemoveSwitchPath", + ], + }, + "allOf": [ + {"contains": {"const": "FromPlan"}}, + {"contains": {"const": "ToPlan"}}, + {"contains": {"const": "ImmediateSwitchAllowed"}}, + {"contains": {"const": "SubsPeriodChange"}}, + {"contains": {"const": "UpsizeAllowed"}}, + {"contains": {"const": "DownsizeAllowed"}}, + ], + "additionalItems": False, + }, +} +PLAN_PERIODS_SCHEMA = { + "PlanPeriods": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ServicePlan", + "Period", + "PeriodType", + "Trial", + "FullRefundPeriod", + "AfterRefundPeriod", + "CancellationType", + "CancellationFeeValue", + "MPN", + ], + }, + "allOf": [ + {"contains": {"const": "ServicePlan"}}, + {"contains": {"const": "Period"}}, + {"contains": {"const": "PeriodType"}}, + ], + "additionalItems": False, + }, +} +RESOURCES_RATES_SCHEMA = { + "ResourceRates": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ServicePlan", + "Resource", + "IncUnits", + "MinUnits", + "MaxUnits", + "Measurable", + "ShowInCP", + "ShowInStore", + "ShowZeroRecurringFeeInOrder", + "ShowZeroSetupFeeInOrder", + "ShowZeroOveruseFeeInOrder", + "SetupFeePerUnit", + "RecurringFeePerUnit", + "MPN", + ], + }, + "allOf": [ + {"contains": {"const": "ServicePlan"}}, + {"contains": {"const": "Resource"}}, + {"contains": {"const": "MinUnits"}}, + {"contains": {"const": "MaxUnits"}}, + ], + "additionalItems": False, + }, +} +SALES_CATEGORIES_SCHEMA = { + "SalesCategories": {**CATEGORIES_SCHEMA}, +} +RESOURCE_CATEGORIES_SCHEMA = { + "ResourceCategories": {**CATEGORIES_SCHEMA}, +} +DOWNSIZE_POLICIES_SCHEMA = { + "DownsizePolicies": {**POLICIES_SCHEMA}, +} +CANCELATION_POLICIES_SCHEMA = { + "CancelationPolicies": {**POLICIES_SCHEMA}, +} +NOTIFICATION_TEMPLATES_SCHEMA = { + "NotificationTemplates": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "TemplateName", + "Language", + "MessageType", + "MessageCategory", + "Subject", + "ToAddr", + "ToName", + "BccAddr", + "FromAddr", + "FromName", + "PDFFileName", + "Active", + "Condition", + "VisibleToReseller", + "Security", + "HTML", + "PlainText", + ], + }, + "allOf": [ + {"contains": {"const": "TemplateName"}}, + {"contains": {"const": "Language"}}, + {"contains": {"const": "MessageCategory"}}, + {"contains": {"const": "Subject"}}, + {"contains": {"const": "ToAddr"}}, + {"contains": {"const": "ToName"}}, + {"contains": {"const": "FromAddr"}}, + {"contains": {"const": "FromName"}}, + {"contains": {"const": "HTML"}}, + {"contains": {"const": "PlainText"}}, + ], + "additionalItems": False, + }, +} +RESOURCE_DEPENDENCIES_SCHEMA = { + "ResourceDependencies": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ChildResource", + "ParentResource", + "DependenceKind", + "DependenceMultiplier", + "MPN", + ], + }, + "allOf": [ + {"contains": {"const": "ChildResource"}}, + {"contains": {"const": "ParentResource"}}, + {"contains": {"const": "DependenceKind"}}, + ], + "additionalItems": False, + }, +} +UPGRADE_RESOURCE_MAPPING_SCHEMA = { + "UpgradeResourceMapping": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(FromResource|ToResource_\\d+)$", + }, + "uniqueItems": True, + }, +} +TERMS_CONDITIONS_SCHEMA = { + "TermsConditions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "Name", + "Acceptance", + "Active", + "Content", + ], + }, + "allOf": [ + {"contains": {"const": "Name"}}, + {"contains": {"const": "Content"}}, + ], + "additionalItems": False, + }, +} +OP_UNIT_SERVICE_PLANS_SHCEMA = { + "OpUnitServicePlans": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(OpUnit|ServicePlanName|TermsConditions_\\d+" + "|Published|ResellerGroupName_\\d+|MPN)$", + }, + "uniqueItems": True, + }, +} +REQUIRED_SHEETS = { + "required": ["Resources", "ServicePlans", "PlanPeriods", "ResourceRates"], +} +PPR_SCHEMA = { + "type": "object", + "properties": { + **RESOURCES_SCHEMA, + **SERVICE_PLANS_SCHEMA, + **PLAN_SWITCH_PATHS_SCHEMA, + **PLAN_PERIODS_SCHEMA, + **RESOURCES_RATES_SCHEMA, + **SALES_CATEGORIES_SCHEMA, + **RESOURCE_CATEGORIES_SCHEMA, + **DOWNSIZE_POLICIES_SCHEMA, + **CANCELATION_POLICIES_SCHEMA, + **NOTIFICATION_TEMPLATES_SCHEMA, + **RESOURCE_DEPENDENCIES_SCHEMA, + **UPGRADE_RESOURCE_MAPPING_SCHEMA, + **TERMS_CONDITIONS_SCHEMA, + **OP_UNIT_SERVICE_PLANS_SHCEMA, + }, + **REQUIRED_SHEETS, +} diff --git a/connect_ext_ppr/service.py b/connect_ext_ppr/service.py index c4dc182..790af90 100644 --- a/connect_ext_ppr/service.py +++ b/connect_ext_ppr/service.py @@ -1,5 +1,12 @@ +from typing import Any, Dict + +import jsonschema +from connect.client import ClientError + from connect_ext_ppr.db import get_db_ctx_manager from connect_ext_ppr.models.deployment import Deployment +from connect_ext_ppr.utils import _parse_json_schema_error +from connect_ext_ppr.constants import PPR_SCHEMA def add_deployments(installation, listings, config, logger): @@ -40,3 +47,16 @@ def add_deployments(installation, listings, config, logger): db.expire_all() dep_ids = ', '.join([d.id for d in deployments]) logger.info(f"The following Deployments have been created: {dep_ids}.") + + +def validate_ppr_schema(dict_file: Dict[str, Any]): + try: + jsonschema.validate(dict_file, PPR_SCHEMA) + except jsonschema.ValidationError as ex: + error = ClientError( + message=ex.message, + status_code=400, + error_code='VAL_000', + errors=_parse_json_schema_error(ex), + ) + raise error diff --git a/connect_ext_ppr/utils.py b/connect_ext_ppr/utils.py index 00a3d07..3f1c15e 100644 --- a/connect_ext_ppr/utils.py +++ b/connect_ext_ppr/utils.py @@ -3,6 +3,8 @@ from connect.client import ClientError, ConnectClient from connect.client.rql import R from connect.eaas.core.logging import RequestLogger +from jsonschema.exceptions import _Error +import pandas as pd from connect_ext_ppr.schemas import DeploymentSchema @@ -105,3 +107,24 @@ def get_client_object(client, collection_name, obj_id): return getattr(client, collection_name)[obj_id].get() except ClientError as exc: raise _process_exc(exc) + + +def workbook_to_dict(wb: pd.ExcelFile, row_data=False): + dict_wb = {} + for sheet in wb.sheet_names: + df = wb.parse(sheet_name=sheet) + dict_wb[sheet] = process_worksheet(df, row_data) + return dict_wb + + +def process_worksheet(df: pd.DataFrame, row_data=False): + return df.to_dict(orient='list') if row_data else df.columns.to_list() + + +def _parse_json_schema_error(ex: _Error): + error_list = [ex.message] + if ex.context: + for sub_ex in ex.context: + sub_list = _parse_json_schema_error(sub_ex) + error_list.extend(sub_list) + return error_list diff --git a/poetry.lock b/poetry.lock index 831c512..0d2b1e3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -352,6 +352,17 @@ files = [ {file = "eradicate-2.3.0.tar.gz", hash = "sha256:06df115be3b87d0fc1c483db22a2ebb12bcf40585722810d809cc770f5031c37"}, ] +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] + [[package]] name = "exceptiongroup" version = "1.1.2" @@ -806,6 +817,77 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "numpy" +version = "1.24.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, + {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, + {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, + {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, + {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, + {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, + {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, + {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, + {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, + {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, + {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, +] + +[[package]] +name = "numpy" +version = "1.25.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77d339465dff3eb33c701430bcb9c325b60354698340229e1dff97745e6b3efa"}, + {file = "numpy-1.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d736b75c3f2cb96843a5c7f8d8ccc414768d34b0a75f466c05f3a739b406f10b"}, + {file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a90725800caeaa160732d6b31f3f843ebd45d6b5f3eec9e8cc287e30f2805bf"}, + {file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c6c9261d21e617c6dc5eacba35cb68ec36bb72adcff0dee63f8fbc899362588"}, + {file = "numpy-1.25.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0def91f8af6ec4bb94c370e38c575855bf1d0be8a8fbfba42ef9c073faf2cf19"}, + {file = "numpy-1.25.1-cp310-cp310-win32.whl", hash = "sha256:fd67b306320dcadea700a8f79b9e671e607f8696e98ec255915c0c6d6b818503"}, + {file = "numpy-1.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1516db588987450b85595586605742879e50dcce923e8973f79529651545b57"}, + {file = "numpy-1.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b82655dd8efeea69dbf85d00fca40013d7f503212bc5259056244961268b66e"}, + {file = "numpy-1.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e8f6049c4878cb16960fbbfb22105e49d13d752d4d8371b55110941fb3b17800"}, + {file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41a56b70e8139884eccb2f733c2f7378af06c82304959e174f8e7370af112e09"}, + {file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5154b1a25ec796b1aee12ac1b22f414f94752c5f94832f14d8d6c9ac40bcca6"}, + {file = "numpy-1.25.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38eb6548bb91c421261b4805dc44def9ca1a6eef6444ce35ad1669c0f1a3fc5d"}, + {file = "numpy-1.25.1-cp311-cp311-win32.whl", hash = "sha256:791f409064d0a69dd20579345d852c59822c6aa087f23b07b1b4e28ff5880fcb"}, + {file = "numpy-1.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:c40571fe966393b212689aa17e32ed905924120737194b5d5c1b20b9ed0fb171"}, + {file = "numpy-1.25.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3d7abcdd85aea3e6cdddb59af2350c7ab1ed764397f8eec97a038ad244d2d105"}, + {file = "numpy-1.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a180429394f81c7933634ae49b37b472d343cccb5bb0c4a575ac8bbc433722f"}, + {file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d412c1697c3853c6fc3cb9751b4915859c7afe6a277c2bf00acf287d56c4e625"}, + {file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e1266411120a4f16fad8efa8e0454d21d00b8c7cee5b5ccad7565d95eb42dd"}, + {file = "numpy-1.25.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f76aebc3358ade9eacf9bc2bb8ae589863a4f911611694103af05346637df1b7"}, + {file = "numpy-1.25.1-cp39-cp39-win32.whl", hash = "sha256:247d3ffdd7775bdf191f848be8d49100495114c82c2bd134e8d5d075fb386a1c"}, + {file = "numpy-1.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:1d5d3c68e443c90b38fdf8ef40e60e2538a27548b39b12b73132456847f4b631"}, + {file = "numpy-1.25.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:35a9527c977b924042170a0887de727cd84ff179e478481404c5dc66b4170009"}, + {file = "numpy-1.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d3fe3dd0506a28493d82dc3cf254be8cd0d26f4008a417385cbf1ae95b54004"}, + {file = "numpy-1.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:012097b5b0d00a11070e8f2e261128c44157a8689f7dedcf35576e525893f4fe"}, + {file = "numpy-1.25.1.tar.gz", hash = "sha256:9a3a9f3a61480cc086117b426a8bd86869c213fc4072e606f01c4e4b66eb92bf"}, +] + [[package]] name = "oauthlib" version = "3.2.2" @@ -822,6 +904,20 @@ rsa = ["cryptography (>=3.0.0)"] signals = ["blinker (>=1.4.0)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] +[[package]] +name = "openpyxl" +version = "3.1.2" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +optional = false +python-versions = ">=3.6" +files = [ + {file = "openpyxl-3.1.2-py2.py3-none-any.whl", hash = "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5"}, + {file = "openpyxl-3.1.2.tar.gz", hash = "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184"}, +] + +[package.dependencies] +et-xmlfile = "*" + [[package]] name = "packaging" version = "23.1" @@ -833,6 +929,73 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "pandas" +version = "2.0.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, + {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, + {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, + {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, + {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, + {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, + {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, + {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, + {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, + {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, + {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, + {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, + {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, + {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, + {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, + {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, + {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, + {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, + {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, + {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, + {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, + {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, + {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, + {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, + {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.20.3", markers = "python_version < \"3.10\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.1" + +[package.extras] +all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] +aws = ["s3fs (>=2021.08.0)"] +clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] +compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] +computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2021.07.0)"] +gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] +hdf5 = ["tables (>=3.6.1)"] +html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] +mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] +spss = ["pyreadstat (>=1.1.2)"] +sql-other = ["SQLAlchemy (>=1.4.16)"] +test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.6.3)"] + [[package]] name = "pluggy" version = "1.2.0" @@ -1099,6 +1262,31 @@ pytest = ">=5.0" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + [[package]] name = "pyyaml" version = "6.0" @@ -1271,6 +1459,8 @@ description = "Database Abstraction Library" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ + {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, + {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, @@ -1395,6 +1585,17 @@ files = [ {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] + [[package]] name = "urllib3" version = "1.26.16" @@ -1424,4 +1625,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "656f05dbc86abd69b596a754af00415df7d50c7d0053e11461718dfdf53d68e9" +content-hash = "6295f5cb2941f991b8f7fd60d2fe49edc92829f3d38f91009ff3a935d6951337" diff --git a/pyproject.toml b/pyproject.toml index 03f780a..ceb5a9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,8 @@ connect-eaas-core = ">=28.7,<29" requests-oauthlib= ">=1.3.1" sqlalchemy = "^1.3.12" psycopg2-binary = "^2.9.6" +pandas = "^2.0.3" +openpyxl = "^3.1.2" [tool.poetry.dev-dependencies] pytest = ">=6.1.2,<8" diff --git a/tests/conftest.py b/tests/conftest.py index cb646db..5bbd46a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,9 @@ # All rights reserved. # from contextlib import contextmanager +import json +import pandas as pd import pytest from connect.client import AsyncConnectClient, ConnectClient from sqlalchemy.orm import sessionmaker @@ -378,3 +380,14 @@ def api_client(test_client_factory, dbsession): get_db: lambda: dbsession, } yield client + + +@pytest.fixture +def ppr_workbook(): + return pd.ExcelFile('./tests/fixtures/test_PPR_file.xlsx') + + +@pytest.fixture +def ppr_valid_schema(): + with open('./tests/fixtures/ppr_valid_schema.json') as json_file: + return json.load(json_file) diff --git a/tests/fixtures/ppr_valid_schema.json b/tests/fixtures/ppr_valid_schema.json new file mode 100644 index 0000000..c291572 --- /dev/null +++ b/tests/fixtures/ppr_valid_schema.json @@ -0,0 +1,168 @@ +{ + "Resources": [ + "Name_EN", + "Description_EN", + "ResourceCategory", + "MPN", + "UOM", + "Measurable" + ], + "ServicePlans": [ + "OldName_1", + "Name_EN", + "Description_EN", + "PlanCategory", + "ServiceTerms", + "SalesCategory_1", + "BillingPeriodDuration", + "BillingPeriodType", + "AlignBillingOrderWithStatementDay", + "NewDayOfStatement", + "AlignSalesOrderWithStatementDay", + "AllowScheduledChanges", + "BillingPeriodAlignment", + "CotermingPossibilities", + "ExpirationDateAlignedWithEndOfMonth", + "ExpirationDateAlignedWithSubscription", + "FirstBillingPeriodForFree", + "PricePeriod", + "RecurringType", + "AutoRenew", + "RenewOrderInterval", + "AutoRenewPlan", + "AutoRenewPeriod", + "AutoRenewPeriodType", + "BillingAlignmentResellerRedefineAllowed", + "WelcomeNotificationTemplate", + "ExpirationNotificationTemplate", + "ProcessByRatingEngine", + "UpgradePath_1", + "SubscriptionStartDateAfterUpgrade", + "OpUnit_DE", + "ResellerGroupName_1", + "Published", "VendorTimezone", + "MPN" + ], + "PlanSwitchPaths": [ + "FromPlan", + "ToPlan", + "ImmediateSwitchAllowed", + "SubsStartDateAfterSwitch", + "SubsPeriodChange", + "UpsizeAllowed", + "DownsizeAllowed", + "PartialSwitchAllowed", + "RemoveSwitchPath" + ], + "PlanPeriods": [ + "ServicePlan", + "Period", + "PeriodType", + "Trial", + "FullRefundPeriod", + "AfterRefundPeriod", + "CancellationType", + "CancellationFeeValue", + "MPN" + ], + "ResourceRates": [ + "ServicePlan", + "Resource", + "IncUnits", + "MinUnits", + "MaxUnits", + "Measurable", + "ShowInCP", + "ShowInStore", + "ShowZeroRecurringFeeInOrder", + "ShowZeroSetupFeeInOrder", + "ShowZeroOveruseFeeInOrder", + "SetupFeePerUnit", + "RecurringFeePerUnit", + "MPN" + ], + "SalesCategories": [ + "Name_EN", + "Description_EN", + "ExpandCategory", + "DisplayInCCP", + "ParentCategory", + "Name_DE", + "Description_DE" + ], + "ResourceCategories": [ + "Name_EN", + "Description_EN", + "ExpandCategory", + "DisplayInCCP", + "ParentCategory", + "Name_DE", + "Description_DE" + ], + "DownsizePolicies": [ + "ServicePlan", + "Period", + "PeriodType", + "Trial", + "Action", + "ActionPeriod", + "ActionPeriodTimezone", + "ApplicableTo" + ], + "CancelationPolicies": [ + "ServicePlan", + "Period", + "PeriodType", + "Trial", + "Action", + "ActionPeriod", + "ActionPeriodTimezone", + "ApplicableTo" + ], + "NotificationTemplates": [ + "TemplateName", + "Language", + "MessageType", + "MessageCategory", + "Subject", + "ToAddr", + "ToName", + "BccAddr", + "FromAddr", + "FromName", + "PDFFileName", + "Active", + "Condition", + "VisibleToReseller", + "Security", + "HTML", + "PlainText" + ], + "ResourceDependencies": [ + "ChildResource", + "ParentResource", + "DependenceKind", + "DependenceMultiplier", + "MPN" + ], + "UpgradeResourceMapping": [ + "FromResource", + "ToResource_1" + ], + "TermsConditions": [ + "Name", + "Acceptance", + "Active", + "Content" + ], + "OpUnitServicePlans": [ + "OpUnit", + "ServicePlanName", + "TermsConditions_1", + "TermsConditions_2", + "TermsConditions_3", + "Published", + "ResellerGroupName_1", + "MPN" + ] +} \ No newline at end of file diff --git a/tests/fixtures/test_PPR_file.xlsx b/tests/fixtures/test_PPR_file.xlsx new file mode 100644 index 0000000..de0cf19 Binary files /dev/null and b/tests/fixtures/test_PPR_file.xlsx differ diff --git a/tests/test_service.py b/tests/test_service.py index 1bb6c84..41f82e6 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -1,5 +1,8 @@ +import pytest +from connect.client import ClientError + from connect_ext_ppr.models.deployment import Deployment -from connect_ext_ppr.service import add_deployments +from connect_ext_ppr.service import add_deployments, validate_ppr_schema def test_add_deployments( @@ -39,3 +42,76 @@ def test_nothing_to_create( assert count_before == 1 assert count_before == count_after + + +def test_valid_schema(ppr_valid_schema): + assert validate_ppr_schema(ppr_valid_schema) is None + + +@pytest.mark.parametrize( + 'required_sheet', + ('Resources', 'ServicePlans', 'PlanPeriods', 'ResourceRates'), +) +def test_required_sheet_not_present(ppr_valid_schema, required_sheet): + ppr_valid_schema.pop(required_sheet) + with pytest.raises(ClientError) as ex: + validate_ppr_schema(ppr_valid_schema) + assert ex.value.message == f"'{required_sheet}' is a required property" + assert ex.value.errors == [f"'{required_sheet}' is a required property"] + + +@pytest.mark.parametrize( + 'required_column', + ('Name_EN', 'Description_EN', 'ResourceCategory', 'MPN', 'UOM', 'Measurable'), +) +def test_required_column_not_present(ppr_valid_schema, required_column): + ppr_valid_schema['Resources'].remove(required_column) + with pytest.raises(ClientError) as ex: + validate_ppr_schema(ppr_valid_schema) + assert ex.value.message == ( + f"{ppr_valid_schema['Resources']}" + f" does not contain items matching the given schema" + ) + + +def test_extra_field_not_allowed(ppr_valid_schema): + ppr_valid_schema['Resources'].append('FooBar') + with pytest.raises(ClientError) as ex: + validate_ppr_schema(ppr_valid_schema) + + assert ex.value.message == ( + "'FooBar' is not one of ['Name_EN', 'Description_EN', " + "'ResourceCategory', 'MPN', 'UOM', 'Measurable']" + ) + + +@pytest.mark.parametrize( + 'not_allow', + ('OpUnit_123', 'ResellerGroupName_US'), +) +def test_extra_field_not_matching_pattern_allowed(ppr_valid_schema, not_allow): + ppr_valid_schema['ServicePlans'].append(not_allow) + with pytest.raises(ClientError) as ex: + validate_ppr_schema(ppr_valid_schema) + + assert ex.value.message == f"'{not_allow}' is not valid under any of the given schemas" + assert ex.value.errors == [ + f"'{not_allow}' is not valid under any of the given schemas", + ( + f"'{not_allow}' does not match '^((Description|Name|OpUnit)_(?:[a-zA-Z]+" + f"(?:\\\\s+[a-zA-Z]+)*)|(ResellerGroupName|UpgradePath|SalesCategory)_\\\\d+)$'" + ), + ( + f"'{not_allow}' is not one of ['OldName_1', 'Name_EN', 'Description_EN', " + f"'PlanCategory', 'ServiceTerms', 'BillingPeriodDuration', 'BillingPeriodType', " + f"'AlignBillingOrderWithStatementDay', 'NewDayOfStatement', " + f"'AlignSalesOrderWithStatementDay', 'AllowScheduledChanges', 'BillingPeriodAlignment'," + f" 'CotermingPossibilities', 'ExpirationDateAlignedWithEndOfMonth', " + f"'ExpirationDateAlignedWithSubscription', 'FirstBillingPeriodForFree', 'PricePeriod', " + f"'RecurringType', 'AutoRenew', 'RenewOrderInterval', 'AutoRenewPlan', 'AutoRenewPeriod" + f"', 'AutoRenewPeriodType', 'BillingAlignmentResellerRedefineAllowed', " + f"'WelcomeNotificationTemplate', 'ExpirationNotificationTemplate', " + f"'ProcessByRatingEngine', 'SubscriptionStartDateAfterUpgrade', " + f"'Published', 'VendorTimezone', 'MPN']" + ), + ] diff --git a/tests/test_utils.py b/tests/test_utils.py index 777353d..aeb7a06 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,14 +1,18 @@ import copy +from jsonschema import ValidationError +import pandas as pd import pytest from connect.client import ClientError from connect.client.rql import R from connect_ext_ppr.utils import ( + _parse_json_schema_error, _process_exc, filter_object_list_by_id, get_all_info, get_marketplaces, + workbook_to_dict, ) @@ -77,3 +81,64 @@ def test_filter_key_error(marketplace): with pytest.raises(KeyError) as ex: filter_object_list_by_id(mkp_list, 'MP-XXXX') assert str(ex.value).startswith("'MP-XXXX'") + + +def test_workbook_to_dict_only_column_names(ppr_workbook): + dict_wb = workbook_to_dict(ppr_workbook) + + assert isinstance(dict_wb, dict) + + for key in dict_wb: + assert isinstance(dict_wb[key], list) + + +def test_workbook_to_dict_full_data(ppr_workbook, mocker): + data = [ + 'Dynamo XXX Customer Voice XXX', + 'User license for Dynamo XXX', + 'Capsule corp Commercial', + 'XXXXXXXXXX:0000', + 'Licenses', + False, + ] + resources = ppr_workbook.parse('Resources') + for col_name, value in zip(resources.columns, data): + setattr(resources, col_name, pd.Series(value)) + + mocker.patch( + 'connect_ext_ppr.utils.process_worksheet', + return_value=resources.to_dict(orient='list'), + ) + + dict_wb = workbook_to_dict(ppr_workbook, row_data=True) + + for key in dict_wb: + assert isinstance(dict_wb[key], dict) + for col in dict_wb[key]: + assert isinstance(dict_wb[key][col], list) + + assert dict_wb['Resources'] == { + 'Name_EN': ['Dynamo XXX Customer Voice XXX'], + 'Description_EN': ['User license for Dynamo XXX'], + 'ResourceCategory': ['Capsule corp Commercial'], + 'MPN': ['XXXXXXXXXX:0000'], + 'UOM': ['Licenses'], + 'Measurable': [False], + } + + +def test_parse_validation_error(): + error = ValidationError( + message='some', + context=[ + ValidationError(message='really'), + ValidationError(message='nested', context=[ + ValidationError(message='error', context=[ + ValidationError(message='to'), + ValidationError(message='parse'), + ]), + ]), + ]) + assert _parse_json_schema_error(error) == [ + 'some', 'really', 'nested', 'error', 'to', 'parse', + ]