diff --git a/.gitignore b/.gitignore index 413c2da..0e32120 100644 --- a/.gitignore +++ b/.gitignore @@ -167,6 +167,8 @@ cython_debug/ .ruff_cache .idea .openapi/ +openapi/openapi-dev.json +.github/copilot-instructions.md # Makefile make/local.mk diff --git a/mpt_api_client/models/model.py b/mpt_api_client/models/model.py index d3736b5..0da3323 100644 --- a/mpt_api_client/models/model.py +++ b/mpt_api_client/models/model.py @@ -1,6 +1,7 @@ import re from collections import UserList from collections.abc import Iterable +from types import MappingProxyType from typing import Any, Self, get_args, get_origin, override from mpt_api_client.http.types import Response @@ -12,9 +13,47 @@ _SNAKE_CASE_BOUNDARY = re.compile(r"([a-z0-9])([A-Z])") _SNAKE_CASE_ACRONYM = re.compile(r"(?<=[A-Z])(?=[A-Z][a-z0-9])") +# Explicit bidirectional mappings for API field names that contain two or more consecutive +# uppercase letters (e.g. PPx1, unitLP). The generic regex cannot round-trip these correctly, +# so we maintain an explicit lookup table that is checked before the regex is applied. +_FIELD_NAME_MAPPINGS: MappingProxyType[str, str] = MappingProxyType({ + # PP* price columns + "PPx1": "ppx1", + "PPxM": "ppxm", + "PPxY": "ppxy", + # SP* price columns + "SPx1": "spx1", + "SPxM": "spxm", + "SPxY": "spxy", + # LP* price columns + "LPx1": "lpx1", + "LPxM": "lpxm", + "LPxY": "lpxy", + # unit + 2-letter acronym suffix + "unitLP": "unit_lp", + "unitPP": "unit_pp", + "unitSP": "unit_sp", + # total + 2-letter acronym suffix + "totalGT": "total_gt", + "totalPP": "total_pp", + "totalSP": "total_sp", + "totalST": "total_st", +}) + +_FIELD_NAME_MAPPINGS_REVERSE: MappingProxyType[str, str] = MappingProxyType({ + snake: camel for camel, snake in _FIELD_NAME_MAPPINGS.items() +}) + def to_snake_case(key: str) -> str: - """Converts a camelCase string to snake_case.""" + """Converts a camelCase string to snake_case. + + Explicit mappings in ``_FIELD_NAME_MAPPINGS`` take priority over the generic + regex for fields that contain two or more consecutive uppercase letters. + """ + mapped = _FIELD_NAME_MAPPINGS.get(key) + if mapped is not None: + return mapped if "_" in key and key.islower(): return key # Common pattern for PascalCase/camelCase conversion @@ -24,7 +63,14 @@ def to_snake_case(key: str) -> str: def to_camel_case(key: str) -> str: - """Converts a snake_case string to camelCase.""" + """Converts a snake_case string to camelCase. + + Explicit mappings in ``_FIELD_NAME_MAPPINGS_REVERSE`` take priority over the + generic logic for fields that contain two or more consecutive uppercase letters. + """ + mapped = _FIELD_NAME_MAPPINGS_REVERSE.get(key) + if mapped is not None: + return mapped parts = key.split("_") return parts[0] + "".join(x.title() for x in parts[1:]) # noqa: WPS111 WPS221 diff --git a/mpt_api_client/resources/catalog/authorizations.py b/mpt_api_client/resources/catalog/authorizations.py index e49513b..a9756bb 100644 --- a/mpt_api_client/resources/catalog/authorizations.py +++ b/mpt_api_client/resources/catalog/authorizations.py @@ -6,10 +6,37 @@ ManagedResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel class Authorization(Model): - """Authorization resource.""" + """Authorization resource. + + Attributes: + name: Authorization name. + external_ids: External identifiers for the authorization. + currency: Currency code associated with the authorization. + notes: Additional notes. + product: Reference to the product. + vendor: Reference to the vendor. + owner: Reference to the owner account. + statistics: Authorization statistics. + journal: Journal reference. + eligibility: Eligibility information. + audit: Audit information (created, updated events). + """ + + name: str | None + external_ids: BaseModel | None + currency: str | None + notes: str | None + product: BaseModel | None + vendor: BaseModel | None + owner: BaseModel | None + statistics: BaseModel | None + journal: BaseModel | None + eligibility: BaseModel | None + audit: BaseModel | None class AuthorizationsServiceConfig: diff --git a/mpt_api_client/resources/catalog/items.py b/mpt_api_client/resources/catalog/items.py index 0fb88e4..c171a24 100644 --- a/mpt_api_client/resources/catalog/items.py +++ b/mpt_api_client/resources/catalog/items.py @@ -6,6 +6,7 @@ ManagedResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.mixins import ( AsyncPublishableMixin, PublishableMixin, @@ -13,7 +14,33 @@ class Item(Model): # noqa: WPS110 - """Item resource.""" + """Item resource. + + Attributes: + name: Item name. + description: Item description. + external_ids: External identifiers for the item. + group: Reference to the item group. + unit: Reference to the unit of measure. + terms: Reference to the terms and conditions. + quantity_not_applicable: Whether quantity is not applicable to this item. + status: Item status. + product: Reference to the product. + parameters: List of parameters associated with this item. + audit: Audit information (created, updated events). + """ + + name: str | None + description: str | None + external_ids: BaseModel | None + group: BaseModel | None + unit: BaseModel | None + terms: BaseModel | None + quantity_not_applicable: bool | None + status: str | None + product: BaseModel | None + parameters: list[BaseModel] | None + audit: BaseModel | None class ItemsServiceConfig: diff --git a/mpt_api_client/resources/catalog/listings.py b/mpt_api_client/resources/catalog/listings.py index 1780a47..ea87a2a 100644 --- a/mpt_api_client/resources/catalog/listings.py +++ b/mpt_api_client/resources/catalog/listings.py @@ -6,10 +6,35 @@ ManagedResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel class Listing(Model): - """Listing resource.""" + """Listing resource. + + Attributes: + authorization: Reference to the authorization. + product: Reference to the product. + vendor: Reference to the vendor. + seller: Reference to the seller. + price_list: Reference to the associated price list. + primary: Whether this is the primary listing. + notes: Additional notes. + statistics: Listing statistics. + eligibility: Eligibility information. + audit: Audit information (created, updated events). + """ + + authorization: BaseModel | None + product: BaseModel | None + vendor: BaseModel | None + seller: BaseModel | None + price_list: BaseModel | None + primary: bool | None + notes: str | None + statistics: BaseModel | None + eligibility: BaseModel | None + audit: BaseModel | None class ListingsServiceConfig: diff --git a/mpt_api_client/resources/catalog/price_list_items.py b/mpt_api_client/resources/catalog/price_list_items.py index ece1656..11779ce 100644 --- a/mpt_api_client/resources/catalog/price_list_items.py +++ b/mpt_api_client/resources/catalog/price_list_items.py @@ -8,10 +8,55 @@ UpdateMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel class PriceListItem(Model): - """Price List Item resource.""" + """Price List Item resource. + + Attributes: + status: Price list item status. + description: Price list item description. + reason_for_change: Reason for the price change. + unit_lp: Unit list price. + unit_pp: Unit purchase price. + markup: Markup percentage. + margin: Margin percentage. + unit_sp: Unit sell price. + ppx1: Purchase price for 1-year term. + ppxm: Purchase price for monthly term. + ppxy: Purchase price for yearly term. + spx1: Sell price for 1-year term. + spxm: Sell price for monthly term. + spxy: Sell price for yearly term. + lpx1: List price for 1-year term. + lpxm: List price for monthly term. + lpxy: List price for yearly term. + price_list: Reference to the parent price list. + item: Reference to the associated item. + audit: Audit information (created, updated events). + """ + + status: str | None + description: str | None + reason_for_change: str | None + unit_lp: float | None + unit_pp: float | None + markup: float | None + margin: float | None + unit_sp: float | None + ppx1: float | None + ppxm: float | None + ppxy: float | None + spx1: float | None + spxm: float | None + spxy: float | None + lpx1: float | None + lpxm: float | None + lpxy: float | None + price_list: BaseModel | None + item: BaseModel | None + audit: BaseModel | None class PriceListItemsServiceConfig: diff --git a/mpt_api_client/resources/catalog/price_lists.py b/mpt_api_client/resources/catalog/price_lists.py index 9c5eaad..6a216cd 100644 --- a/mpt_api_client/resources/catalog/price_lists.py +++ b/mpt_api_client/resources/catalog/price_lists.py @@ -6,6 +6,7 @@ ManagedResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.price_list_items import ( AsyncPriceListItemsService, PriceListItemsService, @@ -13,7 +14,31 @@ class PriceList(Model): - """Price List resource.""" + """Price List resource. + + Attributes: + currency: Currency code for this price list. + precision: Decimal precision for prices. + default_markup: Default markup percentage. + default_margin: Default margin percentage. + notes: Additional notes. + external_ids: External identifiers for the price list. + statistics: Price list statistics. + product: Reference to the associated product. + vendor: Reference to the vendor. + audit: Audit information (created, updated events). + """ + + currency: str | None + precision: int | None + default_markup: float | None + default_margin: float | None + notes: str | None + external_ids: BaseModel | None + statistics: BaseModel | None + product: BaseModel | None + vendor: BaseModel | None + audit: BaseModel | None class PriceListsServiceConfig: diff --git a/mpt_api_client/resources/catalog/pricing_policies.py b/mpt_api_client/resources/catalog/pricing_policies.py index 23b6667..80c16bf 100644 --- a/mpt_api_client/resources/catalog/pricing_policies.py +++ b/mpt_api_client/resources/catalog/pricing_policies.py @@ -9,6 +9,7 @@ ManagedResourceMixin, ) from mpt_api_client.models import Model, ResourceData +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.pricing_policy_attachments import ( AsyncPricingPolicyAttachmentsService, PricingPolicyAttachmentsService, @@ -16,7 +17,33 @@ class PricingPolicy(Model): - """Pricing policy resource.""" + """Pricing policy resource. + + Attributes: + name: Pricing policy name. + external_ids: External identifiers for the pricing policy. + client: Reference to the client account. + eligibility: Eligibility information. + markup: Markup percentage. + margin: Margin percentage. + notes: Additional notes. + products: List of associated products. + status: Pricing policy status. + statistics: Pricing policy statistics. + audit: Audit information (created, updated events). + """ + + name: str | None + external_ids: BaseModel | None + client: BaseModel | None + eligibility: BaseModel | None + markup: float | None + margin: float | None + notes: str | None + products: list[BaseModel] | None + status: str | None + statistics: BaseModel | None + audit: BaseModel | None class PricingPoliciesServiceConfig: diff --git a/mpt_api_client/resources/catalog/pricing_policy_attachments.py b/mpt_api_client/resources/catalog/pricing_policy_attachments.py index 6362ee1..c414b3c 100644 --- a/mpt_api_client/resources/catalog/pricing_policy_attachments.py +++ b/mpt_api_client/resources/catalog/pricing_policy_attachments.py @@ -10,10 +10,31 @@ ModifiableResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel class PricingPolicyAttachment(Model): - """Pricing Policy Attachment resource.""" + """Pricing Policy Attachment resource. + + Attributes: + name: Attachment name. + type: Attachment type. + size: File size in bytes. + description: Attachment description. + file_name: Original file name. + content_type: MIME content type of the attachment. + status: Attachment status. + audit: Audit information (created, updated events). + """ + + name: str | None + type: str | None + size: int | None + description: str | None + file_name: str | None + content_type: str | None + status: str | None + audit: BaseModel | None class PricingPolicyAttachmentsServiceConfig: diff --git a/mpt_api_client/resources/catalog/product_term_variants.py b/mpt_api_client/resources/catalog/product_term_variants.py index bee9c30..5c5328c 100644 --- a/mpt_api_client/resources/catalog/product_term_variants.py +++ b/mpt_api_client/resources/catalog/product_term_variants.py @@ -10,6 +10,7 @@ ModifiableResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.mixins import ( AsyncPublishableMixin, PublishableMixin, @@ -17,7 +18,35 @@ class TermVariant(Model): - """Term variant resource.""" + """Term variant resource. + + Attributes: + type: Variant type. + asset_url: URL to the term variant asset. + language_code: Language code for this variant. + name: Variant name. + description: Variant description. + status: Variant status. + filename: Original file name. + size: File size in bytes. + content_type: MIME content type of the file. + terms_and_conditions: Reference to the parent terms and conditions. + file_id: Identifier of the uploaded file. + audit: Audit information (created, updated events). + """ + + type: str | None + asset_url: str | None + language_code: str | None + name: str | None + description: str | None + status: str | None + filename: str | None + size: int | None + content_type: str | None + terms_and_conditions: BaseModel | None + file_id: str | None + audit: BaseModel | None class TermVariantServiceConfig: diff --git a/mpt_api_client/resources/catalog/product_terms.py b/mpt_api_client/resources/catalog/product_terms.py index 9afd7b5..611cd5a 100644 --- a/mpt_api_client/resources/catalog/product_terms.py +++ b/mpt_api_client/resources/catalog/product_terms.py @@ -6,6 +6,7 @@ ManagedResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.mixins import AsyncPublishableMixin, PublishableMixin from mpt_api_client.resources.catalog.product_term_variants import ( AsyncTermVariantService, @@ -14,7 +15,23 @@ class Term(Model): - """Term resource.""" + """Term resource. + + Attributes: + name: Term name. + description: Term description. + display_order: Display order of the term. + status: Term status. + product: Reference to the product. + audit: Audit information (created, updated events). + """ + + name: str | None + description: str | None + display_order: int | None + status: str | None + product: BaseModel | None + audit: BaseModel | None class TermServiceConfig: diff --git a/mpt_api_client/resources/catalog/products.py b/mpt_api_client/resources/catalog/products.py index cfe04dd..b6c6f57 100644 --- a/mpt_api_client/resources/catalog/products.py +++ b/mpt_api_client/resources/catalog/products.py @@ -12,6 +12,7 @@ UpdateFileMixin, ) from mpt_api_client.models import Model, ResourceData +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.mixins import ( AsyncPublishableMixin, PublishableMixin, @@ -51,7 +52,33 @@ class Product(Model): - """Product resource.""" + """Product resource. + + Attributes: + name: Product name. + short_description: Short description of the product. + long_description: Long description of the product. + external_ids: External identifiers for the product. + website: Product website URL. + icon: URL or identifier for the product icon. + status: Product status. + vendor: Reference to the vendor account. + settings: Product settings. + statistics: Product statistics. + audit: Audit information (created, updated events). + """ + + name: str | None + short_description: str | None + long_description: str | None + external_ids: BaseModel | None + website: str | None + icon: str | None + status: str | None + vendor: BaseModel | None + settings: BaseModel | None + statistics: BaseModel | None + audit: BaseModel | None class ProductsServiceConfig: diff --git a/mpt_api_client/resources/catalog/products_documents.py b/mpt_api_client/resources/catalog/products_documents.py index c0f59ba..b3ad483 100644 --- a/mpt_api_client/resources/catalog/products_documents.py +++ b/mpt_api_client/resources/catalog/products_documents.py @@ -6,6 +6,7 @@ ModifiableResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.mixins import ( AsyncDocumentMixin, DocumentMixin, @@ -13,7 +14,33 @@ class Document(Model): - """Document resource.""" + """Document resource. + + Attributes: + name: Document name. + type: Document type. + description: Document description. + status: Document status. + filename: Original file name. + size: File size in bytes. + content_type: MIME content type of the document. + url: URL to access the document. + language: Language code of the document. + product: Reference to the product. + audit: Audit information (created, updated events). + """ + + name: str | None + type: str | None + description: str | None + status: str | None + filename: str | None + size: int | None + content_type: str | None + url: str | None + language: str | None + product: BaseModel | None + audit: BaseModel | None class DocumentServiceConfig: diff --git a/mpt_api_client/resources/catalog/products_item_groups.py b/mpt_api_client/resources/catalog/products_item_groups.py index 29cfdaa..b4f76ad 100644 --- a/mpt_api_client/resources/catalog/products_item_groups.py +++ b/mpt_api_client/resources/catalog/products_item_groups.py @@ -6,10 +6,35 @@ ManagedResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel class ItemGroup(Model): - """Item Group resource.""" + """Item Group resource. + + Attributes: + name: Item group name. + label: Display label for the item group. + description: Item group description. + display_order: Display order of the group. + default: Whether this is the default item group. + multiple: Whether multiple items can be selected from this group. + required: Whether a selection from this group is required. + item_count: Number of items in this group. + product: Reference to the product. + audit: Audit information (created, updated events). + """ + + name: str | None + label: str | None + description: str | None + display_order: int | None + default: bool | None + multiple: bool | None + required: bool | None + item_count: int | None + product: BaseModel | None + audit: BaseModel | None class ItemGroupsServiceConfig: diff --git a/mpt_api_client/resources/catalog/products_media.py b/mpt_api_client/resources/catalog/products_media.py index 767add5..c4d5018 100644 --- a/mpt_api_client/resources/catalog/products_media.py +++ b/mpt_api_client/resources/catalog/products_media.py @@ -6,6 +6,7 @@ ModifiableResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.mixins import ( AsyncMediaMixin, MediaMixin, @@ -13,7 +14,33 @@ class Media(Model): - """Media resource.""" + """Media resource. + + Attributes: + name: Media name. + type: Media type. + description: Media description. + status: Media status. + filename: Original file name. + size: File size in bytes. + content_type: MIME content type of the media file. + display_order: Display order of the media item. + url: URL to access the media file. + product: Reference to the product. + audit: Audit information (created, updated events). + """ + + name: str | None + type: str | None + description: str | None + status: str | None + filename: str | None + size: int | None + content_type: str | None + display_order: int | None + url: str | None + product: BaseModel | None + audit: BaseModel | None class MediaServiceConfig: diff --git a/mpt_api_client/resources/catalog/products_templates.py b/mpt_api_client/resources/catalog/products_templates.py index 963bc1b..a052f6b 100644 --- a/mpt_api_client/resources/catalog/products_templates.py +++ b/mpt_api_client/resources/catalog/products_templates.py @@ -6,10 +6,27 @@ ManagedResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel class Template(Model): - """Template resource.""" + """Template resource. + + Attributes: + name: Template name. + content: Template content. + type: Template type. + default: Whether this is the default template. + product: Reference to the product. + audit: Audit information (created, updated events). + """ + + name: str | None + content: str | None + type: str | None + default: bool | None + product: BaseModel | None + audit: BaseModel | None class TemplatesServiceConfig: diff --git a/mpt_api_client/resources/catalog/units_of_measure.py b/mpt_api_client/resources/catalog/units_of_measure.py index 151e5de..9e4066d 100644 --- a/mpt_api_client/resources/catalog/units_of_measure.py +++ b/mpt_api_client/resources/catalog/units_of_measure.py @@ -6,10 +6,23 @@ ManagedResourceMixin, ) from mpt_api_client.models import Model +from mpt_api_client.models.model import BaseModel class UnitOfMeasure(Model): - """Unit of Measure resource.""" + """Unit of Measure resource. + + Attributes: + description: Unit of measure description. + name: Unit of measure name. + statistics: Unit of measure statistics. + audit: Audit information (created, updated events). + """ + + description: str | None + name: str | None + statistics: BaseModel | None + audit: BaseModel | None class UnitsOfMeasureServiceConfig: diff --git a/tests/unit/models/test_model.py b/tests/unit/models/test_model.py index 9082303..8285efb 100644 --- a/tests/unit/models/test_model.py +++ b/tests/unit/models/test_model.py @@ -2,7 +2,12 @@ from httpx import Response from mpt_api_client.models import Meta, Model -from mpt_api_client.models.model import BaseModel, ModelList, to_snake_case # noqa: WPS347 +from mpt_api_client.models.model import ( # noqa: WPS347 + BaseModel, + ModelList, + to_camel_case, + to_snake_case, +) class AgreementDummy(Model): # noqa: WPS431 @@ -326,3 +331,33 @@ def test_process_value_scalar_list_elements(): assert isinstance(container.tags, ModelList) assert list(container.tags) == ["a", "b", "c"] + + +@pytest.mark.parametrize( + ("camel", "snake"), + [ + ("PPx1", "ppx1"), + ("SPxM", "spxm"), + ("unitLP", "unit_lp"), + ("totalGT", "total_gt"), + ], +) +def test_to_snake_case_consecutive_uppercase(camel, snake): + result = to_snake_case(camel) # act + + assert result == snake + + +@pytest.mark.parametrize( + ("snake", "camel"), + [ + ("ppx1", "PPx1"), + ("spxm", "SPxM"), + ("unit_lp", "unitLP"), + ("total_gt", "totalGT"), + ], +) +def test_to_camel_case_consecutive_uppercase(snake, camel): + result = to_camel_case(snake) # act + + assert result == camel diff --git a/tests/unit/resources/catalog/test_authorizations.py b/tests/unit/resources/catalog/test_authorizations.py index a7ab0e2..351ef90 100644 --- a/tests/unit/resources/catalog/test_authorizations.py +++ b/tests/unit/resources/catalog/test_authorizations.py @@ -1,7 +1,9 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.authorizations import ( AsyncAuthorizationsService, + Authorization, AuthorizationsService, ) @@ -16,6 +18,24 @@ def async_authorizations_service(async_http_client): return AsyncAuthorizationsService(http_client=async_http_client) +@pytest.fixture +def authorization_data(): + return { + "id": "AUT-001", + "name": "My Authorization", + "currency": "USD", + "notes": "Some notes", + "externalIds": {"vendor": "ext-001"}, + "product": {"id": "PRD-001", "name": "My Product"}, + "vendor": {"id": "ACC-001", "name": "Vendor"}, + "owner": {"id": "ACC-002", "name": "Owner"}, + "statistics": {"items": 5}, + "journal": {"id": "JRN-001"}, + "eligibility": {"status": "Eligible"}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + @pytest.mark.parametrize("method", ["get", "create", "update", "delete", "iterate"]) def test_mixins_present(authorizations_service, method): result = hasattr(authorizations_service, method) @@ -28,3 +48,28 @@ def test_async_mixins_present(async_authorizations_service, method): result = hasattr(async_authorizations_service, method) assert result is True + + +def test_authorization_primitive_fields(authorization_data): + result = Authorization(authorization_data) + + assert result.to_dict() == authorization_data + + +def test_authorization_nested_base_models(authorization_data): + result = Authorization(authorization_data) + + assert isinstance(result.external_ids, BaseModel) + assert isinstance(result.product, BaseModel) + assert isinstance(result.vendor, BaseModel) + assert isinstance(result.statistics, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_authorization_optional_fields_absent(): + result = Authorization({"id": "AUT-001"}) + + assert result.id == "AUT-001" + assert not hasattr(result, "name") + assert not hasattr(result, "currency") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_items.py b/tests/unit/resources/catalog/test_items.py index 52451c7..bf52688 100644 --- a/tests/unit/resources/catalog/test_items.py +++ b/tests/unit/resources/catalog/test_items.py @@ -1,6 +1,7 @@ import pytest -from mpt_api_client.resources.catalog.items import AsyncItemsService, ItemsService +from mpt_api_client.models.model import BaseModel +from mpt_api_client.resources.catalog.items import AsyncItemsService, Item, ItemsService @pytest.fixture @@ -13,6 +14,24 @@ def async_items_service(async_http_client): return AsyncItemsService(http_client=async_http_client) +@pytest.fixture +def item_data(): + return { + "id": "ITM-001", + "name": "My Item", + "description": "Item description", + "externalIds": {"vendor": "ext-001"}, + "group": {"id": "GRP-001", "name": "Group"}, + "unit": {"id": "UOM-001", "name": "Each"}, + "terms": {"id": "TRM-001", "name": "Terms"}, + "quantityNotApplicable": False, + "status": "Active", + "product": {"id": "PRD-001", "name": "My Product"}, + "parameters": [{"id": "PRM-001", "name": "Param"}], + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + @pytest.mark.parametrize( "method", [ @@ -49,3 +68,28 @@ def test_async_mixins_present(async_items_service, method): result = hasattr(async_items_service, method) assert result is True + + +def test_item_primitive_fields(item_data): + result = Item(item_data) + + assert result.to_dict() == item_data + + +def test_item_nested_base_models(item_data): + result = Item(item_data) + + assert isinstance(result.external_ids, BaseModel) + assert isinstance(result.group, BaseModel) + assert isinstance(result.unit, BaseModel) + assert isinstance(result.product, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_item_optional_fields_absent(): + result = Item({"id": "ITM-001"}) + + assert result.id == "ITM-001" + assert not hasattr(result, "name") + assert not hasattr(result, "status") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_listings.py b/tests/unit/resources/catalog/test_listings.py index b048dd9..7d7e04a 100644 --- a/tests/unit/resources/catalog/test_listings.py +++ b/tests/unit/resources/catalog/test_listings.py @@ -1,7 +1,9 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.listings import ( AsyncListingsService, + Listing, ListingsService, ) @@ -16,6 +18,23 @@ def async_listings_service(async_http_client): return AsyncListingsService(http_client=async_http_client) +@pytest.fixture +def listing_data(): + return { + "id": "LST-001", + "authorization": {"id": "AUT-001", "name": "My Auth"}, + "product": {"id": "PRD-001", "name": "My Product"}, + "vendor": {"id": "ACC-001", "name": "Vendor"}, + "seller": {"id": "ACC-002", "name": "Seller"}, + "priceList": {"id": "PRC-001", "currency": "USD"}, + "primary": True, + "notes": "Some notes", + "statistics": {"items": 3}, + "eligibility": {"status": "Eligible"}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + @pytest.mark.parametrize("method", ["get", "create", "update", "delete", "iterate"]) def test_mixins_present(listings_service, method): result = hasattr(listings_service, method) @@ -28,3 +47,28 @@ def test_async_mixins_present(async_listings_service, method): result = hasattr(async_listings_service, method) assert result is True + + +def test_listing_primitive_fields(listing_data): + result = Listing(listing_data) + + assert result.to_dict() == listing_data + + +def test_listing_nested_base_models(listing_data): + result = Listing(listing_data) + + assert isinstance(result.authorization, BaseModel) + assert isinstance(result.product, BaseModel) + assert isinstance(result.price_list, BaseModel) + assert isinstance(result.statistics, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_listing_optional_fields_absent(): + result = Listing({"id": "LST-001"}) + + assert result.id == "LST-001" + assert not hasattr(result, "notes") + assert not hasattr(result, "primary") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_price_list_items.py b/tests/unit/resources/catalog/test_price_list_items.py index 4002a94..80032ac 100644 --- a/tests/unit/resources/catalog/test_price_list_items.py +++ b/tests/unit/resources/catalog/test_price_list_items.py @@ -1,7 +1,9 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.price_list_items import ( AsyncPriceListItemsService, + PriceListItem, PriceListItemsService, ) @@ -20,6 +22,33 @@ def async_price_list_items_service(async_http_client): ) +@pytest.fixture +def price_list_item_data(): + return { + "id": "PLI-001", + "status": "Active", + "description": "Item description", + "reasonForChange": "Price update", + "unitLP": 100.0, + "unitPP": 80.0, + "markup": 25.0, + "margin": 20.0, + "unitSP": 90.0, + "PPx1": 80.0, + "PPxM": 7.0, + "PPxY": 84.0, + "SPx1": 90.0, + "SPxM": 8.0, + "SPxY": 96.0, + "LPx1": 100.0, + "LPxM": 9.0, + "LPxY": 108.0, + "priceList": {"id": "PRC-001", "currency": "USD"}, + "item": {"id": "ITM-001", "name": "My Item"}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + @pytest.fixture def test_endpoint(price_list_items_service): result = price_list_items_service.path == "/public/v1/catalog/price-lists/ITM-0000-0001/items" @@ -48,3 +77,36 @@ def test_async_methods_present(async_price_list_items_service, method): result = hasattr(async_price_list_items_service, method) assert result is True + + +def test_price_list_item_primitive_fields(price_list_item_data): + result = PriceListItem(price_list_item_data) + + assert result.to_dict() == price_list_item_data + + +def test_price_list_item_price_fields(price_list_item_data): + result = PriceListItem(price_list_item_data) + + assert result.unit_lp == pytest.approx(100.0) + assert result.unit_pp == pytest.approx(80.0) + assert result.unit_sp == pytest.approx(90.0) + assert result.spxm == pytest.approx(8.0) + assert result.lpx1 == pytest.approx(100.0) + + +def test_price_list_item_nested_models(price_list_item_data): + result = PriceListItem(price_list_item_data) + + assert isinstance(result.price_list, BaseModel) + assert isinstance(result.item, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_price_list_item_optional_fields_absent(): + result = PriceListItem({"id": "PLI-001"}) + + assert result.id == "PLI-001" + assert not hasattr(result, "status") + assert not hasattr(result, "unit_lp") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_price_lists.py b/tests/unit/resources/catalog/test_price_lists.py index ab37716..ad3e943 100644 --- a/tests/unit/resources/catalog/test_price_lists.py +++ b/tests/unit/resources/catalog/test_price_lists.py @@ -1,11 +1,13 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.price_list_items import ( AsyncPriceListItemsService, PriceListItemsService, ) from mpt_api_client.resources.catalog.price_lists import ( AsyncPriceListsService, + PriceList, PriceListsService, ) @@ -20,6 +22,23 @@ def async_price_lists_service(http_client): return AsyncPriceListsService(http_client=http_client) +@pytest.fixture +def price_list_data(): + return { + "id": "PRC-001", + "currency": "USD", + "precision": 2, + "defaultMarkup": 25.0, + "defaultMargin": 20.0, + "notes": "Some notes", + "externalIds": {"vendor": "ext-001"}, + "statistics": {"items": 10}, + "product": {"id": "PRD-001", "name": "My Product"}, + "vendor": {"id": "ACC-001", "name": "Vendor"}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + @pytest.mark.parametrize("method", ["get", "create", "update", "delete", "iterate"]) def test_mixins_present(price_lists_service, method): result = hasattr(price_lists_service, method) @@ -58,3 +77,28 @@ def test_async_property_services(async_price_lists_service, service_method, expe assert isinstance(result, expected_model_class) assert result.endpoint_params == {"price_list_id": "ITM-0000-0001"} + + +def test_price_list_primitive_fields(price_list_data): + result = PriceList(price_list_data) + + assert result.to_dict() == price_list_data + + +def test_price_list_nested_fields_are_base_models(price_list_data): + result = PriceList(price_list_data) + + assert isinstance(result.external_ids, BaseModel) + assert isinstance(result.statistics, BaseModel) + assert isinstance(result.product, BaseModel) + assert isinstance(result.vendor, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_price_list_optional_fields_absent(): + result = PriceList({"id": "PRC-001"}) + + assert result.id == "PRC-001" + assert not hasattr(result, "currency") + assert not hasattr(result, "precision") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_pricing_policies.py b/tests/unit/resources/catalog/test_pricing_policies.py index 628391f..9aabafb 100644 --- a/tests/unit/resources/catalog/test_pricing_policies.py +++ b/tests/unit/resources/catalog/test_pricing_policies.py @@ -2,9 +2,11 @@ import pytest import respx +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.pricing_policies import ( AsyncPricingPoliciesService, PricingPoliciesService, + PricingPolicy, ) from mpt_api_client.resources.catalog.pricing_policy_attachments import ( AsyncPricingPolicyAttachmentsService, @@ -22,6 +24,24 @@ def async_pricing_policies_service(async_http_client): return AsyncPricingPoliciesService(http_client=async_http_client) +@pytest.fixture +def pricing_policy_data(): + return { + "id": "PRP-001", + "name": "My Policy", + "notes": "Some notes", + "status": "Active", + "markup": 25.0, + "margin": 20.0, + "externalIds": {"vendor": "ext-001"}, + "client": {"id": "ACC-001", "name": "Client"}, + "eligibility": {"status": "Eligible"}, + "products": [{"id": "PRD-001", "name": "My Product"}], + "statistics": {"items": 5}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + def test_activate(pricing_policies_service): pricing_policy_expected = { "id": "PRP-0000-0001", @@ -160,3 +180,28 @@ def test_async_mixins_present(async_pricing_policies_service, method): result = hasattr(async_pricing_policies_service, method) assert result is True + + +def test_pricing_policy_primitive_fields(pricing_policy_data): + result = PricingPolicy(pricing_policy_data) + + assert result.to_dict() == pricing_policy_data + + +def test_pricing_policy_nested_models(pricing_policy_data): + result = PricingPolicy(pricing_policy_data) + + assert isinstance(result.external_ids, BaseModel) + assert isinstance(result.client, BaseModel) + assert isinstance(result.eligibility, BaseModel) + assert isinstance(result.statistics, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_pricing_policy_optional_fields_absent(): + result = PricingPolicy({"id": "PRP-001"}) + + assert result.id == "PRP-001" + assert not hasattr(result, "name") + assert not hasattr(result, "status") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_pricing_policy_attachments.py b/tests/unit/resources/catalog/test_pricing_policy_attachments.py index 061918a..ec92397 100644 --- a/tests/unit/resources/catalog/test_pricing_policy_attachments.py +++ b/tests/unit/resources/catalog/test_pricing_policy_attachments.py @@ -1,7 +1,9 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.pricing_policy_attachments import ( AsyncPricingPolicyAttachmentsService, + PricingPolicyAttachment, PricingPolicyAttachmentsService, ) @@ -22,6 +24,21 @@ def async_pricing_policy_attachments_service( ) +@pytest.fixture +def pricing_policy_attachment_data(): + return { + "id": "ATT-001", + "name": "Terms of Service", + "type": "Document", + "size": 1024, + "description": "Attachment description", + "fileName": "terms.pdf", + "contentType": "application/pdf", + "status": "Active", + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + def test_endpoint(pricing_policy_attachments_service) -> None: result = ( pricing_policy_attachments_service.path @@ -52,3 +69,24 @@ def test_async_methods_present(async_pricing_policy_attachments_service, method: result = hasattr(async_pricing_policy_attachments_service, method) assert result is True + + +def test_pricing_policy_attach_primitives(pricing_policy_attachment_data): + result = PricingPolicyAttachment(pricing_policy_attachment_data) + + assert result.to_dict() == pricing_policy_attachment_data + + +def test_pricing_policy_attachment_nested_models(pricing_policy_attachment_data): + result = PricingPolicyAttachment(pricing_policy_attachment_data) + + assert isinstance(result.audit, BaseModel) + + +def test_pricing_policy_attachment_absent(): + result = PricingPolicyAttachment({"id": "ATT-001"}) + + assert result.id == "ATT-001" + assert not hasattr(result, "name") + assert not hasattr(result, "status") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_product_term_variants.py b/tests/unit/resources/catalog/test_product_term_variants.py index a028c89..940ff4f 100644 --- a/tests/unit/resources/catalog/test_product_term_variants.py +++ b/tests/unit/resources/catalog/test_product_term_variants.py @@ -2,8 +2,10 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.product_term_variants import ( AsyncTermVariantService, + TermVariant, TermVariantService, ) @@ -23,6 +25,25 @@ def async_term_variant_service(async_http_client: Any) -> AsyncTermVariantServic ) +@pytest.fixture +def term_variant_data(): + return { + "id": "TRV-001", + "type": "PDF", + "assetUrl": "https://example.com/file.pdf", + "languageCode": "en-US", + "name": "English Terms", + "description": "English language terms", + "status": "Active", + "filename": "terms.pdf", + "size": 2048, + "contentType": "application/pdf", + "termsAndConditions": {"id": "TRM-001", "name": "Terms of Service"}, + "fileId": "FILE-001", + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + def test_endpoint(term_variant_service: TermVariantService) -> None: result = ( term_variant_service.path == "/public/v1/catalog/products/PRD-001/terms/TRM-001/variants" @@ -60,3 +81,25 @@ def test_async_methods_present( result = hasattr(async_term_variant_service, method) assert result is True + + +def test_term_variant_primitive_fields(term_variant_data: dict) -> None: + result = TermVariant(term_variant_data) + + assert result.to_dict() == term_variant_data + + +def test_term_variant_nested_base_models(term_variant_data: dict) -> None: + result = TermVariant(term_variant_data) + + assert isinstance(result.terms_and_conditions, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_term_variant_optional_fields_absent() -> None: + result = TermVariant({"id": "TRV-001"}) + + assert result.id == "TRV-001" + assert not hasattr(result, "name") + assert not hasattr(result, "status") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_product_terms.py b/tests/unit/resources/catalog/test_product_terms.py index f638410..941d817 100644 --- a/tests/unit/resources/catalog/test_product_terms.py +++ b/tests/unit/resources/catalog/test_product_terms.py @@ -2,12 +2,14 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.product_term_variants import ( AsyncTermVariantService, TermVariantService, ) from mpt_api_client.resources.catalog.product_terms import ( AsyncTermService, + Term, TermService, ) @@ -24,6 +26,19 @@ def async_term_service(async_http_client: Any) -> AsyncTermService: ) +@pytest.fixture +def term_data(): + return { + "id": "TRM-001", + "name": "Terms of Service", + "description": "Standard terms", + "displayOrder": 1, + "status": "Active", + "product": {"id": "PRD-001", "name": "My Product"}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + def test_endpoint(term_service: TermService) -> None: result = term_service.path == "/public/v1/catalog/products/PRD-001/terms" @@ -68,3 +83,25 @@ def test_async_variants_property(async_term_service: AsyncTermService) -> None: assert isinstance(result, AsyncTermVariantService) assert result.http_client == async_term_service.http_client assert result.endpoint_params == {"product_id": "PRD-001", "term_id": "TCS-001"} + + +def test_term_primitive_fields(term_data: dict) -> None: + result = Term(term_data) + + assert result.to_dict() == term_data + + +def test_term_nested_fields_are_base_models(term_data: dict) -> None: + result = Term(term_data) + + assert isinstance(result.product, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_term_optional_fields_absent() -> None: + result = Term({"id": "TRM-001"}) + + assert result.id == "TRM-001" + assert not hasattr(result, "name") + assert not hasattr(result, "status") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_products.py b/tests/unit/resources/catalog/test_products.py index 6ba3e93..4f5c802 100644 --- a/tests/unit/resources/catalog/test_products.py +++ b/tests/unit/resources/catalog/test_products.py @@ -2,11 +2,12 @@ import pytest import respx +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.product_terms import ( AsyncTermService, TermService, ) -from mpt_api_client.resources.catalog.products import AsyncProductsService, ProductsService +from mpt_api_client.resources.catalog.products import AsyncProductsService, Product, ProductsService from mpt_api_client.resources.catalog.products_documents import ( AsyncDocumentService, DocumentService, @@ -227,6 +228,49 @@ def test_sync_product_update(products_service, tmp_path): assert result.to_dict() == expected_response +@pytest.fixture +def product_data(): + return { + "id": "PRD-001", + "name": "My Product", + "shortDescription": "Short desc", + "longDescription": "Long description of the product", + "externalIds": {"vendor": "ext-001"}, + "website": "https://example.com", + "icon": "https://example.com/icon.png", + "status": "Active", + "vendor": {"id": "ACC-001", "name": "Vendor"}, + "settings": {"allowCustomization": True}, + "statistics": {"listings": 3}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + +def test_product_primitive_fields(product_data): + result = Product(product_data) + + assert result.to_dict() == product_data + + +def test_product_nested_fields_are_base_models(product_data): + result = Product(product_data) + + assert isinstance(result.external_ids, BaseModel) + assert isinstance(result.vendor, BaseModel) + assert isinstance(result.settings, BaseModel) + assert isinstance(result.statistics, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_product_optional_fields_absent(): + result = Product({"id": "PRD-001"}) + + assert result.id == "PRD-001" + assert not hasattr(result, "name") + assert not hasattr(result, "status") + assert not hasattr(result, "audit") + + async def test_async_product_update(async_products_service, tmp_path): product_id = "PRD-456" update_data = {"name": "Async Updated Product", "category": "Gadgets"} diff --git a/tests/unit/resources/catalog/test_products_documents.py b/tests/unit/resources/catalog/test_products_documents.py index 8d65275..af22ccf 100644 --- a/tests/unit/resources/catalog/test_products_documents.py +++ b/tests/unit/resources/catalog/test_products_documents.py @@ -1,7 +1,9 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.products_documents import ( AsyncDocumentService, + Document, DocumentService, ) @@ -18,6 +20,24 @@ def async_document_service(async_http_client) -> AsyncDocumentService: ) +@pytest.fixture +def document_data(): + return { + "id": "DOC-001", + "name": "User Guide", + "type": "UserGuide", + "description": "Product user guide", + "status": "Active", + "filename": "guide.pdf", + "size": 4096, + "contentType": "application/pdf", + "url": "https://example.com/guide.pdf", + "language": "en", + "product": {"id": "PRD-001", "name": "My Product"}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + def test_endpoint(document_service) -> None: result = document_service.path == "/public/v1/catalog/products/PRD-001/documents" @@ -48,3 +68,25 @@ def test_async_methods_present(async_document_service, method: str) -> None: result = hasattr(async_document_service, method) assert result is True + + +def test_document_primitive_fields(document_data): + result = Document(document_data) + + assert result.to_dict() == document_data + + +def test_document_nested_fields_are_base_models(document_data): + result = Document(document_data) + + assert isinstance(result.product, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_document_optional_fields_absent(): + result = Document({"id": "DOC-001"}) + + assert result.id == "DOC-001" + assert not hasattr(result, "name") + assert not hasattr(result, "status") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_products_item_groups.py b/tests/unit/resources/catalog/test_products_item_groups.py index 23fe588..a0eb6f8 100644 --- a/tests/unit/resources/catalog/test_products_item_groups.py +++ b/tests/unit/resources/catalog/test_products_item_groups.py @@ -1,7 +1,9 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.products_item_groups import ( AsyncItemGroupsService, + ItemGroup, ItemGroupsService, ) @@ -18,6 +20,23 @@ def async_item_groups_service(async_http_client): ) +@pytest.fixture +def item_group_data(): + return { + "id": "GRP-001", + "name": "Standard Options", + "label": "Standard", + "description": "Standard option group", + "displayOrder": 1, + "default": True, + "multiple": False, + "required": True, + "itemCount": 5, + "product": {"id": "PRD-001", "name": "My Product"}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + def test_endpoint(item_groups_service): result = item_groups_service.path == "/public/v1/catalog/products/PRD-001/item-groups" @@ -42,3 +61,25 @@ def test_async_methods_present(async_item_groups_service, method): result = hasattr(async_item_groups_service, method) assert result is True + + +def test_item_group_primitive_fields(item_group_data): + result = ItemGroup(item_group_data) + + assert result.to_dict() == item_group_data + + +def test_item_group_nested_fields_are_base_models(item_group_data): + result = ItemGroup(item_group_data) + + assert isinstance(result.product, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_item_group_optional_fields_absent(): + result = ItemGroup({"id": "GRP-001"}) + + assert result.id == "GRP-001" + assert not hasattr(result, "name") + assert not hasattr(result, "default") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_products_media.py b/tests/unit/resources/catalog/test_products_media.py index f4b023c..bd789af 100644 --- a/tests/unit/resources/catalog/test_products_media.py +++ b/tests/unit/resources/catalog/test_products_media.py @@ -1,7 +1,9 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.products_media import ( AsyncMediaService, + Media, MediaService, ) @@ -18,6 +20,24 @@ def async_media_service(async_http_client) -> AsyncMediaService: ) +@pytest.fixture +def media_data(): + return { + "id": "MED-001", + "name": "Product Screenshot", + "type": "Image", + "description": "Main product screenshot", + "status": "Active", + "filename": "screenshot.png", + "size": 512000, + "contentType": "image/png", + "displayOrder": 1, + "url": "https://example.com/screenshot.png", + "product": {"id": "PRD-001", "name": "My Product"}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + def test_endpoint(media_service) -> None: result = media_service.path == "/public/v1/catalog/products/PRD-001/media" @@ -48,3 +68,25 @@ def test_async_methods_present(async_media_service, method: str) -> None: result = hasattr(async_media_service, method) assert result is True + + +def test_media_primitive_fields(media_data): + result = Media(media_data) + + assert result.to_dict() == media_data + + +def test_media_nested_fields_are_base_models(media_data): + result = Media(media_data) + + assert isinstance(result.product, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_media_optional_fields_absent(): + result = Media({"id": "MED-001"}) + + assert result.id == "MED-001" + assert not hasattr(result, "name") + assert not hasattr(result, "status") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_products_templates.py b/tests/unit/resources/catalog/test_products_templates.py index 9c2443f..7a643a1 100644 --- a/tests/unit/resources/catalog/test_products_templates.py +++ b/tests/unit/resources/catalog/test_products_templates.py @@ -1,7 +1,9 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.products_templates import ( AsyncTemplatesService, + Template, TemplatesService, ) @@ -18,6 +20,19 @@ def async_templates_service(async_http_client): ) +@pytest.fixture +def template_data(): + return { + "id": "TPL-001", + "name": "Order Confirmation", + "content": "
Your order has been confirmed.
", + "type": "Email", + "default": True, + "product": {"id": "PRD-001", "name": "My Product"}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + def test_endpoint(templates_service): result = templates_service.path == "/public/v1/catalog/products/PRD-001/templates" @@ -42,3 +57,25 @@ def test_async_methods_present(async_templates_service, method): result = hasattr(async_templates_service, method) assert result is True + + +def test_template_primitive_fields(template_data): + result = Template(template_data) + + assert result.to_dict() == template_data + + +def test_template_nested_fields_are_base_models(template_data): + result = Template(template_data) + + assert isinstance(result.product, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_template_optional_fields_absent(): + result = Template({"id": "TPL-001"}) + + assert result.id == "TPL-001" + assert not hasattr(result, "name") + assert not hasattr(result, "default") + assert not hasattr(result, "audit") diff --git a/tests/unit/resources/catalog/test_units_of_measure.py b/tests/unit/resources/catalog/test_units_of_measure.py index d471455..d85502a 100644 --- a/tests/unit/resources/catalog/test_units_of_measure.py +++ b/tests/unit/resources/catalog/test_units_of_measure.py @@ -1,7 +1,9 @@ import pytest +from mpt_api_client.models.model import BaseModel from mpt_api_client.resources.catalog.units_of_measure import ( AsyncUnitsOfMeasureService, + UnitOfMeasure, UnitsOfMeasureService, ) @@ -16,6 +18,17 @@ def async_units_of_measure_service(async_http_client): return AsyncUnitsOfMeasureService(http_client=async_http_client) +@pytest.fixture +def unit_of_measure_data(): + return { + "id": "UOM-001", + "description": "A single unit", + "name": "Each", + "statistics": {"items": 100}, + "audit": {"created": {"at": "2024-01-01T00:00:00Z"}}, + } + + @pytest.mark.parametrize("method", ["get", "create", "update", "delete", "iterate"]) def test_mixins_present(units_of_measure_service, method): result = hasattr(units_of_measure_service, method) @@ -28,3 +41,25 @@ def test_async_mixins_present(async_units_of_measure_service, method): result = hasattr(async_units_of_measure_service, method) assert result is True + + +def test_unit_of_measure_primitive_fields(unit_of_measure_data): + result = UnitOfMeasure(unit_of_measure_data) + + assert result.to_dict() == unit_of_measure_data + + +def test_unit_of_measure_nested_models(unit_of_measure_data): + result = UnitOfMeasure(unit_of_measure_data) + + assert isinstance(result.statistics, BaseModel) + assert isinstance(result.audit, BaseModel) + + +def test_unit_of_measure_optional_fields_absent(): + result = UnitOfMeasure({"id": "UOM-001"}) + + assert result.id == "UOM-001" + assert not hasattr(result, "name") + assert not hasattr(result, "description") + assert not hasattr(result, "audit")