From eb876084a0196d1dc79174fc4c07cb89f8c74b05 Mon Sep 17 00:00:00 2001 From: Robert Segal Date: Wed, 19 Nov 2025 15:45:48 -0700 Subject: [PATCH] Updated mixins for create and update in account, added an update mixin in product --- mpt_api_client/http/mixins.py | 219 +++---- mpt_api_client/resources/accounts/account.py | 148 +---- mpt_api_client/resources/accounts/buyers.py | 143 +---- .../resources/accounts/licensees.py | 141 +---- mpt_api_client/resources/accounts/mixins.py | 3 - mpt_api_client/resources/catalog/mixins.py | 61 +- .../catalog/pricing_policy_attachments.py | 6 +- .../catalog/product_term_variants.py | 4 +- mpt_api_client/resources/catalog/products.py | 135 +---- seed/catalog/product.py | 76 ++- .../accounts/account/test_async_account.py | 21 +- .../e2e/accounts/account/test_sync_account.py | 22 +- .../api_tokens/test_async_api_tokens.py | 10 + .../api_tokens/test_sync_api_tokens.py | 10 + .../e2e/accounts/buyers/test_async_buyers.py | 16 +- tests/e2e/accounts/buyers/test_sync_buyers.py | 16 +- .../licensees/test_async_licensees.py | 17 +- .../accounts/licensees/test_sync_licensees.py | 6 +- .../user_groups/test_async_user_groups.py | 8 + .../user_groups/test_sync_user_groups.py | 7 + .../e2e/catalog/product/test_async_product.py | 4 +- .../e2e/catalog/product/test_sync_product.py | 4 +- tests/unit/http/test_mixins.py | 562 ++++++++++-------- tests/unit/resources/accounts/test_account.py | 8 +- tests/unit/resources/accounts/test_buyers.py | 8 +- .../unit/resources/accounts/test_licensees.py | 8 +- tests/unit/resources/accounts/test_mixins.py | 242 ++++++++ tests/unit/resources/catalog/test_mixins.py | 12 +- tests/unit/resources/catalog/test_products.py | 16 +- 29 files changed, 912 insertions(+), 1021 deletions(-) diff --git a/mpt_api_client/http/mixins.py b/mpt_api_client/http/mixins.py index eb45c19f..1e10c9ea 100644 --- a/mpt_api_client/http/mixins.py +++ b/mpt_api_client/http/mixins.py @@ -1,4 +1,3 @@ -import json from collections.abc import AsyncIterator, Iterator from typing import Self from urllib.parse import urljoin @@ -113,73 +112,146 @@ def create( return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] -class CreateWithIconMixin[Model]: - """Create resource with icon mixin.""" +class CreateFileMixin[Model]: + """Create file mixin.""" - def create( + def create(self, resource_data: ResourceData, file: FileTypes | None = None) -> Model: # noqa: WPS110 + """Create logo. + + Create a file resource by specifying a file image. + + Args: + resource_data: Resource data. + file: File image. + + Returns: + Model: Created resource. + """ + files = {} + + if file: + files[self._upload_file_key] = file # type: ignore[attr-defined] + + response = self.http_client.request( # type: ignore[attr-defined] + "post", + self.path, # type: ignore[attr-defined] + json=resource_data, + files=files, + json_file_key=self._upload_data_key, # type: ignore[attr-defined] + force_multipart=True, + ) + + return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] + + +class UpdateFileMixin[Model]: + """Update file mixin.""" + + def update( self, + resource_id: str, resource_data: ResourceData, - icon: FileTypes, - data_key: str, - icon_key: str, + file: FileTypes | None = None, # noqa: WPS110 ) -> Model: - """Create resource with icon. + """Update file. + + Update a file resource by specifying a file. Args: + resource_id: Resource ID. resource_data: Resource data. - data_key: Key for the resource data. - icon: Icon image in jpg, png, GIF, etc. - icon_key: Key for the icon. + file: File image. Returns: - Created resource. + Model: Updated resource. """ - files: dict[str, FileTypes] = {} - files[data_key] = ( - None, - json.dumps(resource_data), - APPLICATION_JSON, + files = {} + + url = urljoin(f"{self.path}/", resource_id) # type: ignore[attr-defined] + + if file: + files[self._upload_file_key] = file # type: ignore[attr-defined] + + response = self.http_client.request( # type: ignore[attr-defined] + "put", + url, + json=resource_data, + files=files, + json_file_key=self._upload_data_key, # type: ignore[attr-defined] + force_multipart=True, ) - files[icon_key] = icon - response = self.http_client.request("post", self.path, files=files) # type: ignore[attr-defined] return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] -class UpdateWithIconMixin[Model]: - """Update resource with icon mixin.""" +class AsyncCreateFileMixin[Model]: + """Asynchronous Create file mixin.""" - def update( + async def create(self, resource_data: ResourceData, file: FileTypes | None = None) -> Model: # noqa: WPS110 + """Create file. + + Create a file resource by specifying a file. + + Args: + resource_data: Resource data. + file: File image. + + Returns: + Model: Created resource. + """ + files = {} + + if file: + files[self._upload_file_key] = file # type: ignore[attr-defined] + + response = await self.http_client.request( # type: ignore[attr-defined] + "post", + self.path, # type: ignore[attr-defined] + json=resource_data, + files=files, + json_file_key=self._upload_data_key, # type: ignore[attr-defined] + force_multipart=True, + ) + + return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] + + +class AsyncUpdateFileMixin[Model]: + """Asynchronous Update file mixin.""" + + async def update( self, resource_id: str, resource_data: ResourceData, - icon: FileTypes, - data_key: str, - icon_key: str, + file: FileTypes | None = None, # noqa: WPS110 ) -> Model: - """Update resource with icon. + """Update file. + + Update a file resource by specifying a file. Args: resource_id: Resource ID. resource_data: Resource data. - data_key: Key for the resource data. - icon: Icon image in jpg, png, GIF, etc. - icon_key: Key for the icon. + file: File image. Returns: - Updated resource. + Model: Updated resource. """ - files: dict[str, FileTypes] = {} - files[data_key] = ( - None, - json.dumps(resource_data), - APPLICATION_JSON, - ) - files[icon_key] = icon + files = {} url = urljoin(f"{self.path}/", resource_id) # type: ignore[attr-defined] - response = self.http_client.request("put", url, files=files) # type: ignore[attr-defined] + if file: + files[self._upload_file_key] = file # type: ignore[attr-defined] + + response = await self.http_client.request( # type: ignore[attr-defined] + "put", + url, + json=resource_data, + files=files, + json_file_key=self._upload_data_key, # type: ignore[attr-defined] + force_multipart=True, + ) return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] @@ -286,77 +358,6 @@ async def create( return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] -class AsyncCreateWithIconMixin[Model]: - """Create resource with icon mixin.""" - - async def create( - self, - resource_data: ResourceData, - icon: FileTypes, - data_key: str, - icon_key: str, - ) -> Model: - """Create resource with icon. - - Args: - resource_data: Resource data. - data_key: Key for the resource data. - icon: Icon image in jpg, png, GIF, etc. - icon_key: Key for the icon. - - Returns: - Created resource. - """ - files: dict[str, FileTypes] = {} - files[data_key] = ( - None, - json.dumps(resource_data), - APPLICATION_JSON, - ) - files[icon_key] = icon - response = await self.http_client.request("post", self.path, files=files) # type: ignore[attr-defined] - - return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] - - -class AsyncUpdateWithIconMixin[Model]: - """Update resource with icon mixin.""" - - async def update( - self, - resource_id: str, - resource_data: ResourceData, - icon: FileTypes, - data_key: str, - icon_key: str, - ) -> Model: - """Update resource with icon. - - Args: - resource_id: Resource ID. - resource_data: Resource data. - data_key: Key for the resource data. - icon: Icon image in jpg, png, GIF, etc. - icon_key: Key for the icon. - - Returns: - Updated resource. - """ - files: dict[str, FileTypes] = {} - files[data_key] = ( - None, - json.dumps(resource_data), - APPLICATION_JSON, - ) - files[icon_key] = icon - - url = urljoin(f"{self.path}/", resource_id) # type: ignore[attr-defined] - - response = await self.http_client.request("put", url, files=files) # type: ignore[attr-defined] - - return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] - - class GetMixin[Model]: """Get resource mixin.""" diff --git a/mpt_api_client/resources/accounts/account.py b/mpt_api_client/resources/accounts/account.py index ba56dfa8..9a5da385 100644 --- a/mpt_api_client/resources/accounts/account.py +++ b/mpt_api_client/resources/accounts/account.py @@ -1,19 +1,15 @@ -from typing import override - from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, - AsyncCreateWithIconMixin, + AsyncCreateFileMixin, AsyncGetMixin, - AsyncUpdateWithIconMixin, + AsyncUpdateFileMixin, CollectionMixin, - CreateWithIconMixin, + CreateFileMixin, GetMixin, - UpdateWithIconMixin, + UpdateFileMixin, ) -from mpt_api_client.http.types import FileTypes from mpt_api_client.models import Model -from mpt_api_client.models.model import ResourceData from mpt_api_client.resources.accounts.accounts_users import ( AccountsUsersService, AsyncAccountsUsersService, @@ -38,14 +34,16 @@ class AccountsServiceConfig: _endpoint = "/public/v1/accounts/accounts" _model_class = Account _collection_key = "data" + _upload_file_key = "logo" + _upload_data_key = "account" class AccountsService( - CreateWithIconMixin[Account], - UpdateWithIconMixin[Account], - ActivatableMixin[Account], - EnablableMixin[Account], - ValidateMixin[Account], + CreateFileMixin[Model], + UpdateFileMixin[Model], + ActivatableMixin[Model], + EnablableMixin[Model], + ValidateMixin[Model], GetMixin[Account], CollectionMixin[Account], Service[Account], @@ -53,63 +51,6 @@ class AccountsService( ): """Accounts service.""" - @override - def create( - self, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "account", - icon_key: str = "logo", - ) -> Account: - """ - Create a new account with logo. - - Args: - resource_data (ResourceData): Account data. - logo: Logo image in jpg, png, GIF, etc. - data_key: Key for the account data. - icon_key: Key for the logo. - - Returns: - Account: The created account. - """ - return super().create( - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - - @override - def update( - self, - resource_id: str, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "account", - icon_key: str = "logo", - ) -> Account: - """ - Update an existing account with logo. - - Args: - resource_id (str): The ID of the account to update. - resource_data (ResourceData): Account data. - logo: Logo image in jpg, png, GIF, etc. - data_key: Key for the account data. - icon_key: Key for the logo. - - Returns: - Account: The updated account. - """ - return super().update( - resource_id=resource_id, - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - def users(self, account_id: str) -> AccountsUsersService: """Return account users service.""" return AccountsUsersService( @@ -118,11 +59,11 @@ def users(self, account_id: str) -> AccountsUsersService: class AsyncAccountsService( - AsyncCreateWithIconMixin[Account], - AsyncUpdateWithIconMixin[Account], - AsyncActivatableMixin[Account], - AsyncEnablableMixin[Account], - AsyncValidateMixin[Account], + AsyncCreateFileMixin[Model], + AsyncUpdateFileMixin[Model], + AsyncActivatableMixin[Model], + AsyncEnablableMixin[Model], + AsyncValidateMixin[Model], AsyncGetMixin[Account], AsyncCollectionMixin[Account], AsyncService[Account], @@ -130,63 +71,6 @@ class AsyncAccountsService( ): """Async Accounts service.""" - @override - async def create( - self, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "account", - icon_key: str = "logo", - ) -> Account: - """ - Create a new account with logo. - - Args: - resource_data (ResourceData): Account data. - logo: Logo image in jpg, png, GIF, etc. - data_key: Key for the account data. - icon_key: Key for the logo. - - Returns: - Account: The created account. - """ - return await super().create( - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - - @override - async def update( - self, - resource_id: str, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "account", - icon_key: str = "logo", - ) -> Account: - """ - Update an existing account with logo. - - Args: - resource_id (str): The ID of the account to update. - resource_data (ResourceData): Account data. - logo: Logo image in jpg, png, GIF, etc. - data_key: Key for the account data. - icon_key: Key for the logo. - - Returns: - Account: The updated account. - """ - return await super().update( - resource_id=resource_id, - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - def users(self, account_id: str) -> AsyncAccountsUsersService: """Return account users service.""" return AsyncAccountsUsersService( diff --git a/mpt_api_client/resources/accounts/buyers.py b/mpt_api_client/resources/accounts/buyers.py index d9695b9e..0b770167 100644 --- a/mpt_api_client/resources/accounts/buyers.py +++ b/mpt_api_client/resources/accounts/buyers.py @@ -1,19 +1,16 @@ -from typing import override - from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, - AsyncCreateWithIconMixin, + AsyncCreateFileMixin, AsyncDeleteMixin, AsyncGetMixin, - AsyncUpdateWithIconMixin, + AsyncUpdateFileMixin, CollectionMixin, - CreateWithIconMixin, + CreateFileMixin, DeleteMixin, GetMixin, - UpdateWithIconMixin, + UpdateFileMixin, ) -from mpt_api_client.http.types import FileTypes from mpt_api_client.models import Model from mpt_api_client.models.model import ResourceData from mpt_api_client.resources.accounts.mixins import ( @@ -36,77 +33,24 @@ class BuyersServiceConfig: _endpoint = "/public/v1/accounts/buyers" _model_class = Buyer _collection_key = "data" + _upload_file_key = "logo" + _upload_data_key = "buyer" class BuyersService( - CreateWithIconMixin[Buyer], - UpdateWithIconMixin[Buyer], + CreateFileMixin[Model], + UpdateFileMixin[Model], + ActivatableMixin[Model], + EnablableMixin[Model], + ValidateMixin[Model], GetMixin[Buyer], DeleteMixin, - ActivatableMixin[Buyer], - EnablableMixin[Buyer], - ValidateMixin[Buyer], CollectionMixin[Buyer], Service[Buyer], BuyersServiceConfig, ): """Buyers Service.""" - @override - def create( - self, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "buyer", - icon_key: str = "logo", - ) -> Buyer: - """Create a buyer. - - Args: - resource_data (ResourceData): Buyer data. - logo: Logo image in jpg, png, GIF, etc. - data_key: The key for the buyer data. - icon_key: The key for the logo image. - - Returns: - Buyer: Created buyer - """ - return super().create( - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - - @override - def update( - self, - resource_id: str, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "buyer", - icon_key: str = "logo", - ) -> Buyer: - """Update a buyer. - - Args: - resource_id: Resource ID - resource_data (ResourceData): Buyer data. - logo: Logo image in jpg, png, GIF, etc. - data_key: The key for the buyer data. - icon_key: The key for the logo image. - - Returns: - Buyer: Updated buyer - """ - return super().update( - resource_id=resource_id, - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - def synchronize(self, resource_id: str, resource_data: ResourceData | None = None) -> Buyer: """Synchronize a buyer. @@ -127,74 +71,19 @@ def transfer(self, resource_id: str, resource_data: ResourceData | None = None) class AsyncBuyersService( - AsyncCreateWithIconMixin[Buyer], - AsyncUpdateWithIconMixin[Buyer], + AsyncCreateFileMixin[Model], + AsyncUpdateFileMixin[Model], + AsyncActivatableMixin[Model], + AsyncEnablableMixin[Model], + AsyncValidateMixin[Model], AsyncGetMixin[Buyer], AsyncDeleteMixin, - AsyncActivatableMixin[Buyer], - AsyncEnablableMixin[Buyer], - AsyncValidateMixin[Buyer], AsyncCollectionMixin[Buyer], AsyncService[Buyer], BuyersServiceConfig, ): """Async Buyers Service.""" - @override - async def create( - self, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "buyer", - icon_key: str = "logo", - ) -> Buyer: - """Create a buyer. - - Args: - resource_data (ResourceData): Buyer data. - logo: Logo image in jpg, png, GIF, etc. - data_key: The key for the buyer data. - icon_key: The key for the logo image. - - Returns: - Buyer: Created buyer - """ - return await super().create( - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - - @override - async def update( - self, - resource_id: str, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "buyer", - icon_key: str = "logo", - ) -> Buyer: - """Update a buyer. - - Args: - resource_id: Resource ID - resource_data (ResourceData): Buyer data. - logo: Logo image in jpg, png, GIF, etc. - data_key: The key for the buyer data. - icon_key: The key for the logo image. - - Returns: - Buyer: Updated buyer - """ - return await super().update( - resource_id=resource_id, - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - async def synchronize( self, resource_id: str, resource_data: ResourceData | None = None ) -> Buyer: diff --git a/mpt_api_client/resources/accounts/licensees.py b/mpt_api_client/resources/accounts/licensees.py index 7c6fa548..3f8da979 100644 --- a/mpt_api_client/resources/accounts/licensees.py +++ b/mpt_api_client/resources/accounts/licensees.py @@ -1,22 +1,21 @@ -from typing import override - from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, - AsyncCreateWithIconMixin, + AsyncCreateFileMixin, AsyncDeleteMixin, AsyncGetMixin, - AsyncUpdateWithIconMixin, + AsyncUpdateFileMixin, CollectionMixin, - CreateWithIconMixin, + CreateFileMixin, DeleteMixin, GetMixin, - UpdateWithIconMixin, + UpdateFileMixin, ) -from mpt_api_client.http.types import FileTypes from mpt_api_client.models import Model -from mpt_api_client.models.model import ResourceData -from mpt_api_client.resources.accounts.mixins import AsyncEnablableMixin, EnablableMixin +from mpt_api_client.resources.accounts.mixins import ( + AsyncEnablableMixin, + EnablableMixin, +) class Licensee(Model): @@ -29,139 +28,31 @@ class LicenseesServiceConfig: _endpoint = "/public/v1/accounts/licensees" _model_class = Licensee _collection_key = "data" + _upload_file_key = "logo" + _upload_data_key = "licensee" class LicenseesService( - CreateWithIconMixin[Licensee], - UpdateWithIconMixin[Licensee], + CreateFileMixin[Model], + UpdateFileMixin[Model], + EnablableMixin[Model], GetMixin[Licensee], DeleteMixin, - EnablableMixin[Licensee], CollectionMixin[Licensee], Service[Licensee], LicenseesServiceConfig, ): """Licensees Service.""" - @override - def create( - self, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "licensee", - icon_key: str = "logo", - ) -> Licensee: - """Create a licensee. - - Args: - resource_data (ResourceData): Licensee data. - logo: Logo image in jpg, png, GIF, etc. - data_key: The key for the licensee data. - icon_key: The key for the logo image. - - Returns: - Licensee: Created licensee - """ - return super().create( - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - - @override - def update( - self, - resource_id: str, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "licensee", - icon_key: str = "logo", - ) -> Licensee: - """Update a licensee. - - Args: - resource_id (str): Licensee ID. - resource_data (ResourceData): Licensee data. - logo: Logo image in jpg, png, GIF, etc. - data_key: The key for the licensee data. - icon_key: The key for the logo image. - - Returns: - Licensee: Updated licensee - """ - return super().update( - resource_id=resource_id, - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - class AsyncLicenseesService( - AsyncCreateWithIconMixin[Licensee], - AsyncUpdateWithIconMixin[Licensee], + AsyncCreateFileMixin[Model], + AsyncUpdateFileMixin[Model], + AsyncEnablableMixin[Model], AsyncGetMixin[Licensee], AsyncDeleteMixin, - AsyncEnablableMixin[Licensee], AsyncCollectionMixin[Licensee], AsyncService[Licensee], LicenseesServiceConfig, ): """Async Licensees Service.""" - - @override - async def create( - self, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "licensee", - icon_key: str = "logo", - ) -> Licensee: - """Create a licensee. - - Args: - resource_data (ResourceData): Licensee data. - logo: Logo image in jpg, png, GIF, etc. - data_key: The key for the licensee data. - icon_key: The key for the logo image. - - Returns: - Licensee: Created licensee - """ - return await super().create( - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) - - @override - async def update( - self, - resource_id: str, - resource_data: ResourceData, - logo: FileTypes, - data_key: str = "licensee", - icon_key: str = "logo", - ) -> Licensee: - """Update a licensee. - - Args: - resource_id (str): Licensee ID. - resource_data (ResourceData): Licensee data. - logo: Logo image in jpg, png, GIF, etc. - data_key: The key for the licensee data. - icon_key: The key for the logo image. - - Returns: - Licensee: Updated licensee - """ - return await super().update( - resource_id=resource_id, - resource_data=resource_data, - icon=logo, - data_key=data_key, - icon_key=icon_key, - ) diff --git a/mpt_api_client/resources/accounts/mixins.py b/mpt_api_client/resources/accounts/mixins.py index 10af417c..9cce1738 100644 --- a/mpt_api_client/resources/accounts/mixins.py +++ b/mpt_api_client/resources/accounts/mixins.py @@ -1,8 +1,5 @@ from mpt_api_client.models import ResourceData -# TODO: Consider reorganizing functions in mixins to reduce duplication and differences amongst -# different domains - class ActivatableMixin[Model]: """Activatable mixin for activating, enabling, disabling and deactivating resources.""" diff --git a/mpt_api_client/resources/catalog/mixins.py b/mpt_api_client/resources/catalog/mixins.py index ca8593cc..e7b2970e 100644 --- a/mpt_api_client/resources/catalog/mixins.py +++ b/mpt_api_client/resources/catalog/mixins.py @@ -1,8 +1,9 @@ from mpt_api_client.http.mixins import ( + AsyncCreateFileMixin, AsyncDownloadFileMixin, + CreateFileMixin, DownloadFileMixin, ) -from mpt_api_client.http.types import FileTypes from mpt_api_client.models import ResourceData @@ -81,35 +82,6 @@ async def unpublish(self, resource_id: str, resource_data: ResourceData | None = ) -class AsyncCreateFileMixin[Model]: - """Create file mixin.""" - - async def create(self, resource_data: ResourceData, file: FileTypes | None = None) -> Model: - """Create document. - - Creates a document resource by specifying a `file` or an `url`. - - Args: - resource_data: Resource data. - file: File to upload. - - Returns: - Created resource. - """ - files = {} - if file: - files[self._upload_file_key] = file # type: ignore[attr-defined] - response = await self.http_client.request( # type: ignore[attr-defined] - "post", - self.path, # type: ignore[attr-defined] - json=resource_data, - files=files, - json_file_key=self._upload_data_key, # type: ignore[attr-defined] - force_multipart=True, - ) - return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] - - class AsyncDocumentMixin[Model]( AsyncCreateFileMixin[Model], AsyncDownloadFileMixin[Model], @@ -118,35 +90,6 @@ class AsyncDocumentMixin[Model]( """Async document mixin.""" -class CreateFileMixin[Model]: - """Create file mixin.""" - - def create(self, resource_data: ResourceData, file: FileTypes | None = None) -> Model: - """Create document. - - Creates a document resource by specifying a `file` or an `url`. - - Args: - resource_data: Resource data. - file: File to upload. - - Returns: - Created resource. - """ - files = {} - if file: - files[self._upload_file_key] = file # type: ignore[attr-defined] - response = self.http_client.request( # type: ignore[attr-defined] - "post", - self.path, # type: ignore[attr-defined] - json=resource_data, - files=files, - json_file_key=self._upload_data_key, # type: ignore[attr-defined] - force_multipart=True, - ) - return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] - - class DocumentMixin[Model]( CreateFileMixin[Model], DownloadFileMixin[Model], diff --git a/mpt_api_client/resources/catalog/pricing_policy_attachments.py b/mpt_api_client/resources/catalog/pricing_policy_attachments.py index ef27ce3f..6362ee16 100644 --- a/mpt_api_client/resources/catalog/pricing_policy_attachments.py +++ b/mpt_api_client/resources/catalog/pricing_policy_attachments.py @@ -1,17 +1,15 @@ from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, + AsyncCreateFileMixin, AsyncDownloadFileMixin, AsyncModifiableResourceMixin, CollectionMixin, + CreateFileMixin, DownloadFileMixin, ModifiableResourceMixin, ) from mpt_api_client.models import Model -from mpt_api_client.resources.catalog.mixins import ( - AsyncCreateFileMixin, - CreateFileMixin, -) class PricingPolicyAttachment(Model): diff --git a/mpt_api_client/resources/catalog/product_term_variants.py b/mpt_api_client/resources/catalog/product_term_variants.py index 826e4ce9..bee9c300 100644 --- a/mpt_api_client/resources/catalog/product_term_variants.py +++ b/mpt_api_client/resources/catalog/product_term_variants.py @@ -1,17 +1,17 @@ from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, + AsyncCreateFileMixin, AsyncDownloadFileMixin, AsyncModifiableResourceMixin, CollectionMixin, + CreateFileMixin, DownloadFileMixin, ModifiableResourceMixin, ) from mpt_api_client.models import Model from mpt_api_client.resources.catalog.mixins import ( - AsyncCreateFileMixin, AsyncPublishableMixin, - CreateFileMixin, PublishableMixin, ) diff --git a/mpt_api_client/resources/catalog/products.py b/mpt_api_client/resources/catalog/products.py index e98caf93..268561f3 100644 --- a/mpt_api_client/resources/catalog/products.py +++ b/mpt_api_client/resources/catalog/products.py @@ -1,19 +1,16 @@ -from typing import override - from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, - AsyncCreateWithIconMixin, + AsyncCreateFileMixin, AsyncDeleteMixin, AsyncGetMixin, - AsyncUpdateWithIconMixin, + AsyncUpdateFileMixin, CollectionMixin, - CreateWithIconMixin, + CreateFileMixin, DeleteMixin, GetMixin, - UpdateWithIconMixin, + UpdateFileMixin, ) -from mpt_api_client.http.types import FileTypes from mpt_api_client.models import Model, ResourceData from mpt_api_client.resources.catalog.mixins import ( AsyncPublishableMixin, @@ -59,12 +56,14 @@ class ProductsServiceConfig: _endpoint = "/public/v1/catalog/products" _model_class = Product _collection_key = "data" + _upload_file_key = "icon" + _upload_data_key = "product" class ProductsService( - CreateWithIconMixin[Product], - UpdateWithIconMixin[Product], - PublishableMixin[Product], + CreateFileMixin[Model], + UpdateFileMixin[Model], + PublishableMixin[Model], GetMixin[Product], DeleteMixin, CollectionMixin[Product], @@ -73,61 +72,6 @@ class ProductsService( ): """Products service.""" - @override - def create( - self, - resource_data: ResourceData, - icon: FileTypes, - data_key: str = "product", - icon_key: str = "icon", - ) -> Product: - """Create product with icon. - - Args: - resource_data: Product data. - icon: Icon image in jpg, png, GIF, etc. - data_key: Key for the product data. - icon_key: Key for the icon. - - Returns: - Created resource. - """ - return super().create( - resource_data=resource_data, - icon=icon, - data_key=data_key, - icon_key=icon_key, - ) - - @override - def update( - self, - resource_id: str, - resource_data: ResourceData, - icon: FileTypes, - data_key: str = "product", - icon_key: str = "icon", - ) -> Product: - """Update product with icon. - - Args: - resource_id: Product ID. - resource_data: Product data. - icon: Icon image in jpg, png, GIF, etc. - data_key: Key for the product data. - icon_key: Key for the icon. - - Returns: - Updated resource. - """ - return super().update( - resource_id=resource_id, - resource_data=resource_data, - icon=icon, - data_key=data_key, - icon_key=icon_key, - ) - def item_groups(self, product_id: str) -> ItemGroupsService: """Return item_groups service.""" return ItemGroupsService( @@ -174,9 +118,9 @@ def update_settings(self, product_id: str, settings: ResourceData) -> Product: class AsyncProductsService( - AsyncCreateWithIconMixin[Product], - AsyncUpdateWithIconMixin[Product], - AsyncPublishableMixin[Product], + AsyncCreateFileMixin[Model], + AsyncUpdateFileMixin[Model], + AsyncPublishableMixin[Model], AsyncGetMixin[Product], AsyncDeleteMixin, AsyncCollectionMixin[Product], @@ -185,61 +129,6 @@ class AsyncProductsService( ): """Products service.""" - @override - async def create( - self, - resource_data: ResourceData, - icon: FileTypes, - data_key: str = "product", - icon_key: str = "icon", - ) -> Product: - """Create product with icon. - - Args: - resource_data: Product data. - icon: Icon image in jpg, png, GIF, etc. - data_key: Key for the product data. - icon_key: Key for the icon. - - Returns: - Created resource. - """ - return await super().create( - resource_data=resource_data, - data_key=data_key, - icon=icon, - icon_key=icon_key, - ) - - @override - async def update( - self, - resource_id: str, - resource_data: ResourceData, - icon: FileTypes, - data_key: str = "product", - icon_key: str = "icon", - ) -> Product: - """Update product with icon. - - Args: - resource_id: Product ID. - resource_data: Product data. - icon: Icon image in jpg, png, GIF, etc. - data_key: Key for the product data. - icon_key: Key for the icon. - - Returns: - Updated resource. - """ - return await super().update( - resource_id=resource_id, - resource_data=resource_data, - data_key=data_key, - icon=icon, - icon_key=icon_key, - ) - def item_groups(self, product_id: str) -> AsyncItemGroupsService: """Return item_groups service.""" return AsyncItemGroupsService( diff --git a/seed/catalog/product.py b/seed/catalog/product.py index 01c73a98..e1af9340 100644 --- a/seed/catalog/product.py +++ b/seed/catalog/product.py @@ -23,19 +23,22 @@ async def get_product( """Get product from context or fetch from API.""" product_id = context.get_string(f"{namespace}.id") logger.debug("Getting product: %s", product_id) - if not product_id: - return None - try: - product = context.get_resource(namespace, product_id) - except ValueError: - product = None - if not isinstance(product, Product): - logger.debug("Refreshing product: %s", product_id) - product = await mpt_vendor.catalog.products.get(product_id) - context.set_resource(namespace, product) - context[f"{namespace}.id"] = product.id - return product - return product + result: Product | None = None + if product_id: + try: + maybe = context.get_resource(namespace, product_id) + except ValueError: + maybe = None + if isinstance(maybe, Product): + result = maybe + else: + logger.debug("Refreshing product: %s", product_id) + refreshed = await mpt_vendor.catalog.products.get(product_id) + if isinstance(refreshed, Product): + context.set_resource(namespace, refreshed) + context[f"{namespace}.id"] = refreshed.id + result = refreshed + return result @inject @@ -45,18 +48,21 @@ async def init_product( ) -> Product: """Get or create product.""" product = await get_product() - if product is None: - logger.debug("Creating product ...") - with pathlib.Path.open(icon, "rb") as icon_file: - product = await mpt_vendor.catalog.products.create( - {"name": "E2E Seeded", "website": "https://www.example.com"}, icon=icon_file - ) - context.set_resource(namespace, product) - context[f"{namespace}.id"] = product.id - logger.info("Product created: %s", product.id) - else: + if product is not None: logger.info("Product found: %s", product.id) - return product + return product + logger.debug("Creating product ...") + with pathlib.Path.open(icon, "rb") as icon_file: + created = await mpt_vendor.catalog.products.create( + {"name": "E2E Seeded", "website": "https://www.example.com"}, file=icon_file + ) + if isinstance(created, Product): + context.set_resource(namespace, created) + context[f"{namespace}.id"] = created.id + logger.info("Product created: %s", created.id) + return created + logger.warning("Product creation failed") + raise ValueError("Product creation failed") @inject @@ -66,12 +72,15 @@ async def review_product( ) -> Product | None: """Review product if in draft status.""" product = await get_product() - if not product or product.status != "Draft": + if not isinstance(product, Product) or product.status != "Draft": return product logger.debug("Reviewing product: %s", product.id) - product = await mpt_vendor.catalog.products.review(product.id) - context.set_resource(namespace, product) - return product + reviewed = await mpt_vendor.catalog.products.review(product.id) + if isinstance(reviewed, Product): + context.set_resource(namespace, reviewed) + return reviewed + logger.warning("Product review failed") + return None @inject @@ -81,12 +90,15 @@ async def publish_product( ) -> Product | None: """Publish product if in reviewing status.""" product = await get_product() - if not product or product.status != "Reviewing": + if not isinstance(product, Product) or product.status != "Reviewing": return product logger.debug("Publishing product: %s", product.id) - product = await mpt_operations.catalog.products.publish(product.id) - context.set_resource(namespace, product) - return product + published = await mpt_operations.catalog.products.publish(product.id) + if isinstance(published, Product): + context.set_resource(namespace, published) + return published + logger.warning("Product publish failed") + return None async def seed_product() -> None: diff --git a/tests/e2e/accounts/account/test_async_account.py b/tests/e2e/accounts/account/test_async_account.py index a221e4de..bbd7c237 100644 --- a/tests/e2e/accounts/account/test_async_account.py +++ b/tests/e2e/accounts/account/test_async_account.py @@ -8,9 +8,10 @@ @pytest.fixture async def async_created_account(logger, async_mpt_ops, account_factory, account_icon): + """Fixture to create and yield an asynchronous account for testing.""" account_data = account_factory() - res_account = await async_mpt_ops.accounts.accounts.create(account_data, logo=account_icon) + res_account = await async_mpt_ops.accounts.accounts.create(account_data, file=account_icon) yield res_account @@ -21,6 +22,7 @@ async def async_created_account(logger, async_mpt_ops, account_factory, account_ async def test_get_account_by_id_not_found(async_mpt_ops): + """Test fetching an account by an invalid ID raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.accounts.get("INVALID-ID") @@ -32,6 +34,7 @@ async def test_get_account_by_id(async_mpt_ops, account_id): async def test_list_accounts(async_mpt_ops): + """Test listing accounts with a limit.""" limit = 10 result = await async_mpt_ops.accounts.accounts.fetch_page(limit=limit) @@ -46,10 +49,11 @@ def test_create_account(async_created_account): async def test_update_account(async_mpt_ops, async_created_account, account_factory, account_icon): + """Test updating an account asynchronously.""" updated_data = account_factory(name="Updated Account Name") result = await async_mpt_ops.accounts.accounts.update( - async_created_account.id, updated_data, logo=account_icon + async_created_account.id, updated_data, file=account_icon ) assert result is not None @@ -58,26 +62,29 @@ async def test_update_account(async_mpt_ops, async_created_account, account_fact async def test_update_account_invalid_data( async_mpt_ops, account_factory, async_created_account, account_icon ): + """Test updating an account with invalid data raises a 400 error.""" updated_data = account_factory(name="") with pytest.raises(MPTAPIError, match=r"400 Bad Request"): await async_mpt_ops.accounts.accounts.update( - async_created_account.id, updated_data, logo=account_icon + async_created_account.id, updated_data, file=account_icon ) async def test_update_account_not_found( async_mpt_ops, account_factory, invalid_account_id, account_icon ): + """Test updating a non-existent account raises a 404 error.""" non_existent_account = account_factory(name="Non Existent Account") with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.accounts.update( - invalid_account_id, non_existent_account, logo=account_icon + invalid_account_id, non_existent_account, file=account_icon ) -async def test_account_enable(async_mpt_ops, account_factory, async_created_account): +async def test_account_enable(async_mpt_ops, async_created_account): + """Test enabling an account asynchronously.""" await async_mpt_ops.accounts.accounts.disable(async_created_account.id) result = await async_mpt_ops.accounts.accounts.enable(async_created_account.id) @@ -86,22 +93,26 @@ async def test_account_enable(async_mpt_ops, account_factory, async_created_acco async def test_account_enable_not_found(async_mpt_ops, invalid_account_id): + """Test enabling a non-existent account raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.accounts.enable(invalid_account_id) async def test_account_disable(async_mpt_ops, async_created_account): + """Test disabling an account asynchronously.""" result = await async_mpt_ops.accounts.accounts.disable(async_created_account.id) assert result is not None async def test_account_disable_not_found(async_mpt_ops, invalid_account_id): + """Test disabling a non-existent account raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.accounts.disable(invalid_account_id) async def test_account_rql_filter(async_mpt_ops, account_id): + """Test filtering accounts using RQL asynchronously.""" selected_fields = ["-address"] filtered_accounts = ( async_mpt_ops.accounts.accounts.filter(RQLQuery(id=account_id)) diff --git a/tests/e2e/accounts/account/test_sync_account.py b/tests/e2e/accounts/account/test_sync_account.py index 9d3e4850..65408ec6 100644 --- a/tests/e2e/accounts/account/test_sync_account.py +++ b/tests/e2e/accounts/account/test_sync_account.py @@ -7,10 +7,11 @@ @pytest.fixture -def created_account(logger, mpt_ops, account_factory, account_icon): +def created_account(mpt_ops, account_factory, account_icon): + """Fixture to create and yield an account for testing.""" account_data = account_factory() - res_account = mpt_ops.accounts.accounts.create(account_data, logo=account_icon) + res_account = mpt_ops.accounts.accounts.create(account_data, file=account_icon) yield res_account @@ -21,6 +22,7 @@ def created_account(logger, mpt_ops, account_factory, account_icon): def test_get_account_by_id_not_found(mpt_ops): + """Test fetching an account by an invalid ID raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.accounts.get("INVALID-ID") @@ -32,6 +34,7 @@ def test_get_account_by_id(mpt_ops, account_id): def test_list_accounts(mpt_ops): + """Test listing accounts with a limit.""" limit = 10 result = mpt_ops.accounts.accounts.fetch_page(limit=limit) @@ -46,30 +49,34 @@ def test_create_account(created_account): def test_update_account(mpt_ops, created_account, account_factory, account_icon): + """Test updating an account synchronously.""" updated_data = account_factory(name="Updated Account Name") - result = mpt_ops.accounts.accounts.update(created_account.id, updated_data, logo=account_icon) + result = mpt_ops.accounts.accounts.update(created_account.id, updated_data, file=account_icon) assert result is not None def test_update_account_invalid_data(mpt_ops, account_factory, created_account, account_icon): + """Test updating an account with invalid data raises a 400 error.""" updated_data = account_factory(name="") with pytest.raises(MPTAPIError, match=r"400 Bad Request"): - mpt_ops.accounts.accounts.update(created_account.id, updated_data, logo=account_icon) + mpt_ops.accounts.accounts.update(created_account.id, updated_data, file=account_icon) def test_update_account_not_found(mpt_ops, account_factory, invalid_account_id, account_icon): + """Test updating a non-existent account raises a 404 error.""" non_existent_account = account_factory(name="Non Existent Account") with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.accounts.update( - invalid_account_id, non_existent_account, logo=account_icon + invalid_account_id, non_existent_account, file=account_icon ) -def test_account_enable(mpt_ops, account_factory, created_account): +def test_account_enable(mpt_ops, created_account): + """Test enabling an account synchronously.""" mpt_ops.accounts.accounts.disable(created_account.id) result = mpt_ops.accounts.accounts.enable(created_account.id) @@ -78,6 +85,7 @@ def test_account_enable(mpt_ops, account_factory, created_account): def test_account_enable_not_found(mpt_ops, invalid_account_id): + """Test enabling a non-existent account raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.accounts.enable(invalid_account_id) @@ -89,11 +97,13 @@ def test_account_disable(mpt_ops, created_account): def test_account_disable_not_found(mpt_ops, invalid_account_id): + """Test disabling a non-existent account raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.accounts.disable(invalid_account_id) def test_account_rql_filter(mpt_ops, account_id): + """Test filtering accounts using RQL synchronously.""" selected_fields = ["-address"] filtered_accounts = ( mpt_ops.accounts.accounts.filter(RQLQuery(id=account_id)) diff --git a/tests/e2e/accounts/api_tokens/test_async_api_tokens.py b/tests/e2e/accounts/api_tokens/test_async_api_tokens.py index 8195cacf..24ab43b0 100644 --- a/tests/e2e/accounts/api_tokens/test_async_api_tokens.py +++ b/tests/e2e/accounts/api_tokens/test_async_api_tokens.py @@ -8,6 +8,7 @@ @pytest.fixture async def created_api_token(async_mpt_vendor, api_token_factory): + """Fixture to create and yield an asynchronous API token for testing.""" new_api_token_request_data = api_token_factory() created_api_token = await async_mpt_vendor.accounts.api_tokens.create( new_api_token_request_data @@ -28,6 +29,7 @@ async def test_get_api_token_by_id(async_mpt_vendor, api_token_id): async def test_list_api_tokens(async_mpt_vendor): + """Test listing API tokens with a limit.""" limit = 10 result = await async_mpt_vendor.accounts.api_tokens.fetch_page(limit=limit) @@ -36,11 +38,13 @@ async def test_list_api_tokens(async_mpt_vendor): async def test_get_api_token_by_id_not_found(async_mpt_vendor, invalid_api_token_id): + """Test retrieving an API token by an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_vendor.accounts.api_tokens.get(invalid_api_token_id) async def test_filter_api_tokens(async_mpt_vendor, api_token_id): + """Test filtering API tokens with specific criteria.""" select_fields = ["-description"] filtered_api_tokens = ( async_mpt_vendor.accounts.api_tokens.filter(RQLQuery(id=api_token_id)) @@ -64,11 +68,13 @@ async def test_delete_api_token(async_mpt_vendor, created_api_token): async def test_delete_api_token_not_found(async_mpt_vendor, invalid_api_token_id): + """Test deleting an API token with an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_vendor.accounts.api_tokens.delete(invalid_api_token_id) async def test_update_api_token(async_mpt_vendor, api_token_factory, created_api_token): + """Test updating an API token.""" updated_api_token_data = api_token_factory(name="E2E Updated API Token") result = await async_mpt_vendor.accounts.api_tokens.update( @@ -81,6 +87,7 @@ async def test_update_api_token(async_mpt_vendor, api_token_factory, created_api async def test_update_api_token_not_found( async_mpt_vendor, api_token_factory, invalid_api_token_id ): + """Test updating an API token with an invalid ID, expecting a 404 error.""" updated_api_token_data = api_token_factory(name="Nonexistent API Token") with pytest.raises(MPTAPIError, match=r"404 Not Found"): @@ -96,11 +103,13 @@ async def test_api_token_disable(async_mpt_vendor, created_api_token): async def test_api_token_disable_not_found(async_mpt_vendor, invalid_api_token_id): + """Test disabling an API token with an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_vendor.accounts.api_tokens.disable(invalid_api_token_id) async def test_api_token_enable(async_mpt_vendor, created_api_token): + """Test enabling an API token.""" await async_mpt_vendor.accounts.api_tokens.disable(created_api_token.id) result = await async_mpt_vendor.accounts.api_tokens.enable(created_api_token.id) @@ -109,5 +118,6 @@ async def test_api_token_enable(async_mpt_vendor, created_api_token): async def test_api_token_enable_not_found(async_mpt_vendor, invalid_api_token_id): + """Test enabling an API token with an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_vendor.accounts.api_tokens.enable(invalid_api_token_id) diff --git a/tests/e2e/accounts/api_tokens/test_sync_api_tokens.py b/tests/e2e/accounts/api_tokens/test_sync_api_tokens.py index 43884e7e..73f5ccf5 100644 --- a/tests/e2e/accounts/api_tokens/test_sync_api_tokens.py +++ b/tests/e2e/accounts/api_tokens/test_sync_api_tokens.py @@ -8,6 +8,7 @@ @pytest.fixture def created_api_token(mpt_vendor, api_token_factory): + """Fixture to create and yield an API token for testing.""" new_api_token_request_data = api_token_factory() created_api_token = mpt_vendor.accounts.api_tokens.create(new_api_token_request_data) @@ -26,6 +27,7 @@ def test_get_api_token_by_id(mpt_vendor, api_token_id): def test_list_api_tokens(mpt_vendor): + """Test listing API tokens with a limit.""" limit = 10 result = mpt_vendor.accounts.api_tokens.fetch_page(limit=limit) @@ -34,11 +36,13 @@ def test_list_api_tokens(mpt_vendor): def test_get_api_token_by_id_not_found(mpt_vendor, invalid_api_token_id): + """Test retrieving an API token by an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_vendor.accounts.api_tokens.get(invalid_api_token_id) def test_filter_api_tokens(mpt_vendor, api_token_id): + """Test filtering API tokens with specific criteria.""" select_fields = ["-name"] filtered_api_tokens = ( mpt_vendor.accounts.api_tokens.filter(RQLQuery(id=api_token_id)) @@ -62,11 +66,13 @@ def test_delete_api_token(mpt_vendor, created_api_token): def test_delete_api_token_not_found(mpt_vendor, invalid_api_token_id): + """Test deleting an API token with an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_vendor.accounts.api_tokens.delete(invalid_api_token_id) def test_update_api_token(mpt_vendor, api_token_factory, created_api_token): + """Test updating an API token.""" updated_api_token_data = api_token_factory(name="E2E Updated API Token") result = mpt_vendor.accounts.api_tokens.update(created_api_token.id, updated_api_token_data) @@ -75,6 +81,7 @@ def test_update_api_token(mpt_vendor, api_token_factory, created_api_token): def test_update_api_token_not_found(mpt_vendor, api_token_factory, invalid_api_token_id): + """Test updating an API token with an invalid ID, expecting a 404 error.""" updated_api_token_data = api_token_factory(name="Nonexistent API Token") with pytest.raises(MPTAPIError, match=r"404 Not Found"): @@ -88,11 +95,13 @@ def test_api_token_disable(mpt_vendor, created_api_token): def test_api_token_disable_not_found(mpt_vendor, invalid_api_token_id): + """Test disabling an API token with an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_vendor.accounts.api_tokens.disable(invalid_api_token_id) def test_api_token_enable(mpt_vendor, created_api_token): + """Test enabling an API token.""" mpt_vendor.accounts.api_tokens.disable(created_api_token.id) result = mpt_vendor.accounts.api_tokens.enable(created_api_token.id) @@ -101,5 +110,6 @@ def test_api_token_enable(mpt_vendor, created_api_token): def test_api_token_enable_not_found(mpt_vendor, invalid_api_token_id): + """Test enabling an API token with an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_vendor.accounts.api_tokens.enable(invalid_api_token_id) diff --git a/tests/e2e/accounts/buyers/test_async_buyers.py b/tests/e2e/accounts/buyers/test_async_buyers.py index ebb61a57..388ae955 100644 --- a/tests/e2e/accounts/buyers/test_async_buyers.py +++ b/tests/e2e/accounts/buyers/test_async_buyers.py @@ -8,13 +8,14 @@ @pytest.fixture async def async_created_buyer(async_mpt_ops, buyer_factory, buyer_account_id, account_icon): + """Fixture to create and yield an asynchronous buyer for testing.""" new_buyer_request_data = buyer_factory( name="E2E Created Buyer", account_id=buyer_account_id, ) new_buyer = await async_mpt_ops.accounts.buyers.create( - new_buyer_request_data, logo=account_icon + new_buyer_request_data, file=account_icon ) yield new_buyer @@ -32,6 +33,7 @@ async def test_get_buyer_by_id(async_mpt_ops, buyer_id): async def test_list_buyers(async_mpt_ops): + """Test listing buyers with a limit.""" limit = 10 result = await async_mpt_ops.accounts.buyers.fetch_page(limit=limit) @@ -40,11 +42,13 @@ async def test_list_buyers(async_mpt_ops): async def test_get_buyer_by_id_not_found(async_mpt_ops, invalid_buyer_id): + """Test fetching a buyer by an invalid ID raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.buyers.get(invalid_buyer_id) async def test_filter_buyers(async_mpt_ops, buyer_id): + """Test filtering buyers using RQL asynchronously.""" select_fields = ["-address"] async_filtered_buyers = ( async_mpt_ops.accounts.buyers.filter(RQLQuery(id=buyer_id)) @@ -68,6 +72,7 @@ async def test_delete_buyer(async_mpt_ops, async_created_buyer): async def test_delete_buyer_not_found(async_mpt_ops, invalid_buyer_id): + """Test deleting a non-existent buyer raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.buyers.delete(invalid_buyer_id) @@ -75,10 +80,11 @@ async def test_delete_buyer_not_found(async_mpt_ops, invalid_buyer_id): async def test_update_buyer( async_mpt_ops, buyer_factory, buyer_account_id, account_icon, async_created_buyer ): + """Test updating a buyer asynchronously.""" updated_buyer_data = buyer_factory(name="E2E Updated Buyer", account_id=buyer_account_id) result = await async_mpt_ops.accounts.buyers.update( - async_created_buyer.id, updated_buyer_data, logo=account_icon + async_created_buyer.id, updated_buyer_data, file=account_icon ) assert result is not None @@ -87,11 +93,12 @@ async def test_update_buyer( async def test_update_buyer_not_found( async_mpt_ops, buyer_factory, buyer_account_id, account_icon, invalid_buyer_id ): + """Test updating a non-existent buyer raises a 404 error.""" updated_buyer_data = buyer_factory(name="Nonexistent Buyer", account_id=buyer_account_id) with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.buyers.update( - invalid_buyer_id, updated_buyer_data, logo=account_icon + invalid_buyer_id, updated_buyer_data, file=account_icon ) @@ -102,11 +109,13 @@ async def test_buyer_disable(async_mpt_ops, async_created_buyer): async def test_buyer_disable_not_found(async_mpt_ops, invalid_buyer_id): + """Test disabling a non-existent buyer raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.buyers.disable(invalid_buyer_id) async def test_buyer_enable(async_mpt_ops, async_created_buyer): + """Test enabling a buyer asynchronously.""" await async_mpt_ops.accounts.buyers.disable(async_created_buyer.id) result = await async_mpt_ops.accounts.buyers.enable(async_created_buyer.id) @@ -115,5 +124,6 @@ async def test_buyer_enable(async_mpt_ops, async_created_buyer): async def test_buyer_enable_not_found(async_mpt_ops, invalid_buyer_id): + """Test enabling a non-existent buyer raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.buyers.enable(invalid_buyer_id) diff --git a/tests/e2e/accounts/buyers/test_sync_buyers.py b/tests/e2e/accounts/buyers/test_sync_buyers.py index a3bbd55f..b7acc44c 100644 --- a/tests/e2e/accounts/buyers/test_sync_buyers.py +++ b/tests/e2e/accounts/buyers/test_sync_buyers.py @@ -8,12 +8,13 @@ @pytest.fixture def created_buyer(mpt_ops, buyer_factory, buyer_account_id, account_icon): + """Fixture to create and yield a buyer for testing.""" new_buyer_request_data = buyer_factory( name="E2E Created Buyer", account_id=buyer_account_id, ) - new_buyer = mpt_ops.accounts.buyers.create(new_buyer_request_data, logo=account_icon) + new_buyer = mpt_ops.accounts.buyers.create(new_buyer_request_data, file=account_icon) yield new_buyer @@ -30,6 +31,7 @@ def test_get_buyer_by_id(mpt_ops, buyer_id): def test_list_buyers(mpt_ops): + """Test listing buyers with a limit.""" limit = 10 result = mpt_ops.accounts.buyers.fetch_page(limit=limit) @@ -38,11 +40,13 @@ def test_list_buyers(mpt_ops): def test_get_buyer_by_id_not_found(mpt_ops, invalid_buyer_id): + """Test fetching a buyer by an invalid ID raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.buyers.get(invalid_buyer_id) def test_filter_buyers(mpt_ops, buyer_id): + """Test filtering buyers using RQL synchronously.""" select_fields = ["-address"] filtered_buyers = ( mpt_ops.accounts.buyers.filter(RQLQuery(id=buyer_id)) @@ -66,14 +70,16 @@ def test_delete_buyer(mpt_ops, created_buyer): def test_delete_buyer_not_found(mpt_ops, invalid_buyer_id): + """Test deleting a non-existent buyer raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.buyers.delete(invalid_buyer_id) def test_update_buyer(mpt_ops, buyer_factory, buyer_account_id, account_icon, created_buyer): + """Test updating a buyer synchronously.""" updated_buyer_data = buyer_factory(name="E2E Updated Buyer", account_id=buyer_account_id) - result = mpt_ops.accounts.buyers.update(created_buyer.id, updated_buyer_data, logo=account_icon) + result = mpt_ops.accounts.buyers.update(created_buyer.id, updated_buyer_data, file=account_icon) assert result is not None @@ -81,10 +87,11 @@ def test_update_buyer(mpt_ops, buyer_factory, buyer_account_id, account_icon, cr def test_update_buyer_not_found( mpt_ops, buyer_factory, buyer_account_id, account_icon, invalid_buyer_id ): + """Test updating a non-existent buyer raises a 404 error.""" updated_buyer_data = buyer_factory(name="Nonexistent Buyer", account_id=buyer_account_id) with pytest.raises(MPTAPIError, match=r"404 Not Found"): - mpt_ops.accounts.buyers.update(invalid_buyer_id, updated_buyer_data, logo=account_icon) + mpt_ops.accounts.buyers.update(invalid_buyer_id, updated_buyer_data, file=account_icon) def test_buyer_disable(mpt_ops, created_buyer): @@ -94,11 +101,13 @@ def test_buyer_disable(mpt_ops, created_buyer): def test_buyer_disable_not_found(mpt_ops, invalid_buyer_id): + """Test disabling a non-existent buyer raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.buyers.disable(invalid_buyer_id) def test_buyer_enable(mpt_ops, created_buyer): + """Test enabling a buyer synchronously.""" mpt_ops.accounts.buyers.disable(created_buyer.id) result = mpt_ops.accounts.buyers.enable(created_buyer.id) @@ -107,5 +116,6 @@ def test_buyer_enable(mpt_ops, created_buyer): def test_buyer_enable_not_found(mpt_ops, invalid_buyer_id): + """Test enabling a non-existent buyer raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.buyers.enable(invalid_buyer_id) diff --git a/tests/e2e/accounts/licensees/test_async_licensees.py b/tests/e2e/accounts/licensees/test_async_licensees.py index 731c0c9f..6c31ff43 100644 --- a/tests/e2e/accounts/licensees/test_async_licensees.py +++ b/tests/e2e/accounts/licensees/test_async_licensees.py @@ -8,10 +8,11 @@ @pytest.fixture async def async_created_licensee(async_mpt_client, licensee_factory, account_icon): + """Fixture to create and yield an asynchronous licensee for testing.""" new_licensee_request_data = licensee_factory(name="E2E Created licensee") new_licensee = await async_mpt_client.accounts.licensees.create( - new_licensee_request_data, logo=account_icon + new_licensee_request_data, file=account_icon ) yield new_licensee @@ -29,6 +30,7 @@ async def test_get_licensee_by_id(async_mpt_client, licensee_id): async def test_list_licensees(async_mpt_client): + """Test listing licensees with a limit.""" limit = 10 result = await async_mpt_client.accounts.licensees.fetch_page(limit=limit) @@ -37,11 +39,13 @@ async def test_list_licensees(async_mpt_client): async def test_get_licensee_by_id_not_found(async_mpt_client, invalid_licensee_id): + """Test fetching a licensee by an invalid ID raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_client.accounts.licensees.get(invalid_licensee_id) async def test_filter_licensees(async_mpt_client, licensee_id): + """Test filtering licensees using RQL asynchronously.""" select_fields = ["-address"] async_filtered_licensees = ( async_mpt_client.accounts.licensees.filter(RQLQuery(id=licensee_id)) @@ -61,10 +65,12 @@ def test_create_licensee(async_created_licensee): async def test_delete_licensee(async_mpt_client, async_created_licensee): + """Test deleting a licensee asynchronously.""" await async_mpt_client.accounts.licensees.delete(async_created_licensee.id) async def test_delete_licensee_not_found(async_mpt_client, invalid_licensee_id): + """Test deleting a non-existent licensee raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_client.accounts.licensees.delete(invalid_licensee_id) @@ -72,10 +78,11 @@ async def test_delete_licensee_not_found(async_mpt_client, invalid_licensee_id): async def test_update_licensee( async_mpt_client, licensee_factory, account_icon, async_created_licensee ): + """Test updating a licensee asynchronously.""" updated_licensee_data = licensee_factory(name="E2E Updated Licensee") result = await async_mpt_client.accounts.licensees.update( - async_created_licensee.id, updated_licensee_data, logo=account_icon + async_created_licensee.id, updated_licensee_data, file=account_icon ) assert result is not None @@ -84,11 +91,12 @@ async def test_update_licensee( async def test_update_licensee_not_found( async_mpt_client, licensee_factory, account_icon, invalid_licensee_id ): + """Test updating a non-existent licensee raises a 404 error.""" updated_licensee_data = licensee_factory(name="Nonexistent Licensee") with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_client.accounts.licensees.update( - invalid_licensee_id, updated_licensee_data, logo=account_icon + invalid_licensee_id, updated_licensee_data, file=account_icon ) @@ -99,11 +107,13 @@ async def test_licensee_disable(async_mpt_client, async_created_licensee): async def test_licensee_disable_not_found(async_mpt_client, invalid_licensee_id): + """Test disabling a non-existent licensee raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_client.accounts.licensees.disable(invalid_licensee_id) async def test_licensee_enable(async_mpt_client, async_created_licensee): + """Test enabling a licensee asynchronously.""" await async_mpt_client.accounts.licensees.disable(async_created_licensee.id) result = await async_mpt_client.accounts.licensees.enable(async_created_licensee.id) @@ -112,5 +122,6 @@ async def test_licensee_enable(async_mpt_client, async_created_licensee): async def test_licensee_enable_not_found(async_mpt_client, invalid_licensee_id): + """Test enabling a non-existent licensee raises a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_client.accounts.licensees.enable(invalid_licensee_id) diff --git a/tests/e2e/accounts/licensees/test_sync_licensees.py b/tests/e2e/accounts/licensees/test_sync_licensees.py index c89d5492..45450068 100644 --- a/tests/e2e/accounts/licensees/test_sync_licensees.py +++ b/tests/e2e/accounts/licensees/test_sync_licensees.py @@ -11,7 +11,7 @@ def created_licensee(mpt_client, licensee_factory, account_icon): new_licensee_request_data = licensee_factory(name="E2E Created licensee") new_licensee = mpt_client.accounts.licensees.create( - new_licensee_request_data, logo=account_icon + new_licensee_request_data, file=account_icon ) yield new_licensee @@ -73,7 +73,7 @@ def test_update_licensee(mpt_client, licensee_factory, account_icon, created_lic updated_licensee_data = licensee_factory(name="E2E Updated Licensee") result = mpt_client.accounts.licensees.update( - created_licensee.id, updated_licensee_data, logo=account_icon + created_licensee.id, updated_licensee_data, file=account_icon ) assert result is not None @@ -84,7 +84,7 @@ def test_update_licensee_not_found(mpt_client, licensee_factory, account_icon, i with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_client.accounts.licensees.update( - invalid_licensee_id, updated_licensee_data, logo=account_icon + invalid_licensee_id, updated_licensee_data, file=account_icon ) diff --git a/tests/e2e/accounts/user_groups/test_async_user_groups.py b/tests/e2e/accounts/user_groups/test_async_user_groups.py index d93d85c7..4dc4cb1c 100644 --- a/tests/e2e/accounts/user_groups/test_async_user_groups.py +++ b/tests/e2e/accounts/user_groups/test_async_user_groups.py @@ -8,6 +8,7 @@ @pytest.fixture async def created_user_group(async_mpt_ops, user_group_factory): + """Fixture to create and yield an asynchronous user group for testing.""" new_user_group_request_data = user_group_factory() created_user_group = await async_mpt_ops.accounts.user_groups.create( new_user_group_request_data @@ -22,11 +23,13 @@ async def created_user_group(async_mpt_ops, user_group_factory): async def test_get_user_group_by_id(async_mpt_ops, user_group_id): + """Test retrieving a user group by its ID.""" user_group = await async_mpt_ops.accounts.user_groups.get(user_group_id) assert user_group is not None async def test_list_user_groups(async_mpt_ops): + """Test listing user groups with a limit.""" limit = 10 result = await async_mpt_ops.accounts.user_groups.fetch_page(limit=limit) @@ -35,11 +38,13 @@ async def test_list_user_groups(async_mpt_ops): async def test_get_user_group_by_id_not_found(async_mpt_ops, invalid_user_group_id): + """Test retrieving a user group by an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.user_groups.get(invalid_user_group_id) async def test_filter_user_groups(async_mpt_ops, user_group_id): + """Test filtering user groups with specific criteria.""" select_fields = ["-name"] filtered_user_groups = ( async_mpt_ops.accounts.user_groups.filter(RQLQuery(id=user_group_id)) @@ -63,11 +68,13 @@ async def test_delete_user_group(async_mpt_ops, created_user_group): async def test_delete_user_group_not_found(async_mpt_ops, invalid_user_group_id): + """Test deleting a user group with an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): await async_mpt_ops.accounts.user_groups.delete(invalid_user_group_id) async def test_update_user_group(async_mpt_ops, user_group_factory, created_user_group): + """Test updating a user group.""" updated_user_group_data = user_group_factory(name="E2E Updated User Group") result = await async_mpt_ops.accounts.user_groups.update( @@ -80,6 +87,7 @@ async def test_update_user_group(async_mpt_ops, user_group_factory, created_user async def test_update_user_group_not_found( async_mpt_ops, user_group_factory, invalid_user_group_id ): + """Test updating a user group with an invalid ID, expecting a 404 error.""" updated_user_group_data = user_group_factory(name="Nonexistent User Group") with pytest.raises(MPTAPIError, match=r"404 Not Found"): diff --git a/tests/e2e/accounts/user_groups/test_sync_user_groups.py b/tests/e2e/accounts/user_groups/test_sync_user_groups.py index 228a1147..e1c45514 100644 --- a/tests/e2e/accounts/user_groups/test_sync_user_groups.py +++ b/tests/e2e/accounts/user_groups/test_sync_user_groups.py @@ -8,6 +8,7 @@ @pytest.fixture def created_user_group(mpt_ops, user_group_factory): + """Fixture to create and yield a user group for testing.""" new_user_group_request_data = user_group_factory() created_user_group = mpt_ops.accounts.user_groups.create(new_user_group_request_data) @@ -26,6 +27,7 @@ def test_get_user_group_by_id(mpt_ops, user_group_id): def test_list_user_groups(mpt_ops): + """Test listing user groups with a limit.""" limit = 10 result = mpt_ops.accounts.user_groups.fetch_page(limit=limit) @@ -34,11 +36,13 @@ def test_list_user_groups(mpt_ops): def test_get_user_group_by_id_not_found(mpt_ops, invalid_user_group_id): + """Test retrieving a user group by an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.user_groups.get(invalid_user_group_id) def test_filter_user_groups(mpt_ops, user_group_id): + """Test filtering user groups with specific criteria.""" select_fields = ["-name"] filtered_user_groups = ( mpt_ops.accounts.user_groups.filter(RQLQuery(id=user_group_id)) @@ -62,11 +66,13 @@ def test_delete_user_group(mpt_ops, created_user_group): def test_delete_user_group_not_found(mpt_ops, invalid_user_group_id): + """Test deleting a user group with an invalid ID, expecting a 404 error.""" with pytest.raises(MPTAPIError, match=r"404 Not Found"): mpt_ops.accounts.user_groups.delete(invalid_user_group_id) def test_update_user_group(mpt_ops, user_group_factory, created_user_group): + """Test updating a user group.""" updated_user_group_data = user_group_factory(name="E2E Updated User Group") result = mpt_ops.accounts.user_groups.update(created_user_group.id, updated_user_group_data) @@ -75,6 +81,7 @@ def test_update_user_group(mpt_ops, user_group_factory, created_user_group): def test_update_user_group_not_found(mpt_ops, user_group_factory, invalid_user_group_id): + """Test updating a user group with an invalid ID, expecting a 404 error.""" updated_user_group_data = user_group_factory(name="Nonexistent User Group") with pytest.raises(MPTAPIError, match=r"404 Not Found"): diff --git a/tests/e2e/catalog/product/test_async_product.py b/tests/e2e/catalog/product/test_async_product.py index 842c2bcd..a203e14c 100644 --- a/tests/e2e/catalog/product/test_async_product.py +++ b/tests/e2e/catalog/product/test_async_product.py @@ -6,7 +6,7 @@ @pytest.fixture async def async_created_product(async_mpt_vendor, product_data, logo_fd): - product = await async_mpt_vendor.catalog.products.create(product_data, icon=logo_fd) + product = await async_mpt_vendor.catalog.products.create(product_data, file=logo_fd) yield product @@ -30,7 +30,7 @@ async def test_update_product(async_mpt_vendor, async_created_product, logo_fd): result = await async_mpt_vendor.catalog.products.update( async_created_product.id, update_data, - icon=logo_fd, + file=logo_fd, ) assert result.name == update_data["name"] diff --git a/tests/e2e/catalog/product/test_sync_product.py b/tests/e2e/catalog/product/test_sync_product.py index a42e14e0..156ed6a5 100644 --- a/tests/e2e/catalog/product/test_sync_product.py +++ b/tests/e2e/catalog/product/test_sync_product.py @@ -6,7 +6,7 @@ @pytest.fixture def created_product(mpt_vendor, product_data, logo_fd): - product = mpt_vendor.catalog.products.create(product_data, icon=logo_fd) + product = mpt_vendor.catalog.products.create(product_data, file=logo_fd) yield product @@ -27,7 +27,7 @@ def test_create_product(created_product, product_data): def test_update_product(mpt_vendor, created_product, logo_fd): update_data = {"name": "Updated Product"} - result = mpt_vendor.catalog.products.update(created_product.id, update_data, icon=logo_fd) + result = mpt_vendor.catalog.products.update(created_product.id, update_data, file=logo_fd) assert result.name == update_data["name"] diff --git a/tests/unit/http/test_mixins.py b/tests/unit/http/test_mixins.py index a2427d41..03cc897e 100644 --- a/tests/unit/http/test_mixins.py +++ b/tests/unit/http/test_mixins.py @@ -9,16 +9,15 @@ from mpt_api_client.exceptions import MPTAPIError from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( - AsyncCreateWithIconMixin, + AsyncFilesOperationsMixin, AsyncManagedResourceMixin, AsyncModifiableResourceMixin, - AsyncUpdateWithIconMixin, - CreateWithIconMixin, + AsyncUpdateFileMixin, + FilesOperationsMixin, ManagedResourceMixin, ModifiableResourceMixin, - UpdateWithIconMixin, + UpdateFileMixin, ) -from mpt_api_client.http.types import FileTypes from mpt_api_client.resources.catalog.products_media import ( AsyncMediaService, MediaService, @@ -26,109 +25,86 @@ from tests.unit.conftest import DummyModel -@pytest.fixture -def media_service(http_client): - return MediaService(http_client=http_client, endpoint_params={"product_id": "PRD-001"}) - - -@pytest.fixture -def async_media_service(async_http_client): - return AsyncMediaService( - http_client=async_http_client, endpoint_params={"product_id": "PRD-001"} - ) - +class DummyFileOperationsService( + FilesOperationsMixin[DummyModel], + Service[DummyModel], +): + _endpoint = "/public/v1/dummy/file-ops/" + _model_class = DummyModel + _collection_key = "data" -@pytest.fixture -def icon_service(http_client): - return DummyIconService(http_client=http_client) +class DummyAsyncFileOperationsService( + AsyncFilesOperationsMixin[DummyModel], + AsyncService[DummyModel], +): + """Dummy asynchronous file operations service for testing.""" -@pytest.fixture -def async_icon_service(async_http_client): - return AsyncDummyIconService(http_client=async_http_client) + _endpoint = "/public/v1/dummy/file-ops/" + _model_class = DummyModel + _collection_key = "data" -class DummyIconService( - CreateWithIconMixin[DummyModel], - UpdateWithIconMixin[DummyModel], +class DummyUpdateFileService( + UpdateFileMixin[DummyModel], Service[DummyModel], ): - _endpoint = "/public/v1/dummy/icon/" + _endpoint = "/public/v1/dummy/update-file" _model_class = DummyModel _collection_key = "data" + _upload_file_key = "file" + _upload_data_key = "document" - def create( - self, - resource_data: dict, - icon: FileTypes, - icon_key: str = "icon", - data_key: str = "data", - ) -> DummyModel: - return super().create( - resource_data=resource_data, - icon=icon, - icon_key=icon_key, - data_key=data_key, - ) - def update( - self, - resource_id: str, - resource_data: dict, - icon: FileTypes, - icon_key: str = "icon", - data_key: str = "data", - ) -> DummyModel: - return super().update( - resource_id=resource_id, - resource_data=resource_data, - icon=icon, - icon_key=icon_key, - data_key=data_key, - ) - - -class AsyncDummyIconService( - AsyncCreateWithIconMixin[DummyModel], - AsyncUpdateWithIconMixin[DummyModel], +class DummyAsyncUpdateFileService( + AsyncUpdateFileMixin[DummyModel], AsyncService[DummyModel], ): - _endpoint = "/public/v1/dummy/icon/" + _endpoint = "/public/v1/dummy/update-file" _model_class = DummyModel _collection_key = "data" + _upload_file_key = "file" + _upload_data_key = "document" - async def create( - self, - resource_data: dict, - icon: FileTypes, - icon_key: str = "icon", - data_key: str = "data", - ) -> DummyModel: - return await super().create( - resource_data=resource_data, - icon=icon, - icon_key=icon_key, - data_key=data_key, - ) - async def update( - self, - resource_id: str, - resource_data: dict, - icon: FileTypes, - icon_key: str = "icon", - data_key: str = "data", - ) -> DummyModel: - return await super().update( - resource_id=resource_id, - resource_data=resource_data, - icon=icon, - icon_key=icon_key, - data_key=data_key, - ) +@pytest.fixture +def dummy_file_operations_service(http_client): + """Fixture for DummyFileOperationsService.""" + return DummyFileOperationsService(http_client=http_client) + + +@pytest.fixture +def async_dummy_file_operations_service(async_http_client): + """Fixture for DummyAsyncFileOperationsService.""" + return DummyAsyncFileOperationsService(http_client=async_http_client) + + +@pytest.fixture +def media_service(http_client): + """Fixture for MediaService.""" + return MediaService(http_client=http_client, endpoint_params={"product_id": "PRD-001"}) + + +@pytest.fixture +def async_media_service(async_http_client): + """Fixture for AsyncMediaService.""" + return AsyncMediaService( + http_client=async_http_client, endpoint_params={"product_id": "PRD-001"} + ) -async def test_async_create_mixin(async_dummy_service): # noqa: WPS210 +@pytest.fixture +def update_file_service(http_client): + return DummyUpdateFileService(http_client=http_client) + + +@pytest.fixture +def async_update_file_service(async_http_client): + return DummyAsyncUpdateFileService(http_client=async_http_client) + + +async def test_async_create_mixin(async_dummy_service): + """Test creating a resource asynchronously.""" resource_data = {"name": "Test Resource", "status": "active"} new_resource_data = {"id": "new-resource-id", "name": "Test Resource", "status": "active"} create_response = httpx.Response(httpx.codes.OK, json=new_resource_data) @@ -147,7 +123,8 @@ async def test_async_create_mixin(async_dummy_service): # noqa: WPS210 assert json.loads(request.content.decode()) == resource_data -async def test_async_delete_mixin(async_dummy_service): # noqa: WPS210 +async def test_async_delete_mixin(async_dummy_service): + """Test deleting a resource asynchronously.""" delete_response = httpx.Response(httpx.codes.NO_CONTENT, json=None) with respx.mock: mock_route = respx.delete("https://api.example.com/api/v1/test/RES-123").mock( @@ -159,7 +136,8 @@ async def test_async_delete_mixin(async_dummy_service): # noqa: WPS210 assert mock_route.call_count == 1 -async def test_async_update_resource(async_dummy_service): # noqa: WPS210 +async def test_async_update_resource(async_dummy_service): + """Test updating a resource asynchronously.""" resource_data = {"name": "Test Resource", "status": "active"} update_response = httpx.Response(httpx.codes.OK, json=resource_data) with respx.mock: @@ -174,7 +152,8 @@ async def test_async_update_resource(async_dummy_service): # noqa: WPS210 assert json.loads(request.content.decode()) == resource_data -def test_sync_create_mixin(dummy_service): # noqa: WPS210 +def test_sync_create_mixin(dummy_service): + """Test creating a resource synchronously.""" resource_data = {"name": "Test Resource", "status": "active"} new_resource_data = {"id": "new-resource-id", "name": "Test Resource", "status": "active"} create_response = httpx.Response(httpx.codes.OK, json=new_resource_data) @@ -194,6 +173,7 @@ def test_sync_create_mixin(dummy_service): # noqa: WPS210 def test_sync_delete_mixin(dummy_service): + """Test deleting a resource synchronously.""" delete_response = httpx.Response(httpx.codes.NO_CONTENT, json=None) with respx.mock: mock_route = respx.delete("https://api.example.com/api/v1/test/RES-123").mock( @@ -206,6 +186,7 @@ def test_sync_delete_mixin(dummy_service): def test_sync_update_resource(dummy_service): + """Test updating a resource synchronously.""" resource_data = {"name": "Test Resource", "status": "active"} update_response = httpx.Response(httpx.codes.OK, json=resource_data) with respx.mock: @@ -221,6 +202,7 @@ def test_sync_update_resource(dummy_service): async def test_async_file_create_with_data(async_media_service): + """Test creating a file resource asynchronously with additional data.""" media_data = {"id": "MED-133"} with respx.mock: mock_route = respx.post( @@ -251,6 +233,7 @@ async def test_async_file_create_with_data(async_media_service): async def test_async_file_create_no_data(async_media_service): + """Test creating a file resource asynchronously without additional data.""" media_data = {"id": "MED-133"} with respx.mock: mock_route = respx.post( @@ -275,9 +258,9 @@ async def test_async_file_create_no_data(async_media_service): assert new_media.to_dict() == media_data -async def test_async_file_download(async_media_service): # noqa: WPS218 +async def test_async_file_download_request_and_headers(async_media_service): + """Test request/response and headers for async file download.""" media_content = b"Image file content or binary data" - with respx.mock: mock_resource = respx.get( "https://api.example.com/public/v1/catalog/products/PRD-001/media/MED-456", @@ -285,9 +268,7 @@ async def test_async_file_download(async_media_service): # noqa: WPS218 ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, - headers={ - "content-type": "application/json", - }, + headers={"content-type": "application/json"}, json={"id": "MED-456", "name": "Product image", "content_type": "image/jpg"}, ) ) @@ -304,35 +285,103 @@ async def test_async_file_download(async_media_service): # noqa: WPS218 content=media_content, ) ) + # Act + await async_media_service.download("MED-456") + + assert mock_resource.call_count == 1 + request = mock_download.calls[0].request + accept_header = (b"Accept", b"image/jpg") + assert accept_header in request.headers.raw + assert mock_download.call_count == 1 + +async def test_async_file_download_content_and_metadata(async_media_service): + """Test file content and metadata for async file download.""" + media_content = b"Image file content or binary data" + with respx.mock: + respx.get( + "https://api.example.com/public/v1/catalog/products/PRD-001/media/MED-456", + headers={"Accept": "application/json"}, + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json={"id": "MED-456", "name": "Product image", "content_type": "image/jpg"}, + ) + ) + respx.get( + "https://api.example.com/public/v1/catalog/products/PRD-001/media/MED-456", + headers={"Accept": "image/jpg"}, + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={ + "content-type": "image/jpg", + "content-disposition": 'form-data; name="file"; filename="product_image.jpg"', + }, + content=media_content, + ) + ) result = await async_media_service.download("MED-456") - assert mock_resource.call_count == 1 - request = mock_download.calls[0].request - accept_header = (b"Accept", b"image/jpg") - assert accept_header in request.headers.raw - assert mock_download.call_count == 1 - assert result.file_contents == media_content - assert result.content_type == "image/jpg" - assert result.filename == "product_image.jpg" + assert result.file_contents == media_content + assert result.content_type == "image/jpg" + assert result.filename == "product_image.jpg" -def test_sync_file_download(media_service): # noqa: WPS218 +def test_sync_file_download_request_and_headers(media_service): + """Test request/response and headers for sync file download.""" media_content = b"Image file content or binary data" with respx.mock: mock_resource = respx.get( "https://api.example.com/public/v1/catalog/products/PRD-001/media/MED-456", headers={"Accept": "application/json"}, + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json={"id": "MED-456", "name": "Product image", "content_type": "image/jpg"}, + ) + ) + mock_download = respx.get( + "https://api.example.com/public/v1/catalog/products/PRD-001/media/MED-456", + headers={"Accept": "image/jpg"}, ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, headers={ - "content-type": "application/json", + "content-type": "image/jpg", + "content-disposition": 'form-data; name="file"; filename="product_image.jpg"', }, + content=media_content, + ) + ) + media_service.download("MED-456") + assert mock_resource.call_count == 1 + + result = mock_download.calls[0].request + + # Assert + accept_header = (b"Accept", b"image/jpg") + assert accept_header in result.headers.raw + assert mock_download.call_count == 1 + + +def test_sync_file_download_content_and_metadata(media_service): + """Test file content and metadata for sync file download.""" + media_content = b"Image file content or binary data" + with respx.mock: + respx.get( + "https://api.example.com/public/v1/catalog/products/PRD-001/media/MED-456", + headers={"Accept": "application/json"}, + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, json={"id": "MED-456", "name": "Product image", "content_type": "image/jpg"}, ) ) - mock_download = respx.get( + respx.get( "https://api.example.com/public/v1/catalog/products/PRD-001/media/MED-456", headers={"Accept": "image/jpg"}, ).mock( @@ -348,17 +397,13 @@ def test_sync_file_download(media_service): # noqa: WPS218 result = media_service.download("MED-456") - assert mock_resource.call_count == 1 - request = mock_download.calls[0].request - accept_header = (b"Accept", b"image/jpg") - assert accept_header in request.headers.raw - assert mock_download.call_count == 1 - assert result.file_contents == media_content - assert result.content_type == "image/jpg" - assert result.filename == "product_image.jpg" + assert result.file_contents == media_content + assert result.content_type == "image/jpg" + assert result.filename == "product_image.jpg" def test_sync_file_create_with_data(media_service): + """Test creating a file resource synchronously with additional data.""" media_data = {"id": "MED-133"} with respx.mock: mock_route = respx.post( @@ -389,6 +434,7 @@ def test_sync_file_create_with_data(media_service): def test_sync_file_create_no_data(media_service): + """Test creating a file resource synchronously without additional data.""" media_data = {"id": "MED-133"} with respx.mock: mock_route = respx.post( @@ -420,6 +466,7 @@ def test_sync_file_create_no_data(media_service): ], ) def test_sync_get_mixin(dummy_service, select_value): + """Test getting a resource synchronously with different select parameter formats.""" resource_data = {"id": "RES-123", "name": "Test Resource"} with respx.mock: mock_route = respx.get( @@ -435,6 +482,7 @@ def test_sync_get_mixin(dummy_service, select_value): async def test_async_get(async_dummy_service): + """Test getting a resource asynchronously with a list select parameter.""" resource_data = {"id": "RES-123", "name": "Test Resource"} with respx.mock: mock_route = respx.get( @@ -450,6 +498,7 @@ async def test_async_get(async_dummy_service): async def test_async_get_select_str(async_dummy_service): + """Test getting a resource asynchronously with a string select parameter.""" resource_data = {"id": "RES-123", "name": "Test Resource"} with respx.mock: mock_route = respx.get( @@ -475,6 +524,7 @@ def test_queryable_mixin_order_by(dummy_service): def test_queryable_mixin_order_by_exception(dummy_service): + """Test that setting order_by multiple times raises an exception.""" ordered_service = dummy_service.order_by("created") with pytest.raises( @@ -494,6 +544,7 @@ def test_queryable_mixin_filter(dummy_service, filter_status_active): def test_queryable_mixin_filters(dummy_service): + """Test applying multiple filters to a queryable service.""" filter1 = RQLQuery(status="active") filter2 = RQLQuery(name="test") @@ -514,6 +565,7 @@ def test_queryable_mixin_select(dummy_service): def test_queryable_mixin_select_exception(dummy_service): + """Test that setting select fields multiple times raises an exception.""" selected_service = dummy_service.select("id", "name") with pytest.raises( @@ -534,6 +586,7 @@ def test_queryable_mixin_method_chaining(dummy_service, filter_status_active): def test_col_mx_fetch_one_success(dummy_service, single_result_response): + """Test fetching a single resource successfully.""" with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( return_value=single_result_response @@ -550,6 +603,7 @@ def test_col_mx_fetch_one_success(dummy_service, single_result_response): def test_col_mx_fetch_one_no_results(dummy_service, no_results_response): + """Test fetching a single resource when no results are returned.""" with respx.mock: respx.get("https://api.example.com/api/v1/test").mock(return_value=no_results_response) @@ -558,6 +612,7 @@ def test_col_mx_fetch_one_no_results(dummy_service, no_results_response): def test_col_mx_fetch_one_multiple_results(dummy_service, multiple_results_response): + """Test fetching a single resource when multiple results are returned.""" with respx.mock: respx.get("https://api.example.com/api/v1/test").mock( return_value=multiple_results_response @@ -568,6 +623,7 @@ def test_col_mx_fetch_one_multiple_results(dummy_service, multiple_results_respo def test_col_mx_fetch_one_with_filters(dummy_service, single_result_response, filter_status_active): + """Test fetching a single resource with filters applied.""" filtered_collection = ( dummy_service.filter(filter_status_active).select("id", "name").order_by("created") ) @@ -590,6 +646,7 @@ def test_col_mx_fetch_one_with_filters(dummy_service, single_result_response, fi def test_col_mx_fetch_page_with_filter(dummy_service, list_response, filter_status_active): + """Test fetching a page of resources with filters applied.""" custom_collection = ( dummy_service.filter(filter_status_active) .select("-audit", "product.agreements", "-product.agreements.product") @@ -617,6 +674,7 @@ def test_col_mx_fetch_page_with_filter(dummy_service, list_response, filter_stat def test_col_mx_iterate_single_page(dummy_service, single_page_response): + """Test iterating over a single page of resources.""" with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( return_value=single_page_response @@ -635,6 +693,7 @@ def test_col_mx_iterate_single_page(dummy_service, single_page_response): def test_col_mx_iterate_multiple_pages( dummy_service, multi_page_response_page1, multi_page_response_page2 ): + """Test iterating over multiple pages of resources.""" with respx.mock: respx.get("https://api.example.com/api/v1/test", params={"limit": 2, "offset": 0}).mock( return_value=multi_page_response_page1 @@ -653,6 +712,7 @@ def test_col_mx_iterate_multiple_pages( def test_col_mx_iterate_empty_results(dummy_service, empty_response): + """Test iterating over an empty set of resources.""" with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( return_value=empty_response @@ -665,6 +725,7 @@ def test_col_mx_iterate_empty_results(dummy_service, empty_response): def test_col_mx_iterate_no_meta(dummy_service, no_meta_response): + """Test iterating over resources when no metadata is provided.""" with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( return_value=no_meta_response @@ -679,6 +740,7 @@ def test_col_mx_iterate_no_meta(dummy_service, no_meta_response): def test_col_mx_iterate_with_filters(dummy_service, filter_status_active): + """Test iterating over resources with filters applied.""" filtered_collection = ( dummy_service.filter(filter_status_active).select("id", "name").order_by("created") ) @@ -711,6 +773,7 @@ def test_col_mx_iterate_with_filters(dummy_service, filter_status_active): def test_col_mx_iterate_lazy_evaluation(dummy_service): + """Test lazy evaluation of iterating over resources.""" response = httpx.Response( httpx.codes.OK, json={ @@ -736,6 +799,7 @@ def test_col_mx_iterate_lazy_evaluation(dummy_service): def test_col_mx_iterate_handles_api_errors(dummy_service): + """Test that API errors are handled during iteration over resources.""" with respx.mock: respx.get("https://api.example.com/api/v1/test").mock( return_value=httpx.Response( @@ -749,6 +813,7 @@ def test_col_mx_iterate_handles_api_errors(dummy_service): async def test_async_col_mx_fetch_one_success(async_dummy_service, single_result_response): + """Test fetching a single resource successfully.""" with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( return_value=single_result_response @@ -765,6 +830,7 @@ async def test_async_col_mx_fetch_one_success(async_dummy_service, single_result async def test_async_col_mx_fetch_one_no_results(async_dummy_service, no_results_response): + """Test fetching a single resource when no results are returned.""" with respx.mock: respx.get("https://api.example.com/api/v1/test").mock(return_value=no_results_response) @@ -775,6 +841,7 @@ async def test_async_col_mx_fetch_one_no_results(async_dummy_service, no_results async def test_async_col_mx_fetch_one_multiple_results( async_dummy_service, multiple_results_response ): + """Test fetching a single resource when multiple results are returned.""" with respx.mock: respx.get("https://api.example.com/api/v1/test").mock( return_value=multiple_results_response @@ -787,6 +854,7 @@ async def test_async_col_mx_fetch_one_multiple_results( async def test_async_col_mx_fetch_one_with_filters( async_dummy_service, single_result_response, filter_status_active ): + """Test fetching a single resource with filters applied.""" filtered_collection = ( async_dummy_service.filter(filter_status_active).select("id", "name").order_by("created") ) @@ -810,6 +878,7 @@ async def test_async_col_mx_fetch_one_with_filters( async def test_async_col_mx_fetch_page_with_filter( async_dummy_service, list_response, filter_status_active ) -> None: + """Test fetching a page of resources with filters applied.""" custom_collection = ( async_dummy_service.filter(filter_status_active) .select("-audit", "product.agreements", "-product.agreements.product") @@ -837,6 +906,7 @@ async def test_async_col_mx_fetch_page_with_filter( async def test_async_col_mx_iterate_single_page(async_dummy_service, single_page_response): + """Test iterating over a single page of resources.""" with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( return_value=single_page_response @@ -855,6 +925,7 @@ async def test_async_col_mx_iterate_single_page(async_dummy_service, single_page async def test_async_col_mx_iterate_multiple_pages( async_dummy_service, multi_page_response_page1, multi_page_response_page2 ): + """Test iterating over multiple pages of resources.""" with respx.mock: respx.get("https://api.example.com/api/v1/test", params={"limit": 2, "offset": 0}).mock( return_value=multi_page_response_page1 @@ -873,6 +944,7 @@ async def test_async_col_mx_iterate_multiple_pages( async def test_async_col_mx_iterate_empty_results(async_dummy_service, empty_response): + """Test iterating over an empty set of resources.""" with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( return_value=empty_response @@ -885,6 +957,7 @@ async def test_async_col_mx_iterate_empty_results(async_dummy_service, empty_res async def test_async_col_mx_iterate_no_meta(async_dummy_service, no_meta_response): + """Test iterating over resources when no metadata is provided.""" with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( return_value=no_meta_response @@ -899,6 +972,7 @@ async def test_async_col_mx_iterate_no_meta(async_dummy_service, no_meta_respons async def test_async_col_mx_iterate_with_filters(async_dummy_service, filter_status_active): + """Test iterating over resources with filters applied.""" filtered_collection = ( async_dummy_service.filter(filter_status_active).select("id", "name").order_by("created") ) @@ -931,6 +1005,7 @@ async def test_async_col_mx_iterate_with_filters(async_dummy_service, filter_sta async def test_async_col_mx_iterate_lazy_evaluation(async_dummy_service): + """Test lazy evaluation of iterating over resources.""" response = httpx.Response( httpx.codes.OK, json={ @@ -949,15 +1024,14 @@ async def test_async_col_mx_iterate_lazy_evaluation(async_dummy_service): result = async_dummy_service.iterate() - # No requests should be made until we start iterating assert mock_route.call_count == 0 - # Get first item to trigger the first request first_resource = await anext(result) assert first_resource.id == "ID-1" assert mock_route.call_count == 1 async def test_async_col_mx_iterate_handles_api_errors(async_dummy_service): + """Test that API errors are handled during iteration over resources.""" with respx.mock: respx.get("https://api.example.com/api/v1/test").mock( return_value=httpx.Response( @@ -978,10 +1052,8 @@ async def test_async_col_mx_iterate_handles_api_errors(async_dummy_service): ], ) def test_modifieable_resource_mixin(method_name): - class Service(ModifiableResourceMixin[DummyModel]): # noqa: WPS431 - """Dummy service class for testing required methods.""" - - result = Service() + """Test that ModifiableResourceMixin has the required methods.""" + result = _ModifiableResourceService() assert hasattr(result, method_name), f"ManagedResourceMixin should have {method_name} method" assert callable(getattr(result, method_name)), f"{method_name} should be callable" @@ -995,11 +1067,9 @@ class Service(ModifiableResourceMixin[DummyModel]): # noqa: WPS431 "get", ], ) -def test_async_modifiable_resource_mixin(async_dummy_service, method_name): - class Service(AsyncModifiableResourceMixin[DummyModel]): # noqa: WPS431 - """Dummy service class for testing required methods.""" - - result = Service() +def test_async_modifiable_resource_mixin(method_name): + """Test that AsyncModifiableResourceMixin has the required methods.""" + result = _AsyncModifiableResourceService() assert hasattr(result, method_name), ( f"AsyncManagedResourceMixin should have {method_name} method" @@ -1017,10 +1087,8 @@ class Service(AsyncModifiableResourceMixin[DummyModel]): # noqa: WPS431 ], ) def test_managed_resource_mixin(method_name): - class ManagedService(ManagedResourceMixin[DummyModel]): # noqa: WPS431 - """Dummy service class for testing required methods.""" - - result = ManagedService() + """Test that ManagedResourceMixin has the required methods.""" + result = _ManagedService() assert hasattr(result, method_name), f"ManagedResourceMixin should have {method_name} method" assert callable(getattr(result, method_name)), f"{method_name} should be callable" @@ -1035,11 +1103,9 @@ class ManagedService(ManagedResourceMixin[DummyModel]): # noqa: WPS431 "get", ], ) -def test_async_managed_resource_mixin(async_dummy_service, method_name): - class AsyncManagedService(AsyncManagedResourceMixin[DummyModel]): # noqa: WPS431 - """Dummy service class for testing required methods.""" - - result = AsyncManagedService() +def test_async_managed_resource_mixin(method_name): + """Test that AsyncManagedResourceMixin has the required methods.""" + result = _AsyncManagedService() assert hasattr(result, method_name), ( f"AsyncManagedResourceMixin should have {method_name} method" @@ -1047,183 +1113,167 @@ class AsyncManagedService(AsyncManagedResourceMixin[DummyModel]): # noqa: WPS43 assert callable(getattr(result, method_name)), f"{method_name} should be callable" -def test_sync_create_with_icon_with_data(icon_service): - resource_data = {"id": "OBJ-0000-0001", "name": "Icon Object"} - resource_key = "icon_data" - icon = ("icon.png", io.BytesIO(b"Icon content"), "image/png") - icon_key = "icon" +class _ModifiableResourceService(ModifiableResourceMixin[DummyModel]): + """Dummy service class for testing required methods.""" + + +class _AsyncModifiableResourceService(AsyncModifiableResourceMixin[DummyModel]): + """Dummy service class for testing required methods.""" + + +class _ManagedService(ManagedResourceMixin[DummyModel]): + """Dummy service class for testing required methods.""" + + +class _AsyncManagedService(AsyncManagedResourceMixin[DummyModel]): + """Dummy service class for testing required methods.""" + + +def test_sync_file_create_with_resource_data(dummy_file_operations_service): + """Test creating a file with resource data.""" + file_data = {"id": "FILE-123"} with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/dummy/icon/").mock( + mock_route = respx.post("https://api.example.com/public/v1/dummy/file-ops/").mock( return_value=httpx.Response( status_code=httpx.codes.OK, - json=resource_data, + json=file_data, ) ) + files = {"file": ("document.pdf", io.BytesIO(b"PDF content"), "application/pdf")} + resource_data = {"name": "Test Document"} - result = icon_service.create( - resource_data=resource_data, - icon=icon, - data_key=resource_key, - icon_key=icon_key, - ) + result = dummy_file_operations_service.create(resource_data=resource_data, files=files) - request = mock_route.calls[0].request - assert ( - b'Content-Disposition: form-data; name="icon_data"\r\n' - b"Content-Type: application/json\r\n\r\n" - b'{"id": "OBJ-0000-0001", "name": "Icon Object"}\r\n' in request.content - ) - assert ( - b'Content-Disposition: form-data; name="icon"; filename="icon.png"\r\n' - b"Content-Type: image/png\r\n\r\n" - b"Icon content\r\n" in request.content - ) - assert "multipart/form-data" in request.headers["Content-Type"] - assert result.to_dict() == resource_data + request = mock_route.calls[0].request + assert b'name="_attachment_data"' in request.content + assert b'"name":"Test Document"' in request.content + assert result.to_dict() == file_data -def test_sync_update_with_icon_with_data(icon_service): - resource_id = "OBJ-0000-0001" - resource_data = {"name": "Updated Icon Object"} - resource_key = "icon_data" - icon = ("icon.png", io.BytesIO(b"Updated icon content"), "image/png") - icon_key = "icon" +async def test_async_file_create_with_resource_data(async_dummy_file_operations_service): + """Test creating a file with resource data.""" + file_data = {"id": "FILE-123"} with respx.mock: - mock_route = respx.put(f"https://api.example.com/public/v1/dummy/icon/{resource_id}").mock( + mock_route = respx.post("https://api.example.com/public/v1/dummy/file-ops/").mock( return_value=httpx.Response( status_code=httpx.codes.OK, - json={"id": resource_id, "name": "Updated Icon Object"}, + json=file_data, ) ) - - result = icon_service.update( - resource_id, - resource_data=resource_data, - icon=icon, - data_key=resource_key, - icon_key=icon_key, + files = {"file": ("document.pdf", io.BytesIO(b"PDF content"), "application/pdf")} + resource_data = {"name": "Test Document"} + result = await async_dummy_file_operations_service.create( + resource_data=resource_data, files=files ) - - request = mock_route.calls[0].request - assert ( - b'Content-Disposition: form-data; name="icon_data"\r\n' - b"Content-Type: application/json\r\n\r\n" - b'{"name": "Updated Icon Object"}\r\n' in request.content - ) - assert ( - b'Content-Disposition: form-data; name="icon"; filename="icon.png"\r\n' - b"Content-Type: image/png\r\n\r\n" - b"Updated icon content\r\n" in request.content - ) - assert "multipart/form-data" in request.headers["Content-Type"] - assert result.to_dict() == { - "id": resource_id, - "name": "Updated Icon Object", - } + request = mock_route.calls[0].request + assert b'name="_attachment_data"' in request.content + assert b'"name":"Test Document"' in request.content + assert result.to_dict() == file_data -async def test_async_create_with_icon_no_data(async_icon_service): - resource_data = {"id": "OBJ-0000-0001", "name": "Icon Object"} +def test_sync_update_file(update_file_service): # noqa: WPS210 + resource_id = "ICON-1234" + response_expected_data = {"id": resource_id, "name": "Updated Icon Object"} + file_tuple = ("icon.png", io.BytesIO(b"PNG DATA"), "image/png") + resource_data = {"name": "Updated Icon Object"} with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/dummy/icon/").mock( + mock_route = respx.put( + f"https://api.example.com/public/v1/dummy/update-file/{resource_id}" + ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, - json=resource_data, + json=response_expected_data, ) ) - icon = ("icon.png", io.BytesIO(b"Icon content"), "image/png") - result = await async_icon_service.create(resource_data=None, icon=icon) + result = update_file_service.update(resource_id, resource_data, file=file_tuple) request = mock_route.calls[0].request + assert mock_route.call_count == 1 assert ( - b'Content-Disposition: form-data; name="icon"; filename="icon.png"\r\n' + b'Content-Disposition: form-data; name="file"; filename="icon.png"\r\n' b"Content-Type: image/png\r\n\r\n" - b"Icon content\r\n" in request.content + b"PNG DATA\r\n" in request.content ) - assert result.to_dict() == resource_data + assert "multipart/form-data" in request.headers["Content-Type"] + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) -def test_sync_create_with_icon_no_data(icon_service): - resource_data = {"id": "OBJ-0000-0001", "name": "Icon Object"} +def test_update_file_no_file(update_file_service): # noqa: WPS210 + resource_id = "ICON-1234" + response_expected_data = {"id": resource_id, "name": "Updated Icon Object"} + resource_data = {"name": "Updated Icon Object"} with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/dummy/icon/").mock( + mock_route = respx.put( + f"https://api.example.com/public/v1/dummy/update-file/{resource_id}" + ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, - json=resource_data, + json=response_expected_data, ) ) - icon = ("icon.png", io.BytesIO(b"Icon content"), "image/png") - result = icon_service.create(resource_data=None, icon=icon) + result = update_file_service.update(resource_id, resource_data) request = mock_route.calls[0].request - assert ( - b'Content-Disposition: form-data; name="icon"; filename="icon.png"\r\n' - b"Content-Type: image/png\r\n\r\n" - b"Icon content\r\n" in request.content - ) - assert result.to_dict() == resource_data + assert mock_route.call_count == 1 + 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() == response_expected_data + assert isinstance(result, DummyModel) -async def test_async_create_with_icon_with_data(async_icon_service): - resource_data = {"id": "OBJ-0000-0001", "name": "Icon Object"} +async def test_async_update_file(async_update_file_service): # noqa: WPS210 + resource_id = "ICON-1234" + response_expected_data = {"id": resource_id, "name": "Updated Icon Object"} + file_tuple = ("icon.png", io.BytesIO(b"PNG DATA"), "image/png") + resource_data = {"name": "Updated Icon Object"} + with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/dummy/icon/").mock( + mock_route = respx.put( + f"https://api.example.com/public/v1/dummy/update-file/{resource_id}" + ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, - json=resource_data, + json=response_expected_data, ) ) - icon = ("icon.png", io.BytesIO(b"Icon content"), "image/png") - - result = await async_icon_service.create(resource_data=None, icon=icon) - + # Act + result = await async_update_file_service.update(resource_id, resource_data, file=file_tuple) + assert mock_route.call_count == 1 request = mock_route.calls[0].request + assert ( - b'Content-Disposition: form-data; name="icon"; filename="icon.png"\r\n' + b'Content-Disposition: form-data; name="file"; filename="icon.png"\r\n' b"Content-Type: image/png\r\n\r\n" - b"Icon content\r\n" in request.content + b"PNG DATA\r\n" in request.content ) - assert result.to_dict() == resource_data + assert "multipart/form-data" in request.headers["Content-Type"] + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) -async def test_async_update_with_icon_with_data(async_icon_service): - resource_id = "OBJ-0000-0001" +async def test_async_update_file_no_file(async_update_file_service): # noqa: WPS210 + resource_id = "ICON-1234" + response_expected_data = {"id": resource_id, "name": "Updated Icon Object"} resource_data = {"name": "Updated Icon Object"} - resource_key = "icon_data" - icon = ("icon.png", io.BytesIO(b"Updated icon content"), "image/png") - icon_key = "icon" with respx.mock: - mock_route = respx.put(f"https://api.example.com/public/v1/dummy/icon/{resource_id}").mock( + mock_route = respx.put( + f"https://api.example.com/public/v1/dummy/update-file/{resource_id}" + ).mock( return_value=httpx.Response( status_code=httpx.codes.OK, - json={"id": resource_id, "name": "Updated Icon Object"}, + json=response_expected_data, ) ) - - result = await async_icon_service.update( - resource_id, - resource_data=resource_data, - icon=icon, - data_key=resource_key, - icon_key=icon_key, - ) - + # Act + result = await async_update_file_service.update(resource_id, resource_data) + assert mock_route.call_count == 1 request = mock_route.calls[0].request - assert ( - b'Content-Disposition: form-data; name="icon_data"\r\n' - b"Content-Type: application/json\r\n\r\n" - b'{"name": "Updated Icon Object"}\r\n' in request.content - ) - assert ( - b'Content-Disposition: form-data; name="icon"; filename="icon.png"\r\n' - b"Content-Type: image/png\r\n\r\n" - b"Updated icon content\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() == { - "id": resource_id, - "name": "Updated Icon Object", - } + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/accounts/test_account.py b/tests/unit/resources/accounts/test_account.py index a12b6c80..914e3140 100644 --- a/tests/unit/resources/accounts/test_account.py +++ b/tests/unit/resources/accounts/test_account.py @@ -89,7 +89,7 @@ def test_account_create(account_service, tmp_path): # noqa: WPS210 return_value=httpx.Response(httpx.codes.CREATED, json=account_data) ) - result = account_service.create(account_data, logo=logo_file) + result = account_service.create(account_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 @@ -110,7 +110,7 @@ def test_account_update(account_service, tmp_path): # noqa: WPS210 return_value=httpx.Response(httpx.codes.OK, json={"id": account_id, **account_data}) ) - result = account_service.update(account_id, account_data, logo=logo_file) + result = account_service.update(account_id, account_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 @@ -131,7 +131,7 @@ async def test_async_account_create(async_account_service, tmp_path): # noqa: W return_value=httpx.Response(httpx.codes.CREATED, json=account_data) ) - result = await async_account_service.create(account_data, logo=logo_file) + result = await async_account_service.create(account_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 @@ -152,7 +152,7 @@ async def test_async_account_update(async_account_service, tmp_path): # noqa: W return_value=httpx.Response(httpx.codes.OK, json={"id": account_id, **account_data}) ) - result = await async_account_service.update(account_id, account_data, logo=logo_file) + result = await async_account_service.update(account_id, account_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 diff --git a/tests/unit/resources/accounts/test_buyers.py b/tests/unit/resources/accounts/test_buyers.py index e6fe8952..0e4615b1 100644 --- a/tests/unit/resources/accounts/test_buyers.py +++ b/tests/unit/resources/accounts/test_buyers.py @@ -177,7 +177,7 @@ def test_buyers_create(buyers_service, tmp_path): # noqa: WPS210 return_value=httpx.Response(httpx.codes.CREATED, json=buyer_data) ) - result = buyers_service.create(buyer_data, logo=logo_file) + result = buyers_service.create(buyer_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 @@ -198,7 +198,7 @@ def test_buyers_update(buyers_service, tmp_path): # noqa: WPS210 return_value=httpx.Response(httpx.codes.OK, json={"id": buyer_id, **buyer_data}) ) - result = buyers_service.update(buyer_id, buyer_data, logo=logo_file) + result = buyers_service.update(buyer_id, buyer_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 @@ -219,7 +219,7 @@ async def test_async_buyers_create(async_buyers_service, tmp_path): # noqa: WPS return_value=httpx.Response(httpx.codes.CREATED, json=buyer_data) ) - result = await async_buyers_service.create(buyer_data, logo=logo_file) + result = await async_buyers_service.create(buyer_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 @@ -241,7 +241,7 @@ async def test_async_buyers_update(async_buyers_service, tmp_path): # noqa: WPS return_value=httpx.Response(httpx.codes.OK, json={"id": buyer_id, **buyer_data}) ) - result = await async_buyers_service.update(buyer_id, buyer_data, logo=logo_file) + result = await async_buyers_service.update(buyer_id, buyer_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 diff --git a/tests/unit/resources/accounts/test_licensees.py b/tests/unit/resources/accounts/test_licensees.py index b067daab..be3a724e 100644 --- a/tests/unit/resources/accounts/test_licensees.py +++ b/tests/unit/resources/accounts/test_licensees.py @@ -47,7 +47,7 @@ def test_create_licensee(licensees_service, tmp_path): # noqa: WPS210 return_value=httpx.Response(httpx.codes.CREATED, json=licensee_data) ) - result = licensees_service.create(licensee_data, logo=logo_file) + result = licensees_service.create(licensee_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 @@ -68,7 +68,7 @@ def test_update_licensees(licensees_service, tmp_path): # noqa: WPS210 return_value=httpx.Response(httpx.codes.OK, json={"id": licensee_id, **licensee_data}) ) - result = licensees_service.update(licensee_id, licensee_data, logo=logo_file) + result = licensees_service.update(licensee_id, licensee_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 @@ -89,7 +89,7 @@ async def test_async_create_licensees(async_licensees_service, tmp_path): # noq return_value=httpx.Response(httpx.codes.CREATED, json=licensee_data) ) - licensee = await async_licensees_service.create(licensee_data, logo=logo_file) + licensee = await async_licensees_service.create(licensee_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 @@ -110,7 +110,7 @@ async def test_async_update_licensees(async_licensees_service, tmp_path): # noq return_value=httpx.Response(httpx.codes.OK, json={"id": licensee_id, **licensee_data}) ) - licensee = await async_licensees_service.update(licensee_id, licensee_data, logo=logo_file) + licensee = await async_licensees_service.update(licensee_id, licensee_data, file=logo_file) request = mock_route.calls[0].request assert mock_route.call_count == 1 diff --git a/tests/unit/resources/accounts/test_mixins.py b/tests/unit/resources/accounts/test_mixins.py index eeabb6c7..d9e15451 100644 --- a/tests/unit/resources/accounts/test_mixins.py +++ b/tests/unit/resources/accounts/test_mixins.py @@ -3,6 +3,12 @@ import respx from mpt_api_client.http import AsyncService, Service +from mpt_api_client.http.mixins import ( + AsyncCreateFileMixin, + AsyncUpdateFileMixin, + CreateFileMixin, + UpdateFileMixin, +) from mpt_api_client.resources.accounts.mixins import ( ActivatableMixin, AsyncActivatableMixin, @@ -108,6 +114,50 @@ class DummyAsyncInvitableService( _collection_key = "data" +class DummyCreateFileService( + CreateFileMixin[DummyModel], + Service[DummyModel], +): + _endpoint = "/public/v1/dummy/create-file/" + _model_class = DummyModel + _collection_key = "data" + _upload_file_key = "file" + _upload_data_key = "resource" + + +class AsyncDummyCreateFileService( + AsyncCreateFileMixin[DummyModel], + AsyncService[DummyModel], +): + _endpoint = "/public/v1/dummy/create-file/" + _model_class = DummyModel + _collection_key = "data" + _upload_file_key = "file" + _upload_data_key = "resource" + + +class DummyUpdateFileService( + UpdateFileMixin[DummyModel], + Service[DummyModel], +): + _endpoint = "/public/v1/dummy/update-file/" + _model_class = DummyModel + _collection_key = "data" + _upload_file_key = "file" + _upload_data_key = "resource" + + +class DummyAsyncUpdateFileService( + AsyncUpdateFileMixin[DummyModel], + AsyncService[DummyModel], +): + _endpoint = "/public/v1/dummy/update-file/" + _model_class = DummyModel + _collection_key = "data" + _upload_file_key = "file" + _upload_data_key = "resource" + + @pytest.fixture def activatable_service(http_client): return DummyActivatableService(http_client=http_client) @@ -158,6 +208,26 @@ def async_invitable_service(async_http_client): return DummyAsyncInvitableService(http_client=async_http_client) +@pytest.fixture +def create_file_service(http_client): + return DummyCreateFileService(http_client=http_client) + + +@pytest.fixture +def async_create_file_service(async_http_client): + return AsyncDummyCreateFileService(http_client=async_http_client) + + +@pytest.fixture +def update_file_service(http_client): + return DummyUpdateFileService(http_client=http_client) + + +@pytest.fixture +def async_update_file_service(async_http_client): + return DummyAsyncUpdateFileService(http_client=async_http_client) + + @pytest.mark.parametrize( ("action", "input_status"), [ @@ -755,3 +825,175 @@ async def test_async_invitable_resource_actions_no_data( assert request.content == request_expected_content assert result.to_dict() == response_expected_data assert isinstance(result, DummyModel) + + +def test_create_file_service(create_file_service, tmp_path): # noqa: WPS210 + resource_data = {"name": "Test File"} + response_expected_data = {"id": "OBJ-0000-0001", **resource_data} + with respx.mock: + mock_route = respx.post("https://api.example.com/public/v1/dummy/create-file/").mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + file_path = tmp_path / "file.png" + file_path.write_bytes(b"fake-file-data") + + with file_path.open("rb") as file_file: + result = create_file_service.create(resource_data, file_file) + + assert mock_route.call_count == 1 + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +def test_create_file_service_no_file(create_file_service): + resource_data = {"name": "Test File"} + response_expected_data = {"id": "OBJ-0000-0001", **resource_data} + with respx.mock: + mock_route = respx.post("https://api.example.com/public/v1/dummy/create-file/").mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = create_file_service.create(resource_data, None) + + assert mock_route.call_count == 1 + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +async def test_async_create_file_service(async_create_file_service, tmp_path): # noqa: WPS210 + resource_data = {"name": "Test File"} + response_expected_data = {"id": "OBJ-0000-0001", **resource_data} + with respx.mock: + mock_route = respx.post("https://api.example.com/public/v1/dummy/create-file/").mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + file_path = tmp_path / "file.png" + file_path.write_bytes(b"fake-file-data") + + with file_path.open("rb") as file_file: + result = await async_create_file_service.create(resource_data, file_file) + + assert mock_route.call_count == 1 + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +async def test_async_create_file_service_no_file(async_create_file_service): + resource_data = {"name": "Test File"} + response_expected_data = {"id": "OBJ-0000-0001", **resource_data} + with respx.mock: + mock_route = respx.post("https://api.example.com/public/v1/dummy/create-file/").mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + result = await async_create_file_service.create(resource_data, None) + + assert mock_route.call_count == 1 + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +def test_update_file_service(update_file_service, tmp_path): # noqa: WPS210 + resource_data = {"name": "Test File"} + response_expected_data = {"id": "OBJ-0000-0001", **resource_data} + with respx.mock: + mock_route = respx.put( + "https://api.example.com/public/v1/dummy/update-file/OBJ-0000-0001" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + update_file_path = tmp_path / "file.png" + update_file_path.write_bytes(b"updated file content") + + with update_file_path.open("rb") as update_file: + result = update_file_service.update("OBJ-0000-0001", resource_data, update_file) + + assert mock_route.call_count == 1 + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +def test_update_file_service_no_file(update_file_service): + resource_data = {"name": "Test File"} + response_expected_data = {"id": "OBJ-0000-0001", **resource_data} + with respx.mock: + mock_route = respx.put( + "https://api.example.com/public/v1/dummy/update-file/OBJ-0000-0001" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = update_file_service.update("OBJ-0000-0001", resource_data, None) + + assert mock_route.call_count == 1 + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +async def test_async_update_file_service(async_update_file_service, tmp_path): # noqa: WPS210 + resource_data = {"name": "Test File"} + response_expected_data = {"id": "OBJ-0000-0001", **resource_data} + with respx.mock: + mock_route = respx.put( + "https://api.example.com/public/v1/dummy/update-file/OBJ-0000-0001" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + update_file_path = tmp_path / "file.png" + update_file_path.write_bytes(b"updated file content") + + with update_file_path.open("rb") as update_file: + result = await async_update_file_service.update( + "OBJ-0000-0001", resource_data, update_file + ) + + assert mock_route.call_count == 1 + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +async def test_async_update_file_service_no_file(async_update_file_service): + resource_data = {"name": "Test File"} + response_expected_data = {"id": "OBJ-0000-0001", **resource_data} + with respx.mock: + mock_route = respx.put( + "https://api.example.com/public/v1/dummy/update-file/OBJ-0000-0001" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + result = await async_update_file_service.update("OBJ-0000-0001", resource_data, None) + + assert mock_route.call_count == 1 + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/catalog/test_mixins.py b/tests/unit/resources/catalog/test_mixins.py index a5137fe6..b2d5fa7e 100644 --- a/tests/unit/resources/catalog/test_mixins.py +++ b/tests/unit/resources/catalog/test_mixins.py @@ -17,7 +17,7 @@ from tests.unit.conftest import DummyModel -class DummyPublishableService( # noqa: WPS215 +class DummyPublishableService( PublishableMixin[DummyModel], Service[DummyModel], ): @@ -26,7 +26,7 @@ class DummyPublishableService( # noqa: WPS215 _collection_key = "data" -class DummyAsyncPublishableService( # noqa: WPS215 +class DummyAsyncPublishableService( AsyncPublishableMixin[DummyModel], AsyncService[DummyModel], ): @@ -35,7 +35,7 @@ class DummyAsyncPublishableService( # noqa: WPS215 _collection_key = "data" -class DummyActivatableService( # noqa: WPS215 +class DummyActivatableService( ActivatableMixin[DummyModel], Service[DummyModel], ): @@ -44,7 +44,7 @@ class DummyActivatableService( # noqa: WPS215 _collection_key = "data" -class DummyAsyncActivatableService( # noqa: WPS215 +class DummyAsyncActivatableService( AsyncActivatableMixin[DummyModel], AsyncService[DummyModel], ): @@ -53,7 +53,7 @@ class DummyAsyncActivatableService( # noqa: WPS215 _collection_key = "data" -class DummyDocumentService( # noqa: WPS215 +class DummyDocumentService( DocumentMixin[DummyModel], Service[DummyModel], ): @@ -64,7 +64,7 @@ class DummyDocumentService( # noqa: WPS215 _upload_data_key = "document" -class DummyAsyncDocumentService( # noqa: WPS215 +class DummyAsyncDocumentService( AsyncDocumentMixin[DummyModel], AsyncService[DummyModel], ): diff --git a/tests/unit/resources/catalog/test_products.py b/tests/unit/resources/catalog/test_products.py index 5dea300e..b3ef2d17 100644 --- a/tests/unit/resources/catalog/test_products.py +++ b/tests/unit/resources/catalog/test_products.py @@ -148,7 +148,7 @@ def test_product_create(products_service, tmp_path): return_value=httpx.Response(httpx.codes.CREATED, json=expected_response) ) - result = products_service.create(product_data, icon=icon_file) + result = products_service.create(product_data, file=icon_file) assert mock_route.call_count == 1 request = mock_route.calls[0].request @@ -168,7 +168,7 @@ async def test_async_product_create(async_products_service, tmp_path): return_value=httpx.Response(httpx.codes.CREATED, json=expected_response) ) - result = await async_products_service.create(product_data, icon=icon_file) + result = await async_products_service.create(product_data, file=icon_file) assert mock_route.call_count == 1 request = mock_route.calls[0].request @@ -188,7 +188,11 @@ def test_sync_product_update(products_service, tmp_path): f"https://api.example.com/public/v1/catalog/products/{product_id}" ).mock(return_value=httpx.Response(httpx.codes.OK, json=expected_response)) - result = products_service.update(product_id, update_data, icon=icon_file) + result = products_service.update( + product_id, + update_data, + file=icon_file, + ) assert mock_route.call_count == 1 request = mock_route.calls[0].request @@ -208,7 +212,11 @@ async def test_async_product_update(async_products_service, tmp_path): f"https://api.example.com/public/v1/catalog/products/{product_id}" ).mock(return_value=httpx.Response(httpx.codes.OK, json=expected_response)) - result = await async_products_service.update(product_id, update_data, icon=icon_file) + result = await async_products_service.update( + product_id, + update_data, + file=icon_file, + ) assert mock_route.call_count == 1 request = mock_route.calls[0].request