"""Tests for Handlers.""" import json from typing import Any from unittest.mock import MagicMock, patch import pytest from _pytest.monkeypatch import MonkeyPatch from owsrequest import request, test_utils from typing_extensions import Generator from werkzeug.exceptions import BadRequest from carveouts import handlers from carveouts.api import app from carveouts.constants import error, header, service_name from carveouts.logic import vendors from carveouts.models.schemas import CarveoutPayload from carveouts.models.service_carveout import ServiceCarveout from carveouts.models.service_country_carveout import ServiceCountryPayload def test_exception_handler(app_context: Generator[None, Any, None]) -> None: """Verify exception_Handler returns 500 status code and json payload.""" message = ( "The server encountered an internal error " "and was unable to complete your request." ) mock_error = Exception(message) server_response = handlers.exception_handler(mock_error) # assert status code is 500 assert server_response.status_code == 500 # assert error message assert server_response.data.decode() == message @patch("carveouts.logic.carveouts.delete_product_service_carveouts") def test_delete_product_service_carveouts( mock_delete_product_carveouts: MagicMock, monkeypatch: MonkeyPatch ) -> None: """Test delete product service carveouts response when success.""" product_id = 1 account_id = 1 account_type = "vendor" path = "/{}/{}/product/{}".format(account_type, account_id, product_id) call_specs = [{"service": service_name.OWS_PRODUCT, "path": path, "status": 200}] monkeypatch.setattr(request, "head", test_utils.mock_ows_requests(call_specs)) headers = { header.GRASS_ACCOUNT_TYPE: account_type, header.GRASS_ACCOUNT_ID: account_id, } url = "/release_dms_master_carveouts/{}".format(product_id) result = app.test_client().delete(url, headers=headers) assert result.status_code == 200 mock_delete_product_carveouts.assert_not_called() @patch("carveouts.logic.carveouts.delete_product_service_carveouts") def test_delete_product_service_carveouts_without_headers( mock_delete_product_service_carveouts: MagicMock, ) -> None: """Test delete product service carveouts without passing headers.""" product_id = 1 url = "/release_dms_master_carveouts/{}".format(product_id) result = app.test_client().delete(url) assert result.status_code == 200 assert mock_delete_product_service_carveouts.called mock_delete_product_service_carveouts.assert_called_once_with(product_id) def test_delete_product_service_carveouts_for_ownership_check( monkeypatch: MonkeyPatch, ) -> None: """Test delete product service carveouts for ownership check.""" product_id = 1 account_id = 2 account_type = "vendor" path = "/{}/{}/product/{}".format(account_type, account_id, product_id) call_specs = [{"service": service_name.OWS_PRODUCT, "path": path, "status": 403}] monkeypatch.setattr(request, "head", test_utils.mock_ows_requests(call_specs)) headers = { header.GRASS_ACCOUNT_TYPE: account_type, header.GRASS_ACCOUNT_ID: account_id, } url = "/release_dms_master_carveouts/{}".format(product_id) result = app.test_client().delete(url, headers=headers) data = result.data.decode() assert result.status_code == 403 assert data == ( f"{error.ERROR_CODE_AUTHORIZATION}: {error.ERROR_MESSAGE_FORBIDDEN_USER}" ) def test_dataload_vendor_contract_store_carveouts(monkeypatch: MonkeyPatch) -> None: """Test dataload vendor store carveouts response when success.""" monkeypatch.setattr( vendors, "dataload_vendor_contract_store_carveouts", MagicMock(return_value=[]), ) url = "/vendor-contract-store-carveouts-dataloader" payload = [ { "vendor_contract_id": 1, "delivery_store_id": 2, } ] result = app.test_client().post( url, data=json.dumps(payload), content_type="application/json" ) assert result.status_code == 200 def test_dataload_vendor_contract_store_carveout_input_validation( monkeypatch: MonkeyPatch, ) -> None: """Test dataload vendor store carveouts with invalid input.""" monkeypatch.setattr( vendors, "dataload_vendor_contract_store_carveouts", MagicMock(return_value=[]), ) url = "/vendor-contract-store-carveouts-dataloader" payload = [ { "unknown_field": 1, "delivery_store_id": 2, } ] result = app.test_client().post( url, data=json.dumps(payload), content_type="application/json" ) assert result.status_code == 400 def test_http_exception_handler(app_context: Generator[None, Any, None]) -> None: """Verify http_exception_handler returns 400 status code and json payload.""" mock_error = BadRequest("invalid vendor_id") server_response = handlers.http_exception_handler(mock_error) # assert status code is 400 assert server_response.status_code == 400 # assert error message assert server_response.data.decode() == "400 Bad Request: invalid vendor_id" @patch("carveouts.handlers.carveouts.save_product_carveouts") def test_save_product_carveouts(mock_add_product_carveouts_logic: MagicMock) -> None: """Test save product carveouts endpoint.""" mock_add_product_carveouts_logic.return_value = 8 product_id = 123 url = f"/delivery-restrictions/product/{product_id}" payload = { "service": [ { "service_id": 1, "distribution_types": [1, 2], } ], "service_country": [ { "service_id": 1, "country_codes": ["FR", "DE"], } ], "country": ["US", "CA"], "new_service_opt_out": [1, 2, 3], "updated_by": 101, } result = app.test_client().post( url, data=json.dumps(payload), content_type="application/json" ) mock_add_product_carveouts_logic.assert_called_once_with( product_id=123, payload=CarveoutPayload( service=[ ServiceCarveout( service_id=1, service_name=None, distribution_types=[1, 2], ), ], service_country=[ ServiceCountryPayload( service_id=1, country_codes=["FR", "DE"], ), ], country=["US", "CA"], opt_out=[1, 2, 3], updated_by=101, ), ) assert result.status_code == 204 assert result.data.decode("utf-8") == "" @pytest.mark.parametrize( "payload, expected_error_code", [ (None, 400), ({"data": "some garbage"}, 400), ({"service": "invalid"}, 400), ({"service_country": "invalid"}, 400), ({"country": "invalid"}, 400), ({"new_service_opt_out": "invalid"}, 400), ], ) def test_save_product_carveouts_bad_payload( payload: dict[str, str] | None, expected_error_code: int ) -> None: """Test save product carveouts endpoint bad payload.""" product_id = 123 url = f"/delivery-restrictions/product/{product_id}" result = app.test_client().post(url, data=payload, content_type="application/json") assert result.status_code == 400 @patch("carveouts.handlers.carveouts.save_account_carveouts") def test_save_account_carveouts(mock_save_account_carveouts_logic: MagicMock) -> None: """Test save account carveouts endpoint.""" mock_save_account_carveouts_logic.return_value = 9 vendor_contract_id = 10 url = f"/delivery-restrictions/account/{vendor_contract_id}" payload = { "service": [ { "service_id": 1, "distribution_types": [1, 2], } ], "service_country": [ { "service_id": 1, "country_codes": ["FR", "DE"], } ], "country": ["US", "CA"], "new_service_opt_out": [1, 2, 3], "updated_by": 101, } result = app.test_client().post( url, data=json.dumps(payload), content_type="application/json" ) mock_save_account_carveouts_logic.assert_called_once_with( vendor_contract_id=vendor_contract_id, payload=CarveoutPayload( service=[ ServiceCarveout( service_id=1, service_name=None, distribution_types=[1, 2], ), ], service_country=[ ServiceCountryPayload( service_id=1, country_codes=["FR", "DE"], ), ], country=["US", "CA"], opt_out=[1, 2, 3], updated_by=101, ), ) assert result.status_code == 204 assert result.data.decode("utf-8") == "" @pytest.mark.parametrize( "payload, expected_error_code", [ (None, 400), ({"data": "some garbage"}, 400), ({"service": "invalid"}, 400), ({"service_country": "invalid"}, 400), ({"country": "invalid"}, 400), ({"new_service_opt_out": "invalid"}, 400), ], ) def test_save_account_carveouts_bad_payload( payload: dict[str, str] | None, expected_error_code: int ) -> None: """Test save account carveouts endpoint bad payload.""" vendor_contract_id = 123 url = f"/delivery-restrictions/account/{vendor_contract_id}" result = app.test_client().post(url, data=payload, content_type="application/json") assert result.status_code == 400 @patch("carveouts.handlers.carveouts.create_new_service_carveouts") @pytest.mark.parametrize( "payload", [ {"updated_by": 101}, None, ], ) def test_create_new_store_carveouts( mock_create_new_service_carveouts: MagicMock, payload: dict[str, Any] | None, ) -> None: """Test create_new_store_carveouts endpoint.""" new_service_id = 123 updated_by = payload["updated_by"] if payload else 179 url = f"/delivery-restrictions/apply-default-restrictions/{new_service_id}" result = app.test_client().post( url, data=json.dumps(payload), content_type="application/json" ) assert result.status_code == 204 assert result.data.decode("utf-8") == "" assert mock_create_new_service_carveouts.call_count == 1 mock_create_new_service_carveouts.assert_called_with( service_id=new_service_id, updated_by=updated_by )