From 42430939f3108d46cf3bc4f0259234ba1844d73e Mon Sep 17 00:00:00 2001 From: Albert Sola Date: Tue, 2 Sep 2025 11:49:25 +0100 Subject: [PATCH 1/2] Fix service and improve orders docstrings --- mpt_api_client/http/async_service.py | 62 ++++++++++++++++++--- mpt_api_client/http/helper.py | 24 ++++++++ mpt_api_client/http/service.py | 57 ++++++++++++++++--- mpt_api_client/http/types.py | 2 + mpt_api_client/resources/commerce/orders.py | 4 +- tests/http/test_async_service.py | 2 +- tests/http/test_service.py | 2 +- 7 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 mpt_api_client/http/helper.py create mode 100644 mpt_api_client/http/types.py diff --git a/mpt_api_client/http/async_service.py b/mpt_api_client/http/async_service.py index d23aeaf4..bfd81c73 100644 --- a/mpt_api_client/http/async_service.py +++ b/mpt_api_client/http/async_service.py @@ -5,6 +5,8 @@ from mpt_api_client.http.async_client import AsyncHTTPClient from mpt_api_client.http.base_service import ServiceBase +from mpt_api_client.http.helper import prepare_query_params +from mpt_api_client.http.types import QueryParam from mpt_api_client.models import Collection, ResourceData from mpt_api_client.models import Model as BaseModel from mpt_api_client.models.collection import ResourceList @@ -23,7 +25,11 @@ class AsyncService[Model: BaseModel](ServiceBase[AsyncHTTPClient, Model]): # no """ async def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[Model]: - """Fetch one page of resources.""" + """Fetch one page of resources. + + Returns: + Collection of resources. + """ response = await self._fetch_page_as_response(limit=limit, offset=offset) return self._create_collection(response) @@ -83,16 +89,39 @@ async def create(self, resource_data: ResourceData) -> Model: return self._model_class.from_response(response) - async def get(self, resource_id: str) -> Model: - """Fetch a specific resource using `GET /endpoint/{resource_id}`.""" - return await self._resource_action(resource_id=resource_id) + async def get(self, resource_id: str, select: list[str] | str | None = None) -> Model: + """Fetch a specific resource using `GET /endpoint/{resource_id}`. + + Args: + resource_id: Resource ID. + select: List of fields to select. + + Returns: + Resource object. + """ + if isinstance(select, list): + select = ",".join(select) if select else None + return await self._resource_action(resource_id=resource_id, query_params={"select": select}) async def update(self, resource_id: str, resource_data: ResourceData) -> Model: - """Update a resource using `PUT /endpoint/{resource_id}`.""" + """Update a resource using `PUT /endpoint/{resource_id}`. + + Args: + resource_id: Resource ID. + resource_data: Resource data. + + Returns: + Resource object. + + """ return await self._resource_action(resource_id, "PUT", json=resource_data) async def delete(self, resource_id: str) -> None: - """Delete resource using `DELETE /endpoint/{resource_id}`.""" + """Delete resource using `DELETE /endpoint/{resource_id}`. + + Args: + resource_id: Resource ID. + """ url = urljoin(f"{self._endpoint}/", resource_id) response = await self.http_client.delete(url) response.raise_for_status() @@ -118,6 +147,7 @@ async def _resource_do_request( method: str = "GET", action: str | None = None, json: ResourceData | ResourceList | None = None, + query_params: QueryParam | None = None, ) -> httpx.Response: """Perform an action on a specific resource using. @@ -129,13 +159,16 @@ async def _resource_do_request( method: The HTTP method to use. action: The action name to use. json: The updated resource data. + query_params: Additional query parameters. Raises: HTTPError: If the action fails. """ resource_url = urljoin(f"{self._endpoint}/", resource_id) url = urljoin(f"{resource_url}/", action) if action else resource_url - response = await self.http_client.request(method, url, json=json) + response = await self.http_client.request( + method, url, json=json, params=prepare_query_params(query_params) + ) response.raise_for_status() return response @@ -145,7 +178,18 @@ async def _resource_action( method: str = "GET", action: str | None = None, json: ResourceData | ResourceList | None = None, + query_params: QueryParam | None = None, ) -> Model: - """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`.""" - response = await self._resource_do_request(resource_id, method, action, json=json) + """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`. + + Args: + resource_id: The resource ID to operate on. + method: The HTTP method to use. + action: The action name to use. + json: The updated resource data. + query_params: Additional query parameters. + """ + response = await self._resource_do_request( + resource_id, method, action, json=json, query_params=query_params + ) return self._model_class.from_response(response) diff --git a/mpt_api_client/http/helper.py b/mpt_api_client/http/helper.py new file mode 100644 index 00000000..f22eba0b --- /dev/null +++ b/mpt_api_client/http/helper.py @@ -0,0 +1,24 @@ +from mpt_api_client.http.types import QueryParam + + +def prepare_query_params(query_params: QueryParam | None) -> dict[str, str] | None: + """ + Prepare and clean the query params dict. + + Converts all params to string and removes any value that is None. + + Args: + query_params: dict of query params. + + Returns: + dict or None if the params were not present. + + """ + if not query_params: + return None + clean_params = { + param_name: str(param_value) + for param_name, param_value in query_params.items() + if param_value is not None + } + return clean_params or None diff --git a/mpt_api_client/http/service.py b/mpt_api_client/http/service.py index a2ab693c..5f339329 100644 --- a/mpt_api_client/http/service.py +++ b/mpt_api_client/http/service.py @@ -5,6 +5,8 @@ from mpt_api_client.http.base_service import ServiceBase from mpt_api_client.http.client import HTTPClient +from mpt_api_client.http.helper import prepare_query_params +from mpt_api_client.http.types import QueryParam from mpt_api_client.models import Collection, ResourceData from mpt_api_client.models import Model as BaseModel from mpt_api_client.models.collection import ResourceList @@ -86,16 +88,40 @@ def create(self, resource_data: ResourceData) -> Model: return self._model_class.from_response(response) - def get(self, resource_id: str) -> Model: - """Fetch a specific resource using `GET /endpoint/{resource_id}`.""" - return self._resource_action(resource_id=resource_id) + def get(self, resource_id: str, select: list[str] | str | None = None) -> Model: + """Fetch a specific resource using `GET /endpoint/{resource_id}`. + + Args: + resource_id: Resource ID. + select: List of fields to select. + + Returns: + Resource object. + """ + if isinstance(select, list): + select = ",".join(select) if select else None + + return self._resource_action(resource_id=resource_id, query_params={"select": select}) def update(self, resource_id: str, resource_data: ResourceData) -> Model: - """Update a resource using `PUT /endpoint/{resource_id}`.""" + """Update a resource using `PUT /endpoint/{resource_id}`. + + Args: + resource_id: Resource ID. + resource_data: Resource data. + + Returns: + Resource object. + + """ return self._resource_action(resource_id, "PUT", json=resource_data) def delete(self, resource_id: str) -> None: - """Delete the resoruce using `DELETE /endpoint/{resource_id}`.""" + """Delete resource using `DELETE /endpoint/{resource_id}`. + + Args: + resource_id: Resource ID. + """ response = self._resource_do_request(resource_id, "DELETE") response.raise_for_status() @@ -120,6 +146,7 @@ def _resource_do_request( method: str = "GET", action: str | None = None, json: ResourceData | ResourceList | None = None, + query_params: QueryParam | None = None, ) -> httpx.Response: """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`. @@ -128,6 +155,7 @@ def _resource_do_request( method: The HTTP method to use. action: The action name to use. json: The updated resource data. + query_params: Additional query parameters. Returns: HTTP response object. @@ -137,7 +165,9 @@ def _resource_do_request( """ resource_url = urljoin(f"{self._endpoint}/", resource_id) url = urljoin(f"{resource_url}/", action) if action else resource_url - response = self.http_client.request(method, url, json=json) + response = self.http_client.request( + method, url, json=json, params=prepare_query_params(query_params) + ) response.raise_for_status() return response @@ -147,7 +177,18 @@ def _resource_action( method: str = "GET", action: str | None = None, json: ResourceData | ResourceList | None = None, + query_params: QueryParam | None = None, ) -> Model: - """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`.""" - response = self._resource_do_request(resource_id, method, action, json=json) + """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`. + + Args: + resource_id: The resource ID to operate on. + method: The HTTP method to use. + action: The action name to use. + json: The updated resource data. + query_params: Additional query parameters. + """ + response = self._resource_do_request( + resource_id, method, action, json=json, query_params=query_params + ) return self._model_class.from_response(response) diff --git a/mpt_api_client/http/types.py b/mpt_api_client/http/types.py new file mode 100644 index 00000000..3a0992ff --- /dev/null +++ b/mpt_api_client/http/types.py @@ -0,0 +1,2 @@ +PrimitiveType = str | int | float | bool | None +QueryParam = dict[str, PrimitiveType] diff --git a/mpt_api_client/resources/commerce/orders.py b/mpt_api_client/resources/commerce/orders.py index a2671b0b..4a469e60 100644 --- a/mpt_api_client/resources/commerce/orders.py +++ b/mpt_api_client/resources/commerce/orders.py @@ -15,7 +15,7 @@ class OrdersServiceConfig: class OrdersService(Service[Order], OrdersServiceConfig): - """Orders client.""" + """Orders service.""" def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to validate state. @@ -85,7 +85,7 @@ def template(self, resource_id: str) -> str: class AsyncOrdersService(AsyncService[Order], OrdersServiceConfig): - """Async Orders client.""" + """Async Orders service.""" async def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to validate state. diff --git a/tests/http/test_async_service.py b/tests/http/test_async_service.py index cf188994..fa76f5e8 100644 --- a/tests/http/test_async_service.py +++ b/tests/http/test_async_service.py @@ -300,6 +300,6 @@ async def test_async_get(async_dummy_service): return_value=httpx.Response(httpx.codes.OK, json=resource_data) ) - resource = await async_dummy_service.get("RES-123") + resource = await async_dummy_service.get("RES-123", select=["id", "name"]) assert isinstance(resource, DummyModel) assert resource.to_dict() == resource_data diff --git a/tests/http/test_service.py b/tests/http/test_service.py index f9d665a6..e1c82a54 100644 --- a/tests/http/test_service.py +++ b/tests/http/test_service.py @@ -101,7 +101,7 @@ def test_sync_get(dummy_service): return_value=httpx.Response(httpx.codes.OK, json=resource_data) ) - resource = dummy_service.get("RES-123") + resource = dummy_service.get("RES-123", select=["id", "name"]) assert isinstance(resource, DummyModel) assert resource.to_dict() == resource_data From 830b3acc984990ffc4a37394096874c7a372ac3c Mon Sep 17 00:00:00 2001 From: Albert Sola Date: Tue, 2 Sep 2025 14:01:31 +0100 Subject: [PATCH 2/2] Extract Create and Delete mixins as Agreements can't be created or deleted --- mpt_api_client/http/__init__.py | 12 +++- mpt_api_client/http/async_service.py | 26 +------- mpt_api_client/http/helper.py | 24 -------- mpt_api_client/http/mixins.py | 60 +++++++++++++++++++ mpt_api_client/http/service.py | 25 +------- mpt_api_client/resources/commerce/orders.py | 23 ++++++- tests/http/conftest.py | 14 +++-- tests/http/test_async_service.py | 66 ++++++++++----------- tests/http/test_service.py | 64 ++++++++++---------- 9 files changed, 168 insertions(+), 146 deletions(-) delete mode 100644 mpt_api_client/http/helper.py create mode 100644 mpt_api_client/http/mixins.py diff --git a/mpt_api_client/http/__init__.py b/mpt_api_client/http/__init__.py index e5affc26..cb7f0135 100644 --- a/mpt_api_client/http/__init__.py +++ b/mpt_api_client/http/__init__.py @@ -1,6 +1,16 @@ 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.service import Service -__all__ = ["AsyncHTTPClient", "AsyncService", "HTTPClient", "Service"] # noqa: WPS410 +__all__ = [ # noqa: WPS410 + "AsyncCreateMixin", + "AsyncDeleteMixin", + "AsyncHTTPClient", + "AsyncService", + "CreateMixin", + "DeleteMixin", + "HTTPClient", + "Service", +] diff --git a/mpt_api_client/http/async_service.py b/mpt_api_client/http/async_service.py index bfd81c73..765cdefd 100644 --- a/mpt_api_client/http/async_service.py +++ b/mpt_api_client/http/async_service.py @@ -5,7 +5,6 @@ from mpt_api_client.http.async_client import AsyncHTTPClient from mpt_api_client.http.base_service import ServiceBase -from mpt_api_client.http.helper import prepare_query_params from mpt_api_client.http.types import QueryParam from mpt_api_client.models import Collection, ResourceData from mpt_api_client.models import Model as BaseModel @@ -78,17 +77,6 @@ async def iterate(self, batch_size: int = 100) -> AsyncIterator[Model]: break offset = items_collection.meta.pagination.next_offset() - async def create(self, resource_data: ResourceData) -> Model: - """Create a new resource using `POST /endpoint`. - - Returns: - New resource created. - """ - response = await self.http_client.post(self._endpoint, json=resource_data) - response.raise_for_status() - - return self._model_class.from_response(response) - async def get(self, resource_id: str, select: list[str] | str | None = None) -> Model: """Fetch a specific resource using `GET /endpoint/{resource_id}`. @@ -116,16 +104,6 @@ async def update(self, resource_id: str, resource_data: ResourceData) -> Model: """ return await self._resource_action(resource_id, "PUT", json=resource_data) - async def delete(self, resource_id: str) -> None: - """Delete resource using `DELETE /endpoint/{resource_id}`. - - Args: - resource_id: Resource ID. - """ - url = urljoin(f"{self._endpoint}/", resource_id) - response = await self.http_client.delete(url) - response.raise_for_status() - async def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Response: """Fetch one page of resources. @@ -166,9 +144,7 @@ async def _resource_do_request( """ resource_url = urljoin(f"{self._endpoint}/", resource_id) url = urljoin(f"{resource_url}/", action) if action else resource_url - response = await self.http_client.request( - method, url, json=json, params=prepare_query_params(query_params) - ) + response = await self.http_client.request(method, url, json=json, params=query_params) response.raise_for_status() return response diff --git a/mpt_api_client/http/helper.py b/mpt_api_client/http/helper.py deleted file mode 100644 index f22eba0b..00000000 --- a/mpt_api_client/http/helper.py +++ /dev/null @@ -1,24 +0,0 @@ -from mpt_api_client.http.types import QueryParam - - -def prepare_query_params(query_params: QueryParam | None) -> dict[str, str] | None: - """ - Prepare and clean the query params dict. - - Converts all params to string and removes any value that is None. - - Args: - query_params: dict of query params. - - Returns: - dict or None if the params were not present. - - """ - if not query_params: - return None - clean_params = { - param_name: str(param_value) - for param_name, param_value in query_params.items() - if param_value is not None - } - return clean_params or None diff --git a/mpt_api_client/http/mixins.py b/mpt_api_client/http/mixins.py new file mode 100644 index 00000000..5c32baff --- /dev/null +++ b/mpt_api_client/http/mixins.py @@ -0,0 +1,60 @@ +from urllib.parse import urljoin + +from mpt_api_client.models import ResourceData + + +class CreateMixin[Model]: + """Create resource mixin.""" + + def create(self, resource_data: ResourceData) -> Model: + """Create a new resource using `POST /endpoint`. + + Returns: + New resource created. + """ + response = self.http_client.post(self._endpoint, json=resource_data) # type: ignore[attr-defined] + response.raise_for_status() + + return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] + + +class DeleteMixin: + """Delete resource mixin.""" + + def delete(self, resource_id: str) -> None: + """Delete resource using `DELETE /endpoint/{resource_id}`. + + Args: + resource_id: Resource ID. + """ + response = self._resource_do_request(resource_id, "DELETE") # type: ignore[attr-defined] + response.raise_for_status() + + +class AsyncCreateMixin[Model]: + """Create resource mixin.""" + + async def create(self, resource_data: ResourceData) -> Model: + """Create a new resource using `POST /endpoint`. + + Returns: + New resource created. + """ + response = await self.http_client.post(self._endpoint, json=resource_data) # type: ignore[attr-defined] + response.raise_for_status() + + return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return] + + +class AsyncDeleteMixin: + """Delete resource mixin.""" + + async def delete(self, resource_id: str) -> None: + """Delete resource using `DELETE /endpoint/{resource_id}`. + + Args: + resource_id: Resource ID. + """ + url = urljoin(f"{self._endpoint}/", resource_id) # type: ignore[attr-defined] + response = await self.http_client.delete(url) # type: ignore[attr-defined] + response.raise_for_status() diff --git a/mpt_api_client/http/service.py b/mpt_api_client/http/service.py index 5f339329..febdaf31 100644 --- a/mpt_api_client/http/service.py +++ b/mpt_api_client/http/service.py @@ -5,7 +5,6 @@ from mpt_api_client.http.base_service import ServiceBase from mpt_api_client.http.client import HTTPClient -from mpt_api_client.http.helper import prepare_query_params from mpt_api_client.http.types import QueryParam from mpt_api_client.models import Collection, ResourceData from mpt_api_client.models import Model as BaseModel @@ -77,17 +76,6 @@ def iterate(self, batch_size: int = 100) -> Iterator[Model]: break offset = items_collection.meta.pagination.next_offset() - def create(self, resource_data: ResourceData) -> Model: - """Create a new resource using `POST /endpoint`. - - Returns: - New resource created. - """ - response = self.http_client.post(self._endpoint, json=resource_data) - response.raise_for_status() - - return self._model_class.from_response(response) - def get(self, resource_id: str, select: list[str] | str | None = None) -> Model: """Fetch a specific resource using `GET /endpoint/{resource_id}`. @@ -116,15 +104,6 @@ def update(self, resource_id: str, resource_data: ResourceData) -> Model: """ return self._resource_action(resource_id, "PUT", json=resource_data) - def delete(self, resource_id: str) -> None: - """Delete resource using `DELETE /endpoint/{resource_id}`. - - Args: - resource_id: Resource ID. - """ - response = self._resource_do_request(resource_id, "DELETE") - response.raise_for_status() - def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Response: """Fetch one page of resources. @@ -165,9 +144,7 @@ def _resource_do_request( """ resource_url = urljoin(f"{self._endpoint}/", resource_id) url = urljoin(f"{resource_url}/", action) if action else resource_url - response = self.http_client.request( - method, url, json=json, params=prepare_query_params(query_params) - ) + response = self.http_client.request(method, url, json=json, params=query_params) response.raise_for_status() return response diff --git a/mpt_api_client/resources/commerce/orders.py b/mpt_api_client/resources/commerce/orders.py index 4a469e60..7878bff4 100644 --- a/mpt_api_client/resources/commerce/orders.py +++ b/mpt_api_client/resources/commerce/orders.py @@ -1,4 +1,11 @@ -from mpt_api_client.http import AsyncService, Service +from mpt_api_client.http import ( + AsyncCreateMixin, + AsyncDeleteMixin, + AsyncService, + CreateMixin, + DeleteMixin, + Service, +) from mpt_api_client.models import Model, ResourceData @@ -14,7 +21,12 @@ class OrdersServiceConfig: _collection_key = "data" -class OrdersService(Service[Order], OrdersServiceConfig): +class OrdersService( # noqa: WPS215 + CreateMixin[Order], + DeleteMixin, + Service[Order], + OrdersServiceConfig, +): """Orders service.""" def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: @@ -84,7 +96,12 @@ def template(self, resource_id: str) -> str: return response.text -class AsyncOrdersService(AsyncService[Order], OrdersServiceConfig): +class AsyncOrdersService( # noqa: WPS215 + AsyncCreateMixin[Order], + AsyncDeleteMixin, + AsyncService[Order], + OrdersServiceConfig, +): """Async Orders service.""" async def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: diff --git a/tests/http/conftest.py b/tests/http/conftest.py index dc1b3507..ced339ed 100644 --- a/tests/http/conftest.py +++ b/tests/http/conftest.py @@ -2,17 +2,23 @@ import pytest from mpt_api_client import RQLQuery -from mpt_api_client.http.async_service import AsyncService -from mpt_api_client.http.service import Service +from mpt_api_client.http import ( + AsyncCreateMixin, + AsyncDeleteMixin, + AsyncService, + CreateMixin, + DeleteMixin, + Service, +) from tests.conftest import DummyModel -class DummyService(Service[DummyModel]): +class DummyService(CreateMixin[DummyModel], DeleteMixin, Service[DummyModel]): _endpoint = "/api/v1/test" _model_class = DummyModel -class AsyncDummyService(AsyncService[DummyModel]): +class AsyncDummyService(AsyncCreateMixin[DummyModel], AsyncDeleteMixin, AsyncService[DummyModel]): _endpoint = "/api/v1/test" _model_class = DummyModel diff --git a/tests/http/test_async_service.py b/tests/http/test_async_service.py index fa76f5e8..e71fcc96 100644 --- a/tests/http/test_async_service.py +++ b/tests/http/test_async_service.py @@ -8,6 +8,39 @@ from tests.http.conftest import AsyncDummyService +async def test_async_create_mixin(async_dummy_service): # noqa: WPS210 + resource_data = {"name": "Test Resource", "status": "active"} + new_resource_data = {"id": "new-resource-id", "name": "Test Resource", "status": "active"} + create_response = httpx.Response(httpx.codes.OK, json=new_resource_data) + + with respx.mock: + mock_route = respx.post("https://api.example.com/api/v1/test").mock( + return_value=create_response + ) + + created_resource = await async_dummy_service.create(resource_data) + + assert created_resource.to_dict() == new_resource_data + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.method == "POST" + assert request.url == "https://api.example.com/api/v1/test" + assert json.loads(request.content.decode()) == resource_data + + +async def test_async_delete_mixin(async_dummy_service): # noqa: WPS210 + delete_response = httpx.Response(httpx.codes.NO_CONTENT, json=None) + + with respx.mock: + mock_route = respx.delete("https://api.example.com/api/v1/test/RES-123").mock( + return_value=delete_response + ) + + await async_dummy_service.delete("RES-123") + + assert mock_route.call_count == 1 + + async def test_async_fetch_one_success(async_dummy_service, single_result_response): with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( @@ -244,39 +277,6 @@ async def test_async_iterate_lazy_evaluation(async_dummy_service): assert mock_route.call_count == 1 -async def test_async_create_resource(async_dummy_service): # noqa: WPS210 - resource_data = {"name": "Test Resource", "status": "active"} - new_resource_data = {"id": "new-resource-id", "name": "Test Resource", "status": "active"} - create_response = httpx.Response(httpx.codes.OK, json=new_resource_data) - - with respx.mock: - mock_route = respx.post("https://api.example.com/api/v1/test").mock( - return_value=create_response - ) - - created_resource = await async_dummy_service.create(resource_data) - - assert created_resource.to_dict() == new_resource_data - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.method == "POST" - assert request.url == "https://api.example.com/api/v1/test" - assert json.loads(request.content.decode()) == resource_data - - -async def test_async_delete_resource(async_dummy_service): # noqa: WPS210 - delete_response = httpx.Response(httpx.codes.NO_CONTENT, json=None) - - with respx.mock: - mock_route = respx.delete("https://api.example.com/api/v1/test/RES-123").mock( - return_value=delete_response - ) - - await async_dummy_service.delete("RES-123") - - assert mock_route.call_count == 1 - - async def test_async_update_resource(async_dummy_service): # noqa: WPS210 resource_data = {"name": "Test Resource", "status": "active"} update_response = httpx.Response(httpx.codes.OK, json=resource_data) diff --git a/tests/http/test_service.py b/tests/http/test_service.py index e1c82a54..7c45e22e 100644 --- a/tests/http/test_service.py +++ b/tests/http/test_service.py @@ -9,6 +9,38 @@ from tests.http.conftest import DummyService +def test_sync_create_mixin(dummy_service): # noqa: WPS210 + resource_data = {"name": "Test Resource", "status": "active"} + new_resource_data = {"id": "new-resource-id", "name": "Test Resource", "status": "active"} + create_response = httpx.Response(httpx.codes.OK, json=new_resource_data) + + with respx.mock: + mock_route = respx.post("https://api.example.com/api/v1/test").mock( + return_value=create_response + ) + + created_resource = dummy_service.create(resource_data) + + assert created_resource.to_dict() == new_resource_data + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.method == "POST" + assert request.url == "https://api.example.com/api/v1/test" + assert json.loads(request.content.decode()) == resource_data + + +def test_sync_delete_mixin(dummy_service): + delete_response = httpx.Response(httpx.codes.NO_CONTENT, json=None) + with respx.mock: + mock_route = respx.delete("https://api.example.com/api/v1/test/RES-123").mock( + return_value=delete_response + ) + + dummy_service.delete("RES-123") + + assert mock_route.call_count == 1 + + def test_sync_fetch_one_success(dummy_service, single_result_response): with respx.mock: mock_route = respx.get("https://api.example.com/api/v1/test").mock( @@ -266,38 +298,6 @@ def test_sync_iterate_handles_api_errors(dummy_service): list(iterator) -def test_sync_create_resource(dummy_service): # noqa: WPS210 - resource_data = {"name": "Test Resource", "status": "active"} - new_resource_data = {"id": "new-resource-id", "name": "Test Resource", "status": "active"} - create_response = httpx.Response(httpx.codes.OK, json=new_resource_data) - - with respx.mock: - mock_route = respx.post("https://api.example.com/api/v1/test").mock( - return_value=create_response - ) - - created_resource = dummy_service.create(resource_data) - - assert created_resource.to_dict() == new_resource_data - assert mock_route.call_count == 1 - request = mock_route.calls[0].request - assert request.method == "POST" - assert request.url == "https://api.example.com/api/v1/test" - assert json.loads(request.content.decode()) == resource_data - - -def test_sync_delete_resource(dummy_service): - delete_response = httpx.Response(httpx.codes.NO_CONTENT, json=None) - with respx.mock: - mock_route = respx.delete("https://api.example.com/api/v1/test/RES-123").mock( - return_value=delete_response - ) - - dummy_service.delete("RES-123") - - assert mock_route.call_count == 1 - - def test_sync_update_resource(dummy_service): resource_data = {"name": "Test Resource", "status": "active"} update_response = httpx.Response(httpx.codes.OK, json=resource_data)