"""Validation / error-path tests for the write endpoints. Every write controller validates its request (body schema and, for updateTerritories, headers) *before* touching the database, returning 400 on failure. These tests exercise those guard paths, so they are safe to run against shared environments: a 400 means nothing was created, updated or deleted. See src/Controllers/Release.php and resources/json_schema/*.json. """ import pytest from Integration.consts import api from Integration.utils import assert_status # An orchard-user header is harmless on the validation path but mirrors how the # UI calls these endpoints. Shared with the lifecycle suite via consts. _HEADERS = api.WRITE_HEADERS # A body that is valid JSON but violates each endpoint's schema (missing the # required fields beyond product_id). The PHP unit tests assert this exact # shape yields 400 across the add/delete/replace controllers. _SCHEMA_INVALID_BODY = {"products": [{"product_id": 1}]} # (label, http-method, url) for every body-schema-validated bulkupdate route. _BULK_ROUTES = [ ("add_store", "post", api.BULK_STORE), ("delete_store", "delete", api.BULK_STORE), ("replace_store", "post", api.BULK_STORE_REPLACE), ("add_substore", "post", api.BULK_SUBSTORE), ("delete_substore", "delete", api.BULK_SUBSTORE), ("add_territory", "post", api.BULK_TERRITORY), ("delete_territory", "delete", api.BULK_TERRITORY), ("replace_territory", "post", api.BULK_TERRITORY_REPLACE), ("add_default_store", "post", api.BULK_DEFAULT_STORE), ("delete_default_store", "delete", api.BULK_DEFAULT_STORE), ("replace_default_store", "post", api.BULK_DEFAULT_STORE_REPLACE), ] @pytest.mark.negative @pytest.mark.parametrize( "method,url", [(m, u) for _, m, u in _BULK_ROUTES], ids=[label for label, _, _ in _BULK_ROUTES]) def test_bulkupdate_schema_invalid_body_is_rejected(http, method, url): """Schema-violating bodies are rejected with 400 before any mutation.""" response = http.request(method, url, json=_SCHEMA_INVALID_BODY, headers=_HEADERS) assert_status(response, 400) @pytest.mark.negative @pytest.mark.parametrize( "method,url", [(m, u) for _, m, u in _BULK_ROUTES], ids=[label for label, _, _ in _BULK_ROUTES]) def test_bulkupdate_empty_body_is_rejected(http, method, url): """A missing/empty body fails the required `products` check with 400.""" response = http.request(method, url, json={}, headers=_HEADERS) assert_status(response, 400) @pytest.mark.negative def test_put_territory_missing_required_headers(http): """updateTerritories requires Content-Type + Correlation-Id headers -> 400.""" # Send the body as raw text so requests does not add a Content-Type header, # which the header schema requires. response = http.put( api.PUT_TERRITORY.format(upc=api.UPC), data="[]") assert_status(response, 400) @pytest.mark.negative def test_put_territory_invalid_body(http): """A body whose items lack the required country_id is rejected with 400.""" headers = {"Content-Type": "application/json", "Correlation-Id": "it-test"} response = http.put( api.PUT_TERRITORY.format(upc=api.UPC), data="[{\"person_id\": 1}]", headers=headers) assert_status(response, 400) @pytest.mark.negative def test_copy_territories_rejects_grass_headers(http): """copyTerritories must reject GRASS-authenticated callers with 400. This 400 short-circuits before the copy service runs (Release::copyTerritories), so no data is written. """ headers = { api.GRASS_ACCOUNT_TYPE_HEADER: "vendor", api.GRASS_ACCOUNT_ID_HEADER: api.VENDOR_ID, } url = api.COPY_TERRITORIES.format(product_id=1, new_product_id=2) response = http.post(url, headers=headers) assert_status(response, 400)