diff --git a/mpt_api_client/resources/accounts/account.py b/mpt_api_client/resources/accounts/account.py index 4657dabc..fd9817e6 100644 --- a/mpt_api_client/resources/accounts/account.py +++ b/mpt_api_client/resources/accounts/account.py @@ -18,9 +18,11 @@ AccountsUsersService, AsyncAccountsUsersService, ) -from mpt_api_client.resources.accounts.mixins import ( +from mpt_api_client.resources.accounts.mixins.activatable_mixin import ( ActivatableMixin, AsyncActivatableMixin, +) +from mpt_api_client.resources.accounts.mixins.validate_mixin import ( AsyncValidateMixin, ValidateMixin, ) diff --git a/mpt_api_client/resources/accounts/account_users.py b/mpt_api_client/resources/accounts/account_users.py index fa9f88d6..faa21378 100644 --- a/mpt_api_client/resources/accounts/account_users.py +++ b/mpt_api_client/resources/accounts/account_users.py @@ -12,7 +12,7 @@ AccountUserGroupsService, AsyncAccountUserGroupsService, ) -from mpt_api_client.resources.accounts.mixins import ( +from mpt_api_client.resources.accounts.mixins.invitable_mixin import ( AsyncInvitableMixin, InvitableMixin, ) diff --git a/mpt_api_client/resources/accounts/accounts_users.py b/mpt_api_client/resources/accounts/accounts_users.py index 8f5f3bc4..f1a09a45 100644 --- a/mpt_api_client/resources/accounts/accounts_users.py +++ b/mpt_api_client/resources/accounts/accounts_users.py @@ -10,7 +10,7 @@ AccountsUserGroupsService, AsyncAccountsUserGroupsService, ) -from mpt_api_client.resources.accounts.mixins import ( +from mpt_api_client.resources.accounts.mixins.invitable_mixin import ( AsyncInvitableMixin, InvitableMixin, ) diff --git a/mpt_api_client/resources/accounts/buyers.py b/mpt_api_client/resources/accounts/buyers.py index 5d9fbcb9..9fe77e73 100644 --- a/mpt_api_client/resources/accounts/buyers.py +++ b/mpt_api_client/resources/accounts/buyers.py @@ -17,9 +17,11 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import ResourceData -from mpt_api_client.resources.accounts.mixins import ( +from mpt_api_client.resources.accounts.mixins.activatable_mixin import ( ActivatableMixin, AsyncActivatableMixin, +) +from mpt_api_client.resources.accounts.mixins.validate_mixin import ( AsyncValidateMixin, ValidateMixin, ) diff --git a/mpt_api_client/resources/accounts/erp_links.py b/mpt_api_client/resources/accounts/erp_links.py index 5da21dc7..21161e20 100644 --- a/mpt_api_client/resources/accounts/erp_links.py +++ b/mpt_api_client/resources/accounts/erp_links.py @@ -10,7 +10,10 @@ UpdateMixin, ) from mpt_api_client.models import Model -from mpt_api_client.resources.accounts.mixins import AsyncBlockableMixin, BlockableMixin +from mpt_api_client.resources.accounts.mixins.blockable_mixin import ( + AsyncBlockableMixin, + BlockableMixin, +) class ErpLink(Model): diff --git a/mpt_api_client/resources/accounts/mixins.py b/mpt_api_client/resources/accounts/mixins.py deleted file mode 100644 index b5d71f32..00000000 --- a/mpt_api_client/resources/accounts/mixins.py +++ /dev/null @@ -1,217 +0,0 @@ -from mpt_api_client.models import ResourceData - - -class ActivatableMixin[Model]: - """Activatable mixin for activating, enabling, disabling and deactivating resources.""" - - def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Activate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "activate", json=resource_data - ) - - def deactivate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Deactivate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "deactivate", json=resource_data - ) - - -class ValidateMixin[Model]: - """Validate mixin adds the ability to validate a resource.""" - - def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Validate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be validated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "validate", json=resource_data - ) - - -class BlockableMixin[Model]: - """Blockable mixin for blocking and unblocking resources.""" - - def block(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Block a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "block", json=resource_data - ) - - def unblock(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Unblock a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "unblock", json=resource_data - ) - - -class InvitableMixin[Model]: - """Invitable mixin for sending and managing invites for resources.""" - - def accept_invite(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Accept an invite for a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept-invite", json=resource_data - ) - - def resend_invite(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Resend an invite to a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "resend-invite", json=resource_data - ) - - def send_new_invite(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Send a new invite to a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "send-new-invite", json=resource_data - ) - - -class AsyncActivatableMixin[Model]: - """Async activatable mixin for activating, enabling, disabling and deactivating resources.""" - - async def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Activate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "activate", json=resource_data - ) - - async def deactivate( - self, resource_id: str, resource_data: ResourceData | None = None - ) -> Model: - """Deactivate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "deactivate", json=resource_data - ) - - -class AsyncValidateMixin[Model]: - """Asynchronous Validate mixin adds the ability to validate a resource.""" - - async def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Validate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be validated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "validate", json=resource_data - ) - - -class AsyncBlockableMixin[Model]: - """Asynchronous Blockable mixin for blocking and unblocking resources.""" - - async def block(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Block a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "block", json=resource_data - ) - - async def unblock(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Unblock a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "unblock", json=resource_data - ) - - -class AsyncInvitableMixin[Model]: - """Asynchronous Invitable mixin for sending and managing invites for resources.""" - - async def accept_invite( - self, resource_id: str, resource_data: ResourceData | None = None - ) -> Model: - """Accept an invite for a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept-invite", json=resource_data - ) - - async def resend_invite( - self, resource_id: str, resource_data: ResourceData | None = None - ) -> Model: - """Resend an invite to a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "resend-invite", json=resource_data - ) - - async def send_new_invite( - self, resource_id: str, resource_data: ResourceData | None = None - ) -> Model: - """Send a new invite to a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "send-new-invite", json=resource_data - ) diff --git a/mpt_api_client/resources/accounts/mixins/__init__.py b/mpt_api_client/resources/accounts/mixins/__init__.py new file mode 100644 index 00000000..053d80f4 --- /dev/null +++ b/mpt_api_client/resources/accounts/mixins/__init__.py @@ -0,0 +1,27 @@ +from mpt_api_client.resources.accounts.mixins.activatable_mixin import ( + ActivatableMixin, + AsyncActivatableMixin, +) +from mpt_api_client.resources.accounts.mixins.blockable_mixin import ( + AsyncBlockableMixin, + BlockableMixin, +) +from mpt_api_client.resources.accounts.mixins.invitable_mixin import ( + AsyncInvitableMixin, + InvitableMixin, +) +from mpt_api_client.resources.accounts.mixins.validate_mixin import ( + AsyncValidateMixin, + ValidateMixin, +) + +__all__ = [ # noqa: WPS410 + "ActivatableMixin", + "AsyncActivatableMixin", + "AsyncBlockableMixin", + "AsyncInvitableMixin", + "AsyncValidateMixin", + "BlockableMixin", + "InvitableMixin", + "ValidateMixin", +] diff --git a/mpt_api_client/resources/accounts/mixins/activatable_mixin.py b/mpt_api_client/resources/accounts/mixins/activatable_mixin.py new file mode 100644 index 00000000..8781ac8c --- /dev/null +++ b/mpt_api_client/resources/accounts/mixins/activatable_mixin.py @@ -0,0 +1,55 @@ +from mpt_api_client.models import ResourceData + + +class ActivatableMixin[Model]: + """Activatable mixin for activating, enabling, disabling and deactivating resources.""" + + def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Activate a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "activate", json=resource_data + ) + + def deactivate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Deactivate a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "deactivate", json=resource_data + ) + + +class AsyncActivatableMixin[Model]: + """Async activatable mixin for activating, enabling, disabling and deactivating resources.""" + + async def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Activate a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "activate", json=resource_data + ) + + async def deactivate( + self, resource_id: str, resource_data: ResourceData | None = None + ) -> Model: + """Deactivate a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "deactivate", json=resource_data + ) diff --git a/mpt_api_client/resources/accounts/mixins/blockable_mixin.py b/mpt_api_client/resources/accounts/mixins/blockable_mixin.py new file mode 100644 index 00000000..d4a05abb --- /dev/null +++ b/mpt_api_client/resources/accounts/mixins/blockable_mixin.py @@ -0,0 +1,53 @@ +from mpt_api_client.models import ResourceData + + +class BlockableMixin[Model]: + """Blockable mixin for blocking and unblocking resources.""" + + def block(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Block a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "block", json=resource_data + ) + + def unblock(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Unblock a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "unblock", json=resource_data + ) + + +class AsyncBlockableMixin[Model]: + """Asynchronous Blockable mixin for blocking and unblocking resources.""" + + async def block(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Block a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "block", json=resource_data + ) + + async def unblock(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Unblock a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "unblock", json=resource_data + ) diff --git a/mpt_api_client/resources/accounts/mixins/invitable_mixin.py b/mpt_api_client/resources/accounts/mixins/invitable_mixin.py new file mode 100644 index 00000000..a4a22e14 --- /dev/null +++ b/mpt_api_client/resources/accounts/mixins/invitable_mixin.py @@ -0,0 +1,81 @@ +from mpt_api_client.models import ResourceData + + +class InvitableMixin[Model]: + """Invitable mixin for sending and managing invites for resources.""" + + def accept_invite(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Accept an invite for a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "accept-invite", json=resource_data + ) + + def resend_invite(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Resend an invite to a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "resend-invite", json=resource_data + ) + + def send_new_invite(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Send a new invite to a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "send-new-invite", json=resource_data + ) + + +class AsyncInvitableMixin[Model]: + """Asynchronous Invitable mixin for sending and managing invites for resources.""" + + async def accept_invite( + self, resource_id: str, resource_data: ResourceData | None = None + ) -> Model: + """Accept an invite for a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "accept-invite", json=resource_data + ) + + async def resend_invite( + self, resource_id: str, resource_data: ResourceData | None = None + ) -> Model: + """Resend an invite to a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "resend-invite", json=resource_data + ) + + async def send_new_invite( + self, resource_id: str, resource_data: ResourceData | None = None + ) -> Model: + """Send a new invite to a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "send-new-invite", json=resource_data + ) diff --git a/mpt_api_client/resources/accounts/mixins/validate_mixin.py b/mpt_api_client/resources/accounts/mixins/validate_mixin.py new file mode 100644 index 00000000..3fbffc7c --- /dev/null +++ b/mpt_api_client/resources/accounts/mixins/validate_mixin.py @@ -0,0 +1,31 @@ +from mpt_api_client.models import ResourceData + + +class ValidateMixin[Model]: + """Validate mixin adds the ability to validate a resource.""" + + def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Validate a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be validated + """ + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "validate", json=resource_data + ) + + +class AsyncValidateMixin[Model]: + """Asynchronous Validate mixin adds the ability to validate a resource.""" + + async def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Validate a resource. + + Args: + resource_id: Resource ID + resource_data: Resource data will be validated + """ + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id, "POST", "validate", json=resource_data + ) diff --git a/mpt_api_client/resources/accounts/sellers.py b/mpt_api_client/resources/accounts/sellers.py index 7677bff3..62e8e463 100644 --- a/mpt_api_client/resources/accounts/sellers.py +++ b/mpt_api_client/resources/accounts/sellers.py @@ -7,7 +7,10 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import ResourceData -from mpt_api_client.resources.accounts.mixins import ActivatableMixin, AsyncActivatableMixin +from mpt_api_client.resources.accounts.mixins.activatable_mixin import ( + ActivatableMixin, + AsyncActivatableMixin, +) class Seller(Model): diff --git a/mpt_api_client/resources/accounts/users.py b/mpt_api_client/resources/accounts/users.py index d8c322a2..8e08d8bd 100644 --- a/mpt_api_client/resources/accounts/users.py +++ b/mpt_api_client/resources/accounts/users.py @@ -11,7 +11,7 @@ ) from mpt_api_client.models import Model from mpt_api_client.models.model import ResourceData -from mpt_api_client.resources.accounts.mixins import ( +from mpt_api_client.resources.accounts.mixins.blockable_mixin import ( AsyncBlockableMixin, BlockableMixin, ) diff --git a/tests/unit/resources/accounts/mixins/__init__.py b/tests/unit/resources/accounts/mixins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/resources/accounts/mixins/test_activatable_mixin.py b/tests/unit/resources/accounts/mixins/test_activatable_mixin.py new file mode 100644 index 00000000..6f51bbab --- /dev/null +++ b/tests/unit/resources/accounts/mixins/test_activatable_mixin.py @@ -0,0 +1,158 @@ +import httpx +import pytest +import respx + +from mpt_api_client.http import AsyncService, Service +from mpt_api_client.resources.accounts.mixins.activatable_mixin import ( + ActivatableMixin, + AsyncActivatableMixin, +) +from tests.unit.conftest import DummyModel + + +class DummyActivatableService( + ActivatableMixin[DummyModel], + Service[DummyModel], +): + _endpoint = "/public/v1/dummy/activatable/" + _model_class = DummyModel + _collection_key = "data" + + +class DummyAsyncActivatableService( + AsyncActivatableMixin[DummyModel], + AsyncService[DummyModel], +): + _endpoint = "/public/v1/dummy/activatable/" + _model_class = DummyModel + _collection_key = "data" + + +@pytest.fixture +def activatable_service(http_client): + return DummyActivatableService(http_client=http_client) + + +@pytest.fixture +def async_activatable_service(async_http_client): + return DummyAsyncActivatableService(http_client=async_http_client) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("activate", {"id": "OBJ-0000-0001", "status": "update"}), + ("deactivate", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +def test_activatable_resource_actions(activatable_service, action, input_status): + request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = getattr(activatable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("activate", None), + ("deactivate", None), + ], +) +def test_actions_no_data(activatable_service, action, input_status): + request_expected_content = b"" + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = getattr(activatable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("activate", {"id": "OBJ-0000-0001", "status": "update"}), + ("deactivate", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +async def test_async_activatable_resource_actions(async_activatable_service, action, input_status): + request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await getattr(async_activatable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("activate", None), + ("deactivate", None), + ], +) +async def test_async_actions_no_data(async_activatable_service, action, input_status): + request_expected_content = b"" + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await getattr(async_activatable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/accounts/mixins/test_blockable_mixin.py b/tests/unit/resources/accounts/mixins/test_blockable_mixin.py new file mode 100644 index 00000000..08bfa3cc --- /dev/null +++ b/tests/unit/resources/accounts/mixins/test_blockable_mixin.py @@ -0,0 +1,160 @@ +import httpx +import pytest +import respx + +from mpt_api_client.http import AsyncService, Service +from mpt_api_client.resources.accounts.mixins.blockable_mixin import ( + AsyncBlockableMixin, + BlockableMixin, +) +from tests.unit.conftest import DummyModel + + +class DummyBlockableService( + BlockableMixin[DummyModel], + Service[DummyModel], +): + _endpoint = "/public/v1/dummy/blockable/" + _model_class = DummyModel + _collection_key = "data" + + +class DummyAsyncBlockableService( + AsyncBlockableMixin[DummyModel], + AsyncService[DummyModel], +): + _endpoint = "/public/v1/dummy/blockable/" + _model_class = DummyModel + _collection_key = "data" + + +@pytest.fixture +def blockable_service(http_client): + return DummyBlockableService(http_client=http_client) + + +@pytest.fixture +def async_blockable_service(async_http_client): + return DummyAsyncBlockableService(http_client=async_http_client) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("block", {"id": "OBJ-0000-0001", "status": "update"}), + ("unblock", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +def test_blockable_resource_actions(blockable_service, action, input_status): + request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/blockable/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = getattr(blockable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("block", None), + ("unblock", None), + ], +) +def test_blockable_resource_actions_no_data(blockable_service, action, input_status): + request_expected_content = b"" + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/blockable/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = getattr(blockable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("block", {"id": "OBJ-0000-0001", "status": "update"}), + ("unblock", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +async def test_async_blockable_resource_actions(async_blockable_service, action, input_status): + request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/blockable/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await getattr(async_blockable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("block", None), + ("unblock", None), + ], +) +async def test_async_blockable_resource_actions_no_data( + async_blockable_service, action, input_status +): + request_expected_content = b"" + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/blockable/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await getattr(async_blockable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/accounts/mixins/test_file_mixins.py b/tests/unit/resources/accounts/mixins/test_file_mixins.py new file mode 100644 index 00000000..d6b37cb8 --- /dev/null +++ b/tests/unit/resources/accounts/mixins/test_file_mixins.py @@ -0,0 +1,249 @@ +import httpx +import pytest +import respx + +from mpt_api_client.http import AsyncService, Service +from mpt_api_client.http.mixins import ( + AsyncCreateFileMixin, + AsyncUpdateFileMixin, + CreateFileMixin, + UpdateFileMixin, +) +from tests.unit.conftest import DummyModel + + +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 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) + + +def test_create_file_service(create_file_service, tmp_path): + 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): + 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): + 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): + 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/accounts/mixins/test_invitable_mixin.py b/tests/unit/resources/accounts/mixins/test_invitable_mixin.py new file mode 100644 index 00000000..49bd531e --- /dev/null +++ b/tests/unit/resources/accounts/mixins/test_invitable_mixin.py @@ -0,0 +1,168 @@ +import httpx +import pytest +import respx + +from mpt_api_client.http import AsyncService, Service +from mpt_api_client.resources.accounts.mixins.invitable_mixin import ( + AsyncInvitableMixin, + InvitableMixin, +) +from tests.unit.conftest import DummyModel + + +class DummyInvitableService( + InvitableMixin[DummyModel], + Service[DummyModel], +): + _endpoint = "/public/v1/dummy/invitable/" + _model_class = DummyModel + _collection_key = "data" + + +class DummyAsyncInvitableService( + AsyncInvitableMixin[DummyModel], + AsyncService[DummyModel], +): + _endpoint = "/public/v1/dummy/invitable/" + _model_class = DummyModel + _collection_key = "data" + + +@pytest.fixture +def invitable_service(http_client): + return DummyInvitableService(http_client=http_client) + + +@pytest.fixture +def async_invitable_service(async_http_client): + return DummyAsyncInvitableService(http_client=async_http_client) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("accept_invite", {"id": "OBJ-0000-0001", "status": "update"}), + ("resend_invite", {"id": "OBJ-0000-0001", "status": "update"}), + ("send_new_invite", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +def test_invitable_resource_actions(invitable_service, action, input_status): + request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + endpoint_action = action.replace("_", "-") + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/invitable/OBJ-0000-0001/{endpoint_action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = getattr(invitable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("accept_invite", None), + ("resend_invite", None), + ("send_new_invite", None), + ], +) +def test_invitable_resource_actions_no_data(invitable_service, action, input_status): + request_expected_content = b"" + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + endpoint_action = action.replace("_", "-") + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/invitable/OBJ-0000-0001/{endpoint_action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = getattr(invitable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("accept_invite", {"id": "OBJ-0000-0001", "status": "update"}), + ("resend_invite", {"id": "OBJ-0000-0001", "status": "update"}), + ("send_new_invite", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +async def test_async_invitable_resource_actions(async_invitable_service, action, input_status): + request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + endpoint_action = action.replace("_", "-") + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/invitable/OBJ-0000-0001/{endpoint_action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await getattr(async_invitable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("accept_invite", None), + ("resend_invite", None), + ("send_new_invite", None), + ], +) +async def test_async_invitable_resource_actions_no_data( + async_invitable_service, action, input_status +): + request_expected_content = b"" + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + endpoint_action = action.replace("_", "-") + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/invitable/OBJ-0000-0001/{endpoint_action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await getattr(async_invitable_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/accounts/mixins/test_validate_mixin.py b/tests/unit/resources/accounts/mixins/test_validate_mixin.py new file mode 100644 index 00000000..5a18987b --- /dev/null +++ b/tests/unit/resources/accounts/mixins/test_validate_mixin.py @@ -0,0 +1,156 @@ +import httpx +import pytest +import respx + +from mpt_api_client.http import AsyncService, Service +from mpt_api_client.resources.accounts.mixins.validate_mixin import ( + AsyncValidateMixin, + ValidateMixin, +) +from tests.unit.conftest import DummyModel + + +class DummyValidateService( + ValidateMixin[DummyModel], + Service[DummyModel], +): + _endpoint = "/public/v1/dummy/validate/" + _model_class = DummyModel + _collection_key = "data" + + +class DummyAsyncValidateService( + AsyncValidateMixin[DummyModel], + AsyncService[DummyModel], +): + _endpoint = "/public/v1/dummy/validate/" + _model_class = DummyModel + _collection_key = "data" + + +@pytest.fixture +def validate_service(http_client): + return DummyValidateService(http_client=http_client) + + +@pytest.fixture +def async_validate_service(async_http_client): + return DummyAsyncValidateService(http_client=async_http_client) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("validate", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +def test_validate_resource_actions(validate_service, action, input_status): + request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/validate/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = getattr(validate_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("validate", None), + ], +) +def test_validate_resource_actions_no_data(validate_service, action, input_status): + request_expected_content = b"" + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/validate/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = getattr(validate_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("validate", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +async def test_async_validate_resource_actions(async_validate_service, action, input_status): + request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/validate/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await getattr(async_validate_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("validate", None), + ], +) +async def test_async_validate_resource_actions_no_data( + async_validate_service, action, input_status +): + request_expected_content = b"" + response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/dummy/validate/OBJ-0000-0001/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await getattr(async_validate_service, action)("OBJ-0000-0001", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert result.to_dict() == response_expected_data + assert isinstance(result, DummyModel) diff --git a/tests/unit/resources/accounts/test_mixins.py b/tests/unit/resources/accounts/test_mixins.py deleted file mode 100644 index 620375e6..00000000 --- a/tests/unit/resources/accounts/test_mixins.py +++ /dev/null @@ -1,847 +0,0 @@ -import httpx -import pytest -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, - AsyncBlockableMixin, - AsyncInvitableMixin, - AsyncValidateMixin, - BlockableMixin, - InvitableMixin, - ValidateMixin, -) -from tests.unit.conftest import DummyModel - - -class DummyActivatableService( - ActivatableMixin[DummyModel], - Service[DummyModel], -): - _endpoint = "/public/v1/dummy/activatable/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyAsyncActivatableService( - AsyncActivatableMixin[DummyModel], - AsyncService[DummyModel], -): - _endpoint = "/public/v1/dummy/activatable/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyValidateService( - ValidateMixin[DummyModel], - Service[DummyModel], -): - _endpoint = "/public/v1/dummy/validate/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyAsyncValidateService( - AsyncValidateMixin[DummyModel], - AsyncService[DummyModel], -): - _endpoint = "/public/v1/dummy/validate/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyBlockableService( - BlockableMixin[DummyModel], - Service[DummyModel], -): - _endpoint = "/public/v1/dummy/blockable/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyAsyncBlockableService( - AsyncBlockableMixin[DummyModel], - AsyncService[DummyModel], -): - _endpoint = "/public/v1/dummy/blockable/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyInvitableService( - InvitableMixin[DummyModel], - Service[DummyModel], -): - _endpoint = "/public/v1/dummy/invitable/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyAsyncInvitableService( - AsyncInvitableMixin[DummyModel], - AsyncService[DummyModel], -): - _endpoint = "/public/v1/dummy/invitable/" - _model_class = DummyModel - _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) - - -@pytest.fixture -def async_activatable_service(async_http_client): - return DummyAsyncActivatableService(http_client=async_http_client) - - -@pytest.fixture -def validate_service(http_client): - return DummyValidateService(http_client=http_client) - - -@pytest.fixture -def async_validate_service(async_http_client): - return DummyAsyncValidateService(http_client=async_http_client) - - -@pytest.fixture -def blockable_service(http_client): - return DummyBlockableService(http_client=http_client) - - -@pytest.fixture -def async_blockable_service(async_http_client): - return DummyAsyncBlockableService(http_client=async_http_client) - - -@pytest.fixture -def invitable_service(http_client): - return DummyInvitableService(http_client=http_client) - - -@pytest.fixture -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"), - [ - ("activate", {"id": "OBJ-0000-0001", "status": "update"}), - ("deactivate", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -def test_activatable_resource_actions(activatable_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(activatable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("activate", None), - ("deactivate", None), - ], -) -def test_activatable_resource_actions_no_data(activatable_service, action, input_status): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(activatable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("activate", {"id": "OBJ-0000-0001", "status": "update"}), - ("deactivate", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -async def test_async_activatable_resource_actions(async_activatable_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_activatable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("activate", None), - ("deactivate", None), - ], -) -async def test_async_activatable_resource_actions_no_data( - async_activatable_service, action, input_status -): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/activatable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_activatable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), [("validate", {"id": "OBJ-0000-0001", "status": "update"})] -) -def test_validate_resource_actions(validate_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/validate/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(validate_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize(("action", "input_status"), [("validate", None)]) -def test_validate_resource_actions_no_data(validate_service, action, input_status): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/validate/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(validate_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), [("validate", {"id": "OBJ-0000-0001", "status": "update"})] -) -async def test_async_validate_resource_actions(async_validate_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/validate/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_validate_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize(("action", "input_status"), [("validate", None)]) -async def test_async_validate_resource_actions_no_data( - async_validate_service, action, input_status -): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/validate/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_validate_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("block", {"id": "OBJ-0000-0001", "status": "update"}), - ("unblock", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -def test_blockable_resource_actions(blockable_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/blockable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(blockable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("block", None), - ("unblock", None), - ], -) -def test_blockable_resource_actions_no_data(blockable_service, action, input_status): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/blockable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(blockable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("block", {"id": "OBJ-0000-0001", "status": "update"}), - ("unblock", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -async def test_async_blockable_resource_actions(async_blockable_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/blockable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_blockable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("block", None), - ("unblock", None), - ], -) -async def test_async_blockable_resource_actions_no_data( - async_blockable_service, action, input_status -): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/dummy/blockable/OBJ-0000-0001/{action}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_blockable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("accept_invite", {"id": "OBJ-0000-0001", "status": "update"}), - ("resend_invite", {"id": "OBJ-0000-0001", "status": "update"}), - ("send_new_invite", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -def test_invitable_resource_actions(invitable_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - "https://api.example.com/public/v1/" - f"dummy/invitable/OBJ-0000-0001/{action.replace('_', '-')}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(invitable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("accept_invite", None), - ("resend_invite", None), - ("send_new_invite", None), - ], -) -def test_invitable_resource_actions_no_data(invitable_service, action, input_status): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - "https://api.example.com/public/v1/" - f"dummy/invitable/OBJ-0000-0001/{action.replace('_', '-')}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = getattr(invitable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("accept_invite", {"id": "OBJ-0000-0001", "status": "update"}), - ("resend_invite", {"id": "OBJ-0000-0001", "status": "update"}), - ("send_new_invite", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -async def test_async_invitable_resource_actions(async_invitable_service, action, input_status): - request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}' - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - "https://api.example.com/public/v1/" - f"dummy/invitable/OBJ-0000-0001/{action.replace('_', '-')}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_invitable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("accept_invite", None), - ("resend_invite", None), - ("send_new_invite", None), - ], -) -async def test_async_invitable_resource_actions_no_data( - async_invitable_service, action, input_status -): - request_expected_content = b"" - response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - "https://api.example.com/public/v1/" - f"dummy/invitable/OBJ-0000-0001/{action.replace('_', '-')}" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - result = await getattr(async_invitable_service, action)("OBJ-0000-0001", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert result.to_dict() == response_expected_data - assert isinstance(result, DummyModel) - - -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)