"""Lifecycle (round-trip) tests for the data-mutating write endpoints. These create a carveout, verify it via the read API, then delete it again, asserting the entity appears and disappears. Because they mutate data they are SKIPPED by default and only run when the operator supplies throwaway IDs that are safe to create/delete on the target environment, e.g.: TEST_WRITE_PRODUCT_ID=123456 \\ TEST_WRITE_UPC=191018928940 \\ TEST_WRITE_STORE_ID=496 \\ TEST_WRITE_DISTRIBUTION_TYPE_IDS=1,3 \\ TEST_WRITE_COUNTRY_ID=58 \\ TEST_WRITE_SUBSTORE_ID=58 \\ py.test test_write_lifecycle.py Cleanup is registered as a *best-effort* finalizer (``_register_cleanup``) rather than a ``finally: assert ...`` block. A re-asserting ``finally`` would shadow the real failure: if the create/verify body raises, an also-failing cleanup DELETE would replace the original exception, making CI triage harder. The finalizer therefore swallows errors, and the happy path asserts the DELETE explicitly (and reads the state back where an endpoint exposes it). """ import pytest from Integration.consts import api from Integration.utils import assert_ok, assert_status # Base requirement shared by every write lifecycle test. _has_product = bool(api.WRITE_PRODUCT_ID and api.WRITE_UPC) requires_store = pytest.mark.skipif( not (_has_product and api.WRITE_STORE_ID and api.WRITE_DISTRIBUTION_TYPE_IDS), reason="needs TEST_WRITE_PRODUCT_ID, TEST_WRITE_UPC, TEST_WRITE_STORE_ID, " "TEST_WRITE_DISTRIBUTION_TYPE_IDS") requires_territory = pytest.mark.skipif( not (_has_product and api.WRITE_COUNTRY_ID), reason="needs TEST_WRITE_PRODUCT_ID, TEST_WRITE_UPC, TEST_WRITE_COUNTRY_ID") requires_default_store = pytest.mark.skipif( not (_has_product and api.WRITE_DISTRIBUTION_TYPE_IDS), reason="needs TEST_WRITE_PRODUCT_ID, TEST_WRITE_UPC, " "TEST_WRITE_DISTRIBUTION_TYPE_IDS") requires_substore = pytest.mark.skipif( not (_has_product and api.WRITE_SUBSTORE_ID), reason="needs TEST_WRITE_PRODUCT_ID, TEST_WRITE_UPC, TEST_WRITE_SUBSTORE_ID") _HEADERS = api.WRITE_HEADERS def _register_cleanup(request, http, method, url, **kwargs): """Register a best-effort teardown request. Unlike a ``finally: assert removed.status_code == 204`` block, this never asserts and never raises, so it cannot mask the exception that actually failed the test. It is a safety net that removes any carveout left behind when the test body raises before its own explicit delete runs. """ def _cleanup(): try: http.request(method, url, **kwargs) except Exception: # noqa: BLE001 - teardown must never raise pass request.addfinalizer(_cleanup) def _product_id(): return int(api.WRITE_PRODUCT_ID) def _upc(): return int(api.WRITE_UPC) def _store_body(): return {"products": [{ "product_id": _product_id(), "upc": _upc(), "store_distros": [{ "store_id": int(api.WRITE_STORE_ID), "distribution_type_ids": api.WRITE_DISTRIBUTION_TYPE_IDS, }], }]} def _territory_body(): return {"products": [{ "product_id": _product_id(), "upc": _upc(), "country_ids": [int(api.WRITE_COUNTRY_ID)], }]} def _default_store_body(): return {"products": [{ "product_id": _product_id(), "upc": _upc(), "distribution_type_ids": api.WRITE_DISTRIBUTION_TYPE_IDS, }]} def _substore_body(): return {"products": [{ "product_id": _product_id(), "upc": _upc(), "substore_ids": [int(api.WRITE_SUBSTORE_ID)], }]} @pytest.mark.write @requires_store def test_store_carveout_lifecycle(http, request): """POST a store carveout, see it in the release dms read, then DELETE it.""" body = _store_body() store_key = api.WRITE_STORE_ID read_url = api.RELEASE_STORE.format(upc=api.WRITE_UPC) _register_cleanup(request, http, "delete", api.BULK_STORE, json=body, headers=_HEADERS) assert_status(http.post(api.BULK_STORE, json=body, headers=_HEADERS), 204) present = assert_ok(http.get(read_url)).json() assert store_key in present, \ "expected store {} to appear in {}".format(store_key, read_url) assert_status(http.delete(api.BULK_STORE, json=body, headers=_HEADERS), 204) gone = assert_ok(http.get(read_url)).json() assert store_key not in gone, \ "store {} should be removed after delete".format(store_key) @pytest.mark.write @requires_store def test_replace_store_carveout(http, request): """POST to storereplace, verify present with the exact distros, then delete. ``storereplace`` sets the release's store carveouts to exactly the posted set, so afterwards the posted store must be present and its distros must match what we sent (not merely be appended alongside pre-existing ones). """ body = _store_body() store_key = api.WRITE_STORE_ID read_url = api.RELEASE_STORE.format(upc=api.WRITE_UPC) _register_cleanup(request, http, "delete", api.BULK_STORE, json=body, headers=_HEADERS) assert_status( http.post(api.BULK_STORE_REPLACE, json=body, headers=_HEADERS), 204) present = assert_ok(http.get(read_url)).json() assert store_key in present # Replace semantics: the store's distros reflect exactly what we posted. expected_distros = {str(d) for d in api.WRITE_DISTRIBUTION_TYPE_IDS} assert set(present[store_key]["distros"]) == expected_distros, \ "storereplace should set distros to {}, got {}".format( expected_distros, present[store_key]["distros"]) assert_status(http.delete(api.BULK_STORE, json=body, headers=_HEADERS), 204) gone = assert_ok(http.get(read_url)).json() assert store_key not in gone, \ "store {} should be removed after delete".format(store_key) @pytest.mark.write @requires_territory def test_territory_carveout_lifecycle(http, request): """POST a territory carveout, see it in the territory read, then DELETE it.""" body = _territory_body() country_key = api.WRITE_COUNTRY_ID read_url = api.RELEASE_TERRITORY.format(upc=api.WRITE_UPC) _register_cleanup(request, http, "delete", api.BULK_TERRITORY, json=body, headers=_HEADERS) assert_status(http.post(api.BULK_TERRITORY, json=body, headers=_HEADERS), 204) present = assert_ok(http.get(read_url)).json() assert country_key in present, \ "expected country {} to appear in {}".format(country_key, read_url) assert_status(http.delete(api.BULK_TERRITORY, json=body, headers=_HEADERS), 204) gone = assert_ok(http.get(read_url)).json() assert country_key not in gone, \ "country {} should be removed after delete".format(country_key) @pytest.mark.write @requires_default_store def test_default_store_carveout_round_trip(http, request): """POST then DELETE a default-store carveout. There is no dedicated read endpoint exposing default-store carveouts, so we assert the DELETE round-trips cleanly (204) rather than reading the state back. The finalizer still guarantees cleanup if the POST-side assert fails. """ body = _default_store_body() _register_cleanup(request, http, "delete", api.BULK_DEFAULT_STORE, json=body, headers=_HEADERS) assert_status( http.post(api.BULK_DEFAULT_STORE, json=body, headers=_HEADERS), 204) assert_status( http.delete(api.BULK_DEFAULT_STORE, json=body, headers=_HEADERS), 204) @pytest.mark.write @requires_substore def test_substore_carveout_round_trip(http, request): """POST then DELETE a substore carveout. The substore read is keyed by dms_id and does not expose the written ``substore_ids`` in a form we can assert on generically, so this stays a create/delete round-trip. The finalizer guarantees cleanup on failure. """ body = _substore_body() _register_cleanup(request, http, "delete", api.BULK_SUBSTORE, json=body, headers=_HEADERS) assert_status(http.post(api.BULK_SUBSTORE, json=body, headers=_HEADERS), 204) assert_status(http.delete(api.BULK_SUBSTORE, json=body, headers=_HEADERS), 204) @pytest.mark.write @requires_territory def test_put_territory_set_and_clear(http, request): """PUT a territory restriction set for the product, then clear it. updateTerritories writes country rows to ``release_territory_restriction`` — the same table the release territory read returns, keyed by ``country_id`` (src/Territory.php) — so we read the state back both ways: the country is present after set and absent after clear. Clearing with an empty list also leaves the throwaway product in a clean state; the finalizer re-clears as a safety net if the set-side assert fails. """ url = api.PUT_TERRITORY.format(upc=api.WRITE_UPC) read_url = api.RELEASE_TERRITORY.format(upc=api.WRITE_UPC) headers = {"Content-Type": "application/json", "Correlation-Id": "it-test"} country_key = api.WRITE_COUNTRY_ID body = [{"country_id": int(api.WRITE_COUNTRY_ID)}] _register_cleanup(request, http, "put", url, json=[], headers=headers) assert_status(http.put(url, json=body, headers=headers), 200) present = assert_ok(http.get(read_url)).json() assert country_key in present, \ "expected country {} to appear in {}".format(country_key, read_url) assert_status(http.put(url, json=[], headers=headers), 200) gone = assert_ok(http.get(read_url)).json() assert country_key not in gone, \ "country {} should be cleared from {}".format(country_key, read_url)