From 2b0487050daeee2b0dbdad05f5bf36d4a4addf80 Mon Sep 17 00:00:00 2001 From: Lukasz Lancucki Date: Fri, 19 Jun 2026 10:09:32 +0100 Subject: [PATCH 1/2] test(e2e): derive product-document fixtures from a created product (MPT-22435) The catalog product-document e2e tests created documents against a hardcoded seeded product (catalog.product.id / PRD-7255-3950). That shared product accumulated documents across runs until it reached the server-side per-product maximum, after which document creation failed at fixture setup with "400 ... maximum number of documents has been reached", turning the suite red. Point the document-service fixtures at a freshly created product per test: - Hoist created_product / async_created_product into the product conftest so the documents subpackage can use them. - vendor_document_service / async_vendor_document_service now build off created_product.id / async_created_product.id. - Rework the get/download tests to create their own document (dropping the seeded document_id) and assert the uploaded file name. - Consolidate the duplicated async document-service fixture. Verified: scoped e2e run green (32 passed), make check clean, unit suite 2310 passed. Co-Authored-By: Claude Opus 4.8 --- tests/e2e/catalog/product/conftest.py | 26 +++++++ .../e2e/catalog/product/documents/conftest.py | 13 +--- .../product/documents/test_async_document.py | 73 +++++++++++-------- .../product/documents/test_sync_document.py | 21 +++--- .../e2e/catalog/product/test_async_product.py | 13 ---- .../e2e/catalog/product/test_sync_product.py | 13 ---- 6 files changed, 82 insertions(+), 77 deletions(-) diff --git a/tests/e2e/catalog/product/conftest.py b/tests/e2e/catalog/product/conftest.py index 3045eee4..16c6b672 100644 --- a/tests/e2e/catalog/product/conftest.py +++ b/tests/e2e/catalog/product/conftest.py @@ -1,11 +1,37 @@ import pytest +from mpt_api_client.exceptions import MPTAPIError + @pytest.fixture def product_data(): return {"name": "Test Product", "website": "https://www.example.com"} +@pytest.fixture +def created_product(mpt_vendor, product_data, logo_fd): + product = mpt_vendor.catalog.products.create(product_data, file=logo_fd) + + yield product + + try: + mpt_vendor.catalog.products.delete(product.id) + except MPTAPIError as error: + print(f"TEARDOWN - Unable to delete product {product.id}: {error.title}") # noqa: WPS421 + + +@pytest.fixture +async def async_created_product(async_mpt_vendor, product_data, logo_fd): + product = await async_mpt_vendor.catalog.products.create(product_data, file=logo_fd) + + yield product + + try: + await async_mpt_vendor.catalog.products.delete(product.id) + except MPTAPIError as error: + print(f"TEARDOWN - Unable to delete product {product.id}: {error.title}") # noqa: WPS421 + + @pytest.fixture def parameter_group_id(e2e_config): return e2e_config["catalog.product.parameter_group.id"] diff --git a/tests/e2e/catalog/product/documents/conftest.py b/tests/e2e/catalog/product/documents/conftest.py index ce29b6c2..9a3d2239 100644 --- a/tests/e2e/catalog/product/documents/conftest.py +++ b/tests/e2e/catalog/product/documents/conftest.py @@ -1,11 +1,6 @@ import pytest -@pytest.fixture -def document_id(e2e_config): - return e2e_config["catalog.product.document.id"] - - @pytest.fixture def document_data(): return { @@ -17,10 +12,10 @@ def document_data(): @pytest.fixture -def vendor_document_service(mpt_vendor, product_id): - return mpt_vendor.catalog.products.documents(product_id) +def vendor_document_service(mpt_vendor, created_product): + return mpt_vendor.catalog.products.documents(created_product.id) @pytest.fixture -def async_vendor_document_service(async_mpt_vendor, product_id): - return async_mpt_vendor.catalog.products.documents(product_id) +def async_vendor_document_service(async_mpt_vendor, async_created_product): + return async_mpt_vendor.catalog.products.documents(async_created_product.id) diff --git a/tests/e2e/catalog/product/documents/test_async_document.py b/tests/e2e/catalog/product/documents/test_async_document.py index 3dfb77c9..01713904 100644 --- a/tests/e2e/catalog/product/documents/test_async_document.py +++ b/tests/e2e/catalog/product/documents/test_async_document.py @@ -7,28 +7,23 @@ @pytest.fixture -def async_document_service(async_mpt_vendor, product_id): - return async_mpt_vendor.catalog.products.documents(product_id) - - -@pytest.fixture -async def created_document_from_file_async(async_document_service, document_data, pdf_fd): +async def created_document_from_file_async(async_vendor_document_service, document_data, pdf_fd): document_data["documentType"] = "File" - document = await async_document_service.create(document_data, file=pdf_fd) + document = await async_vendor_document_service.create(document_data, file=pdf_fd) yield document try: - await async_document_service.delete(document.id) + await async_vendor_document_service.delete(document.id) except MPTAPIError as error: print(f"TEARDOWN - Unable to delete document {document.id}: {error.title}") @pytest.fixture -async def created_document_from_link_async(async_document_service, document_data, pdf_url): +async def created_document_from_link_async(async_vendor_document_service, document_data, pdf_url): document_data["url"] = pdf_url - document = await async_document_service.create(document_data) + document = await async_vendor_document_service.create(document_data) yield document try: - await async_document_service.delete(document.id) + await async_vendor_document_service.delete(document.id) except MPTAPIError as error: print(f"TEARDOWN - Unable to delete document {document.id}: {error.title}") @@ -43,37 +38,47 @@ def test_create_from_link_async(created_document_from_link_async, pdf_url, docum assert created_document_from_link_async.description == document_data["description"] -async def test_update_document_async(async_document_service, created_document_from_file_async): +async def test_update_document_async( + async_vendor_document_service, created_document_from_file_async +): update_data = {"name": "Updated e2e test document - please delete"} - result = await async_document_service.update(created_document_from_file_async.id, update_data) + result = await async_vendor_document_service.update( + created_document_from_file_async.id, update_data + ) assert result.name == update_data["name"] -async def test_get_document_async(async_document_service, document_id): - result = await async_document_service.get(document_id) +async def test_get_document_async(async_vendor_document_service, created_document_from_file_async): + result = await async_vendor_document_service.get(created_document_from_file_async.id) - assert result.id == document_id + assert result.id == created_document_from_file_async.id -async def test_download_document_async(async_document_service, document_id): - result = await async_document_service.download(document_id) +async def test_download_document_async( + async_vendor_document_service, created_document_from_file_async +): + result = await async_vendor_document_service.download(created_document_from_file_async.id) assert result.file_contents is not None - assert result.filename == "pdf - empty.pdf" + assert result.filename == "empty.pdf" -async def test_iterate_documents_async(async_document_service, created_document_from_file_async): - documents = [doc async for doc in async_document_service.iterate()] +async def test_iterate_documents_async( + async_vendor_document_service, created_document_from_file_async +): + documents = [doc async for doc in async_vendor_document_service.iterate()] result = any(doc.id == created_document_from_file_async.id for doc in documents) assert result is True -async def test_filter_documents_async(async_document_service, created_document_from_file_async): - filtered_service = async_document_service.filter( +async def test_filter_documents_async( + async_vendor_document_service, created_document_from_file_async +): + filtered_service = async_vendor_document_service.filter( RQLQuery(id=created_document_from_file_async.id) ) documents = [doc async for doc in filtered_service.iterate()] @@ -82,11 +87,13 @@ async def test_filter_documents_async(async_document_service, created_document_f async def test_review_and_publish_document_async( - async_mpt_vendor, async_mpt_ops, created_document_from_file_async, product_id + async_vendor_document_service, + async_mpt_ops, + created_document_from_file_async, + async_created_product, ): - vendor_service = async_mpt_vendor.catalog.products.documents(product_id) - ops_service = async_mpt_ops.catalog.products.documents(product_id) - document = await vendor_service.review(created_document_from_file_async.id) + ops_service = async_mpt_ops.catalog.products.documents(async_created_product.id) + document = await async_vendor_document_service.review(created_document_from_file_async.id) assert document.status == "Pending" document = await ops_service.publish(created_document_from_file_async.id) assert document.status == "Published" @@ -96,12 +103,14 @@ async def test_review_and_publish_document_async( assert result.status == "Unpublished" -async def test_not_found_async(async_document_service): +async def test_not_found_async(async_vendor_document_service): with pytest.raises(MPTAPIError): - await async_document_service.get("DOC-000-000-000") + await async_vendor_document_service.get("DOC-000-000-000") -async def test_delete_document_async(async_document_service, created_document_from_file_async): - await async_document_service.delete(created_document_from_file_async.id) +async def test_delete_document_async( + async_vendor_document_service, created_document_from_file_async +): + await async_vendor_document_service.delete(created_document_from_file_async.id) with pytest.raises(MPTAPIError): - await async_document_service.get(created_document_from_file_async.id) + await async_vendor_document_service.get(created_document_from_file_async.id) diff --git a/tests/e2e/catalog/product/documents/test_sync_document.py b/tests/e2e/catalog/product/documents/test_sync_document.py index 8fbca611..2e49976e 100644 --- a/tests/e2e/catalog/product/documents/test_sync_document.py +++ b/tests/e2e/catalog/product/documents/test_sync_document.py @@ -46,17 +46,17 @@ def test_update_document(vendor_document_service, created_document_from_file): assert result.name == update_data["name"] -def test_get_document(vendor_document_service, document_id): - result = vendor_document_service.get(document_id) +def test_get_document(vendor_document_service, created_document_from_file): + result = vendor_document_service.get(created_document_from_file.id) - assert result.id == document_id + assert result.id == created_document_from_file.id -def test_download_document(vendor_document_service, document_id): - result = vendor_document_service.download(document_id) +def test_download_document(vendor_document_service, created_document_from_file): + result = vendor_document_service.download(created_document_from_file.id) assert result.file_contents is not None - assert result.filename == "pdf - empty.pdf" + assert result.filename == "empty.pdf" def test_iterate_documents(vendor_document_service, created_document_from_file): @@ -76,10 +76,11 @@ def test_filter_documents(vendor_document_service, created_document_from_file): assert result[0].id == created_document_from_file.id -def test_review_and_publish_document(mpt_vendor, mpt_ops, created_document_from_file, product_id): - vendor_service = mpt_vendor.catalog.products.documents(product_id) - ops_service = mpt_ops.catalog.products.documents(product_id) - document = vendor_service.review(created_document_from_file.id) +def test_review_and_publish_document( + vendor_document_service, mpt_ops, created_document_from_file, created_product +): + ops_service = mpt_ops.catalog.products.documents(created_product.id) + document = vendor_document_service.review(created_document_from_file.id) assert document.status == "Pending" document = ops_service.publish(created_document_from_file.id) assert document.status == "Published" diff --git a/tests/e2e/catalog/product/test_async_product.py b/tests/e2e/catalog/product/test_async_product.py index a8a4c243..ae7b450a 100644 --- a/tests/e2e/catalog/product/test_async_product.py +++ b/tests/e2e/catalog/product/test_async_product.py @@ -1,23 +1,10 @@ import pytest from mpt_api_client import RQLQuery -from mpt_api_client.exceptions import MPTAPIError pytestmark = [pytest.mark.flaky] -@pytest.fixture -async def async_created_product(async_mpt_vendor, product_data, logo_fd): - product = await async_mpt_vendor.catalog.products.create(product_data, file=logo_fd) - - yield product - - try: - await async_mpt_vendor.catalog.products.delete(product.id) - except MPTAPIError as error: - print(f"TEARDOWN - Unable to delete product {product.id}: {error.title}") # noqa: WPS421 - - def test_create_product(async_created_product, product_data): result = async_created_product.name == product_data["name"] diff --git a/tests/e2e/catalog/product/test_sync_product.py b/tests/e2e/catalog/product/test_sync_product.py index 75b493df..fc6da046 100644 --- a/tests/e2e/catalog/product/test_sync_product.py +++ b/tests/e2e/catalog/product/test_sync_product.py @@ -1,23 +1,10 @@ import pytest from mpt_api_client import RQLQuery -from mpt_api_client.exceptions import MPTAPIError pytestmark = [pytest.mark.flaky] -@pytest.fixture -def created_product(mpt_vendor, product_data, logo_fd): - product = mpt_vendor.catalog.products.create(product_data, file=logo_fd) - - yield product - - try: - mpt_vendor.catalog.products.delete(product.id) - except MPTAPIError as error: - print(f"TEARDOWN - Unable to delete product {product.id}: {error.title}") # noqa: WPS421 - - def test_create_product(created_product, product_data): result = created_product.name == product_data["name"] From 3fee2a172624cb1fbd0198ea79a32cba984f3084 Mon Sep 17 00:00:00 2001 From: Lukasz Lancucki Date: Fri, 19 Jun 2026 10:59:20 +0100 Subject: [PATCH 2/2] test(e2e): use shared e2e helpers in product-document tests (MPT-22435) Address review feedback on #351: replace hand-rolled fixture/teardown and assertion boilerplate with the shared tests/e2e/helper.py helpers. - created_document_from_file/_url (sync + async) now use create_fixture_resource_and_delete / async variant. - Extend those helpers with an optional upload_file so file-backed document creation can use them (backward compatible: forwarded only when provided). - test_update_document(_async) -> assert_update_resource / async variant. - test_filter_documents(_async) -> assert_service_filter_with_iterate / async. Verified: scoped e2e green (20 passed), make check clean, unit suite 2310 passed. Co-Authored-By: Claude Opus 4.8 --- .../product/documents/test_async_document.py | 48 +++++++++---------- .../product/documents/test_sync_document.py | 44 ++++++++--------- tests/e2e/helper.py | 14 ++++-- 3 files changed, 52 insertions(+), 54 deletions(-) diff --git a/tests/e2e/catalog/product/documents/test_async_document.py b/tests/e2e/catalog/product/documents/test_async_document.py index 01713904..ca98b7e8 100644 --- a/tests/e2e/catalog/product/documents/test_async_document.py +++ b/tests/e2e/catalog/product/documents/test_async_document.py @@ -1,7 +1,11 @@ import pytest from mpt_api_client.exceptions import MPTAPIError -from mpt_api_client.rql.query_builder import RQLQuery +from tests.e2e.helper import ( + assert_async_service_filter_with_iterate, + assert_async_update_resource, + async_create_fixture_resource_and_delete, +) pytestmark = [pytest.mark.flaky] @@ -9,23 +13,19 @@ @pytest.fixture async def created_document_from_file_async(async_vendor_document_service, document_data, pdf_fd): document_data["documentType"] = "File" - document = await async_vendor_document_service.create(document_data, file=pdf_fd) - yield document - try: - await async_vendor_document_service.delete(document.id) - except MPTAPIError as error: - print(f"TEARDOWN - Unable to delete document {document.id}: {error.title}") + async with async_create_fixture_resource_and_delete( + async_vendor_document_service, document_data, upload_file=pdf_fd + ) as document: + yield document @pytest.fixture async def created_document_from_link_async(async_vendor_document_service, document_data, pdf_url): document_data["url"] = pdf_url - document = await async_vendor_document_service.create(document_data) - yield document - try: - await async_vendor_document_service.delete(document.id) - except MPTAPIError as error: - print(f"TEARDOWN - Unable to delete document {document.id}: {error.title}") + async with async_create_fixture_resource_and_delete( + async_vendor_document_service, document_data + ) as document: + yield document def test_create_document_async(created_document_from_file_async, document_data): # noqa: AAA01 @@ -41,13 +41,12 @@ def test_create_from_link_async(created_document_from_link_async, pdf_url, docum async def test_update_document_async( async_vendor_document_service, created_document_from_file_async ): - update_data = {"name": "Updated e2e test document - please delete"} - - result = await async_vendor_document_service.update( - created_document_from_file_async.id, update_data - ) - - assert result.name == update_data["name"] + await assert_async_update_resource( + async_vendor_document_service, + created_document_from_file_async.id, + "name", + "Updated e2e test document - please delete", + ) # act async def test_get_document_async(async_vendor_document_service, created_document_from_file_async): @@ -78,12 +77,9 @@ async def test_iterate_documents_async( async def test_filter_documents_async( async_vendor_document_service, created_document_from_file_async ): - filtered_service = async_vendor_document_service.filter( - RQLQuery(id=created_document_from_file_async.id) - ) - documents = [doc async for doc in filtered_service.iterate()] - assert len(documents) == 1 - assert documents[0].id == created_document_from_file_async.id + await assert_async_service_filter_with_iterate( + async_vendor_document_service, created_document_from_file_async.id, None + ) # act async def test_review_and_publish_document_async( diff --git a/tests/e2e/catalog/product/documents/test_sync_document.py b/tests/e2e/catalog/product/documents/test_sync_document.py index 2e49976e..3a2f42ac 100644 --- a/tests/e2e/catalog/product/documents/test_sync_document.py +++ b/tests/e2e/catalog/product/documents/test_sync_document.py @@ -1,7 +1,11 @@ import pytest from mpt_api_client.exceptions import MPTAPIError -from mpt_api_client.rql.query_builder import RQLQuery +from tests.e2e.helper import ( + assert_service_filter_with_iterate, + assert_update_resource, + create_fixture_resource_and_delete, +) pytestmark = [pytest.mark.flaky] @@ -9,23 +13,17 @@ @pytest.fixture def created_document_from_file(vendor_document_service, document_data, pdf_fd): document_data["documentType"] = "File" - document = vendor_document_service.create(document_data, pdf_fd) - yield document - try: - vendor_document_service.delete(document.id) - except MPTAPIError as error: - print(f"TEARDOWN - Unable to delete document {document.id}: {error.title}") + with create_fixture_resource_and_delete( + vendor_document_service, document_data, upload_file=pdf_fd + ) as document: + yield document @pytest.fixture def created_document_from_url(vendor_document_service, document_data, pdf_url): document_data["url"] = pdf_url - document = vendor_document_service.create(document_data) - yield document - try: - vendor_document_service.delete(document.id) - except MPTAPIError as error: - print(f"TEARDOWN - Unable to delete document {document.id}: {error.title}") + with create_fixture_resource_and_delete(vendor_document_service, document_data) as document: + yield document def test_create_document(created_document_from_file, document_data): # noqa: AAA01 @@ -39,11 +37,12 @@ def test_create_document_from_url(created_document_from_url, document_data): # def test_update_document(vendor_document_service, created_document_from_file): - update_data = {"name": "Updated e2e test document - please delete"} - - result = vendor_document_service.update(created_document_from_file.id, update_data) - - assert result.name == update_data["name"] + assert_update_resource( + vendor_document_service, + created_document_from_file.id, + "name", + "Updated e2e test document - please delete", + ) # act def test_get_document(vendor_document_service, created_document_from_file): @@ -68,12 +67,9 @@ def test_iterate_documents(vendor_document_service, created_document_from_file): def test_filter_documents(vendor_document_service, created_document_from_file): - result = list( - vendor_document_service.filter(RQLQuery(id=created_document_from_file.id)).iterate() - ) - - assert len(result) == 1 - assert result[0].id == created_document_from_file.id + assert_service_filter_with_iterate( + vendor_document_service, created_document_from_file.id, None + ) # act def test_review_and_publish_document( diff --git a/tests/e2e/helper.py b/tests/e2e/helper.py index db937baf..367a015b 100644 --- a/tests/e2e/helper.py +++ b/tests/e2e/helper.py @@ -33,8 +33,11 @@ def _finalize_resource(finalize, resource, logger): @asynccontextmanager -async def async_create_fixture_resource_and_delete(service, resource_data): - resource = await service.create(resource_data) +async def async_create_fixture_resource_and_delete(service, resource_data, upload_file=None): + if upload_file is None: + resource = await service.create(resource_data) + else: + resource = await service.create(resource_data, file=upload_file) try: yield resource @@ -43,8 +46,11 @@ async def async_create_fixture_resource_and_delete(service, resource_data): @contextmanager -def create_fixture_resource_and_delete(service, resource_data): - resource = service.create(resource_data) +def create_fixture_resource_and_delete(service, resource_data, upload_file=None): + if upload_file is None: + resource = service.create(resource_data) + else: + resource = service.create(resource_data, file=upload_file) try: yield resource