"""Functional tests for POST /product//spatial endpoint.""" import json from datetime import datetime from unittest.mock import MagicMock from flexmock import flexmock from owsrequest import request import pytest from product_digital.connectors import mysql from product_digital.constants import services from product_digital.models.product_additional_upc import ProductAdditionalUpc from product_digital.models.product_additional_upc import ProductAdditionalUpcType from tests.factories import release as release_factory from tests.factories import release_spatial as release_spatial_factory from tests.testutils import db PRODUCT_ID = 12345 SPATIAL_UPC = 9876543210123 @pytest.fixture def product_id(): """Return a fake product id for testing.""" return PRODUCT_ID @pytest.fixture def owner_ok_response(): """Return a mock 200 ownership response from ows-product.""" return MagicMock(status_code=200) @pytest.fixture def not_owner_response(): """Return a mock 403 ownership response from ows-product.""" return MagicMock(status_code=403) @pytest.fixture def provisioned_upc_response(): """Return a mock successful UPC provision response from ows-product.""" mock = MagicMock(status_code=200) mock.json = MagicMock(return_value={'upc': SPATIAL_UPC}) return mock @db.test_schema def test_post_product_spatial_with_provided_upc( client, product_id, valid_headers_for_vendor, owner_ok_response): """Test successful creation when upc is provided in the body.""" existing_release = release_factory.ReleaseFactory.build(release_id=product_id) db.seed_models(existing_release) (flexmock(request) .should_receive('head') .and_return(owner_ok_response)) response = client.post( '/product/{}/spatial'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps({'upc': SPATIAL_UPC})) body = json.loads(response.data.decode()) assert response.status_code == 200 assert body['release_id'] == product_id assert body['upc'] == SPATIAL_UPC @db.test_schema def test_post_product_spatial_mirrors_product_additional_upc( client, product_id, valid_headers_for_vendor, owner_ok_response): """A successful POST also writes the product_additional_upc dual-write mirror.""" existing_release = release_factory.ReleaseFactory.build(release_id=product_id) db.seed_models(existing_release) (flexmock(request) .should_receive('head') .and_return(owner_ok_response)) response = client.post( '/product/{}/spatial'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps({'upc': SPATIAL_UPC})) assert response.status_code == 200 with mysql.db_session() as session: mirror = session.query(ProductAdditionalUpc).filter( ProductAdditionalUpc.product_id == product_id).one() assert mirror.type == ProductAdditionalUpcType.ATMOS assert mirror.upc == SPATIAL_UPC @db.test_schema def test_post_product_spatial_reuses_existing_additional_upc( client, product_id, valid_headers_for_vendor, owner_ok_response): """A POST with no body UPC reuses the product's existing additional UPC -- no provision.""" existing_release = release_factory.ReleaseFactory.build(release_id=product_id) db.seed_models(existing_release) with mysql.db_session() as session: session.add(ProductAdditionalUpc( product_id=product_id, type=ProductAdditionalUpcType.ATMOS, upc=SPATIAL_UPC)) session.commit() (flexmock(request) .should_receive('head') .and_return(owner_ok_response)) (flexmock(request) .should_receive('post') .never()) response = client.post( '/product/{}/spatial'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps({})) body = json.loads(response.data.decode()) assert response.status_code == 200 assert body['upc'] == SPATIAL_UPC @db.test_schema def test_post_product_spatial_restores_soft_deleted_mirror( client, product_id, valid_headers_for_vendor, owner_ok_response): """A re-add reuses the soft-deleted UPC and restores the mirror to active.""" existing_release = release_factory.ReleaseFactory.build(release_id=product_id) db.seed_models(existing_release) with mysql.db_session() as session: session.add(ProductAdditionalUpc( product_id=product_id, type=ProductAdditionalUpcType.ATMOS, upc=SPATIAL_UPC, deleted_at=datetime(2026, 1, 1))) session.commit() (flexmock(request) .should_receive('head') .and_return(owner_ok_response)) (flexmock(request) .should_receive('post') .never()) response = client.post( '/product/{}/spatial'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps({})) assert response.status_code == 200 with mysql.db_session() as session: mirror = session.query(ProductAdditionalUpc).filter( ProductAdditionalUpc.product_id == product_id).one() assert mirror.deleted_at is None assert mirror.upc == SPATIAL_UPC @db.test_schema def test_post_product_spatial_auto_provisioned_upc( client, product_id, valid_headers_for_vendor, owner_ok_response, provisioned_upc_response): """Test successful creation with auto-provisioned UPC when none provided.""" existing_release = release_factory.ReleaseFactory.build(release_id=product_id) db.seed_models(existing_release) (flexmock(request) .should_receive('head') .and_return(owner_ok_response)) (flexmock(request) .should_receive('post') .with_args(services.OWS_PRODUCT, '/upc/provision', json={'mark_used': True}) .and_return(provisioned_upc_response)) response = client.post( '/product/{}/spatial'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps({})) body = json.loads(response.data.decode()) assert response.status_code == 200 assert body['release_id'] == product_id assert body['upc'] == SPATIAL_UPC @db.test_schema def test_post_product_spatial_duplicate( client, product_id, valid_headers_for_vendor, owner_ok_response): """Test 409 when a spatial record already exists for the product.""" existing_release = release_factory.ReleaseFactory.build(release_id=product_id) existing_spatial = release_spatial_factory.ReleaseSpatialFactory.build( release_id=product_id, upc=SPATIAL_UPC) db.seed_models([existing_release, existing_spatial]) (flexmock(request) .should_receive('head') .and_return(owner_ok_response)) response = client.post( '/product/{}/spatial'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps({'upc': SPATIAL_UPC + 1})) assert response.status_code == 409 def test_post_product_spatial_not_owner( client, product_id, account_id, valid_headers_for_vendor, not_owner_response): """Test 403 when the requesting account does not own the product.""" (flexmock(request) .should_receive('head') .with_args(services.OWS_PRODUCT, '/vendor/{}/product/{}'.format( account_id, product_id)) .and_return(not_owner_response)) response = client.post( '/product/{}/spatial'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps({'upc': SPATIAL_UPC})) assert response.status_code == 403 def test_post_product_spatial_incomplete_grass_headers( client, product_id, valid_headers, account_id): """Test 400 when only one Grass header is present (incomplete pair).""" valid_headers['Grass-Account-Id'] = account_id response = client.post( '/product/{}/spatial'.format(product_id), headers=valid_headers, data=json.dumps({'upc': SPATIAL_UPC})) assert response.status_code == 400