diff --git a/e2e_config.test.json b/e2e_config.test.json index 189df24f..8ac6fdd8 100644 --- a/e2e_config.test.json +++ b/e2e_config.test.json @@ -42,5 +42,6 @@ "commerce.subscription.agreement.id": "AGR-2473-3299-1721", "commerce.subscription.id": "SUB-3678-1831-2188", "commerce.subscription.product.item.id": "ITM-1767-7355-0001", + "notifications.subscriber.id": "NTS-0829-7123-7123", "notifications.message.id": "MSG-0000-6215-1019-0139" } diff --git a/mpt_api_client/http/mixins.py b/mpt_api_client/http/mixins.py index 1e10c9ea..66793711 100644 --- a/mpt_api_client/http/mixins.py +++ b/mpt_api_client/http/mixins.py @@ -395,6 +395,46 @@ async def get(self, resource_id: str, select: list[str] | str | None = None) -> return await self._resource_action(resource_id=resource_id, query_params={"select": select}) # type: ignore[attr-defined, no-any-return] +class AsyncEnableMixin[Model: BaseModel]: + """Enable resource mixin.""" + + async def enable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Enable a specific resource.""" + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id=resource_id, method="POST", action="enable", json=resource_data + ) + + +class EnableMixin[Model: BaseModel]: + """Enable resource mixin.""" + + def enable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Enable a specific resource.""" + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id=resource_id, method="POST", action="enable", json=resource_data + ) + + +class AsyncDisableMixin[Model: BaseModel]: + """Disable resource mixin.""" + + async def disable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Disable a specific resource.""" + return await self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id=resource_id, method="POST", action="disable", json=resource_data + ) + + +class DisableMixin[Model: BaseModel]: + """Disable resource mixin.""" + + def disable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Disable a specific resource .""" + return self._resource_action( # type: ignore[attr-defined, no-any-return] + resource_id=resource_id, method="POST", action="disable", json=resource_data + ) + + class QueryableMixin: """Mixin providing query functionality for filtering, ordering, and selecting fields.""" diff --git a/mpt_api_client/resources/accounts/account.py b/mpt_api_client/resources/accounts/account.py index 87563ead..4657dabc 100644 --- a/mpt_api_client/resources/accounts/account.py +++ b/mpt_api_client/resources/accounts/account.py @@ -2,10 +2,14 @@ from mpt_api_client.http.mixins import ( AsyncCollectionMixin, AsyncCreateFileMixin, + AsyncDisableMixin, + AsyncEnableMixin, AsyncGetMixin, AsyncUpdateFileMixin, CollectionMixin, CreateFileMixin, + DisableMixin, + EnableMixin, GetMixin, UpdateFileMixin, ) @@ -17,9 +21,7 @@ from mpt_api_client.resources.accounts.mixins import ( ActivatableMixin, AsyncActivatableMixin, - AsyncEnablableMixin, AsyncValidateMixin, - EnablableMixin, ValidateMixin, ) @@ -42,7 +44,8 @@ class AccountsService( CreateFileMixin[Account], UpdateFileMixin[Account], ActivatableMixin[Account], - EnablableMixin[Account], + EnableMixin[Account], + DisableMixin[Account], ValidateMixin[Account], GetMixin[Account], CollectionMixin[Account], @@ -62,7 +65,8 @@ class AsyncAccountsService( AsyncCreateFileMixin[Account], AsyncUpdateFileMixin[Account], AsyncActivatableMixin[Account], - AsyncEnablableMixin[Account], + AsyncEnableMixin[Account], + AsyncDisableMixin[Account], AsyncValidateMixin[Account], AsyncGetMixin[Account], AsyncCollectionMixin[Account], diff --git a/mpt_api_client/resources/accounts/api_tokens.py b/mpt_api_client/resources/accounts/api_tokens.py index efceeb67..ad317470 100644 --- a/mpt_api_client/resources/accounts/api_tokens.py +++ b/mpt_api_client/resources/accounts/api_tokens.py @@ -1,12 +1,15 @@ from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, + AsyncDisableMixin, + AsyncEnableMixin, AsyncManagedResourceMixin, CollectionMixin, + DisableMixin, + EnableMixin, ManagedResourceMixin, ) from mpt_api_client.models import Model -from mpt_api_client.resources.accounts.mixins import AsyncEnablableMixin, EnablableMixin class ApiToken(Model): @@ -23,7 +26,8 @@ class ApiTokensServiceConfig: class ApiTokensService( ManagedResourceMixin[ApiToken], - EnablableMixin[ApiToken], + EnableMixin[ApiToken], + DisableMixin[ApiToken], CollectionMixin[ApiToken], Service[ApiToken], ApiTokensServiceConfig, @@ -33,7 +37,8 @@ class ApiTokensService( class AsyncApiTokensService( AsyncManagedResourceMixin[ApiToken], - AsyncEnablableMixin[ApiToken], + AsyncEnableMixin[ApiToken], + AsyncDisableMixin[ApiToken], AsyncCollectionMixin[ApiToken], AsyncService[ApiToken], ApiTokensServiceConfig, diff --git a/mpt_api_client/resources/accounts/buyers.py b/mpt_api_client/resources/accounts/buyers.py index 3368a66a..5d9fbcb9 100644 --- a/mpt_api_client/resources/accounts/buyers.py +++ b/mpt_api_client/resources/accounts/buyers.py @@ -3,11 +3,15 @@ AsyncCollectionMixin, AsyncCreateFileMixin, AsyncDeleteMixin, + AsyncDisableMixin, + AsyncEnableMixin, AsyncGetMixin, AsyncUpdateFileMixin, CollectionMixin, CreateFileMixin, DeleteMixin, + DisableMixin, + EnableMixin, GetMixin, UpdateFileMixin, ) @@ -16,9 +20,7 @@ from mpt_api_client.resources.accounts.mixins import ( ActivatableMixin, AsyncActivatableMixin, - AsyncEnablableMixin, AsyncValidateMixin, - EnablableMixin, ValidateMixin, ) @@ -41,7 +43,8 @@ class BuyersService( CreateFileMixin[Buyer], UpdateFileMixin[Buyer], ActivatableMixin[Buyer], - EnablableMixin[Buyer], + EnableMixin[Buyer], + DisableMixin[Buyer], ValidateMixin[Buyer], GetMixin[Buyer], DeleteMixin, @@ -74,7 +77,8 @@ class AsyncBuyersService( AsyncCreateFileMixin[Buyer], AsyncUpdateFileMixin[Buyer], AsyncActivatableMixin[Buyer], - AsyncEnablableMixin[Buyer], + AsyncEnableMixin[Buyer], + AsyncDisableMixin[Buyer], AsyncValidateMixin[Buyer], AsyncGetMixin[Buyer], AsyncDeleteMixin, diff --git a/mpt_api_client/resources/accounts/licensees.py b/mpt_api_client/resources/accounts/licensees.py index 9a057c1f..18c85ef7 100644 --- a/mpt_api_client/resources/accounts/licensees.py +++ b/mpt_api_client/resources/accounts/licensees.py @@ -3,19 +3,19 @@ AsyncCollectionMixin, AsyncCreateFileMixin, AsyncDeleteMixin, + AsyncDisableMixin, + AsyncEnableMixin, AsyncGetMixin, AsyncUpdateFileMixin, CollectionMixin, CreateFileMixin, DeleteMixin, + DisableMixin, + EnableMixin, GetMixin, UpdateFileMixin, ) from mpt_api_client.models import Model -from mpt_api_client.resources.accounts.mixins import ( - AsyncEnablableMixin, - EnablableMixin, -) class Licensee(Model): @@ -35,7 +35,8 @@ class LicenseesServiceConfig: class LicenseesService( CreateFileMixin[Licensee], UpdateFileMixin[Licensee], - EnablableMixin[Licensee], + EnableMixin[Licensee], + DisableMixin[Licensee], GetMixin[Licensee], DeleteMixin, CollectionMixin[Licensee], @@ -48,7 +49,8 @@ class LicenseesService( class AsyncLicenseesService( AsyncCreateFileMixin[Licensee], AsyncUpdateFileMixin[Licensee], - AsyncEnablableMixin[Licensee], + AsyncEnableMixin[Licensee], + AsyncDisableMixin[Licensee], AsyncGetMixin[Licensee], AsyncDeleteMixin, AsyncCollectionMixin[Licensee], diff --git a/mpt_api_client/resources/accounts/mixins.py b/mpt_api_client/resources/accounts/mixins.py index 9cce1738..b5d71f32 100644 --- a/mpt_api_client/resources/accounts/mixins.py +++ b/mpt_api_client/resources/accounts/mixins.py @@ -27,32 +27,6 @@ def deactivate(self, resource_id: str, resource_data: ResourceData | None = None ) -class EnablableMixin[Model]: - """Enablable mixin for enabling and disabling resources.""" - - def enable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Enable 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", "enable", json=resource_data - ) - - def disable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Disable 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", "disable", json=resource_data - ) - - class ValidateMixin[Model]: """Validate mixin adds the ability to validate a resource.""" @@ -159,32 +133,6 @@ async def deactivate( ) -class AsyncEnablableMixin[Model]: - """Asynchronous Enablable mixin for enabling and disabling resources.""" - - async def enable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Enable 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", "enable", json=resource_data - ) - - async def disable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Disable 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", "disable", json=resource_data - ) - - class AsyncValidateMixin[Model]: """Asynchronous Validate mixin adds the ability to validate a resource.""" diff --git a/mpt_api_client/resources/notifications/subscribers.py b/mpt_api_client/resources/notifications/subscribers.py index 47ee4594..e3bf3864 100644 --- a/mpt_api_client/resources/notifications/subscribers.py +++ b/mpt_api_client/resources/notifications/subscribers.py @@ -1,8 +1,12 @@ from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, + AsyncDisableMixin, + AsyncEnableMixin, AsyncManagedResourceMixin, CollectionMixin, + DisableMixin, + EnableMixin, ManagedResourceMixin, ) from mpt_api_client.models import Model @@ -21,6 +25,8 @@ class SubscribersServiceConfig: class SubscribersService( + EnableMixin[Subscriber], + DisableMixin[Subscriber], ManagedResourceMixin[Subscriber], CollectionMixin[Subscriber], Service[Subscriber], @@ -30,6 +36,8 @@ class SubscribersService( class AsyncSubscribersService( + AsyncEnableMixin[Subscriber], + AsyncDisableMixin[Subscriber], AsyncManagedResourceMixin[Subscriber], AsyncCollectionMixin[Subscriber], AsyncService[Subscriber], diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 14985b58..e073a477 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -151,3 +151,8 @@ def authorization_id(e2e_config): @pytest.fixture def price_list_id(e2e_config): return e2e_config["catalog.price_list.id"] + + +@pytest.fixture +def user_group_id(e2e_config): + return e2e_config["accounts.user_group.id"] diff --git a/tests/e2e/notifications/subscribers/__init__.py b/tests/e2e/notifications/subscribers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/notifications/subscribers/conftest.py b/tests/e2e/notifications/subscribers/conftest.py new file mode 100644 index 00000000..aa500836 --- /dev/null +++ b/tests/e2e/notifications/subscribers/conftest.py @@ -0,0 +1,64 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError + + +@pytest.fixture +def subscriber_id(e2e_config): + return e2e_config.get("notifications.subscriber.id") + + +@pytest.fixture +def invalid_subscriber_id(): + return "NTS-0000-0000-0000" + + +@pytest.fixture +def recipients_factory(user_id, user_group_id): + def _recipients( # noqa: WPS430 + users: list | None = None, + user_groups: list | None = None, + ) -> dict: + return { + "users": users or [{"id": user_id}], + "userGroups": user_groups or [{"id": user_group_id}], + } + + return _recipients + + +@pytest.fixture +def subscriber_factory(recipients_factory): + def _subscriber( # noqa: WPS430 + recipients: dict | None = None, + note: str = "Test note", + ) -> dict: + return { + "recipients": recipients or recipients_factory(), + "note": note, + } + + return _subscriber + + +@pytest.fixture +def disabled_subscriber_id(mpt_client, subscriber_id): + subscriber = mpt_client.notifications.subscribers.get(subscriber_id) + if subscriber.status != "Disabled": + subscriber = mpt_client.notifications.subscribers.disable(subscriber_id) + + yield subscriber.id + + try: + mpt_client.notifications.subscribers.enable(subscriber_id) + except MPTAPIError: + print(f"TEARDOWN - Unable to re-enable subscriber {subscriber_id=}") # noqa: WPS421 + + +@pytest.fixture +def active_subscriber_id(mpt_client, subscriber_id): + subscriber = mpt_client.notifications.subscribers.get(subscriber_id) + if subscriber.status != "Active": + subscriber = mpt_client.notifications.subscribers.enable(subscriber_id) + + return subscriber.id diff --git a/tests/e2e/notifications/subscribers/test_async_subscribers.py b/tests/e2e/notifications/subscribers/test_async_subscribers.py new file mode 100644 index 00000000..e468425f --- /dev/null +++ b/tests/e2e/notifications/subscribers/test_async_subscribers.py @@ -0,0 +1,77 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +async def test_get_subscriber_by_id(async_mpt_client, subscriber_id): + result = await async_mpt_client.notifications.subscribers.get(subscriber_id) + + assert result is not None + + +async def test_list_subscribers(async_mpt_client): + limit = 10 + + result = await async_mpt_client.notifications.subscribers.fetch_page(limit=limit) + + assert len(result) > 0 + + +async def test_get_subscriber_by_id_not_found(async_mpt_client, invalid_subscriber_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + await async_mpt_client.notifications.subscribers.get(invalid_subscriber_id) + + +async def test_filter_subscribers(async_mpt_client, subscriber_id): + select_fields = ["-config"] + async_filtered_subscribers = async_mpt_client.notifications.subscribers.filter( + RQLQuery(id=subscriber_id) + ).select(*select_fields) + + result = [ + filtered_subscriber async for filtered_subscriber in async_filtered_subscribers.iterate() + ] + + assert len(result) > 0 + + +async def test_update_subscriber_not_found( + async_mpt_client, subscriber_factory, invalid_subscriber_id, recipients_factory +): + updated_subscriber_data = subscriber_factory(recipients=recipients_factory()) + + with pytest.raises(MPTAPIError): + await async_mpt_client.notifications.subscribers.update( + invalid_subscriber_id, updated_subscriber_data + ) + + +async def test_disable_subscriber(async_mpt_client, active_subscriber_id): + result = await async_mpt_client.notifications.subscribers.disable(active_subscriber_id) + + assert result is not None + assert result.status == "Disabled" + + +async def test_disable_subscriber_not_found(async_mpt_client, invalid_subscriber_id): + with pytest.raises( + MPTAPIError, match=r"400 Bad Request - Subscriber with id 'NTS-0000-0000-0000' not found" + ): + await async_mpt_client.notifications.subscribers.disable(invalid_subscriber_id) + + +async def test_enable_subscriber(async_mpt_client, disabled_subscriber_id): + result = await async_mpt_client.notifications.subscribers.enable(disabled_subscriber_id) + + assert result is not None + assert result.status == "Active" + + +async def test_enable_subscriber_not_found(async_mpt_client, invalid_subscriber_id): + with pytest.raises( + MPTAPIError, match=r"400 Bad Request - Subscriber with id 'NTS-0000-0000-0000' not found" + ): + await async_mpt_client.notifications.subscribers.enable(invalid_subscriber_id) diff --git a/tests/e2e/notifications/subscribers/test_sync_subscribers.py b/tests/e2e/notifications/subscribers/test_sync_subscribers.py new file mode 100644 index 00000000..8197bb14 --- /dev/null +++ b/tests/e2e/notifications/subscribers/test_sync_subscribers.py @@ -0,0 +1,73 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +def test_get_subscriber_by_id(mpt_client, subscriber_id): + result = mpt_client.notifications.subscribers.get(subscriber_id) + + assert result is not None + + +def test_list_subscribers(mpt_client): + limit = 10 + + result = mpt_client.notifications.subscribers.fetch_page(limit=limit) + + assert len(result) > 0 + + +def test_get_subscriber_by_id_not_found(mpt_client, invalid_subscriber_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + mpt_client.notifications.subscribers.get(invalid_subscriber_id) + + +def test_filter_subscribers(mpt_ops, subscriber_id): + select_fields = ["-config"] + filtered_subscribers = mpt_ops.notifications.subscribers.filter( + RQLQuery(id=subscriber_id) + ).select(*select_fields) + + result = list(filtered_subscribers.iterate()) + + assert len(result) > 0 + + +def test_update_subscriber_not_found( + mpt_ops, subscriber_factory, invalid_subscriber_id, recipients_factory +): + updated_subscriber_data = subscriber_factory(recipients=recipients_factory()) + + with pytest.raises(MPTAPIError): + mpt_ops.notifications.subscribers.update(invalid_subscriber_id, updated_subscriber_data) + + +def test_disable_subscriber(mpt_client, active_subscriber_id): + result = mpt_client.notifications.subscribers.disable(active_subscriber_id) + + assert result is not None + assert result.status == "Disabled" + + +def test_disable_subscriber_not_found(mpt_client, invalid_subscriber_id): + with pytest.raises( + MPTAPIError, match=r"400 Bad Request - Subscriber with id 'NTS-0000-0000-0000' not found" + ): + mpt_client.notifications.subscribers.disable(invalid_subscriber_id) + + +def test_enable_subscriber(mpt_client, disabled_subscriber_id): + result = mpt_client.notifications.subscribers.enable(disabled_subscriber_id) + + assert result is not None + assert result.status == "Active" + + +def test_enable_subscriber_not_found(mpt_client, invalid_subscriber_id): + with pytest.raises( + MPTAPIError, match=r"400 Bad Request - Subscriber with id 'NTS-0000-0000-0000' not found" + ): + mpt_client.notifications.subscribers.enable(invalid_subscriber_id) diff --git a/tests/unit/http/test_mixins.py b/tests/unit/http/test_mixins.py index 03cc897e..2af45b23 100644 --- a/tests/unit/http/test_mixins.py +++ b/tests/unit/http/test_mixins.py @@ -8,11 +8,15 @@ from mpt_api_client import RQLQuery from mpt_api_client.exceptions import MPTAPIError from mpt_api_client.http import AsyncService, Service -from mpt_api_client.http.mixins import ( +from mpt_api_client.http.mixins import ( # noqa: WPS235 + AsyncDisableMixin, + AsyncEnableMixin, AsyncFilesOperationsMixin, AsyncManagedResourceMixin, AsyncModifiableResourceMixin, AsyncUpdateFileMixin, + DisableMixin, + EnableMixin, FilesOperationsMixin, ManagedResourceMixin, ModifiableResourceMixin, @@ -67,6 +71,24 @@ class DummyAsyncUpdateFileService( _upload_data_key = "document" +class EnableDisableService( + EnableMixin[DummyModel], + DisableMixin[DummyModel], + Service[DummyModel], +): + _endpoint = "/public/v1/dummy/enablable/" + _model_class = DummyModel + + +class AsyncEnableDisableService( + AsyncEnableMixin[DummyModel], + AsyncDisableMixin[DummyModel], + AsyncService[DummyModel], +): + _endpoint = "/public/v1/dummy/enablable/" + _model_class = DummyModel + + @pytest.fixture def dummy_file_operations_service(http_client): """Fixture for DummyFileOperationsService.""" @@ -1277,3 +1299,135 @@ async def test_async_update_file_no_file(async_update_file_service): # noqa: WP assert "multipart/form-data" in request.headers["Content-Type"] assert result.to_dict() == response_expected_data assert isinstance(result, DummyModel) + + +@pytest.fixture +def async_enablable_service(async_http_client): + return AsyncEnableDisableService(http_client=async_http_client) + + +@pytest.fixture +def enablable_service(http_client): + return EnableDisableService(http_client=http_client) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("enable", {"id": "OBJ-0000-0001", "status": "update"}), + ("disable", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +def test_enablable_resource_actions(enablable_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/enablable/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(enablable_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"), + [ + ("enable", None), + ("disable", None), + ], +) +def test_enablable_resource_actions_no_data(enablable_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/enablable/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(enablable_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"), + [ + ("enable", {"id": "OBJ-0000-0001", "status": "update"}), + ("disable", {"id": "OBJ-0000-0001", "status": "update"}), + ], +) +async def test_async_enablable_resource_actions(async_enablable_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/enablable/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_enablable_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"), + [ + ("enable", None), + ("disable", None), + ], +) +async def test_async_enablable_resource_actions_no_data( + async_enablable_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/enablable/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_enablable_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 index d9e15451..620375e6 100644 --- a/tests/unit/resources/accounts/test_mixins.py +++ b/tests/unit/resources/accounts/test_mixins.py @@ -13,11 +13,9 @@ ActivatableMixin, AsyncActivatableMixin, AsyncBlockableMixin, - AsyncEnablableMixin, AsyncInvitableMixin, AsyncValidateMixin, BlockableMixin, - EnablableMixin, InvitableMixin, ValidateMixin, ) @@ -42,24 +40,6 @@ class DummyAsyncActivatableService( _collection_key = "data" -class DummyEnablableService( - EnablableMixin[DummyModel], - Service[DummyModel], -): - _endpoint = "/public/v1/dummy/enablable/" - _model_class = DummyModel - _collection_key = "data" - - -class DummyAsyncEnablableService( - AsyncEnablableMixin[DummyModel], - AsyncService[DummyModel], -): - _endpoint = "/public/v1/dummy/enablable/" - _model_class = DummyModel - _collection_key = "data" - - class DummyValidateService( ValidateMixin[DummyModel], Service[DummyModel], @@ -168,16 +148,6 @@ def async_activatable_service(async_http_client): return DummyAsyncActivatableService(http_client=async_http_client) -@pytest.fixture -def enablable_service(http_client): - return DummyEnablableService(http_client=http_client) - - -@pytest.fixture -def async_enablable_service(async_http_client): - return DummyAsyncEnablableService(http_client=async_http_client) - - @pytest.fixture def validate_service(http_client): return DummyValidateService(http_client=http_client) @@ -350,128 +320,6 @@ async def test_async_activatable_resource_actions_no_data( assert isinstance(result, DummyModel) -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("enable", {"id": "OBJ-0000-0001", "status": "update"}), - ("disable", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -def test_enablable_resource_actions(enablable_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/enablable/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(enablable_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"), - [ - ("enable", None), - ("disable", None), - ], -) -def test_enablable_resource_actions_no_data(enablable_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/enablable/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(enablable_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"), - [ - ("enable", {"id": "OBJ-0000-0001", "status": "update"}), - ("disable", {"id": "OBJ-0000-0001", "status": "update"}), - ], -) -async def test_async_enablable_resource_actions(async_enablable_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/enablable/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_enablable_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"), - [ - ("enable", None), - ("disable", None), - ], -) -async def test_async_enablable_resource_actions_no_data( - async_enablable_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/enablable/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_enablable_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"})] )