From f6eead4b52f3c36066f1ce7a1b94271f401ce10e Mon Sep 17 00:00:00 2001 From: Albert Sola Date: Mon, 8 Sep 2025 14:39:01 +0100 Subject: [PATCH] MPT-12842 Add commerce requests --- mpt_api_client/http/__init__.py | 11 +- mpt_api_client/resources/commerce/mixins.py | 186 ++++++++++++++++ mpt_api_client/resources/commerce/orders.py | 162 +++----------- .../resources/commerce/orders_subscription.py | 7 +- mpt_api_client/resources/commerce/requests.py | 65 ++++++ pyproject.toml | 2 +- setup.cfg | 3 + tests/resources/commerce/test_mixins.py | 181 ++++++++++++++++ tests/resources/commerce/test_orders.py | 200 +++--------------- tests/resources/commerce/test_requests.py | 29 +++ 10 files changed, 541 insertions(+), 305 deletions(-) create mode 100644 mpt_api_client/resources/commerce/mixins.py create mode 100644 mpt_api_client/resources/commerce/requests.py create mode 100644 tests/resources/commerce/test_mixins.py create mode 100644 tests/resources/commerce/test_requests.py diff --git a/mpt_api_client/http/__init__.py b/mpt_api_client/http/__init__.py index cb7f0135..891be886 100644 --- a/mpt_api_client/http/__init__.py +++ b/mpt_api_client/http/__init__.py @@ -1,7 +1,14 @@ from mpt_api_client.http.async_client import AsyncHTTPClient from mpt_api_client.http.async_service import AsyncService from mpt_api_client.http.client import HTTPClient -from mpt_api_client.http.mixins import AsyncCreateMixin, AsyncDeleteMixin, CreateMixin, DeleteMixin +from mpt_api_client.http.mixins import ( + AsyncCreateMixin, + AsyncDeleteMixin, + AsyncUpdateMixin, + CreateMixin, + DeleteMixin, + UpdateMixin, +) from mpt_api_client.http.service import Service __all__ = [ # noqa: WPS410 @@ -9,8 +16,10 @@ "AsyncDeleteMixin", "AsyncHTTPClient", "AsyncService", + "AsyncUpdateMixin", "CreateMixin", "DeleteMixin", "HTTPClient", "Service", + "UpdateMixin", ] diff --git a/mpt_api_client/resources/commerce/mixins.py b/mpt_api_client/resources/commerce/mixins.py new file mode 100644 index 00000000..664dc4cb --- /dev/null +++ b/mpt_api_client/resources/commerce/mixins.py @@ -0,0 +1,186 @@ +import httpx + +from mpt_api_client.models import ResourceData + + +class ValidateMixin[BaseModel]: + """Validate mixin.""" + + def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> BaseModel: + """Switch order to validate state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + """ + return self._resource_action(resource_id, "POST", "validate", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class ProcessMixin[BaseModel]: + """Process mixin.""" + + def process(self, resource_id: str, resource_data: ResourceData | None = None) -> BaseModel: + """Switch order to process state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + """ + return self._resource_action(resource_id, "POST", "process", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class QueryMixin[BaseModel]: + """Query mixin.""" + + def query(self, resource_id: str, resource_data: ResourceData | None = None) -> BaseModel: + """Switch order to query state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + """ + return self._resource_action(resource_id, "POST", "query", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class CompleteMixin[BaseModel]: + """Complete mixin.""" + + def complete(self, resource_id: str, resource_data: ResourceData | None = None) -> BaseModel: + """Switch order to complete state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + """ + return self._resource_action(resource_id, "POST", "complete", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class FailMixin[BaseModel]: + """Fail mixin.""" + + def fail(self, resource_id: str, resource_data: ResourceData | None = None) -> BaseModel: + """Switch order to fail state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + """ + return self._resource_action(resource_id, "POST", "fail", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class TemplateMixin: + """Template mixin.""" + + def template(self, resource_id: str) -> str: + """Render order template. + + Args: + resource_id: Order resource ID + + Returns: + Order template text in markdown format. + """ + response: httpx.Response = self._resource_do_request(resource_id, "GET", "template") # type: ignore[attr-defined] + return response.text + + +class AsyncValidateMixin[BaseModel]: + """Async Validate Mixin.""" + + async def validate( + self, resource_id: str, resource_data: ResourceData | None = None + ) -> BaseModel: + """Switch order to validate state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + + Returns: + Updated order resource + """ + return await self._resource_action(resource_id, "POST", "validate", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class AsyncProcessMixin[BaseModel]: + """Async Process Mixin.""" + + async def process( + self, resource_id: str, resource_data: ResourceData | None = None + ) -> BaseModel: + """Switch order to process state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + + Returns: + Updated order resource + """ + return await self._resource_action(resource_id, "POST", "process", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class AsyncQueryMixin[BaseModel]: + """Async Query Mixin.""" + + async def query(self, resource_id: str, resource_data: ResourceData | None = None) -> BaseModel: + """Switch order to query state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + + Returns: + Updated order resource + """ + return await self._resource_action(resource_id, "POST", "query", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class AsyncCompleteMixin[BaseModel]: + """Async Complete Mixin.""" + + async def complete( + self, resource_id: str, resource_data: ResourceData | None = None + ) -> BaseModel: + """Switch order to complete state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + + Returns: + Updated order resource + """ + return await self._resource_action(resource_id, "POST", "complete", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class AsyncFailMixin[BaseModel]: + """Async Fail Mixin.""" + + async def fail(self, resource_id: str, resource_data: ResourceData | None = None) -> BaseModel: + """Switch order to fail state. + + Args: + resource_id: Order resource ID + resource_data: Order data will be updated + + Returns: + Updated order resource + """ + return await self._resource_action(resource_id, "POST", "fail", json=resource_data) # type: ignore[attr-defined, no-any-return] + + +class AsyncTemplateMixin: + """Async Template Mixin.""" + + async def template(self, resource_id: str) -> str: + """Render order template. + + Args: + resource_id: Order resource ID + + Returns: + Order template text in markdown format. + """ + response: httpx.Response = await self._resource_do_request(resource_id, "GET", "template") # type: ignore[attr-defined] + return response.text diff --git a/mpt_api_client/resources/commerce/orders.py b/mpt_api_client/resources/commerce/orders.py index 58fb06b1..c1a7351c 100644 --- a/mpt_api_client/resources/commerce/orders.py +++ b/mpt_api_client/resources/commerce/orders.py @@ -2,12 +2,27 @@ AsyncCreateMixin, AsyncDeleteMixin, AsyncService, + AsyncUpdateMixin, CreateMixin, DeleteMixin, Service, + UpdateMixin, ) -from mpt_api_client.http.mixins import AsyncUpdateMixin, UpdateMixin from mpt_api_client.models import Model, ResourceData +from mpt_api_client.resources.commerce.mixins import ( + AsyncCompleteMixin, + AsyncFailMixin, + AsyncProcessMixin, + AsyncQueryMixin, + AsyncTemplateMixin, + AsyncValidateMixin, + CompleteMixin, + FailMixin, + ProcessMixin, + QueryMixin, + TemplateMixin, + ValidateMixin, +) from mpt_api_client.resources.commerce.orders_subscription import ( AsyncOrderSubscriptionsService, OrderSubscriptionsService, @@ -26,60 +41,21 @@ class OrdersServiceConfig: _collection_key = "data" -class OrdersService( # noqa: WPS215 WPS214 +class OrdersService( CreateMixin[Order], DeleteMixin, UpdateMixin[Order], + ValidateMixin[Order], + ProcessMixin[Order], + QueryMixin[Order], + TemplateMixin, + CompleteMixin[Order], + FailMixin[Order], Service[Order], OrdersServiceConfig, ): """Orders service.""" - def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to validate state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - """ - return self._resource_action(resource_id, "POST", "validate", json=resource_data) - - def process(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to process state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - """ - return self._resource_action(resource_id, "POST", "process", json=resource_data) - - def query(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to query state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - """ - return self._resource_action(resource_id, "POST", "query", json=resource_data) - - def complete(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to complete state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - """ - return self._resource_action(resource_id, "POST", "complete", json=resource_data) - - def fail(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to fail state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - """ - return self._resource_action(resource_id, "POST", "fail", json=resource_data) - def notify(self, resource_id: str, user: ResourceData) -> None: """Notify user about order status. @@ -89,18 +65,6 @@ def notify(self, resource_id: str, user: ResourceData) -> None: """ self._resource_do_request(resource_id, "POST", "notify", json=user) - def template(self, resource_id: str) -> str: - """Render order template. - - Args: - resource_id: Order resource ID - - Returns: - Order template text in markdown format. - """ - response = self._resource_do_request(resource_id, "GET", "template") - return response.text - def subscriptions(self, order_id: str) -> OrderSubscriptionsService: """Get the subscription service for the given Order id. @@ -116,75 +80,21 @@ def subscriptions(self, order_id: str) -> OrderSubscriptionsService: ) -class AsyncOrdersService( # noqa: WPS215 WPS214 +class AsyncOrdersService( AsyncCreateMixin[Order], AsyncDeleteMixin, AsyncUpdateMixin[Order], + AsyncValidateMixin[Order], + AsyncProcessMixin[Order], + AsyncQueryMixin[Order], + AsyncCompleteMixin[Order], + AsyncFailMixin[Order], + AsyncTemplateMixin, AsyncService[Order], OrdersServiceConfig, ): """Async Orders service.""" - async def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to validate state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - - Returns: - Updated order resource - """ - return await self._resource_action(resource_id, "POST", "validate", json=resource_data) - - async def process(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to process state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - - Returns: - Updated order resource - """ - return await self._resource_action(resource_id, "POST", "process", json=resource_data) - - async def query(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to query state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - - Returns: - Updated order resource - """ - return await self._resource_action(resource_id, "POST", "query", json=resource_data) - - async def complete(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to complete state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - - Returns: - Updated order resource - """ - return await self._resource_action(resource_id, "POST", "complete", json=resource_data) - - async def fail(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: - """Switch order to fail state. - - Args: - resource_id: Order resource ID - resource_data: Order data will be updated - - Returns: - Updated order resource - """ - return await self._resource_action(resource_id, "POST", "fail", json=resource_data) - async def notify(self, resource_id: str, resource_data: ResourceData) -> None: """Notify user about order status. @@ -194,18 +104,6 @@ async def notify(self, resource_id: str, resource_data: ResourceData) -> None: """ await self._resource_do_request(resource_id, "POST", "notify", json=resource_data) - async def template(self, resource_id: str) -> str: - """Render order template. - - Args: - resource_id: Order resource ID - - Returns: - Order template text in markdown format. - """ - response = await self._resource_do_request(resource_id, "GET", "template") - return response.text - def subscriptions(self, order_id: str) -> AsyncOrderSubscriptionsService: """Get the subscription service for the given Order id. diff --git a/mpt_api_client/resources/commerce/orders_subscription.py b/mpt_api_client/resources/commerce/orders_subscription.py index d2ef0073..cd147169 100644 --- a/mpt_api_client/resources/commerce/orders_subscription.py +++ b/mpt_api_client/resources/commerce/orders_subscription.py @@ -1,8 +1,11 @@ -from mpt_api_client.http import AsyncService, CreateMixin, DeleteMixin, Service -from mpt_api_client.http.mixins import ( +from mpt_api_client.http import ( AsyncCreateMixin, AsyncDeleteMixin, + AsyncService, AsyncUpdateMixin, + CreateMixin, + DeleteMixin, + Service, UpdateMixin, ) from mpt_api_client.models import Model diff --git a/mpt_api_client/resources/commerce/requests.py b/mpt_api_client/resources/commerce/requests.py new file mode 100644 index 00000000..9900fc86 --- /dev/null +++ b/mpt_api_client/resources/commerce/requests.py @@ -0,0 +1,65 @@ +from mpt_api_client.http import ( + AsyncCreateMixin, + AsyncDeleteMixin, + AsyncService, + AsyncUpdateMixin, + CreateMixin, + DeleteMixin, + Service, + UpdateMixin, +) +from mpt_api_client.models import Model +from mpt_api_client.resources.commerce.mixins import ( + AsyncCompleteMixin, + AsyncProcessMixin, + AsyncQueryMixin, + AsyncTemplateMixin, + AsyncValidateMixin, + CompleteMixin, + ProcessMixin, + QueryMixin, + TemplateMixin, + ValidateMixin, +) + + +class Request(Model): + """Request model.""" + + +class RequestServiceConfig: + """Request service config.""" + + _endpoint = "/public/v1/commerce/orders" + _model_class = Request + _collection_key = "data" + + +class RequestService( + CreateMixin[Request], + UpdateMixin[Request], + DeleteMixin, + ValidateMixin[Request], + ProcessMixin[Request], + QueryMixin[Request], + CompleteMixin[Request], + TemplateMixin, + Service[Request], + RequestServiceConfig, +): + """Request service model.""" + + +class AsyncRequestService( + AsyncCreateMixin[Request], + AsyncUpdateMixin[Request], + AsyncDeleteMixin, + AsyncValidateMixin[Request], + AsyncProcessMixin[Request], + AsyncQueryMixin[Request], + AsyncCompleteMixin[Request], + AsyncTemplateMixin, + AsyncService[Request], + RequestServiceConfig, +): + """Async Request service model.""" diff --git a/pyproject.toml b/pyproject.toml index ffa4fadb..95705ea4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ build-backend = "hatchling.build" [tool.pytest.ini_options] testpaths = "tests" pythonpath = "." -addopts = "--cov=mpt_api_client --cov-report=term-missing --cov-report=html --cov-report=xml" +addopts = "--cov=mpt_api_client --cov-report=term-missing --cov-report=html --cov-report=xml --import-mode=importlib" log_cli = false asyncio_mode = "auto" filterwarnings = [ diff --git a/setup.cfg b/setup.cfg index f4133b87..8d0c6149 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,9 @@ extend-ignore = per-file-ignores = + mpt_api_client/resources/commerce/orders.py: WPS214 WPS215 WPS235 + mpt_api_client/resources/commerce/requests.py: WPS215 WPS235 + mpt_api_client/resources/commerce/mixins.py: WPS202 mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214 tests/http/test_async_service.py: WPS204 WPS202 tests/http/test_service.py: WPS204 WPS202 diff --git a/tests/resources/commerce/test_mixins.py b/tests/resources/commerce/test_mixins.py new file mode 100644 index 00000000..0bce39df --- /dev/null +++ b/tests/resources/commerce/test_mixins.py @@ -0,0 +1,181 @@ +import httpx +import pytest +import respx + +from mpt_api_client.resources.commerce.orders import AsyncOrdersService, Order, OrdersService + + +@pytest.fixture +def dummy_service(http_client): + return OrdersService(http_client=http_client) + + +@pytest.fixture +def async_dummy_service(async_http_client): + return AsyncOrdersService(http_client=async_http_client) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("validate", {"id": "ORD-123", "status": "update"}), + ("process", {"id": "ORD-123", "status": "update"}), + ("query", {"id": "ORD-123", "status": "update"}), + ("complete", {"id": "ORD-123", "status": "update"}), + ("fail", {"id": "ORD-123", "status": "update"}), + ], +) +def test_custom_resource_actions(dummy_service, action, input_status): + request_expected_content = b'{"id":"ORD-123","status":"update"}' + response_expected_data = {"id": "ORD-123", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/commerce/orders/ORD-123/{action}" + ).mock( + return_value=httpx.Response( + status_code=200, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + order = getattr(dummy_service, action)("ORD-123", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + + assert request.content == request_expected_content + assert order.to_dict() == response_expected_data + assert isinstance(order, Order) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("validate", None), + ("process", None), + ("query", None), + ("complete", None), + ("fail", None), + ], +) +def test_custom_resource_actions_no_data(dummy_service, action, input_status): + request_expected_content = b"" + response_expected_data = {"id": "ORD-123", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/commerce/orders/ORD-123/{action}" + ).mock( + return_value=httpx.Response( + status_code=200, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + order = getattr(dummy_service, action)("ORD-123") + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert order.to_dict() == response_expected_data + assert isinstance(order, Order) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("validate", {"id": "ORD-123", "status": "update"}), + ("process", {"id": "ORD-123", "status": "update"}), + ("query", {"id": "ORD-123", "status": "update"}), + ("complete", {"id": "ORD-123", "status": "update"}), + ("fail", {"id": "ORD-123", "status": "update"}), + ], +) +async def test_async_custom_resource_actions(async_dummy_service, action, input_status): + request_expected_content = b'{"id":"ORD-123","status":"update"}' + response_expected_data = {"id": "ORD-123", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/commerce/orders/ORD-123/{action}" + ).mock( + return_value=httpx.Response( + status_code=200, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + order = await getattr(async_dummy_service, action)("ORD-123", input_status) + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + + assert request.content == request_expected_content + assert order.to_dict() == response_expected_data + assert isinstance(order, Order) + + +@pytest.mark.parametrize( + ("action", "input_status"), + [ + ("validate", None), + ("process", None), + ("query", None), + ("complete", None), + ("fail", None), + ], +) +async def test_async_custom_resource_actions_nodata(async_dummy_service, action, input_status): + request_expected_content = b"" + response_expected_data = {"id": "ORD-123", "status": "new_status"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/commerce/orders/ORD-123/{action}" + ).mock( + return_value=httpx.Response( + status_code=200, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + order = await getattr(async_dummy_service, action)("ORD-123") + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == request_expected_content + assert order.to_dict() == response_expected_data + assert isinstance(order, Order) + + +def test_template(dummy_service): + with respx.mock: + mock_route = respx.get( + "https://api.example.com/public/v1/commerce/orders/ORD-123/template" + ).mock( + return_value=httpx.Response( + status_code=200, + headers={"content-type": "text/markdown"}, + content="# Order Template\n\nThis is a markdown template.", + ) + ) + + markdown_template = dummy_service.template("ORD-123") + + assert mock_route.called + assert mock_route.call_count == 1 + assert markdown_template == "# Order Template\n\nThis is a markdown template." + + +async def test_async_template(async_dummy_service): + template_content = "# Order Template\n\nThis is a markdown template." + with respx.mock: + respx.get("https://api.example.com/public/v1/commerce/orders/ORD-123/template").mock( + return_value=httpx.Response( + status_code=200, + headers={"content-type": "text/markdown"}, + content=template_content, + ) + ) + + template = await async_dummy_service.template("ORD-123") + + assert template == template_content diff --git a/tests/resources/commerce/test_orders.py b/tests/resources/commerce/test_orders.py index 4132210d..41fe8e82 100644 --- a/tests/resources/commerce/test_orders.py +++ b/tests/resources/commerce/test_orders.py @@ -2,7 +2,7 @@ import pytest import respx -from mpt_api_client.resources.commerce.orders import AsyncOrdersService, Order, OrdersService +from mpt_api_client.resources.commerce.orders import AsyncOrdersService, OrdersService from mpt_api_client.resources.commerce.orders_subscription import ( AsyncOrderSubscriptionsService, OrderSubscriptionsService, @@ -19,72 +19,6 @@ def async_orders_service(async_http_client): return AsyncOrdersService(http_client=async_http_client) -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("validate", {"id": "ORD-123", "status": "update"}), - ("process", {"id": "ORD-123", "status": "update"}), - ("query", {"id": "ORD-123", "status": "update"}), - ("complete", {"id": "ORD-123", "status": "update"}), - ("fail", {"id": "ORD-123", "status": "update"}), - ], -) -def test_custom_resource_actions(orders_service, action, input_status): - request_expected_content = b'{"id":"ORD-123","status":"update"}' - response_expected_data = {"id": "ORD-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/commerce/orders/ORD-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=200, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - order = getattr(orders_service, action)("ORD-123", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - - assert request.content == request_expected_content - assert order.to_dict() == response_expected_data - assert isinstance(order, Order) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("validate", None), - ("process", None), - ("query", None), - ("complete", None), - ("fail", None), - ], -) -def test_custom_resource_actions_no_data(orders_service, action, input_status): - request_expected_content = b"" - response_expected_data = {"id": "ORD-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/commerce/orders/ORD-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=200, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - - order = getattr(orders_service, action)("ORD-123") - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert order.to_dict() == response_expected_data - assert isinstance(order, Order) - - def test_notify(orders_service): with respx.mock: mock_route = respx.post( @@ -105,90 +39,6 @@ def test_notify(orders_service): assert request.content == b'{"email":"user@example.com","name":"John Doe"}' -def test_template(orders_service): - with respx.mock: - mock_route = respx.get( - "https://api.example.com/public/v1/commerce/orders/ORD-123/template" - ).mock( - return_value=httpx.Response( - status_code=200, - headers={"content-type": "text/markdown"}, - content="# Order Template\n\nThis is a markdown template.", - ) - ) - - markdown_template = orders_service.template("ORD-123") - - assert mock_route.called - assert mock_route.call_count == 1 - assert markdown_template == "# Order Template\n\nThis is a markdown template." - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("validate", {"id": "ORD-123", "status": "update"}), - ("process", {"id": "ORD-123", "status": "update"}), - ("query", {"id": "ORD-123", "status": "update"}), - ("complete", {"id": "ORD-123", "status": "update"}), - ("fail", {"id": "ORD-123", "status": "update"}), - ], -) -async def test_async_custom_resource_actions(async_orders_service, action, input_status): - request_expected_content = b'{"id":"ORD-123","status":"update"}' - response_expected_data = {"id": "ORD-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/commerce/orders/ORD-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=200, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - order = await getattr(async_orders_service, action)("ORD-123", input_status) - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - - assert request.content == request_expected_content - assert order.to_dict() == response_expected_data - assert isinstance(order, Order) - - -@pytest.mark.parametrize( - ("action", "input_status"), - [ - ("validate", None), - ("process", None), - ("query", None), - ("complete", None), - ("fail", None), - ], -) -async def test_async_custom_resource_actions_nodata(async_orders_service, action, input_status): - request_expected_content = b"" - response_expected_data = {"id": "ORD-123", "status": "new_status"} - with respx.mock: - mock_route = respx.post( - f"https://api.example.com/public/v1/commerce/orders/ORD-123/{action}" - ).mock( - return_value=httpx.Response( - status_code=200, - headers={"content-type": "application/json"}, - json=response_expected_data, - ) - ) - order = await getattr(async_orders_service, action)("ORD-123") - - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.content == request_expected_content - assert order.to_dict() == response_expected_data - assert isinstance(order, Order) - - async def test_async_notify(async_orders_service): with respx.mock: mock_route = respx.post( @@ -209,22 +59,6 @@ async def test_async_notify(async_orders_service): assert request.content == b'{"email":"user@example.com","name":"John Doe"}' -async def test_async_template(async_orders_service): - template_content = "# Order Template\n\nThis is a markdown template." - with respx.mock: - respx.get("https://api.example.com/public/v1/commerce/orders/ORD-123/template").mock( - return_value=httpx.Response( - status_code=200, - headers={"content-type": "text/markdown"}, - content=template_content, - ) - ) - - template = await async_orders_service.template("ORD-123") - - assert template == template_content - - def test_subscription_service(http_client): orders_service = OrdersService(http_client=http_client) @@ -243,11 +77,39 @@ def test_async_subscription_service(async_http_client): assert subscriptions.endpoint_params == {"order_id": "ORD-123"} -@pytest.mark.parametrize("method", ["get", "create", "update", "delete"]) +@pytest.mark.parametrize( + "method", + [ + "get", + "create", + "update", + "delete", + "validate", + "process", + "query", + "complete", + "fail", + "template", + ], +) def test_mixins_present(orders_service, method): assert hasattr(orders_service, method) -@pytest.mark.parametrize("method", ["get", "create", "update", "delete"]) +@pytest.mark.parametrize( + "method", + [ + "get", + "create", + "update", + "delete", + "validate", + "process", + "query", + "complete", + "fail", + "template", + ], +) def test_async_mixins_present(async_orders_service, method): assert hasattr(async_orders_service, method) diff --git a/tests/resources/commerce/test_requests.py b/tests/resources/commerce/test_requests.py new file mode 100644 index 00000000..ce0fdcba --- /dev/null +++ b/tests/resources/commerce/test_requests.py @@ -0,0 +1,29 @@ +import pytest + +from mpt_api_client.resources.commerce.requests import AsyncRequestService, RequestService + + +@pytest.fixture +def request_service(http_client): + return RequestService(http_client=http_client) + + +@pytest.fixture +def async_request_service(http_client): + return AsyncRequestService(http_client=http_client) + + +@pytest.mark.parametrize( + "method", + ["get", "create", "update", "delete", "validate", "process", "query", "complete", "template"], +) +def test_mixins_present(request_service, method): + assert hasattr(request_service, method) + + +@pytest.mark.parametrize( + "method", + ["get", "create", "update", "delete", "validate", "process", "query", "complete", "template"], +) +def test_async_mixins_present(async_request_service, method): + assert hasattr(async_request_service, method)