From 690b127976a35a44d5653fd227d2ee032c75a67f Mon Sep 17 00:00:00 2001 From: Albert Sola Date: Wed, 17 Dec 2025 14:22:02 +0000 Subject: [PATCH 1/2] Improve categories e2e --- e2e_config.test.json | 1 + .../categories/test_async_categories.py | 30 ++++--------------- .../categories/test_sync_categories.py | 28 ++++------------- tests/e2e/notifications/conftest.py | 6 ++++ 4 files changed, 19 insertions(+), 46 deletions(-) create mode 100644 tests/e2e/notifications/conftest.py diff --git a/e2e_config.test.json b/e2e_config.test.json index 4bf6506..b3be132 100644 --- a/e2e_config.test.json +++ b/e2e_config.test.json @@ -46,6 +46,7 @@ "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.category.id": "NTC-6157-0397", "notifications.subscriber.id": "NTS-0829-7123-7123", "notifications.message.id": "MSG-0000-6215-1019-0139" } diff --git a/tests/e2e/notifications/categories/test_async_categories.py b/tests/e2e/notifications/categories/test_async_categories.py index fa46d72..7437a03 100644 --- a/tests/e2e/notifications/categories/test_async_categories.py +++ b/tests/e2e/notifications/categories/test_async_categories.py @@ -17,23 +17,19 @@ async def async_created_category(async_mpt_ops, category_data): print(f"TEARDOWN - Unable to delete category {category.id}: {error.title}") # noqa: WPS421 -@pytest.mark.skip(reason="async_created_category kills performance due to MPT-13785") # noqa: AAA01 def test_create_category(async_created_category, category_data): assert async_created_category.name == category_data["name"] assert async_created_category.description == category_data["description"] -@pytest.mark.skip(reason="async_created_category kills performance due to MPT-13785") -async def test_get_category(async_mpt_vendor, async_created_category): +async def test_get_category(async_mpt_vendor, category_id): service = async_mpt_vendor.notifications.categories - result = await service.get(async_created_category.id) + result = await service.get(category_id) - assert result.id == async_created_category.id - assert result.name == async_created_category.name + assert result.id == category_id -@pytest.mark.skip(reason="async_created_category kills performance due to MPT-13785") async def test_update_category(async_mpt_ops, async_created_category): service = async_mpt_ops.notifications.categories update_data = { @@ -47,26 +43,16 @@ async def test_update_category(async_mpt_ops, async_created_category): assert result.description == "Async updated description" -@pytest.mark.skip(reason="async_created_category kills performance due to MPT-13785") -async def test_list_categories(async_mpt_vendor, async_created_category): - service = async_mpt_vendor.notifications.categories - - result = [category async for category in service.iterate()] - - assert any(category.id == async_created_category.id for category in result) - - -@pytest.mark.skip(reason="async_created_category kills performance due to MPT-13785") -async def test_filter_categories(async_mpt_vendor, async_created_category): +async def test_filter_categories(async_mpt_vendor, category_id): service = async_mpt_vendor.notifications.categories result = [ category - async for category in service.filter(RQLQuery(id=async_created_category.id)).iterate() + async for category in service.filter(RQLQuery(id=category_id)).iterate() ] assert len(result) == 1 - assert result[0].id == async_created_category.id + assert result[0].id == category_id @pytest.mark.skip(reason="async_created_category kills performance due to MPT-13785") @@ -98,15 +84,11 @@ async def test_category_not_found(async_mpt_vendor, invalid_category_id): await service.get(invalid_category_id) -@pytest.mark.skip(reason="async_created_category kills performance due to MPT-13785") async def test_delete_category(async_mpt_ops, async_created_category): service = async_mpt_ops.notifications.categories await service.delete(async_created_category.id) - with pytest.raises(MPTAPIError): - await service.get(async_created_category.id) - async def test_delete_category_not_found(async_mpt_ops, invalid_category_id): service = async_mpt_ops.notifications.categories diff --git a/tests/e2e/notifications/categories/test_sync_categories.py b/tests/e2e/notifications/categories/test_sync_categories.py index 37370c9..45bb31a 100644 --- a/tests/e2e/notifications/categories/test_sync_categories.py +++ b/tests/e2e/notifications/categories/test_sync_categories.py @@ -17,21 +17,18 @@ def created_category(mpt_ops, category_data): print(f"TEARDOWN - Unable to delete category {category.id}: {error.title}") # noqa: WPS421 -@pytest.mark.skip(reason="created_category kills performance due to MPT-13785") def test_create_category(created_category, category_data): assert created_category.name == category_data["name"] # act assert created_category.description == category_data["description"] -@pytest.mark.skip(reason="created_category kills performance due to MPT-13785") -def test_get_category(mpt_client, created_category): +def test_get_category(mpt_client, category_id): service = mpt_client.notifications.categories - result = service.get(created_category.id) + result = service.get(category_id) - assert result.id == created_category.id - assert result.name == created_category.name + assert result.id == category_id @pytest.mark.skip(reason="created_category kills performance due to MPT-13785") @@ -48,23 +45,14 @@ def test_update_category(mpt_ops, created_category): assert result.description == "Updated description" -@pytest.mark.skip(reason="created_category kills performance due to MPT-13785") -def test_list_categories(mpt_client, created_category): - service = mpt_client.notifications.categories - - result = list(service.iterate()) - - assert any(category.id == created_category.id for category in result) - -@pytest.mark.skip(reason="created_category kills performance due to MPT-13785") -def test_filter_categories(mpt_client, created_category): +def test_filter_categories(mpt_client, category_id): service = mpt_client.notifications.categories - result = list(service.filter(RQLQuery(id=created_category.id)).iterate()) + result = list(service.filter(RQLQuery(id=category_id)).iterate()) assert len(result) == 1 - assert result[0].id == created_category.id + assert result[0].id == category_id @pytest.mark.skip(reason="created_category kills performance due to MPT-13785") @@ -96,15 +84,11 @@ def test_category_not_found(mpt_client, invalid_category_id): service.get(invalid_category_id) -@pytest.mark.skip(reason="created_category kills performance due to MPT-13785") def test_delete_category(mpt_ops, created_category): service = mpt_ops.notifications.categories service.delete(created_category.id) # act - with pytest.raises(MPTAPIError): - service.get(created_category.id) - def test_delete_category_not_found(mpt_ops, invalid_category_id): service = mpt_ops.notifications.categories diff --git a/tests/e2e/notifications/conftest.py b/tests/e2e/notifications/conftest.py new file mode 100644 index 0000000..37f5221 --- /dev/null +++ b/tests/e2e/notifications/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture +def category_id(e2e_config): + return e2e_config["notifications.category.id"] From 0a8eb2e8e7eaf7b1893621d84082afe5a6b327f2 Mon Sep 17 00:00:00 2001 From: Albert Sola Date: Thu, 18 Dec 2025 13:42:03 +0000 Subject: [PATCH 2/2] MPT-14934 E2E for notifications/batches --- e2e_config.test.json | 1 + .../resources/notifications/batches.py | 67 ++----------- tests/e2e/notifications/batch/conftest.py | 26 +++++ .../notifications/batch/test_async_batches.py | 36 +++++++ tests/e2e/notifications/batch/test_batches.py | 36 +++++++ .../categories/test_async_categories.py | 7 +- .../categories/test_sync_categories.py | 1 - .../resources/notifications/test_batches.py | 98 ------------------- 8 files changed, 108 insertions(+), 164 deletions(-) create mode 100644 tests/e2e/notifications/batch/conftest.py create mode 100644 tests/e2e/notifications/batch/test_async_batches.py create mode 100644 tests/e2e/notifications/batch/test_batches.py diff --git a/e2e_config.test.json b/e2e_config.test.json index b3be132..03507e6 100644 --- a/e2e_config.test.json +++ b/e2e_config.test.json @@ -46,6 +46,7 @@ "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.batch.id": "MST-3638-2460-4825", "notifications.category.id": "NTC-6157-0397", "notifications.subscriber.id": "NTS-0829-7123-7123", "notifications.message.id": "MSG-0000-6215-1019-0139" diff --git a/mpt_api_client/resources/notifications/batches.py b/mpt_api_client/resources/notifications/batches.py index a1f63b4..80bdd4e 100644 --- a/mpt_api_client/resources/notifications/batches.py +++ b/mpt_api_client/resources/notifications/batches.py @@ -1,14 +1,13 @@ -from httpx._types import FileTypes - from mpt_api_client.http import AsyncService, Service -from mpt_api_client.http.client import json_to_file_payload from mpt_api_client.http.mixins import ( AsyncCollectionMixin, + AsyncCreateFileMixin, AsyncGetMixin, CollectionMixin, + CreateFileMixin, GetMixin, ) -from mpt_api_client.models import FileModel, Model, ResourceData +from mpt_api_client.models import FileModel, Model class Batch(Model): @@ -21,9 +20,12 @@ class BatchesServiceConfig: _endpoint = "/public/v1/notifications/batches" _model_class = Batch _collection_key = "data" + _upload_file_key = "attachment" + _upload_data_key = "batch" class BatchesService( + CreateFileMixin[Batch], GetMixin[Batch], CollectionMixin[Batch], Service[Batch], @@ -31,34 +33,6 @@ class BatchesService( ): """Notifications Batches service.""" - def create( - self, - resource_data: ResourceData | None = None, - files: dict[str, FileTypes] | None = None, # noqa: WPS221 - data_key: str = "_attachment_data", - ) -> Model: - """Create batch with attachments. - - Args: - resource_data: batch data. - files: Files data. - data_key: Key to use for the JSON data in the multipart form. - - Returns: - Created resource. - """ - files = files or {} - - if resource_data: - files[data_key] = ( - None, - json_to_file_payload(resource_data), - "application/json", - ) - - response = self.http_client.request("post", self.path, files=files) - return self._model_class.from_response(response) - def get_batch_attachment(self, batch_id: str, attachment_id: str) -> FileModel: """Get batch attachment. @@ -77,6 +51,7 @@ def get_batch_attachment(self, batch_id: str, attachment_id: str) -> FileModel: class AsyncBatchesService( + AsyncCreateFileMixin[Batch], AsyncGetMixin[Batch], AsyncCollectionMixin[Batch], AsyncService[Batch], @@ -84,34 +59,6 @@ class AsyncBatchesService( ): """Async Notifications Batches service.""" - async def create( - self, - resource_data: ResourceData | None = None, - files: dict[str, FileTypes] | None = None, # noqa: WPS221 - data_key: str = "_attachment_data", - ) -> Model: - """Create batch with attachments. - - Args: - resource_data: batch data. - files: Files data. - data_key: Key to use for the JSON data in the multipart form. - - Returns: - Created resource. - """ - files = files or {} - - if resource_data: - files[data_key] = ( - None, - json_to_file_payload(resource_data), - "application/json", - ) - - response = await self.http_client.request("post", self.path, files=files) - return self._model_class.from_response(response) - async def get_batch_attachment(self, batch_id: str, attachment_id: str) -> FileModel: """Get batch attachment. diff --git a/tests/e2e/notifications/batch/conftest.py b/tests/e2e/notifications/batch/conftest.py new file mode 100644 index 0000000..4727827 --- /dev/null +++ b/tests/e2e/notifications/batch/conftest.py @@ -0,0 +1,26 @@ +import pytest + + +@pytest.fixture +def batch_service(mpt_ops): + return mpt_ops.notifications.batches + + +@pytest.fixture +def async_batch_service(async_mpt_ops): + return async_mpt_ops.notifications.batches + + +@pytest.fixture +def batch_id(e2e_config): + return e2e_config["notifications.batch.id"] + + +@pytest.fixture +def batch_data(category_id, short_uuid): + return { + "category": {"id": category_id}, + "subject": f"E2E - please delete - {short_uuid}", + "body": "Hello world", + "contacts": [{"email": f"{short_uuid}@example.com"}], + } diff --git a/tests/e2e/notifications/batch/test_async_batches.py b/tests/e2e/notifications/batch/test_async_batches.py new file mode 100644 index 0000000..a5d4d4e --- /dev/null +++ b/tests/e2e/notifications/batch/test_async_batches.py @@ -0,0 +1,36 @@ +import pytest + +from mpt_api_client.rql.query_builder import RQLQuery + + +@pytest.mark.skip(reason="Batches can not be deleted") +async def test_create_batch(async_batch_service, batch_data): + result = await async_batch_service.create(batch_data) + + assert result is not None + + +async def test_get_batch(async_batch_service, batch_id): + result = await async_batch_service.get(batch_id) + + assert result.id == batch_id + + +async def test_iterate_and_filter(async_batch_service, batch_id): + batches = [batch async for batch in async_batch_service.filter(RQLQuery(id=batch_id)).iterate()] + + assert len(batches) == 1 + assert batches[0].id == batch_id + + +@pytest.mark.skip(reason="Batches can not be deleted") +async def test_create_batch_with_file(async_batch_service, batch_data, logo_fd): + result = await async_batch_service.create(batch_data, file=logo_fd) + + assert result is not None + + +@pytest.mark.skip(reason="Batches attachments not implemented") +async def test_download_attachment(): + # TODO - Implement get and download E2E tests for attachments + raise NotImplementedError diff --git a/tests/e2e/notifications/batch/test_batches.py b/tests/e2e/notifications/batch/test_batches.py new file mode 100644 index 0000000..ced54c4 --- /dev/null +++ b/tests/e2e/notifications/batch/test_batches.py @@ -0,0 +1,36 @@ +import pytest + +from mpt_api_client import RQLQuery + + +@pytest.mark.skip(reason="Batches can not be deleted") +def test_create_batch(batch_service, batch_data): + result = batch_service.create(batch_data) + + assert result is not None + + +def test_get_batch(batch_service, batch_id): + result = batch_service.get(batch_id) + + assert result.id == batch_id + + +def test_iterate_and_filter(batch_service, batch_id): + result = list(batch_service.filter(RQLQuery(id=batch_id)).iterate()) + + assert len(result) == 1 + assert result[0].id == batch_id + + +@pytest.mark.skip(reason="Batches can not be deleted") +def test_create_batch_with_file(batch_service, batch_data, logo_fd): + result = batch_service.create(batch_data, file=logo_fd) + + assert result is not None + + +@pytest.mark.skip(reason="Batches attachments not implemented") # noqa: AAA01 +def test_download_attachment(): + # TODO - Implement get and download E2E tests for attachment + raise NotImplementedError diff --git a/tests/e2e/notifications/categories/test_async_categories.py b/tests/e2e/notifications/categories/test_async_categories.py index 7437a03..efe53a7 100644 --- a/tests/e2e/notifications/categories/test_async_categories.py +++ b/tests/e2e/notifications/categories/test_async_categories.py @@ -17,7 +17,7 @@ async def async_created_category(async_mpt_ops, category_data): print(f"TEARDOWN - Unable to delete category {category.id}: {error.title}") # noqa: WPS421 -def test_create_category(async_created_category, category_data): +def test_create_category(async_created_category, category_data): # noqa: AAA01 assert async_created_category.name == category_data["name"] assert async_created_category.description == category_data["description"] @@ -46,10 +46,7 @@ async def test_update_category(async_mpt_ops, async_created_category): async def test_filter_categories(async_mpt_vendor, category_id): service = async_mpt_vendor.notifications.categories - result = [ - category - async for category in service.filter(RQLQuery(id=category_id)).iterate() - ] + result = [category async for category in service.filter(RQLQuery(id=category_id)).iterate()] assert len(result) == 1 assert result[0].id == category_id diff --git a/tests/e2e/notifications/categories/test_sync_categories.py b/tests/e2e/notifications/categories/test_sync_categories.py index 45bb31a..6c8bc1a 100644 --- a/tests/e2e/notifications/categories/test_sync_categories.py +++ b/tests/e2e/notifications/categories/test_sync_categories.py @@ -45,7 +45,6 @@ def test_update_category(mpt_ops, created_category): assert result.description == "Updated description" - def test_filter_categories(mpt_client, category_id): service = mpt_client.notifications.categories diff --git a/tests/unit/resources/notifications/test_batches.py b/tests/unit/resources/notifications/test_batches.py index a58da31..ae97844 100644 --- a/tests/unit/resources/notifications/test_batches.py +++ b/tests/unit/resources/notifications/test_batches.py @@ -1,8 +1,4 @@ -import io - -import httpx import pytest -import respx from mpt_api_client.resources.notifications.batches import ( AsyncBatchesService, @@ -32,97 +28,3 @@ def test_async_batches_service_methods(async_batches_service, method): result = hasattr(async_batches_service, method) assert result is True - - -def test_sync_get_batch_attachment(batches_service): - attachment_content = b"Attachment file content or binary data" - with respx.mock: - mock_route = respx.get( - "https://api.example.com/public/v1/notifications/batches/BAT-123/attachments/ATT-456" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={ - "content-type": "application/octet-stream", - "content-disposition": ( - 'form-data; name="file"; filename="batch_attachment.pdf"' - ), - }, - content=attachment_content, - ) - ) - - result = batches_service.get_batch_attachment("BAT-123", "ATT-456") - - assert mock_route.call_count == 1 - assert result.file_contents == attachment_content - assert result.content_type == "application/octet-stream" - assert result.filename == "batch_attachment.pdf" - - -@pytest.mark.asyncio -async def test_async_get_batch_attachment(async_batches_service): - attachment_content = b"Attachment file content or binary data" - with respx.mock: - mock_route = respx.get( - "https://api.example.com/public/v1/notifications/batches/BAT-123/attachments/ATT-456" - ).mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - headers={ - "content-type": "application/octet-stream", - "content-disposition": ( - 'form-data; name="file"; filename="batch_attachment.pdf"' - ), - }, - content=attachment_content, - ) - ) - - result = await async_batches_service.get_batch_attachment("BAT-123", "ATT-456") - - assert mock_route.call_count == 1 - assert result.file_contents == attachment_content - assert result.content_type == "application/octet-stream" - assert result.filename == "batch_attachment.pdf" - - -def test_sync_batches_create_with_data(batches_service): - batch_data = {"name": "Test Batch"} - with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/notifications/batches").mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - json={"id": "BAT-133", "name": "Test Batch"}, - ) - ) - files = {"attachment": ("test.pdf", io.BytesIO(b"PDF content"), "application/pdf")} - - result = batches_service.create(batch_data, files=files) - - request = mock_route.calls[0].request - assert b'Content-Disposition: form-data; name="_attachment_data"' in request.content - assert mock_route.call_count == 1 - assert result.id == "BAT-133" - assert result.name == "Test Batch" - - -@pytest.mark.asyncio -async def test_async_batches_create_with_data(async_batches_service): - batch_data = {"name": "Test Batch"} - with respx.mock: - mock_route = respx.post("https://api.example.com/public/v1/notifications/batches").mock( - return_value=httpx.Response( - status_code=httpx.codes.OK, - json={"id": "BAT-133", "name": "Test Batch"}, - ) - ) - files = {"attachment": ("test.pdf", io.BytesIO(b"PDF content"), "application/pdf")} - - result = await async_batches_service.create(batch_data, files=files) - - request = mock_route.calls[0].request - assert b'Content-Disposition: form-data; name="_attachment_data"' in request.content - assert mock_route.call_count == 1 - assert result.id == "BAT-133" - assert result.name == "Test Batch"