From 62a19e2dd999a819456a4ca62c16b637a37ce922 Mon Sep 17 00:00:00 2001 From: Lukasz Lancucki Date: Wed, 22 Apr 2026 13:53:35 +0100 Subject: [PATCH] refactor: consolidate resource mixins --- mpt_api_client/resources/accounts/account.py | 3 +- mpt_api_client/resources/accounts/buyers.py | 3 +- .../resources/accounts/mixins/__init__.py | 6 - mpt_api_client/resources/accounts/sellers.py | 5 +- mpt_api_client/resources/catalog/items.py | 6 +- .../catalog/mixins/activatable_mixin.py | 29 ---- .../catalog/mixins/document_mixin.py | 26 --- .../resources/catalog/mixins/media_mixin.py | 26 --- .../catalog/mixins/publishable_mixin.py | 63 ------- .../catalog/product_term_variants.py | 6 +- .../resources/catalog/product_terms.py | 9 +- mpt_api_client/resources/catalog/products.py | 22 ++- .../resources/catalog/products_documents.py | 6 +- .../resources/catalog/products_media.py | 6 +- .../integration/extension_documents.py | 5 +- .../resources/integration/extension_media.py | 8 +- .../integration/extension_term_variants.py | 2 +- .../resources/integration/extension_terms.py | 2 +- .../resources/integration/mixins/__init__.py | 6 - .../{catalog => }/mixins/__init__.py | 14 +- .../mixins/activatable_mixin.py | 0 .../resources/mixins/attachment_mixin.py | 21 +++ .../{program => }/mixins/document_mixin.py | 2 +- .../{program => }/mixins/media_mixin.py | 4 +- .../mixins/publishable_mixin.py | 40 +---- .../{program => }/mixins/render_mixin.py | 0 .../resources/mixins/reviewable_mixin.py | 17 ++ .../resources/program/enrollments.py | 2 +- .../program/enrollments_attachments.py | 5 +- .../resources/program/mixins/__init__.py | 21 --- .../program/mixins/attachment_mixin.py | 32 ---- .../program/mixins/publishable_mixin.py | 45 ----- mpt_api_client/resources/program/programs.py | 2 +- .../resources/program/programs_documents.py | 2 +- .../resources/program/programs_media.py | 5 +- .../resources/program/programs_terms.py | 5 +- .../program/programs_terms_variant.py | 5 +- .../accounts/mixins/test_activatable_mixin.py | 158 ----------------- .../unit/resources/catalog/mixins/__init__.py | 0 .../catalog/mixins/test_publishable_mixin.py | 163 ------------------ tests/unit/resources/mixins/__init__.py | 1 + .../mixins/test_activatable_mixin.py | 80 ++++----- .../mixin => mixins}/test_attachment_mixin.py | 5 +- .../mixins/test_document_mixin.py | 36 ++-- .../mixin => mixins}/test_media_mixin.py | 2 +- .../mixins/test_publishable_mixin.py | 66 ++++--- .../mixin => mixins}/test_render_mixin.py | 2 +- .../resources/mixins/test_reviewable_mixin.py | 129 ++++++++++++++ .../program/mixin/test_document_mixin.py | 157 ----------------- .../program/mixin/test_publishable_mixin.py | 159 ----------------- 50 files changed, 318 insertions(+), 1101 deletions(-) delete mode 100644 mpt_api_client/resources/catalog/mixins/activatable_mixin.py delete mode 100644 mpt_api_client/resources/catalog/mixins/document_mixin.py delete mode 100644 mpt_api_client/resources/catalog/mixins/media_mixin.py delete mode 100644 mpt_api_client/resources/catalog/mixins/publishable_mixin.py rename mpt_api_client/resources/{catalog => }/mixins/__init__.py (50%) rename mpt_api_client/resources/{accounts => }/mixins/activatable_mixin.py (100%) create mode 100644 mpt_api_client/resources/mixins/attachment_mixin.py rename mpt_api_client/resources/{program => }/mixins/document_mixin.py (87%) rename mpt_api_client/resources/{program => }/mixins/media_mixin.py (83%) rename mpt_api_client/resources/{integration => }/mixins/publishable_mixin.py (60%) rename mpt_api_client/resources/{program => }/mixins/render_mixin.py (100%) create mode 100644 mpt_api_client/resources/mixins/reviewable_mixin.py delete mode 100644 mpt_api_client/resources/program/mixins/__init__.py delete mode 100644 mpt_api_client/resources/program/mixins/attachment_mixin.py delete mode 100644 mpt_api_client/resources/program/mixins/publishable_mixin.py delete mode 100644 tests/unit/resources/accounts/mixins/test_activatable_mixin.py delete mode 100644 tests/unit/resources/catalog/mixins/__init__.py delete mode 100644 tests/unit/resources/catalog/mixins/test_publishable_mixin.py create mode 100644 tests/unit/resources/mixins/__init__.py rename tests/unit/resources/{catalog => }/mixins/test_activatable_mixin.py (67%) rename tests/unit/resources/{program/mixin => mixins}/test_attachment_mixin.py (92%) rename tests/unit/resources/{catalog => }/mixins/test_document_mixin.py (86%) rename tests/unit/resources/{program/mixin => mixins}/test_media_mixin.py (97%) rename tests/unit/resources/{integration => }/mixins/test_publishable_mixin.py (56%) rename tests/unit/resources/{program/mixin => mixins}/test_render_mixin.py (94%) create mode 100644 tests/unit/resources/mixins/test_reviewable_mixin.py delete mode 100644 tests/unit/resources/program/mixin/test_document_mixin.py delete mode 100644 tests/unit/resources/program/mixin/test_publishable_mixin.py diff --git a/mpt_api_client/resources/accounts/account.py b/mpt_api_client/resources/accounts/account.py index 4657dabc..592d9309 100644 --- a/mpt_api_client/resources/accounts/account.py +++ b/mpt_api_client/resources/accounts/account.py @@ -19,11 +19,10 @@ AsyncAccountsUsersService, ) from mpt_api_client.resources.accounts.mixins import ( - ActivatableMixin, - AsyncActivatableMixin, AsyncValidateMixin, ValidateMixin, ) +from mpt_api_client.resources.mixins import ActivatableMixin, AsyncActivatableMixin class Account(Model): diff --git a/mpt_api_client/resources/accounts/buyers.py b/mpt_api_client/resources/accounts/buyers.py index ff61d0d6..29cfb465 100644 --- a/mpt_api_client/resources/accounts/buyers.py +++ b/mpt_api_client/resources/accounts/buyers.py @@ -18,11 +18,10 @@ from mpt_api_client.models import Model from mpt_api_client.models.model import ResourceData from mpt_api_client.resources.accounts.mixins import ( - ActivatableMixin, - AsyncActivatableMixin, AsyncValidateMixin, ValidateMixin, ) +from mpt_api_client.resources.mixins import ActivatableMixin, AsyncActivatableMixin class Buyer(Model): diff --git a/mpt_api_client/resources/accounts/mixins/__init__.py b/mpt_api_client/resources/accounts/mixins/__init__.py index 053d80f4..a2b054f2 100644 --- a/mpt_api_client/resources/accounts/mixins/__init__.py +++ b/mpt_api_client/resources/accounts/mixins/__init__.py @@ -1,7 +1,3 @@ -from mpt_api_client.resources.accounts.mixins.activatable_mixin import ( - ActivatableMixin, - AsyncActivatableMixin, -) from mpt_api_client.resources.accounts.mixins.blockable_mixin import ( AsyncBlockableMixin, BlockableMixin, @@ -16,8 +12,6 @@ ) __all__ = [ # noqa: WPS410 - "ActivatableMixin", - "AsyncActivatableMixin", "AsyncBlockableMixin", "AsyncInvitableMixin", "AsyncValidateMixin", diff --git a/mpt_api_client/resources/accounts/sellers.py b/mpt_api_client/resources/accounts/sellers.py index 4e52a8b6..246f8c0d 100644 --- a/mpt_api_client/resources/accounts/sellers.py +++ b/mpt_api_client/resources/accounts/sellers.py @@ -7,10 +7,7 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import ResourceData -from mpt_api_client.resources.accounts.mixins import ( - ActivatableMixin, - AsyncActivatableMixin, -) +from mpt_api_client.resources.mixins import ActivatableMixin, AsyncActivatableMixin class Seller(Model): diff --git a/mpt_api_client/resources/catalog/items.py b/mpt_api_client/resources/catalog/items.py index c171a24f..5515a619 100644 --- a/mpt_api_client/resources/catalog/items.py +++ b/mpt_api_client/resources/catalog/items.py @@ -7,9 +7,11 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.catalog.mixins import ( +from mpt_api_client.resources.mixins import ( AsyncPublishableMixin, + AsyncReviewableMixin, PublishableMixin, + ReviewableMixin, ) @@ -53,6 +55,7 @@ class ItemsServiceConfig: class ItemsService( PublishableMixin[Item], + ReviewableMixin[Item], ManagedResourceMixin[Item], CollectionMixin[Item], Service[Item], @@ -63,6 +66,7 @@ class ItemsService( class AsyncItemsService( AsyncPublishableMixin[Item], + AsyncReviewableMixin[Item], AsyncManagedResourceMixin[Item], AsyncCollectionMixin[Item], AsyncService[Item], diff --git a/mpt_api_client/resources/catalog/mixins/activatable_mixin.py b/mpt_api_client/resources/catalog/mixins/activatable_mixin.py deleted file mode 100644 index 645608bd..00000000 --- a/mpt_api_client/resources/catalog/mixins/activatable_mixin.py +++ /dev/null @@ -1,29 +0,0 @@ -from mpt_api_client.models import ResourceData - - -class ActivatableMixin[Model]: - """Activatable mixin adds the ability to activate and deactivate.""" - - def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Active.""" - return self._resource(resource_id).post("activate", json=resource_data) # type: ignore[attr-defined, no-any-return] - - def deactivate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Inactive.""" - return self._resource(resource_id).post("deactivate", json=resource_data) # type: ignore[attr-defined, no-any-return] - - -class AsyncActivatableMixin[Model]: - """Activatable mixin adds the ability to activate and deactivate.""" - - async def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Active.""" - return await self._resource(resource_id).post("activate", json=resource_data) # type: ignore[attr-defined, no-any-return] - - async def deactivate( - self, - resource_id: str, - resource_data: ResourceData | None = None, - ) -> Model: - """Update state to Inactive.""" - return await self._resource(resource_id).post("deactivate", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/catalog/mixins/document_mixin.py b/mpt_api_client/resources/catalog/mixins/document_mixin.py deleted file mode 100644 index 3021b0a7..00000000 --- a/mpt_api_client/resources/catalog/mixins/document_mixin.py +++ /dev/null @@ -1,26 +0,0 @@ -from mpt_api_client.http.mixins import ( - AsyncCreateFileMixin, - AsyncDownloadFileMixin, - CreateFileMixin, - DownloadFileMixin, -) -from mpt_api_client.resources.catalog.mixins.publishable_mixin import ( - AsyncPublishableMixin, - PublishableMixin, -) - - -class AsyncDocumentMixin[Model]( - AsyncCreateFileMixin[Model], - AsyncDownloadFileMixin[Model], - AsyncPublishableMixin[Model], -): - """Async document mixin.""" - - -class DocumentMixin[Model]( - CreateFileMixin[Model], - DownloadFileMixin[Model], - PublishableMixin[Model], -): - """Document mixin.""" diff --git a/mpt_api_client/resources/catalog/mixins/media_mixin.py b/mpt_api_client/resources/catalog/mixins/media_mixin.py deleted file mode 100644 index aab9f66d..00000000 --- a/mpt_api_client/resources/catalog/mixins/media_mixin.py +++ /dev/null @@ -1,26 +0,0 @@ -from mpt_api_client.http.mixins import ( - AsyncCreateFileMixin, - AsyncDownloadFileMixin, - CreateFileMixin, - DownloadFileMixin, -) -from mpt_api_client.resources.catalog.mixins.publishable_mixin import ( - AsyncPublishableMixin, - PublishableMixin, -) - - -class MediaMixin[Model]( - CreateFileMixin[Model], - DownloadFileMixin[Model], - PublishableMixin[Model], -): - """Media mixin.""" - - -class AsyncMediaMixin[Model]( - AsyncCreateFileMixin[Model], - AsyncDownloadFileMixin[Model], - AsyncPublishableMixin[Model], -): - """Media mixin.""" diff --git a/mpt_api_client/resources/catalog/mixins/publishable_mixin.py b/mpt_api_client/resources/catalog/mixins/publishable_mixin.py deleted file mode 100644 index b77ebf35..00000000 --- a/mpt_api_client/resources/catalog/mixins/publishable_mixin.py +++ /dev/null @@ -1,63 +0,0 @@ -from mpt_api_client.models import ResourceData - - -class PublishableMixin[Model]: - """Publishable mixin adds the ability to review, publish and unpublish.""" - - def review(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Pending. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource(resource_id).post("review", json=resource_data) # type: ignore[attr-defined, no-any-return] - - def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Published. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource(resource_id).post("publish", json=resource_data) # type: ignore[attr-defined, no-any-return] - - def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Unpublished. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource(resource_id).post("unpublish", json=resource_data) # type: ignore[attr-defined, no-any-return] - - -class AsyncPublishableMixin[Model]: - """Publishable mixin adds the ability to review, publish and unpublish.""" - - async def review(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to reviewing. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource(resource_id).post("review", json=resource_data) # type: ignore[attr-defined, no-any-return] - - async def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Published. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource(resource_id).post("publish", json=resource_data) # type: ignore[attr-defined, no-any-return] - - async def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Unpublished. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource(resource_id).post("unpublish", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/catalog/product_term_variants.py b/mpt_api_client/resources/catalog/product_term_variants.py index 5c5328c9..caa4e0e0 100644 --- a/mpt_api_client/resources/catalog/product_term_variants.py +++ b/mpt_api_client/resources/catalog/product_term_variants.py @@ -11,9 +11,11 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.catalog.mixins import ( +from mpt_api_client.resources.mixins import ( AsyncPublishableMixin, + AsyncReviewableMixin, PublishableMixin, + ReviewableMixin, ) @@ -64,6 +66,7 @@ class TermVariantService( DownloadFileMixin[TermVariant], ModifiableResourceMixin[TermVariant], PublishableMixin[TermVariant], + ReviewableMixin[TermVariant], CollectionMixin[TermVariant], Service[TermVariant], TermVariantServiceConfig, @@ -76,6 +79,7 @@ class AsyncTermVariantService( AsyncDownloadFileMixin[TermVariant], AsyncModifiableResourceMixin[TermVariant], AsyncPublishableMixin[TermVariant], + AsyncReviewableMixin[TermVariant], AsyncCollectionMixin[TermVariant], AsyncService[TermVariant], TermVariantServiceConfig, diff --git a/mpt_api_client/resources/catalog/product_terms.py b/mpt_api_client/resources/catalog/product_terms.py index 611cd5ad..d1af0b6a 100644 --- a/mpt_api_client/resources/catalog/product_terms.py +++ b/mpt_api_client/resources/catalog/product_terms.py @@ -7,11 +7,16 @@ ) 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, TermVariantService, ) +from mpt_api_client.resources.mixins import ( + AsyncPublishableMixin, + AsyncReviewableMixin, + PublishableMixin, + ReviewableMixin, +) class Term(Model): @@ -44,6 +49,7 @@ class TermServiceConfig: class TermService( PublishableMixin[Term], + ReviewableMixin[Term], ManagedResourceMixin[Term], CollectionMixin[Term], Service[Term], @@ -61,6 +67,7 @@ def variants(self, term_id: str) -> TermVariantService: class AsyncTermService( AsyncPublishableMixin[Term], + AsyncReviewableMixin[Term], AsyncManagedResourceMixin[Term], AsyncCollectionMixin[Term], AsyncService[Term], diff --git a/mpt_api_client/resources/catalog/products.py b/mpt_api_client/resources/catalog/products.py index 6244e6a8..d75ad2ad 100644 --- a/mpt_api_client/resources/catalog/products.py +++ b/mpt_api_client/resources/catalog/products.py @@ -13,14 +13,7 @@ ) 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, -) -from mpt_api_client.resources.catalog.product_terms import ( - AsyncTermService, - TermService, -) +from mpt_api_client.resources.catalog.product_terms import AsyncTermService, TermService from mpt_api_client.resources.catalog.products_documents import ( AsyncDocumentService, DocumentService, @@ -33,10 +26,7 @@ AsyncProductItemService, ProductItemService, ) -from mpt_api_client.resources.catalog.products_media import ( - AsyncMediaService, - MediaService, -) +from mpt_api_client.resources.catalog.products_media import AsyncMediaService, MediaService from mpt_api_client.resources.catalog.products_parameter_groups import ( AsyncParameterGroupsService, ParameterGroupsService, @@ -49,6 +39,12 @@ AsyncTemplatesService, TemplatesService, ) +from mpt_api_client.resources.mixins import ( + AsyncPublishableMixin, + AsyncReviewableMixin, + PublishableMixin, + ReviewableMixin, +) class Product(Model): @@ -95,6 +91,7 @@ class ProductsService( CreateFileMixin[Product], UpdateFileMixin[Product], PublishableMixin[Product], + ReviewableMixin[Product], GetMixin[Product], DeleteMixin, CollectionMixin[Product], @@ -158,6 +155,7 @@ class AsyncProductsService( AsyncCreateFileMixin[Product], AsyncUpdateFileMixin[Product], AsyncPublishableMixin[Product], + AsyncReviewableMixin[Product], AsyncGetMixin[Product], AsyncDeleteMixin, AsyncCollectionMixin[Product], diff --git a/mpt_api_client/resources/catalog/products_documents.py b/mpt_api_client/resources/catalog/products_documents.py index b3ad483f..adf2b7ea 100644 --- a/mpt_api_client/resources/catalog/products_documents.py +++ b/mpt_api_client/resources/catalog/products_documents.py @@ -7,9 +7,11 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.catalog.mixins import ( +from mpt_api_client.resources.mixins import ( AsyncDocumentMixin, + AsyncReviewableMixin, DocumentMixin, + ReviewableMixin, ) @@ -55,6 +57,7 @@ class DocumentServiceConfig: class DocumentService( DocumentMixin[Document], + ReviewableMixin[Document], ModifiableResourceMixin[Document], CollectionMixin[Document], Service[Document], @@ -65,6 +68,7 @@ class DocumentService( class AsyncDocumentService( AsyncDocumentMixin[Document], + AsyncReviewableMixin[Document], AsyncModifiableResourceMixin[Document], AsyncCollectionMixin[Document], AsyncService[Document], diff --git a/mpt_api_client/resources/catalog/products_media.py b/mpt_api_client/resources/catalog/products_media.py index c4d50181..39993098 100644 --- a/mpt_api_client/resources/catalog/products_media.py +++ b/mpt_api_client/resources/catalog/products_media.py @@ -7,9 +7,11 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.catalog.mixins import ( +from mpt_api_client.resources.mixins import ( AsyncMediaMixin, + AsyncReviewableMixin, MediaMixin, + ReviewableMixin, ) @@ -55,6 +57,7 @@ class MediaServiceConfig: class MediaService( MediaMixin[Media], + ReviewableMixin[Media], ModifiableResourceMixin[Media], CollectionMixin[Media], Service[Media], @@ -65,6 +68,7 @@ class MediaService( class AsyncMediaService( AsyncMediaMixin[Media], + AsyncReviewableMixin[Media], AsyncModifiableResourceMixin[Media], AsyncCollectionMixin[Media], AsyncService[Media], diff --git a/mpt_api_client/resources/integration/extension_documents.py b/mpt_api_client/resources/integration/extension_documents.py index 5f8d355a..96931a67 100644 --- a/mpt_api_client/resources/integration/extension_documents.py +++ b/mpt_api_client/resources/integration/extension_documents.py @@ -11,10 +11,7 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.integration.mixins import ( - AsyncPublishableMixin, - PublishableMixin, -) +from mpt_api_client.resources.mixins import AsyncPublishableMixin, PublishableMixin class ExtensionDocument(Model): diff --git a/mpt_api_client/resources/integration/extension_media.py b/mpt_api_client/resources/integration/extension_media.py index 92330ac9..5f304bfb 100644 --- a/mpt_api_client/resources/integration/extension_media.py +++ b/mpt_api_client/resources/integration/extension_media.py @@ -13,12 +13,8 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.integration.mixins import ( - AsyncMediaMixin, - AsyncPublishableMixin, - MediaMixin, - PublishableMixin, -) +from mpt_api_client.resources.integration.mixins import AsyncMediaMixin, MediaMixin +from mpt_api_client.resources.mixins import AsyncPublishableMixin, PublishableMixin class ExtensionMedia(Model): diff --git a/mpt_api_client/resources/integration/extension_term_variants.py b/mpt_api_client/resources/integration/extension_term_variants.py index 364758f2..3e2be48f 100644 --- a/mpt_api_client/resources/integration/extension_term_variants.py +++ b/mpt_api_client/resources/integration/extension_term_variants.py @@ -9,7 +9,7 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.integration.mixins import ( +from mpt_api_client.resources.mixins import ( AsyncPublishableMixin, PublishableMixin, ) diff --git a/mpt_api_client/resources/integration/extension_terms.py b/mpt_api_client/resources/integration/extension_terms.py index b1c73da0..a7bd485b 100644 --- a/mpt_api_client/resources/integration/extension_terms.py +++ b/mpt_api_client/resources/integration/extension_terms.py @@ -11,7 +11,7 @@ AsyncExtensionTermVariantsService, ExtensionTermVariantsService, ) -from mpt_api_client.resources.integration.mixins import ( +from mpt_api_client.resources.mixins import ( AsyncPublishableMixin, PublishableMixin, ) diff --git a/mpt_api_client/resources/integration/mixins/__init__.py b/mpt_api_client/resources/integration/mixins/__init__.py index ad32140b..67fe3d1d 100644 --- a/mpt_api_client/resources/integration/mixins/__init__.py +++ b/mpt_api_client/resources/integration/mixins/__init__.py @@ -6,16 +6,10 @@ AsyncMediaMixin, MediaMixin, ) -from mpt_api_client.resources.integration.mixins.publishable_mixin import ( - AsyncPublishableMixin, - PublishableMixin, -) __all__ = [ # noqa: WPS410 "AsyncExtensionMixin", "AsyncMediaMixin", - "AsyncPublishableMixin", "ExtensionMixin", "MediaMixin", - "PublishableMixin", ] diff --git a/mpt_api_client/resources/catalog/mixins/__init__.py b/mpt_api_client/resources/mixins/__init__.py similarity index 50% rename from mpt_api_client/resources/catalog/mixins/__init__.py rename to mpt_api_client/resources/mixins/__init__.py index e24094c7..e34dc5c8 100644 --- a/mpt_api_client/resources/catalog/mixins/__init__.py +++ b/mpt_api_client/resources/mixins/__init__.py @@ -1,19 +1,23 @@ -from mpt_api_client.resources.catalog.mixins.activatable_mixin import ( +from mpt_api_client.resources.mixins.activatable_mixin import ( ActivatableMixin, AsyncActivatableMixin, ) -from mpt_api_client.resources.catalog.mixins.document_mixin import ( +from mpt_api_client.resources.mixins.document_mixin import ( AsyncDocumentMixin, DocumentMixin, ) -from mpt_api_client.resources.catalog.mixins.media_mixin import ( +from mpt_api_client.resources.mixins.media_mixin import ( AsyncMediaMixin, MediaMixin, ) -from mpt_api_client.resources.catalog.mixins.publishable_mixin import ( +from mpt_api_client.resources.mixins.publishable_mixin import ( AsyncPublishableMixin, PublishableMixin, ) +from mpt_api_client.resources.mixins.reviewable_mixin import ( + AsyncReviewableMixin, + ReviewableMixin, +) __all__ = [ # noqa: WPS410 "ActivatableMixin", @@ -21,7 +25,9 @@ "AsyncDocumentMixin", "AsyncMediaMixin", "AsyncPublishableMixin", + "AsyncReviewableMixin", "DocumentMixin", "MediaMixin", "PublishableMixin", + "ReviewableMixin", ] diff --git a/mpt_api_client/resources/accounts/mixins/activatable_mixin.py b/mpt_api_client/resources/mixins/activatable_mixin.py similarity index 100% rename from mpt_api_client/resources/accounts/mixins/activatable_mixin.py rename to mpt_api_client/resources/mixins/activatable_mixin.py diff --git a/mpt_api_client/resources/mixins/attachment_mixin.py b/mpt_api_client/resources/mixins/attachment_mixin.py new file mode 100644 index 00000000..73567e17 --- /dev/null +++ b/mpt_api_client/resources/mixins/attachment_mixin.py @@ -0,0 +1,21 @@ +from mpt_api_client.http import mixins + + +class AttachmentMixin[Model]( + mixins.CreateFileMixin[Model], + mixins.UpdateMixin[Model], + mixins.DeleteMixin, + mixins.DownloadFileMixin[Model], + mixins.GetMixin[Model], +): + """Attachment mixin.""" + + +class AsyncAttachmentMixin[Model]( + mixins.AsyncCreateFileMixin[Model], + mixins.AsyncUpdateMixin[Model], + mixins.AsyncDeleteMixin, + mixins.AsyncDownloadFileMixin[Model], + mixins.AsyncGetMixin[Model], +): + """Async Attachment mixin.""" diff --git a/mpt_api_client/resources/program/mixins/document_mixin.py b/mpt_api_client/resources/mixins/document_mixin.py similarity index 87% rename from mpt_api_client/resources/program/mixins/document_mixin.py rename to mpt_api_client/resources/mixins/document_mixin.py index d548da51..b4203674 100644 --- a/mpt_api_client/resources/program/mixins/document_mixin.py +++ b/mpt_api_client/resources/mixins/document_mixin.py @@ -4,7 +4,7 @@ CreateFileMixin, DownloadFileMixin, ) -from mpt_api_client.resources.program.mixins.publishable_mixin import ( +from mpt_api_client.resources.mixins.publishable_mixin import ( AsyncPublishableMixin, PublishableMixin, ) diff --git a/mpt_api_client/resources/program/mixins/media_mixin.py b/mpt_api_client/resources/mixins/media_mixin.py similarity index 83% rename from mpt_api_client/resources/program/mixins/media_mixin.py rename to mpt_api_client/resources/mixins/media_mixin.py index 0392b551..1b0f7971 100644 --- a/mpt_api_client/resources/program/mixins/media_mixin.py +++ b/mpt_api_client/resources/mixins/media_mixin.py @@ -4,7 +4,7 @@ CreateFileMixin, DownloadFileMixin, ) -from mpt_api_client.resources.program.mixins.publishable_mixin import ( +from mpt_api_client.resources.mixins.publishable_mixin import ( AsyncPublishableMixin, PublishableMixin, ) @@ -23,4 +23,4 @@ class AsyncMediaMixin[Model]( AsyncDownloadFileMixin[Model], AsyncPublishableMixin[Model], ): - """Media mixin.""" + """Async media mixin.""" diff --git a/mpt_api_client/resources/integration/mixins/publishable_mixin.py b/mpt_api_client/resources/mixins/publishable_mixin.py similarity index 60% rename from mpt_api_client/resources/integration/mixins/publishable_mixin.py rename to mpt_api_client/resources/mixins/publishable_mixin.py index d79b79db..d26a97a8 100644 --- a/mpt_api_client/resources/integration/mixins/publishable_mixin.py +++ b/mpt_api_client/resources/mixins/publishable_mixin.py @@ -5,27 +5,11 @@ class PublishableMixin[Model]: """Publishable mixin adds the ability to publish and unpublish a resource.""" def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Publish the resource. - - Args: - resource_id: Resource ID. - resource_data: Optional request body. - - Returns: - Updated resource. - """ + """Publish the resource.""" return self._resource(resource_id).post("publish", json=resource_data) # type: ignore[attr-defined, no-any-return] def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Unpublish the resource. - - Args: - resource_id: Resource ID. - resource_data: Optional request body. - - Returns: - Updated resource. - """ + """Unpublish the resource.""" return self._resource(resource_id).post("unpublish", json=resource_data) # type: ignore[attr-defined, no-any-return] @@ -33,25 +17,9 @@ class AsyncPublishableMixin[Model]: """Async publishable mixin adds the ability to publish and unpublish a resource.""" async def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Publish the resource. - - Args: - resource_id: Resource ID. - resource_data: Optional request body. - - Returns: - Updated resource. - """ + """Publish the resource.""" return await self._resource(resource_id).post("publish", json=resource_data) # type: ignore[attr-defined, no-any-return] async def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Unpublish the resource. - - Args: - resource_id: Resource ID. - resource_data: Optional request body. - - Returns: - Updated resource. - """ + """Unpublish the resource.""" return await self._resource(resource_id).post("unpublish", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/program/mixins/render_mixin.py b/mpt_api_client/resources/mixins/render_mixin.py similarity index 100% rename from mpt_api_client/resources/program/mixins/render_mixin.py rename to mpt_api_client/resources/mixins/render_mixin.py diff --git a/mpt_api_client/resources/mixins/reviewable_mixin.py b/mpt_api_client/resources/mixins/reviewable_mixin.py new file mode 100644 index 00000000..1092901d --- /dev/null +++ b/mpt_api_client/resources/mixins/reviewable_mixin.py @@ -0,0 +1,17 @@ +from mpt_api_client.models import ResourceData + + +class ReviewableMixin[Model]: + """Reviewable mixin adds the ability to review a resource.""" + + def review(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Review the resource.""" + return self._resource(resource_id).post("review", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class AsyncReviewableMixin[Model]: + """Async reviewable mixin adds the ability to review a resource.""" + + async def review(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Review the resource.""" + return await self._resource(resource_id).post("review", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/program/enrollments.py b/mpt_api_client/resources/program/enrollments.py index 9fe942be..32299747 100644 --- a/mpt_api_client/resources/program/enrollments.py +++ b/mpt_api_client/resources/program/enrollments.py @@ -7,11 +7,11 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel, ResourceData +from mpt_api_client.resources.mixins.render_mixin import AsyncRenderMixin, RenderMixin from mpt_api_client.resources.program.enrollments_attachments import ( AsyncEnrollmentAttachmentsService, EnrollmentAttachmentsService, ) -from mpt_api_client.resources.program.mixins.render_mixin import AsyncRenderMixin, RenderMixin class Enrollment(Model): diff --git a/mpt_api_client/resources/program/enrollments_attachments.py b/mpt_api_client/resources/program/enrollments_attachments.py index 436c6737..e285b83a 100644 --- a/mpt_api_client/resources/program/enrollments_attachments.py +++ b/mpt_api_client/resources/program/enrollments_attachments.py @@ -5,10 +5,7 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.program.mixins.attachment_mixin import ( - AsyncAttachmentMixin, - AttachmentMixin, -) +from mpt_api_client.resources.mixins.attachment_mixin import AsyncAttachmentMixin, AttachmentMixin class EnrollmentAttachment(Model): diff --git a/mpt_api_client/resources/program/mixins/__init__.py b/mpt_api_client/resources/program/mixins/__init__.py deleted file mode 100644 index 7ab0b0e6..00000000 --- a/mpt_api_client/resources/program/mixins/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from mpt_api_client.resources.program.mixins.document_mixin import ( - AsyncDocumentMixin, - DocumentMixin, -) -from mpt_api_client.resources.program.mixins.media_mixin import ( - AsyncMediaMixin, - MediaMixin, -) -from mpt_api_client.resources.program.mixins.publishable_mixin import ( - AsyncPublishableMixin, - PublishableMixin, -) - -__all__ = [ # noqa: WPS410 - "AsyncDocumentMixin", - "AsyncMediaMixin", - "AsyncPublishableMixin", - "DocumentMixin", - "MediaMixin", - "PublishableMixin", -] diff --git a/mpt_api_client/resources/program/mixins/attachment_mixin.py b/mpt_api_client/resources/program/mixins/attachment_mixin.py deleted file mode 100644 index dbb8a03f..00000000 --- a/mpt_api_client/resources/program/mixins/attachment_mixin.py +++ /dev/null @@ -1,32 +0,0 @@ -from mpt_api_client.http.mixins import ( - AsyncCreateFileMixin, - AsyncDeleteMixin, - AsyncDownloadFileMixin, - AsyncGetMixin, - AsyncUpdateMixin, - CreateFileMixin, - DeleteMixin, - DownloadFileMixin, - GetMixin, - UpdateMixin, -) - - -class AttachmentMixin[Model]( - CreateFileMixin[Model], - UpdateMixin[Model], - DeleteMixin, - DownloadFileMixin[Model], - GetMixin[Model], -): - """Attachment mixin.""" - - -class AsyncAttachmentMixin[Model]( - AsyncCreateFileMixin[Model], - AsyncUpdateMixin[Model], - AsyncDeleteMixin, - AsyncDownloadFileMixin[Model], - AsyncGetMixin[Model], -): - """Async Attachment mixin.""" diff --git a/mpt_api_client/resources/program/mixins/publishable_mixin.py b/mpt_api_client/resources/program/mixins/publishable_mixin.py deleted file mode 100644 index b85b6b25..00000000 --- a/mpt_api_client/resources/program/mixins/publishable_mixin.py +++ /dev/null @@ -1,45 +0,0 @@ -from mpt_api_client.models import ResourceData - - -class PublishableMixin[Model]: - """Publishable mixin adds the ability to publish and unpublish.""" - - def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Published. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource(resource_id).post("publish", json=resource_data) # type: ignore[attr-defined, no-any-return] - - def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Unpublished. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource(resource_id).post("unpublish", json=resource_data) # type: ignore[attr-defined, no-any-return] - - -class AsyncPublishableMixin[Model]: - """Publishable mixin adds the ability to publish and unpublish.""" - - async def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Published. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource(resource_id).post("publish", json=resource_data) # type: ignore[attr-defined, no-any-return] - - async def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Unpublished. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource(resource_id).post("unpublish", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/program/programs.py b/mpt_api_client/resources/program/programs.py index fabb756f..3e1f0e57 100644 --- a/mpt_api_client/resources/program/programs.py +++ b/mpt_api_client/resources/program/programs.py @@ -6,7 +6,7 @@ from mpt_api_client.http.mixins.update_file_mixin import AsyncUpdateFileMixin, UpdateFileMixin from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel, ResourceData -from mpt_api_client.resources.program.mixins import ( +from mpt_api_client.resources.mixins import ( AsyncPublishableMixin, PublishableMixin, ) diff --git a/mpt_api_client/resources/program/programs_documents.py b/mpt_api_client/resources/program/programs_documents.py index 10653f56..a434a1d5 100644 --- a/mpt_api_client/resources/program/programs_documents.py +++ b/mpt_api_client/resources/program/programs_documents.py @@ -7,7 +7,7 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.program.mixins import AsyncDocumentMixin, DocumentMixin +from mpt_api_client.resources.mixins import AsyncDocumentMixin, DocumentMixin class Document(Model): diff --git a/mpt_api_client/resources/program/programs_media.py b/mpt_api_client/resources/program/programs_media.py index 2d4612d5..ad8e9c10 100644 --- a/mpt_api_client/resources/program/programs_media.py +++ b/mpt_api_client/resources/program/programs_media.py @@ -7,10 +7,7 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.program.mixins import ( - AsyncMediaMixin, - MediaMixin, -) +from mpt_api_client.resources.mixins import AsyncMediaMixin, MediaMixin class Media(Model): diff --git a/mpt_api_client/resources/program/programs_terms.py b/mpt_api_client/resources/program/programs_terms.py index 8dfe13df..31ce4371 100644 --- a/mpt_api_client/resources/program/programs_terms.py +++ b/mpt_api_client/resources/program/programs_terms.py @@ -7,10 +7,7 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.program.mixins import ( - AsyncPublishableMixin, - PublishableMixin, -) +from mpt_api_client.resources.mixins import AsyncPublishableMixin, PublishableMixin from mpt_api_client.resources.program.programs_terms_variant import ( AsyncTermVariantService, TermVariantService, diff --git a/mpt_api_client/resources/program/programs_terms_variant.py b/mpt_api_client/resources/program/programs_terms_variant.py index 4c0f062f..f826b3e7 100644 --- a/mpt_api_client/resources/program/programs_terms_variant.py +++ b/mpt_api_client/resources/program/programs_terms_variant.py @@ -11,10 +11,7 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import BaseModel -from mpt_api_client.resources.program.mixins import ( - AsyncPublishableMixin, - PublishableMixin, -) +from mpt_api_client.resources.mixins import AsyncPublishableMixin, PublishableMixin class TermVariant(Model): diff --git a/tests/unit/resources/accounts/mixins/test_activatable_mixin.py b/tests/unit/resources/accounts/mixins/test_activatable_mixin.py deleted file mode 100644 index 6f51bbab..00000000 --- a/tests/unit/resources/accounts/mixins/test_activatable_mixin.py +++ /dev/null @@ -1,158 +0,0 @@ -import httpx -import pytest -import respx - -from mpt_api_client.http import AsyncService, Service -from mpt_api_client.resources.accounts.mixins.activatable_mixin import ( - ActivatableMixin, - AsyncActivatableMixin, -) -from tests.unit.conftest import DummyModel - - -class DummyActivatableService( - ActivatableMixin[DummyModel], - Service[DummyModel], -): - _endpoint = "/public/v1/dummy/activatable/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyAsyncActivatableService( - AsyncActivatableMixin[DummyModel], - AsyncService[DummyModel], -): - _endpoint = "/public/v1/dummy/activatable/" - _model_class = DummyModel - _collection_key = "data" - - -@pytest.fixture -def activatable_service(http_client): - return DummyActivatableService(http_client=http_client) - - -@pytest.fixture -def async_activatable_service(async_http_client): - return DummyAsyncActivatableService(http_client=async_http_client) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("activate", {"id": "OBJ-0000-0001", "status": "update"}), - ("deactivate", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -def test_activatable_resource_actions(activatable_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(activatable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("activate", None), - ("deactivate", None), - ], -) -def test_actions_no_data(activatable_service, action, input_status): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(activatable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("activate", {"id": "OBJ-0000-0001", "status": "update"}), - ("deactivate", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -async def test_async_activatable_resource_actions(async_activatable_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_activatable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("activate", None), - ("deactivate", None), - ], -) -async def test_async_actions_no_data(async_activatable_service, action, input_status): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_activatable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/catalog/mixins/__init__.py b/tests/unit/resources/catalog/mixins/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unit/resources/catalog/mixins/test_publishable_mixin.py b/tests/unit/resources/catalog/mixins/test_publishable_mixin.py deleted file mode 100644 index 18ea1884..00000000 --- a/tests/unit/resources/catalog/mixins/test_publishable_mixin.py +++ /dev/null @@ -1,163 +0,0 @@ -import httpx -import pytest -import respx - -from mpt_api_client.http.async_service import AsyncService -from mpt_api_client.http.service import Service -from mpt_api_client.resources.catalog.mixins import ( - AsyncPublishableMixin, - PublishableMixin, -) -from tests.unit.conftest import DummyModel - - -class DummyPublishableService( - PublishableMixin[DummyModel], - Service[DummyModel], -): - _endpoint = "/public/v1/dummy/publishable/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyAsyncPublishableService( - AsyncPublishableMixin[DummyModel], - AsyncService[DummyModel], -): - _endpoint = "/public/v1/dummy/publishable/" - _model_class = DummyModel - _collection_key = "data" - - -@pytest.fixture -def publishable_service(http_client): - return DummyPublishableService(http_client=http_client) - - -@pytest.fixture -def async_publishable_service(async_http_client): - return DummyAsyncPublishableService(http_client=async_http_client) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("review", {"id": "PRD-123", "status": "update"}), - ("publish", {"id": "PRD-123", "status": "update"}), - ("unpublish", {"id": "PRD-123", "status": "update"}), - ], -) -def test_custom_resource_actions(publishable_service, action, input_status): - request_expected_content = b'{"id":"PRD-123","status":"update"}' - response_expected_data = {"id": "PRD-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/publishable/PRD-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(publishable_service, action)("PRD-123", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action"), - [ - ("review"), - ("publish"), - ("unpublish"), - ], -) -def test_custom_resource_actions_no_data(publishable_service, action): - request_expected_content = b"" - response_expected_data = {"id": "PRD-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/publishable/PRD-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(publishable_service, action)("PRD-123") - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("review", {"id": "PRD-123", "status": "update"}), - ("publish", {"id": "PRD-123", "status": "update"}), - ("unpublish", {"id": "PRD-123", "status": "update"}), - ], -) -async def test_async_custom_resource_actions(async_publishable_service, action, input_status): - request_expected_content = b'{"id":"PRD-123","status":"update"}' - response_expected_data = {"id": "PRD-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/publishable/PRD-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_publishable_service, action)("PRD-123", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action"), - [ - ("review"), - ("publish"), - ("unpublish"), - ], -) -async def test_async_custom_resource_actions_no_data(async_publishable_service, action): - request_expected_content = b"" - response_expected_data = {"id": "PRD-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/publishable/PRD-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_publishable_service, action)("PRD-123") - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/mixins/__init__.py b/tests/unit/resources/mixins/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/unit/resources/mixins/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/unit/resources/catalog/mixins/test_activatable_mixin.py b/tests/unit/resources/mixins/test_activatable_mixin.py similarity index 67% rename from tests/unit/resources/catalog/mixins/test_activatable_mixin.py rename to tests/unit/resources/mixins/test_activatable_mixin.py index 84e99ab6..6b07e321 100644 --- a/tests/unit/resources/catalog/mixins/test_activatable_mixin.py +++ b/tests/unit/resources/mixins/test_activatable_mixin.py @@ -4,7 +4,7 @@ from mpt_api_client.http.async_service import AsyncService from mpt_api_client.http.service import Service -from mpt_api_client.resources.catalog.mixins import ( +from mpt_api_client.resources.mixins import ( ActivatableMixin, AsyncActivatableMixin, ) @@ -40,13 +40,13 @@ def async_activatable_service(async_http_client): @pytest.mark.parametrize( - ("action", "input_status"), + ("action", "resource_data"), [ ("activate", {"id": "OBJ-0000-0001", "status": "update"}), ("deactivate", {"id": "OBJ-0000-0001", "status": "update"}), ], ) -def test_actions(activatable_service, action, input_status): +def test_actions(activatable_service, action, resource_data): request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} with respx.mock: @@ -60,24 +60,17 @@ def test_actions(activatable_service, action, input_status): ) ) - result = getattr(activatable_service, action)("OBJ-0000-0001", input_status) + result = getattr(activatable_service, action)("OBJ-0000-0001", resource_data) - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("activate", None), - ("deactivate", None), - ], -) -def test_actions_no_data(activatable_service, action, input_status): - request_expected_content = b"" +@pytest.mark.parametrize("action", ["activate", "deactivate"]) +def test_actions_no_data(activatable_service, action): response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} with respx.mock: mock_route = respx.post( @@ -90,23 +83,23 @@ def test_actions_no_data(activatable_service, action, input_status): ) ) - result = getattr(activatable_service, action)("OBJ-0000-0001", input_status) + result = getattr(activatable_service, action)("OBJ-0000-0001") - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == b"" + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) @pytest.mark.parametrize( - ("action", "input_status"), + ("action", "resource_data"), [ ("activate", {"id": "OBJ-0000-0001", "status": "update"}), ("deactivate", {"id": "OBJ-0000-0001", "status": "update"}), ], ) -async def test_async_actions(async_activatable_service, action, input_status): +async def test_async_actions(async_activatable_service, action, resource_data): request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} with respx.mock: @@ -120,24 +113,17 @@ async def test_async_actions(async_activatable_service, action, input_status): ) ) - result = await getattr(async_activatable_service, action)("OBJ-0000-0001", input_status) + result = await getattr(async_activatable_service, action)("OBJ-0000-0001", resource_data) - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("activate", None), - ("deactivate", None), - ], -) -async def test_async_actions_no_data(async_activatable_service, action, input_status): - request_expected_content = b"" +@pytest.mark.parametrize("action", ["activate", "deactivate"]) +async def test_async_actions_no_data(async_activatable_service, action): response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} with respx.mock: mock_route = respx.post( @@ -150,10 +136,10 @@ async def test_async_actions_no_data(async_activatable_service, action, input_st ) ) - result = await getattr(async_activatable_service, action)("OBJ-0000-0001", input_status) + result = await getattr(async_activatable_service, action)("OBJ-0000-0001") - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == b"" + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/program/mixin/test_attachment_mixin.py b/tests/unit/resources/mixins/test_attachment_mixin.py similarity index 92% rename from tests/unit/resources/program/mixin/test_attachment_mixin.py rename to tests/unit/resources/mixins/test_attachment_mixin.py index 8ff3d1c5..3fdc7d80 100644 --- a/tests/unit/resources/program/mixin/test_attachment_mixin.py +++ b/tests/unit/resources/mixins/test_attachment_mixin.py @@ -2,10 +2,7 @@ from mpt_api_client.http.async_service import AsyncService from mpt_api_client.http.service import Service -from mpt_api_client.resources.program.mixins.attachment_mixin import ( - AsyncAttachmentMixin, - AttachmentMixin, -) +from mpt_api_client.resources.mixins.attachment_mixin import AsyncAttachmentMixin, AttachmentMixin from tests.unit.conftest import DummyModel diff --git a/tests/unit/resources/catalog/mixins/test_document_mixin.py b/tests/unit/resources/mixins/test_document_mixin.py similarity index 86% rename from tests/unit/resources/catalog/mixins/test_document_mixin.py rename to tests/unit/resources/mixins/test_document_mixin.py index 4e8224b9..be10e5b5 100644 --- a/tests/unit/resources/catalog/mixins/test_document_mixin.py +++ b/tests/unit/resources/mixins/test_document_mixin.py @@ -6,7 +6,7 @@ from mpt_api_client.http.async_service import AsyncService from mpt_api_client.http.service import Service -from mpt_api_client.resources.catalog.mixins import ( +from mpt_api_client.resources.mixins import ( AsyncDocumentMixin, DocumentMixin, ) @@ -45,7 +45,7 @@ def async_document_service(async_http_client): return DummyAsyncDocumentService(http_client=async_http_client) -def test_document_create_with_url(document_service): +def test_document_create_with_url(document_service): # noqa: AAA01 resource_data = { "name": "My Doc", "description": "My Doc", @@ -53,53 +53,47 @@ def test_document_create_with_url(document_service): } with respx.mock: mock_route = respx.post("https://api.example.com/public/v1/dummy/documents").mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - json=resource_data, - ) + return_value=httpx.Response(status_code=httpx.codes.OK, json=resource_data) ) - new_doc = document_service.create(resource_data=resource_data) - result = mock_route.calls[0].request + new_doc = document_service.create(resource_data=resource_data) + request = mock_route.calls[0].request assert ( b'Content-Disposition: form-data; name="document"\r\n' b"Content-Type: application/json\r\n\r\n" b'{"name":"My Doc","description":"My Doc","url":"https://example.com/file.pdf"}\r\n' - in result.content + in request.content ) - assert b'Content-Disposition: form-data; name="file"' not in result.content - assert "multipart/form-data" in result.headers["Content-Type"] + assert b'Content-Disposition: form-data; name="file"' not in request.content + assert "multipart/form-data" in request.headers["Content-Type"] assert new_doc.to_dict() == resource_data assert isinstance(new_doc, DummyModel) def test_document_create_with_file(document_service): # noqa: WPS210 resource_data = {"id": "DOC-125", "name": "Data And File"} - response_data = resource_data file_tuple = ("manual.pdf", io.BytesIO(b"PDF DATA"), "application/pdf") with respx.mock: mock_route = respx.post("https://api.example.com/public/v1/dummy/documents").mock( - return_value=httpx.Response(status_code=httpx.codes.OK, json=response_data) + return_value=httpx.Response(status_code=httpx.codes.OK, json=resource_data) ) result = document_service.create(resource_data=resource_data, file=file_tuple) request = mock_route.calls[0].request - # JSON part assert ( b'Content-Disposition: form-data; name="document"\r\n' b"Content-Type: application/json\r\n\r\n" b'{"id":"DOC-125","name":"Data And File"}\r\n' in request.content ) - # File part assert ( b'Content-Disposition: form-data; name="file"; filename="manual.pdf"\r\n' b"Content-Type: application/pdf\r\n\r\n" b"PDF DATA\r\n" in request.content ) assert "multipart/form-data" in request.headers["Content-Type"] - assert result.to_dict() == response_data + assert result.to_dict() == resource_data assert isinstance(result, DummyModel) @@ -111,10 +105,7 @@ async def test_async_document_create_with_url(async_document_service): } with respx.mock: mock_route = respx.post("https://api.example.com/public/v1/dummy/documents").mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - json=resource_data, - ) + return_value=httpx.Response(status_code=httpx.codes.OK, json=resource_data) ) result = await async_document_service.create(resource_data=resource_data) @@ -134,11 +125,10 @@ async def test_async_document_create_with_url(async_document_service): async def test_async_document_create_with_file(async_document_service): # noqa: WPS210 resource_data = {"id": "DOC-125", "name": "Data And File"} - response_data = resource_data file_tuple = ("manual.pdf", io.BytesIO(b"PDF DATA"), "application/pdf") with respx.mock: mock_route = respx.post("https://api.example.com/public/v1/dummy/documents").mock( - return_value=httpx.Response(status_code=httpx.codes.OK, json=response_data) + return_value=httpx.Response(status_code=httpx.codes.OK, json=resource_data) ) result = await async_document_service.create(resource_data, file_tuple) @@ -155,5 +145,5 @@ async def test_async_document_create_with_file(async_document_service): # noqa: b"PDF DATA\r\n" in request.content ) assert "multipart/form-data" in request.headers["Content-Type"] - assert result.to_dict() == response_data + assert result.to_dict() == resource_data assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/program/mixin/test_media_mixin.py b/tests/unit/resources/mixins/test_media_mixin.py similarity index 97% rename from tests/unit/resources/program/mixin/test_media_mixin.py rename to tests/unit/resources/mixins/test_media_mixin.py index ede1c6fd..28406a01 100644 --- a/tests/unit/resources/program/mixin/test_media_mixin.py +++ b/tests/unit/resources/mixins/test_media_mixin.py @@ -4,7 +4,7 @@ from mpt_api_client.http.async_service import AsyncService from mpt_api_client.http.service import Service -from mpt_api_client.resources.program.mixins.media_mixin import ( +from mpt_api_client.resources.mixins import ( AsyncMediaMixin, MediaMixin, ) diff --git a/tests/unit/resources/integration/mixins/test_publishable_mixin.py b/tests/unit/resources/mixins/test_publishable_mixin.py similarity index 56% rename from tests/unit/resources/integration/mixins/test_publishable_mixin.py rename to tests/unit/resources/mixins/test_publishable_mixin.py index c8ac3e5e..8f1f54ff 100644 --- a/tests/unit/resources/integration/mixins/test_publishable_mixin.py +++ b/tests/unit/resources/mixins/test_publishable_mixin.py @@ -4,7 +4,7 @@ from mpt_api_client.http.async_service import AsyncService from mpt_api_client.http.service import Service -from mpt_api_client.resources.integration.mixins import ( +from mpt_api_client.resources.mixins import ( AsyncPublishableMixin, PublishableMixin, ) @@ -15,7 +15,7 @@ class DummyPublishableService( PublishableMixin[DummyModel], Service[DummyModel], ): - _endpoint = "/public/v1/integration/extensions/EXT-001/terms" + _endpoint = "/public/v1/dummy/publishable/" _model_class = DummyModel _collection_key = "data" @@ -24,7 +24,7 @@ class DummyAsyncPublishableService( AsyncPublishableMixin[DummyModel], AsyncService[DummyModel], ): - _endpoint = "/public/v1/integration/extensions/EXT-001/terms" + _endpoint = "/public/v1/dummy/publishable/" _model_class = DummyModel _collection_key = "data" @@ -40,98 +40,94 @@ def async_publishable_service(async_http_client): @pytest.mark.parametrize("action", ["publish", "unpublish"]) -def test_action_with_data(publishable_service, action): - resource_data = {"id": "TERM-001", "status": "update"} - expected_response = {"id": "TERM-001", "status": "Published"} +def test_actions_with_data(publishable_service, action): + resource_data = {"id": "PRD-123", "status": "update"} + response_expected_data = {"id": "PRD-123", "status": "new_status"} with respx.mock: mock_route = respx.post( - f"https://api.example.com/public/v1/integration/extensions/EXT-001/terms/TERM-001/{action}" + f"https://api.example.com/public/v1/dummy/publishable/PRD-123/{action}" ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, headers={"content-type": "application/json"}, - json=expected_response, + json=response_expected_data, ) ) - result = getattr(publishable_service, action)("TERM-001", resource_data) + result = getattr(publishable_service, action)("PRD-123", resource_data) assert mock_route.call_count == 1 request = mock_route.calls[0].request - assert request.method == "POST" - assert request.content == b'{"id":"TERM-001","status":"update"}' - assert result.to_dict() == expected_response + assert request.content == b'{"id":"PRD-123","status":"update"}' + assert result.to_dict() == response_expected_data assert isinstance(result, DummyModel) @pytest.mark.parametrize("action", ["publish", "unpublish"]) -def test_action_no_data(publishable_service, action): - expected_response = {"id": "TERM-001", "status": "Published"} +def test_actions_no_data(publishable_service, action): + response_expected_data = {"id": "PRD-123", "status": "new_status"} with respx.mock: mock_route = respx.post( - f"https://api.example.com/public/v1/integration/extensions/EXT-001/terms/TERM-001/{action}" + f"https://api.example.com/public/v1/dummy/publishable/PRD-123/{action}" ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, headers={"content-type": "application/json"}, - json=expected_response, + json=response_expected_data, ) ) - result = getattr(publishable_service, action)("TERM-001") + result = getattr(publishable_service, action)("PRD-123") assert mock_route.call_count == 1 request = mock_route.calls[0].request - assert request.method == "POST" assert request.content == b"" - assert result.to_dict() == expected_response + assert result.to_dict() == response_expected_data assert isinstance(result, DummyModel) @pytest.mark.parametrize("action", ["publish", "unpublish"]) -async def test_async_action_with_data(async_publishable_service, action): - resource_data = {"id": "TERM-001", "status": "update"} - expected_response = {"id": "TERM-001", "status": "Published"} +async def test_async_actions_with_data(async_publishable_service, action): + resource_data = {"id": "PRD-123", "status": "update"} + response_expected_data = {"id": "PRD-123", "status": "new_status"} with respx.mock: mock_route = respx.post( - f"https://api.example.com/public/v1/integration/extensions/EXT-001/terms/TERM-001/{action}" + f"https://api.example.com/public/v1/dummy/publishable/PRD-123/{action}" ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, headers={"content-type": "application/json"}, - json=expected_response, + json=response_expected_data, ) ) - result = await getattr(async_publishable_service, action)("TERM-001", resource_data) + result = await getattr(async_publishable_service, action)("PRD-123", resource_data) assert mock_route.call_count == 1 request = mock_route.calls[0].request - assert request.method == "POST" - assert request.content == b'{"id":"TERM-001","status":"update"}' - assert result.to_dict() == expected_response + assert request.content == b'{"id":"PRD-123","status":"update"}' + assert result.to_dict() == response_expected_data assert isinstance(result, DummyModel) @pytest.mark.parametrize("action", ["publish", "unpublish"]) -async def test_async_action_no_data(async_publishable_service, action): - expected_response = {"id": "TERM-001", "status": "Published"} +async def test_async_actions_no_data(async_publishable_service, action): + response_expected_data = {"id": "PRD-123", "status": "new_status"} with respx.mock: mock_route = respx.post( - f"https://api.example.com/public/v1/integration/extensions/EXT-001/terms/TERM-001/{action}" + f"https://api.example.com/public/v1/dummy/publishable/PRD-123/{action}" ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, headers={"content-type": "application/json"}, - json=expected_response, + json=response_expected_data, ) ) - result = await getattr(async_publishable_service, action)("TERM-001") + result = await getattr(async_publishable_service, action)("PRD-123") assert mock_route.call_count == 1 request = mock_route.calls[0].request - assert request.method == "POST" assert request.content == b"" - assert result.to_dict() == expected_response + assert result.to_dict() == response_expected_data assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/program/mixin/test_render_mixin.py b/tests/unit/resources/mixins/test_render_mixin.py similarity index 94% rename from tests/unit/resources/program/mixin/test_render_mixin.py rename to tests/unit/resources/mixins/test_render_mixin.py index f5aa86f7..65552a8b 100644 --- a/tests/unit/resources/program/mixin/test_render_mixin.py +++ b/tests/unit/resources/mixins/test_render_mixin.py @@ -2,7 +2,7 @@ import respx from mpt_api_client.http import AsyncService, Service -from mpt_api_client.resources.program.mixins.render_mixin import AsyncRenderMixin, RenderMixin +from mpt_api_client.resources.mixins.render_mixin import AsyncRenderMixin, RenderMixin from tests.unit.conftest import DummyModel diff --git a/tests/unit/resources/mixins/test_reviewable_mixin.py b/tests/unit/resources/mixins/test_reviewable_mixin.py new file mode 100644 index 00000000..cff875c1 --- /dev/null +++ b/tests/unit/resources/mixins/test_reviewable_mixin.py @@ -0,0 +1,129 @@ +import httpx +import pytest +import respx + +from mpt_api_client.http.async_service import AsyncService +from mpt_api_client.http.service import Service +from mpt_api_client.resources.mixins import ( + AsyncReviewableMixin, + ReviewableMixin, +) +from tests.unit.conftest import DummyModel + + +class DummyReviewableService( + ReviewableMixin[DummyModel], + Service[DummyModel], +): + _endpoint = "/public/v1/dummy/reviewable/" + _model_class = DummyModel + _collection_key = "data" + + +class DummyAsyncReviewableService( + AsyncReviewableMixin[DummyModel], + AsyncService[DummyModel], +): + _endpoint = "/public/v1/dummy/reviewable/" + _model_class = DummyModel + _collection_key = "data" + + +@pytest.fixture +def reviewable_service(http_client): + return DummyReviewableService(http_client=http_client) + + +@pytest.fixture +def async_reviewable_service(async_http_client): + return DummyAsyncReviewableService(http_client=async_http_client) + + +def test_review_with_data(reviewable_service): + resource_data = {"id": "OBJ-0000-0001", "status": "update"} + response_expected_data = {"id": "OBJ-0000-0001", "status": "review"} + with respx.mock: + mock_route = respx.post( + "https://api.example.com/public/v1/dummy/reviewable/OBJ-0000-0001/review" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = reviewable_service.review("OBJ-0000-0001", resource_data) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == b'{"id":"OBJ-0000-0001","status":"update"}' + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +def test_review_no_data(reviewable_service): + response_expected_data = {"id": "OBJ-0000-0001", "status": "review"} + with respx.mock: + mock_route = respx.post( + "https://api.example.com/public/v1/dummy/reviewable/OBJ-0000-0001/review" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = reviewable_service.review("OBJ-0000-0001") + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == b"" + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +async def test_async_review_with_data(async_reviewable_service): + resource_data = {"id": "OBJ-0000-0001", "status": "update"} + response_expected_data = {"id": "OBJ-0000-0001", "status": "review"} + with respx.mock: + mock_route = respx.post( + "https://api.example.com/public/v1/dummy/reviewable/OBJ-0000-0001/review" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await async_reviewable_service.review("OBJ-0000-0001", resource_data) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == b'{"id":"OBJ-0000-0001","status":"update"}' + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +async def test_async_review_no_data(async_reviewable_service): + response_expected_data = {"id": "OBJ-0000-0001", "status": "review"} + with respx.mock: + mock_route = respx.post( + "https://api.example.com/public/v1/dummy/reviewable/OBJ-0000-0001/review" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await async_reviewable_service.review("OBJ-0000-0001") + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == b"" + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/program/mixin/test_document_mixin.py b/tests/unit/resources/program/mixin/test_document_mixin.py deleted file mode 100644 index 0854d016..00000000 --- a/tests/unit/resources/program/mixin/test_document_mixin.py +++ /dev/null @@ -1,157 +0,0 @@ -import io - -import httpx -import pytest -import respx - -from mpt_api_client.http.async_service import AsyncService -from mpt_api_client.http.service import Service -from mpt_api_client.resources.program.mixins import ( - AsyncDocumentMixin, - DocumentMixin, -) -from tests.unit.conftest import DummyModel - - -class DummyDocumentService( - DocumentMixin[DummyModel], - Service[DummyModel], -): - _endpoint = "/public/v1/dummy/documents" - _model_class = DummyModel - _collection_key = "data" - _upload_file_key = "file" - _upload_data_key = "document" - - -class DummyAsyncDocumentService( - AsyncDocumentMixin[DummyModel], - AsyncService[DummyModel], -): - _endpoint = "/public/v1/dummy/documents" - _model_class = DummyModel - _collection_key = "data" - _upload_file_key = "file" - _upload_data_key = "document" - - -@pytest.fixture -def document_service(http_client): - return DummyDocumentService(http_client=http_client) - - -@pytest.fixture -def async_document_service(async_http_client): - return DummyAsyncDocumentService(http_client=async_http_client) - - -def test_document_create_with_url(document_service): - resource_data = { - "name": "Dummy Doc", - "description": "Dummy Doc", - "url": "https://example.com/dummyfile.pdf", - } - with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/dummy/documents").mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - json=resource_data, - ) - ) - new_document = document_service.create(resource_data=resource_data) - - result = mock_route.calls[0].request - - assert ( - b'Content-Disposition: form-data; name="document"\r\n' - b"Content-Type: application/json\r\n\r\n" - b'{"name":"Dummy Doc","description":"Dummy Doc","url":"https://example.com/dummyfile.pdf"}\r\n' - in result.content - ) - assert b'Content-Disposition: form-data; name="file"' not in result.content - assert "multipart/form-data" in result.headers["Content-Type"] - assert new_document.to_dict() == resource_data - assert isinstance(new_document, DummyModel) - - -def test_document_create_with_file(document_service): - resource_data = {"id": "DOC-123", "name": "Dummy Data And File"} - response_data = resource_data - file_tuple = ("dummyfile.pdf", io.BytesIO(b"dummy file content"), "application/pdf") - with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/dummy/documents").mock( - return_value=httpx.Response(status_code=httpx.codes.OK, json=response_data) - ) - - result = document_service.create(resource_data=resource_data, file=file_tuple) - - request = mock_route.calls[0].request - assert ( - b'Content-Disposition: form-data; name="document"\r\n' - b"Content-Type: application/json\r\n\r\n" - b'{"id":"DOC-123","name":"Dummy Data And File"}\r\n' in request.content - ) - assert ( - b'Content-Disposition: form-data; name="file"; filename="dummyfile.pdf"\r\n' - b"Content-Type: application/pdf\r\n\r\n" - b"dummy file content\r\n" in request.content - ) - assert "multipart/form-data" in request.headers["Content-Type"] - assert result.to_dict() == response_data - assert isinstance(result, DummyModel) - - -async def test_async_document_create_with_url(async_document_service): - resource_data = { - "name": "Async Dummy Doc", - "description": "Async Dummy Doc", - "url": "https://example.com/async_dummyfile.pdf", - } - with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/dummy/documents").mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - json=resource_data, - ) - ) - - result = await async_document_service.create(resource_data=resource_data) - - request = mock_route.calls[0].request - assert ( - b'Content-Disposition: form-data; name="document"\r\n' - b"Content-Type: application/json\r\n\r\n" - b'{"name":"Async Dummy Doc","description":"Async Dummy Doc","url":"https://example.com/async_dummyfile.pdf"}\r\n' - in request.content - ) - assert b'Content-Disposition: form-data; name="file"' not in request.content - assert "multipart/form-data" in request.headers["Content-Type"] - assert result.to_dict() == resource_data - assert isinstance(result, DummyModel) - - -async def test_async_document_create_with_file(async_document_service): # noqa: WPS210 - resource_data = {"id": "DOC-124", "name": "Async Dummy Data And File"} - response_data = resource_data - file_tuple = ("manual.pdf", io.BytesIO(b"PDF DATA"), "application/pdf") - with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/dummy/documents").mock( - return_value=httpx.Response(status_code=httpx.codes.OK, json=response_data) - ) - - result = await async_document_service.create(resource_data=resource_data, file=file_tuple) - - request = mock_route.calls[0].request - assert ( - b'Content-Disposition: form-data; name="document"\r\n' - b"Content-Type: application/json\r\n\r\n" - b'{"id":"DOC-124","name":"Async Dummy Data And File"}\r\n' in request.content - ) - assert ( - b'Content-Disposition: form-data; name="file"; filename="manual.pdf"\r\n' - b"Content-Type: application/pdf\r\n\r\n" - b"PDF DATA\r\n" in request.content - ) - assert "multipart/form-data" in request.headers["Content-Type"] - assert result.to_dict() == response_data - assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/program/mixin/test_publishable_mixin.py b/tests/unit/resources/program/mixin/test_publishable_mixin.py deleted file mode 100644 index 5d5a7e13..00000000 --- a/tests/unit/resources/program/mixin/test_publishable_mixin.py +++ /dev/null @@ -1,159 +0,0 @@ -import httpx -import pytest -import respx - -from mpt_api_client.http.async_service import AsyncService -from mpt_api_client.http.service import Service -from mpt_api_client.resources.program.mixins import ( - AsyncPublishableMixin, - PublishableMixin, -) -from tests.unit.conftest import DummyModel - - -class DummyPublishableService( - PublishableMixin[DummyModel], - Service[DummyModel], -): - _endpoint = "/public/v1/dummy/publishable/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyAsyncPublishableService( - AsyncPublishableMixin[DummyModel], - AsyncService[DummyModel], -): - _endpoint = "/public/v1/dummy/publishable/" - _model_class = DummyModel - _collection_key = "data" - - -@pytest.fixture -def publishable_service(http_client): - return DummyPublishableService(http_client=http_client) - - -@pytest.fixture -def async_publishable_service(async_http_client): - return DummyAsyncPublishableService(http_client=async_http_client) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("publish", {"id": "PRG-123", "status": "update"}), - ("unpublish", {"id": "PRG-123", "status": "update"}), - ], -) -def test_custom_resource_actions(publishable_service, action, input_status): - request_expected_content = b'{"id":"PRG-123","status":"update"}' - response_expected_data = {"id": "PRG-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/publishable/PRG-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(publishable_service, action)("PRG-123", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action"), - [ - ("publish"), - ("unpublish"), - ], -) -def test_custom_resource_actions_no_data(publishable_service, action): - request_expected_content = b"" - response_expected_data = {"id": "PRG-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/publishable/PRG-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(publishable_service, action)("PRG-123") - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("publish", {"id": "PRG-123", "status": "update"}), - ("unpublish", {"id": "PRG-123", "status": "update"}), - ], -) -async def test_async_custom_resource_actions(async_publishable_service, action, input_status): - request_expected_content = b'{"id":"PRG-123","status":"update"}' - response_expected_data = {"id": "PRG-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/publishable/PRG-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_publishable_service, action)("PRG-123", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action"), - [ - ("publish"), - ("unpublish"), - ], -) -async def test_async_custom_resource_actions_no_data(async_publishable_service, action): - request_expected_content = b"" - response_expected_data = {"id": "PRG-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/publishable/PRG-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_publishable_service, action)("PRG-123") - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel)