diff --git a/mpt_api_client/mpt_client.py b/mpt_api_client/mpt_client.py index 802da1a..e639190 100644 --- a/mpt_api_client/mpt_client.py +++ b/mpt_api_client/mpt_client.py @@ -8,10 +8,12 @@ AsyncBilling, AsyncCatalog, AsyncCommerce, + AsyncNotifications, Audit, Billing, Catalog, Commerce, + Notifications, ) @@ -63,6 +65,11 @@ def accounts(self) -> AsyncAccounts: """Accounts MPT API Client.""" return AsyncAccounts(http_client=self.http_client) + @property + def notifications(self) -> AsyncNotifications: + """Notifications MPT API Client.""" + return AsyncNotifications(http_client=self.http_client) + class MPTClient: """MPT API Client.""" @@ -116,3 +123,8 @@ def billing(self) -> Billing: def accounts(self) -> Accounts: """Accounts MPT API Client.""" return Accounts(http_client=self.http_client) + + @property + def notifications(self) -> Notifications: + """Notifications MPT API Client.""" + return Notifications(http_client=self.http_client) diff --git a/mpt_api_client/resources/__init__.py b/mpt_api_client/resources/__init__.py index 74dd4ff..0231606 100644 --- a/mpt_api_client/resources/__init__.py +++ b/mpt_api_client/resources/__init__.py @@ -3,6 +3,7 @@ from mpt_api_client.resources.billing import AsyncBilling, Billing from mpt_api_client.resources.catalog import AsyncCatalog, Catalog from mpt_api_client.resources.commerce import AsyncCommerce, Commerce +from mpt_api_client.resources.notifications import AsyncNotifications, Notifications __all__ = [ # noqa: WPS410 "Accounts", @@ -11,8 +12,10 @@ "AsyncBilling", "AsyncCatalog", "AsyncCommerce", + "AsyncNotifications", "Audit", "Billing", "Catalog", "Commerce", + "Notifications", ] diff --git a/mpt_api_client/resources/notifications/__init__.py b/mpt_api_client/resources/notifications/__init__.py new file mode 100644 index 0000000..0fc709e --- /dev/null +++ b/mpt_api_client/resources/notifications/__init__.py @@ -0,0 +1,3 @@ +from mpt_api_client.resources.notifications.notifications import AsyncNotifications, Notifications + +__all__ = ["AsyncNotifications", "Notifications"] # noqa: WPS410 diff --git a/mpt_api_client/resources/notifications/categories.py b/mpt_api_client/resources/notifications/categories.py new file mode 100644 index 0000000..67bb8da --- /dev/null +++ b/mpt_api_client/resources/notifications/categories.py @@ -0,0 +1,71 @@ +from mpt_api_client.http import AsyncService, CreateMixin, Service +from mpt_api_client.http.mixins import AsyncDeleteMixin, AsyncUpdateMixin, DeleteMixin, UpdateMixin +from mpt_api_client.models import Model, ResourceData + + +class Category(Model): + """Notifications Category resource.""" + + +class CategoriesServiceConfig: + """Notifications Categories service configuration.""" + + _endpoint = "/public/v1/notifications/categories" + _model_class = Category + _collection_key = "data" + + +class CategoriesService( + CreateMixin[Category], + UpdateMixin[Category], + DeleteMixin, + Service[Category], + CategoriesServiceConfig, +): + """Notifications Categories service.""" + + def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Update state to Published. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return self._resource_action(resource_id, "POST", "publish", json=resource_data) + + def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Update state to Unpublished. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return self._resource_action(resource_id, "POST", "unpublish", json=resource_data) + + +class AsyncCategoriesService( + CreateMixin[Category], + AsyncUpdateMixin[Category], + AsyncDeleteMixin, + AsyncService[Category], + CategoriesServiceConfig, +): + """Async Notifications Categories service.""" + + async def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Update state to Published. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return await self._resource_action(resource_id, "POST", "publish", json=resource_data) + + async def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: + """Update state to Unpublished. + + Args: + resource_id: Resource ID + resource_data: Resource data will be updated + """ + return await self._resource_action(resource_id, "POST", "unpublish", json=resource_data) diff --git a/mpt_api_client/resources/notifications/notifications.py b/mpt_api_client/resources/notifications/notifications.py new file mode 100644 index 0000000..2a1778d --- /dev/null +++ b/mpt_api_client/resources/notifications/notifications.py @@ -0,0 +1,29 @@ +from mpt_api_client.http import AsyncHTTPClient, HTTPClient +from mpt_api_client.resources.notifications.categories import ( + AsyncCategoriesService, + CategoriesService, +) + + +class Notifications: + """Notifications MPT API Module.""" + + def __init__(self, http_client: HTTPClient): + self.http_client = http_client + + @property + def categories(self) -> CategoriesService: + """Categories service.""" + return CategoriesService(http_client=self.http_client) + + +class AsyncNotifications: + """Notifications MPT API Module.""" + + def __init__(self, http_client: AsyncHTTPClient): + self.http_client = http_client + + @property + def categories(self) -> AsyncCategoriesService: + """Categories service.""" + return AsyncCategoriesService(http_client=self.http_client) diff --git a/setup.cfg b/setup.cfg index ad8d96b..4f1ca68 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,12 +33,14 @@ extend-ignore = per-file-ignores = mpt_api_client/resources/accounts/*.py: WPS215 + mpt_api_client/mpt_client.py: WPS214 WPS235 mpt_api_client/resources/audit/*.py: WPS215 mpt_api_client/resources/billing/*.py: WPS215 WPS202 WPS214 WPS204 mpt_api_client/resources/catalog/*.py: WPS110 WPS215 WPS214 mpt_api_client/resources/commerce/*.py: WPS215 mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214 mpt_api_client/resources/catalog/products.py: WPS204 WPS214 WPS215 + mpt_api_client/resources/notifications/categories.py: WPS215 mpt_api_client/http/mixins.py: WPS202 mpt_api_client/mpt_client.py: WPS235 tests/http/test_async_service.py: WPS204 WPS202 @@ -46,7 +48,7 @@ per-file-ignores = tests/http/test_mixins.py: WPS204 WPS202 tests/resources/catalog/test_products.py: WPS202 WPS210 tests/resources/*/test_mixins.py: WPS118 WPS202 WPS204 - tests/test_mpt.py: WPS210 WPS218 WPS235 + tests/test_mpt_client.py: WPS235 tests/*: # Allow magic strings. diff --git a/tests/resources/notifications/test_categories.py b/tests/resources/notifications/test_categories.py new file mode 100644 index 0000000..878dd5e --- /dev/null +++ b/tests/resources/notifications/test_categories.py @@ -0,0 +1,75 @@ +import httpx +import pytest +import respx + +from mpt_api_client.resources.notifications.categories import ( + AsyncCategoriesService, + CategoriesService, +) + + +@pytest.fixture +def categories_service(http_client): + return CategoriesService(http_client=http_client) + + +@pytest.fixture +def async_categories_service(async_http_client): + return AsyncCategoriesService(http_client=async_http_client) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("publish", {"id": "CAT-123", "status": "to_publish"}), + ("unpublish", {"id": "CAT-123", "status": "to_unpublish"}), + ], +) +def test_custom_category_actions(categories_service, action, input_status): + request_expected_content = b'{"id":"CAT-123","status":"%s"}' % input_status["status"].encode() + response_expected_data = {"id": "CAT-123", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/notifications/categories/CAT-123/{action}" + ).mock( + return_value=httpx.Response( + status_code=200, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + category = getattr(categories_service, action)("CAT-123", input_status) + + assert mock_route.call_count == 1 + assert category.to_dict() == response_expected_data + request = mock_route.calls[0].request + assert request.content == request_expected_content + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("publish", {"id": "CAT-123", "status": "to_publish"}), + ("unpublish", {"id": "CAT-123", "status": "to_unpublish"}), + ], +) +@pytest.mark.asyncio +async def test_async_custom_category_actions(async_categories_service, action, input_status): + request_expected_content = b'{"id":"CAT-123","status":"%s"}' % input_status["status"].encode() + response_expected_data = {"id": "CAT-123", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/notifications/categories/CAT-123/{action}" + ).mock( + return_value=httpx.Response( + status_code=200, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + category = await getattr(async_categories_service, action)("CAT-123", input_status) + + assert category.to_dict() == response_expected_data + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content diff --git a/tests/resources/notifications/test_notifications.py b/tests/resources/notifications/test_notifications.py new file mode 100644 index 0000000..feeada4 --- /dev/null +++ b/tests/resources/notifications/test_notifications.py @@ -0,0 +1,49 @@ +import pytest + +from mpt_api_client.resources import AsyncNotifications, Notifications +from mpt_api_client.resources.notifications.categories import ( + AsyncCategoriesService, + CategoriesService, +) + + +def test_notifications_init(http_client): + commerce = Notifications(http_client=http_client) + + assert isinstance(commerce, Notifications) + assert commerce.http_client is http_client + + +def test_async_notifications_init(async_http_client): + notifications = AsyncNotifications(http_client=async_http_client) + + assert isinstance(notifications, AsyncNotifications) + assert notifications.http_client is async_http_client + + +@pytest.mark.parametrize( + ("attr_name", "expected"), + [ + ("categories", CategoriesService), + ], +) +def test_notifications_properties(http_client, attr_name, expected): + commerce = Notifications(http_client=http_client) + + service = getattr(commerce, attr_name) + + assert isinstance(service, expected) + + +@pytest.mark.parametrize( + ("attr_name", "expected"), + [ + ("categories", AsyncCategoriesService), + ], +) +def test_async_notifications_properties(http_client, attr_name, expected): + commerce = AsyncNotifications(http_client=http_client) + + service = getattr(commerce, attr_name) + + assert isinstance(service, expected) diff --git a/tests/test_mpt.py b/tests/test_mpt_client.py similarity index 50% rename from tests/test_mpt.py rename to tests/test_mpt_client.py index 7f680df..24f53fe 100644 --- a/tests/test_mpt.py +++ b/tests/test_mpt_client.py @@ -9,10 +9,12 @@ AsyncBilling, AsyncCatalog, AsyncCommerce, + AsyncNotifications, Audit, Billing, Catalog, Commerce, + Notifications, ) from tests.conftest import API_TOKEN, API_URL @@ -21,23 +23,22 @@ def get_mpt_client(): return MPTClient.from_config(base_url=API_URL, api_token=API_TOKEN) -def get_async_mpt_client(): - return AsyncMPTClient.from_config(base_url=API_URL, api_token=API_TOKEN) - - @pytest.mark.parametrize( - ("domain_module", "domain_type"), + ("resource_name", "expected_type"), [ - (get_mpt_client(), MPTClient), - (get_mpt_client().commerce, Commerce), - (get_mpt_client().catalog, Catalog), - (get_mpt_client().audit, Audit), - (get_mpt_client().billing, Billing), - (get_mpt_client().accounts, Accounts), + ("commerce", Commerce), + ("catalog", Catalog), + ("audit", Audit), + ("billing", Billing), + ("accounts", Accounts), + ("notifications", Notifications), ], ) -def test_mpt_client(domain_module, domain_type) -> None: - assert isinstance(domain_module, domain_type) +def test_mpt_client(resource_name: str, expected_type: type) -> None: + mpt = MPTClient.from_config(base_url=API_URL, api_token=API_TOKEN) + resource = getattr(mpt, resource_name) + assert isinstance(mpt, MPTClient) + assert isinstance(resource, expected_type) def test_mpt_client_env(monkeypatch: pytest.MonkeyPatch) -> None: @@ -51,18 +52,22 @@ def test_mpt_client_env(monkeypatch: pytest.MonkeyPatch) -> None: @pytest.mark.parametrize( - ("domain_module", "domain_type"), + ("resource_name", "expected_type"), [ - (get_async_mpt_client(), AsyncMPTClient), - (get_async_mpt_client().commerce, AsyncCommerce), - (get_async_mpt_client().catalog, AsyncCatalog), - (get_async_mpt_client().audit, AsyncAudit), - (get_async_mpt_client().billing, AsyncBilling), - (get_async_mpt_client().accounts, AsyncAccounts), + ("commerce", AsyncCommerce), + ("catalog", AsyncCatalog), + ("audit", AsyncAudit), + ("billing", AsyncBilling), + ("accounts", AsyncAccounts), + ("notifications", AsyncNotifications), ], ) -def test_async_mpt_client(domain_module, domain_type) -> None: - assert isinstance(domain_module, domain_type) +def test_async_mpt_client(resource_name: str, expected_type: type) -> None: + mpt = AsyncMPTClient.from_config(base_url=API_URL, api_token=API_TOKEN) + resource = getattr(mpt, resource_name) + + assert isinstance(mpt, AsyncMPTClient) + assert isinstance(resource, expected_type) def test_async_mpt_client_env(monkeypatch: pytest.MonkeyPatch) -> None: