From 9b0ac03f77ddb3dd309e2361f16a4b84d0198e1e Mon Sep 17 00:00:00 2001 From: Robert Segal Date: Wed, 5 Nov 2025 19:59:24 -0700 Subject: [PATCH] Added sellers E2E tests --- .gitignore | 3 + e2e_config.test.json | 3 +- setup.cfg | 1 + tests/e2e/accounts/conftest.py | 58 ++++++++++ tests/e2e/accounts/test_async_sellers.py | 134 +++++++++++++++++++++++ tests/e2e/accounts/test_sync_sellers.py | 131 ++++++++++++++++++++++ tests/e2e/conftest.py | 10 ++ 7 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/accounts/conftest.py create mode 100644 tests/e2e/accounts/test_async_sellers.py create mode 100644 tests/e2e/accounts/test_sync_sellers.py diff --git a/.gitignore b/.gitignore index be768412..67743650 100644 --- a/.gitignore +++ b/.gitignore @@ -170,3 +170,6 @@ cython_debug/ # VS Code dev container .devcontainer/ + +# E2E report +e2e-report.xml diff --git a/e2e_config.test.json b/e2e_config.test.json index c9bcf67c..c32853a5 100644 --- a/e2e_config.test.json +++ b/e2e_config.test.json @@ -1,3 +1,4 @@ { - "catalog.product.id": "PRD-7255-3950" + "catalog.product.id": "PRD-7255-3950", + "accounts.seller.id": "SEL-7310-3075" } diff --git a/setup.cfg b/setup.cfg index eb8d4599..25dcf8b0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,6 +47,7 @@ per-file-ignores = tests/unit/resources/*/test_mixins.py: WPS118 WPS202 WPS204 WPS235 tests/unit/resources/accounts/test_users.py: WPS204 WPS202 WPS210 tests/unit/test_mpt_client.py: WPS235 + tests/e2e/accounts/*.py: WPS430 WPS202 tests/*: # Allow magic strings. diff --git a/tests/e2e/accounts/conftest.py b/tests/e2e/accounts/conftest.py new file mode 100644 index 00000000..bca3f876 --- /dev/null +++ b/tests/e2e/accounts/conftest.py @@ -0,0 +1,58 @@ +import datetime as dt +import pathlib + +import pytest + + +@pytest.fixture(scope="session") +def timestamp(): + return int(dt.datetime.now(tz=dt.UTC).strftime("%Y%m%d%H%M%S")) + + +@pytest.fixture +def account_data(): + return { + "name": "Test Api Client Vendor", + "address": { + "addressLine1": "123 Test St", + "city": "San Francisco", + "state": "CA", + "postCode": "12345", + "country": "US", + }, + "type": "Vendor", + "status": "Active", + } + + +@pytest.fixture +def account_icon(): + return pathlib.Path(__file__).parent / "logo.png" + + +@pytest.fixture +def currencies(): + return ["USD", "EUR"] + + +@pytest.fixture +def seller(currencies): + def _seller( + external_id: str, # Must be unique in Marketplace + name="E2E Test Seller", + currencies=currencies, + ): + return { + "name": name, + "address": { + "addressLine1": "123 Main St", + "city": "Anytown", + "state": "CA", + "postCode": "12345", + "country": "US", + }, + "currencies": currencies, + "externalId": external_id, + } + + return _seller diff --git a/tests/e2e/accounts/test_async_sellers.py b/tests/e2e/accounts/test_async_sellers.py new file mode 100644 index 00000000..646d81df --- /dev/null +++ b/tests/e2e/accounts/test_async_sellers.py @@ -0,0 +1,134 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +# TODO: Handle create and teardown more gracefully with fixture that doesn't cause teardown issues +@pytest.fixture +async def async_created_seller(async_mpt_ops, seller, logger): + ret_seller = None + + async def _async_created_seller( + external_id: str, + name: str = "E2E Test Seller", + ): + nonlocal ret_seller # noqa: WPS420 + seller_data = seller(external_id=external_id, name=name) + ret_seller = await async_mpt_ops.accounts.sellers.create(seller_data) + return ret_seller + + yield _async_created_seller + + if ret_seller: + try: + await async_mpt_ops.accounts.sellers.delete(ret_seller.id) + except MPTAPIError: + logger.exception("TEARDOWN - Unable to delete seller %s", ret_seller.id) + + +async def test_get_seller_by_id(async_mpt_ops, seller_id): + seller = await async_mpt_ops.accounts.sellers.get(seller_id) + assert seller is not None + + +async def test_list_sellers(async_mpt_ops): + limit = 10 + + sellers = await async_mpt_ops.accounts.sellers.fetch_page(limit=limit) + + assert len(sellers) > 0 + + +async def test_get_seller_by_id_not_found(async_mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + await async_mpt_ops.accounts.sellers.get(invalid_seller_id) + + +async def test_filter_sellers(async_mpt_ops, seller_id): + select_fields = ["-address"] + + async_filtered_sellers = ( + async_mpt_ops.accounts.sellers.filter(RQLQuery(id=seller_id)) + .filter(RQLQuery(name="E2E Seeded Seller")) + .select(*select_fields) + ) + + sellers = [filtered_seller async for filtered_seller in async_filtered_sellers.iterate()] + + assert len(sellers) == 1 + + +async def test_create_seller(async_created_seller, timestamp): + seller_data = await async_created_seller(external_id=f"Async Create E2E Seller - {timestamp}") + assert seller_data is not None + + +async def test_delete_seller(async_mpt_ops, async_created_seller, timestamp): + seller_data = await async_created_seller(external_id=f"Async Delete E2E Seller - {timestamp}") + await async_mpt_ops.accounts.sellers.delete(seller_data.id) + + +async def test_delete_seller_not_found(async_mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + await async_mpt_ops.accounts.sellers.delete(invalid_seller_id) + + +async def test_update_seller(async_mpt_ops, seller, async_created_seller, timestamp): + seller_data = await async_created_seller(external_id=f"Async Update E2E Seller - {timestamp}") + update_data = seller( + external_id=f"Async Update E2E Seller - {timestamp}", + name=f"Updated Update E2E Seller - {timestamp}", + ) + updated_seller = await async_mpt_ops.accounts.sellers.update(seller_data.id, update_data) + assert updated_seller is not None + + +async def test_update_seller_mpt_error(async_mpt_ops, seller, timestamp, invalid_seller_id): + update_data = seller( + external_id=f"Async Update E2E Seller Not Found - {timestamp}", + name=f"Updated Update E2E Seller Not Found - {timestamp}", + ) + with pytest.raises(MPTAPIError): + await async_mpt_ops.accounts.sellers.update(invalid_seller_id, update_data) + + +async def test_activate_seller(async_mpt_ops, async_created_seller, timestamp): + seller_data = await async_created_seller(external_id=f"Async Activate E2E Seller - {timestamp}") + await async_mpt_ops.accounts.sellers.deactivate(seller_data.id) + activated_seller = await async_mpt_ops.accounts.sellers.activate(seller_data.id) + + assert activated_seller is not None + + +async def test_activate_seller_mpt_error(async_mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError): + await async_mpt_ops.accounts.sellers.activate(invalid_seller_id) + + +async def test_deactivate_seller(async_mpt_ops, async_created_seller, timestamp): + seller_data = await async_created_seller( + external_id=f"Async Deactivate E2E Seller - {timestamp}" + ) + deactivated_seller = await async_mpt_ops.accounts.sellers.deactivate(seller_data.id) + + assert deactivated_seller is not None + + +async def test_deactivate_seller_mpt_error(async_mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError): + await async_mpt_ops.accounts.sellers.deactivate(invalid_seller_id) + + +async def test_disable_seller(async_mpt_ops, async_created_seller, timestamp): + seller_data = await async_created_seller(external_id=f"Async Disable E2E Seller - {timestamp}") + disabled_seller = await async_mpt_ops.accounts.sellers.disable(seller_data.id) + + assert disabled_seller is not None + + +async def test_disable_seller_mpt_error(async_mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError): + await async_mpt_ops.accounts.sellers.disable(invalid_seller_id) diff --git a/tests/e2e/accounts/test_sync_sellers.py b/tests/e2e/accounts/test_sync_sellers.py new file mode 100644 index 00000000..8314bfd8 --- /dev/null +++ b/tests/e2e/accounts/test_sync_sellers.py @@ -0,0 +1,131 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +@pytest.fixture +def created_seller(mpt_ops, seller, logger): + ret_seller = None + + def _created_seller( + external_id: str, + name: str = "E2E Test Seller", + ): + nonlocal ret_seller # noqa: WPS420 + seller_data = seller(external_id=external_id, name=name) + ret_seller = mpt_ops.accounts.sellers.create(seller_data) + return ret_seller + + yield _created_seller + + if ret_seller: + try: + mpt_ops.accounts.sellers.delete(ret_seller.id) + except MPTAPIError: + logger.exception("TEARDOWN - Unable to delete seller %s", ret_seller.id) + + +def test_get_seller_by_id(mpt_ops, seller_id): + seller = mpt_ops.accounts.sellers.get(seller_id) + assert seller is not None + + +def test_list_sellers(mpt_ops): + limit = 10 + + sellers = mpt_ops.accounts.sellers.fetch_page(limit=limit) + + assert len(sellers) > 0 + + +def test_get_seller_by_id_not_found(mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + mpt_ops.accounts.sellers.get(invalid_seller_id) + + +def test_filter_sellers(mpt_ops, seller_id): + select_fields = ["-address"] + + filtered_sellers = ( + mpt_ops.accounts.sellers.filter(RQLQuery(id=seller_id)) + .filter(RQLQuery(name="E2E Seeded Seller")) + .select(*select_fields) + ) + + sellers = list(filtered_sellers.iterate()) + + assert len(sellers) == 1 + + +def test_create_seller(created_seller, timestamp): + seller_data = created_seller(external_id=f"Create E2E Seller - {timestamp}") + assert seller_data is not None + + +def test_delete_seller(mpt_ops, created_seller, timestamp): + seller_data = created_seller(external_id=f"Delete E2E Seller - {timestamp}") + mpt_ops.accounts.sellers.delete(seller_data.id) + + +def test_delete_seller_not_found(mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + mpt_ops.accounts.sellers.delete(invalid_seller_id) + + +def test_update_seller(mpt_ops, seller, created_seller, timestamp): + seller_data = created_seller(external_id=f"Update E2E Seller - {timestamp}") + update_data = seller( + external_id=f"Update E2E Seller - {timestamp}", + name=f"Updated Update E2E Seller - {timestamp}", + ) + updated_seller = mpt_ops.accounts.sellers.update(seller_data.id, update_data) + assert updated_seller is not None + + +def test_update_seller_mpt_error(mpt_ops, seller, timestamp, invalid_seller_id): + update_data = seller( + external_id=f"Async Update E2E Seller Not Found - {timestamp}", + name=f"Updated Update E2E Seller Not Found - {timestamp}", + ) + with pytest.raises(MPTAPIError): + mpt_ops.accounts.sellers.update(invalid_seller_id, update_data) + + +def test_activate_seller(mpt_ops, created_seller, timestamp): + seller_data = created_seller(external_id=f"Activate E2E Seller - {timestamp}") + mpt_ops.accounts.sellers.deactivate(seller_data.id) + activated_seller = mpt_ops.accounts.sellers.activate(seller_data.id) + + assert activated_seller is not None + + +def test_activate_seller_mpt_error(mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError): + mpt_ops.accounts.sellers.activate(invalid_seller_id) + + +def test_deactivate_seller(mpt_ops, created_seller, timestamp): + seller_data = created_seller(external_id=f"Deactivate E2E Seller - {timestamp}") + deactivated_seller = mpt_ops.accounts.sellers.deactivate(seller_data.id) + + assert deactivated_seller is not None + + +def test_deactivate_seller_mpt_error(mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError): + mpt_ops.accounts.sellers.deactivate(invalid_seller_id) + + +def test_disable_seller(mpt_ops, created_seller, timestamp): + seller_data = created_seller(external_id=f"Disable E2E Seller - {timestamp}") + disabled_seller = mpt_ops.accounts.sellers.disable(seller_data.id) + + assert disabled_seller is not None + + +def test_disable_seller_mpt_error(mpt_ops, invalid_seller_id): + with pytest.raises(MPTAPIError): + mpt_ops.accounts.sellers.disable(invalid_seller_id) diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 08001695..df3e1a27 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -78,3 +78,13 @@ def e2e_config(project_root_path): @pytest.fixture def product_id(e2e_config): return e2e_config["catalog.product.id"] + + +@pytest.fixture +def invalid_seller_id(): + return "SEL-0000-0000" + + +@pytest.fixture +def seller_id(e2e_config): + return e2e_config["accounts.seller.id"]