"""Test for Carveouts logic.""" from unittest.mock import MagicMock, Mock, patch import pytest from carveouts.exceptions import ProductInvalid, SubaccountInvalid, VendorInvalid from carveouts.logic import carveouts from carveouts.models.country_carveout import CountryCarveout from carveouts.models.schemas import ( AccountCarveoutResponse, CarveoutPayload, CarveoutResponse, Product, ProductCarveoutResponse, SubaccountCarveoutResponse, ) from carveouts.models.service_carveout import ServiceCarveout from carveouts.models.service_country_carveout import ( ServiceCountryCarveout, ServiceCountryPayload, ) @patch("carveouts.models.service_carveout.delete_product_carveouts") def test_delete_product_service_carveouts( mock_delete_product_carveouts: MagicMock, ) -> None: """Test to delete product service carveouts.""" carveouts.delete_product_service_carveouts(123) assert mock_delete_product_carveouts.called mock_delete_product_carveouts.assert_called_once_with(123) @patch("carveouts.models.service_country_carveout.delete_product_carveouts") def test_delete_product_service_country_carveouts( mock_delete_product_carveouts: MagicMock, ) -> None: """Test to delete product service country carveouts.""" carveouts.delete_product_service_country_carveouts(123) assert mock_delete_product_carveouts.called mock_delete_product_carveouts.assert_called_once_with(123) @pytest.mark.parametrize( ( "description", "vendor_id", "subaccount_id", "active_contract_id", "vendor_service_carveouts", "vendor_service_country_carveouts", "vendor_country_carveouts", "vendor_opt_outs", "subaccount_service_carveouts", "subaccount_service_country_carveouts", "subaccount_country_carveouts", "product_service_carveouts", "product_service_country_carveouts", "product_country_carveouts", "product_opt_outs", "expected_result", ), [ ( "no carveouts, no active contract", 1234, 0, None, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, CarveoutResponse( account=AccountCarveoutResponse( country=[], service=[], service_country=[], new_service_opt_outs=[], ), subaccount=SubaccountCarveoutResponse( country=[], service=[], service_country=[], ), product=ProductCarveoutResponse( country=[], service=[], service_country=[], new_service_opt_outs=[], ), ), ), ( "account level carveouts - vendor has active contract with carveouts", 1234, 0, 12345, { ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), }, { ServiceCountryCarveout( service_id=3, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=3, country_id=2, country_code="GB", ), }, { CountryCarveout( country_id=146, country_code="RU", ) }, {1, 2, 3}, {}, {}, {}, {}, {}, {}, {}, CarveoutResponse( account=AccountCarveoutResponse( country=["RU"], service=[ ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), ], service_country=[{"service_id": 3, "countries": ["GB", "US"]}], new_service_opt_outs=[1, 2, 3], ), subaccount=SubaccountCarveoutResponse( country=[], service=[], service_country=[], ), product=ProductCarveoutResponse( country=[], service=[], service_country=[], new_service_opt_outs=[], ), ), ), ( "subaccount has carveouts, no account/product carveouts", None, 123456, None, {}, {}, {}, {}, { ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[], ), }, { ServiceCountryCarveout( service_id=3, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=3, country_id=2, country_code="GB", ), }, { CountryCarveout( country_id=146, country_code="RU", ) }, {}, {}, {}, {}, CarveoutResponse( account=AccountCarveoutResponse( country=[], service=[], service_country=[], new_service_opt_outs=[], ), subaccount=SubaccountCarveoutResponse( country=["RU"], service=[ ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[], ), ], service_country=[{"service_id": 3, "countries": ["GB", "US"]}], ), product=ProductCarveoutResponse( country=[], service=[], service_country=[], new_service_opt_outs=[], ), ), ), ( "product has carveouts, no account/subaccount carveouts", None, None, None, {}, {}, {}, {}, {}, {}, {}, { ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), }, { ServiceCountryCarveout( service_id=3, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=3, country_id=2, country_code="GB", ), }, { CountryCarveout( country_id=146, country_code="RU", ) }, {1, 2}, CarveoutResponse( account=AccountCarveoutResponse( country=[], service=[], service_country=[], new_service_opt_outs=[], ), subaccount=SubaccountCarveoutResponse( country=[], service=[], service_country=[], ), product=ProductCarveoutResponse( country=["RU"], service=[ ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), ], service_country=[{"service_id": 3, "countries": ["GB", "US"]}], new_service_opt_outs=[1, 2], ), ), ), ( "carveouts from all levels", 1234, 123456, 12345, { ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), }, { ServiceCountryCarveout( service_id=1, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=1, country_id=2, country_code="GB", ), }, { CountryCarveout( country_id=146, country_code="RU", ) }, {1, 2}, { ServiceCarveout( service_id=187, service_name="Amazon Digital Service", distribution_types=[], ), }, { ServiceCountryCarveout( service_id=3, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=3, country_id=2, country_code="GB", ), }, { CountryCarveout( country_id=146, country_code="RU", ) }, { ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1], ), ServiceCarveout( service_id=286, service_name="Spotify", distribution_types=[1], ), }, { ServiceCountryCarveout( service_id=3, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=3, country_id=2, country_code="GB", ), }, { CountryCarveout( country_id=146, country_code="RU", ), CountryCarveout( country_id=190, country_code="CS", ), }, {3}, CarveoutResponse( account=AccountCarveoutResponse( country=["RU"], service=[ ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), ], service_country=[ { "service_id": 1, "countries": ["GB", "US"], }, ], new_service_opt_outs=[1, 2], ), subaccount=SubaccountCarveoutResponse( country=["RU"], service=[ ServiceCarveout( service_id=187, service_name="Amazon Digital Service", distribution_types=[], ), ], service_country=[ { "service_id": 3, "countries": ["GB", "US"], }, ], ), product=ProductCarveoutResponse( country=["CS", "RU"], service=[ ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1], ), ServiceCarveout( service_id=286, service_name="Spotify", distribution_types=[1], ), ], service_country=[ { "service_id": 3, "countries": ["GB", "US"], }, ], new_service_opt_outs=[3], ), ), ), ], ) def test_get_product_carveouts( description: str, vendor_id: int, subaccount_id: int, active_contract_id: int, vendor_service_carveouts: dict[int, ServiceCarveout], vendor_service_country_carveouts: set[ServiceCountryCarveout], vendor_country_carveouts: set[CountryCarveout], vendor_opt_outs: set[int], subaccount_service_carveouts: dict[int, ServiceCarveout], subaccount_service_country_carveouts: set[ServiceCountryCarveout], subaccount_country_carveouts: set[CountryCarveout], product_service_carveouts: dict[int, ServiceCarveout], product_service_country_carveouts: set[ServiceCountryCarveout], product_country_carveouts: set[CountryCarveout], product_opt_outs: set[int], expected_result: CarveoutResponse, mocker: Mock, ) -> None: """Test to get_product_carveouts.""" mocker.patch.multiple( "carveouts.logic.carveouts.releases", get_product=MagicMock(return_value=True), get_vendor_id=MagicMock(return_value=vendor_id), get_subaccount_id=MagicMock(return_value=subaccount_id), ) mocker.patch.multiple( "carveouts.logic.carveouts.vendor_contract", get_active_contract_id=MagicMock(return_value=active_contract_id), ) mocker.patch.multiple( "carveouts.logic.carveouts.country_carveout", get_vendor_carveouts=MagicMock(return_value=vendor_country_carveouts), get_subaccount_carveouts=MagicMock(return_value=subaccount_country_carveouts), get_product_carveouts=MagicMock(return_value=product_country_carveouts), ) mocker.patch.multiple( "carveouts.logic.carveouts.service_carveout", get_vendor_carveouts=MagicMock(return_value=vendor_service_carveouts), get_subaccount_carveouts=MagicMock(return_value=subaccount_service_carveouts), get_product_carveouts=MagicMock(return_value=product_service_carveouts), ) mocker.patch.multiple( "carveouts.logic.carveouts.service_country_carveout", get_vendor_carveouts=MagicMock(return_value=vendor_service_country_carveouts), get_subaccount_carveouts=MagicMock( return_value=subaccount_service_country_carveouts ), get_product_carveouts=MagicMock(return_value=product_service_country_carveouts), ) mocker.patch.multiple( "carveouts.logic.carveouts.service_opt_out", get_vendor_opt_outs=MagicMock(return_value=vendor_opt_outs), get_product_opt_outs=MagicMock(return_value=product_opt_outs), ) result = carveouts.get_product_carveouts(123) assert result == expected_result @patch( "carveouts.logic.carveouts.releases.get_vendor_id", side_effect=VendorInvalid("Vendor account for product 123 is invalid."), ) @patch("carveouts.logic.carveouts.releases", name="get_product") def test_get_product_carveouts_vendor_not_found( releases_model_mock: MagicMock, mock_get_vendor_id: MagicMock, ) -> None: """Test to get_product_carveouts vendor not found.""" releases_model_mock.get_product.return_value = True with pytest.raises(VendorInvalid) as e: carveouts.get_product_carveouts(123) assert mock_get_vendor_id.called mock_get_vendor_id.assert_called_once_with(123) assert str(e.value) == "400 Bad Request: Vendor account for product 123 is invalid." def test__format_service_country_carveouts() -> None: result = carveouts._format_service_country_carveouts( { ServiceCountryCarveout( service_id=1, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=1, country_id=2, country_code="GB", ), ServiceCountryCarveout( service_id=187, country_id=3, country_code="FR", ), } ) assert result == {1: {"GB", "US"}, 187: {"FR"}} @patch( "carveouts.logic.carveouts.releases.get_product", side_effect=ProductInvalid("Product ID 123 is invalid"), ) def test_get_product_carveouts_product_not_found( get_product_mock: MagicMock, ) -> None: """Test to get_product_carveouts product not found.""" with pytest.raises( ProductInvalid, match="^400 Bad Request: Product ID 123 is invalid$" ): carveouts.get_product_carveouts(123) get_product_mock.assert_called_once_with(123) @pytest.mark.parametrize( "status, side_effect, exception_message", [ ( "400 Bad Request", SubaccountInvalid, "Subaccount ID 123 is invalid", ), ( "400 Bad Request", VendorInvalid, "Vendor for subaccount 123 is invalid", ), ], ) @patch("carveouts.logic.carveouts.subaccount.get_subaccount") def test_get_subaccount_carveouts_with_exception( subaccount_model_mock: MagicMock, status: str, side_effect: type[Exception], exception_message: str, ) -> None: """Test to get_subaccount_carveouts subaccount not found.""" subaccount_model_mock.side_effect = side_effect(exception_message) with pytest.raises(side_effect) as e: carveouts.get_subaccount_carveouts(123) subaccount_model_mock.assert_called_once_with(123) assert str(e.value) == f"{status}: {exception_message}" @pytest.mark.parametrize( ( "description", "vendor_id", "subaccount_id", "active_contract_id", "vendor_service_carveouts", "vendor_service_country_carveouts", "vendor_country_carveouts", "vendor_opt_outs", "subaccount_service_carveouts", "subaccount_service_country_carveouts", "subaccount_country_carveouts", "expected_result", ), [ ( "no carveouts, no active contract", 1234, 0, None, {}, {}, {}, {}, {}, {}, {}, CarveoutResponse( account=AccountCarveoutResponse( country=[], service=[], service_country=[], new_service_opt_outs=[], ), subaccount=SubaccountCarveoutResponse( country=[], service=[], service_country=[], ), ), ), ( "account level carveouts - vendor has active contract with carveouts", 1234, 0, 12345, { ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), }, { ServiceCountryCarveout( service_id=3, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=3, country_id=2, country_code="GB", ), }, { CountryCarveout( country_id=146, country_code="RU", ) }, {1, 2, 3}, {}, {}, {}, CarveoutResponse( account=AccountCarveoutResponse( country=["RU"], service=[ ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), ], service_country=[{"service_id": 3, "countries": ["GB", "US"]}], new_service_opt_outs=[1, 2, 3], ), subaccount=SubaccountCarveoutResponse( country=[], service=[], service_country=[], ), ), ), ( "subaccount has carveouts, no account/product carveouts", None, 123456, None, {}, {}, {}, {}, { ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[], ), }, { ServiceCountryCarveout( service_id=3, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=3, country_id=2, country_code="GB", ), }, { CountryCarveout( country_id=146, country_code="RU", ) }, CarveoutResponse( account=AccountCarveoutResponse( country=[], service=[], service_country=[], new_service_opt_outs=[], ), subaccount=SubaccountCarveoutResponse( country=["RU"], service=[ ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[], ), ], service_country=[{"service_id": 3, "countries": ["GB", "US"]}], ), ), ), ], ) def test_get_subaccount_carveouts( description: str, vendor_id: int, subaccount_id: int, active_contract_id: int, vendor_service_carveouts: dict[int, ServiceCarveout], vendor_service_country_carveouts: set[ServiceCountryCarveout], vendor_country_carveouts: set[CountryCarveout], vendor_opt_outs: set[int], subaccount_service_carveouts: dict[int, ServiceCarveout], subaccount_service_country_carveouts: set[ServiceCountryCarveout], subaccount_country_carveouts: set[CountryCarveout], expected_result: CarveoutResponse, mocker: Mock, ) -> None: """Test to get_subaccount_carveouts""" mocker.patch( "carveouts.logic.carveouts.subaccount", get_subaccount=MagicMock(return_value=(subaccount_id, vendor_id)), ) mocker.patch.multiple( "carveouts.logic.carveouts.vendor_contract", get_active_contract_id=MagicMock(return_value=active_contract_id), ) mocker.patch.multiple( "carveouts.logic.carveouts.country_carveout", get_vendor_carveouts=MagicMock(return_value=vendor_country_carveouts), get_subaccount_carveouts=MagicMock(return_value=subaccount_country_carveouts), ) mocker.patch.multiple( "carveouts.logic.carveouts.service_carveout", get_vendor_carveouts=MagicMock(return_value=vendor_service_carveouts), get_subaccount_carveouts=MagicMock(return_value=subaccount_service_carveouts), ) mocker.patch.multiple( "carveouts.logic.carveouts.service_country_carveout", get_vendor_carveouts=MagicMock(return_value=vendor_service_country_carveouts), get_subaccount_carveouts=MagicMock( return_value=subaccount_service_country_carveouts ), ) mocker.patch.multiple( "carveouts.logic.carveouts.service_opt_out", get_vendor_opt_outs=MagicMock(return_value=vendor_opt_outs), ) result = carveouts.get_subaccount_carveouts(subaccount_id) assert result == expected_result @patch( "carveouts.logic.carveouts.vendor.get_vendor_id", side_effect=VendorInvalid("Vendor ID 123 is invalid"), ) def test_get_account_carveouts_with_exception(vendor_model_mock: MagicMock) -> None: """Test to get_subaccount_carveouts subaccount not found.""" with pytest.raises(VendorInvalid) as e: carveouts.get_account_carveouts(123) vendor_model_mock.assert_called_once_with(123) assert str(e.value) == "400 Bad Request: Vendor ID 123 is invalid" @pytest.mark.parametrize( ( "description", "vendor_id", "active_contract_id", "vendor_service_carveouts", "vendor_service_country_carveouts", "vendor_country_carveouts", "vendor_opt_outs", "expected_result", ), [ ( "no carveouts, no active contract", 1234, None, {}, {}, {}, {}, CarveoutResponse( account=AccountCarveoutResponse( country=[], service=[], service_country=[], new_service_opt_outs=[], ), ), ), ( "account level carveouts - vendor has active contract with carveouts", 1234, 12345, { ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), }, { ServiceCountryCarveout( service_id=3, country_id=1, country_code="US", ), ServiceCountryCarveout( service_id=3, country_id=2, country_code="GB", ), }, { CountryCarveout( country_id=146, country_code="RU", ) }, {1, 2, 3}, CarveoutResponse( account=AccountCarveoutResponse( country=["RU"], service=[ ServiceCarveout( service_id=1, service_name="iTunes/Apple Music", distribution_types=[1, 2, 3], ), ], service_country=[{"service_id": 3, "countries": ["GB", "US"]}], new_service_opt_outs=[1, 2, 3], ), ), ), ], ) def test_get_account_carveouts( description: str, vendor_id: int, active_contract_id: int, vendor_service_carveouts: dict[int, ServiceCarveout], vendor_service_country_carveouts: set[ServiceCountryCarveout], vendor_country_carveouts: set[CountryCarveout], vendor_opt_outs: set[int], expected_result: CarveoutResponse, mocker: Mock, ) -> None: """Test to get_subaccount_carveouts""" mocker.patch( "carveouts.logic.carveouts.vendor", get_vendor_id=MagicMock(return_value=vendor_id), ) mocker.patch.multiple( "carveouts.logic.carveouts.vendor_contract", get_active_contract_id=MagicMock(return_value=active_contract_id), ) mocker.patch.multiple( "carveouts.logic.carveouts.country_carveout", get_vendor_carveouts=MagicMock(return_value=vendor_country_carveouts), ) mocker.patch.multiple( "carveouts.logic.carveouts.service_carveout", get_vendor_carveouts=MagicMock(return_value=vendor_service_carveouts), ) mocker.patch.multiple( "carveouts.logic.carveouts.service_country_carveout", get_vendor_carveouts=MagicMock(return_value=vendor_service_country_carveouts), ) mocker.patch.multiple( "carveouts.logic.carveouts.service_opt_out", get_vendor_opt_outs=MagicMock(return_value=vendor_opt_outs), ) result = carveouts.get_account_carveouts(vendor_id) assert result == expected_result @patch("carveouts.logic.carveouts.releases", name="get_product") @patch("carveouts.models.country_carveout.delete_product_carveouts") def test_delete_product_country_carveouts( mock_delete_product_carveouts: MagicMock, releases_model_mock: MagicMock, ) -> None: """Test to delete product country carveouts.""" releases_model_mock.get_product.return_value = True carveouts.delete_product_country_carveouts(123) assert mock_delete_product_carveouts.called mock_delete_product_carveouts.assert_called_once_with(123) @patch( "carveouts.logic.carveouts.releases.get_product", side_effect=ProductInvalid("Product ID 123 is invalid"), ) def test_delete_product_country_carveouts_product_not_found( get_product_mock: MagicMock, ) -> None: """Test to delete_product_country_carveouts product not found.""" with pytest.raises( ProductInvalid, match="^400 Bad Request: Product ID 123 is invalid$" ): carveouts.delete_product_country_carveouts(123) get_product_mock.assert_called_once_with(123) @patch("carveouts.logic.carveouts.country_carveout.add_product_carveouts") @patch("carveouts.logic.carveouts.service_opt_out.add_product_opt_outs") @patch("carveouts.logic.carveouts.service_country_carveout.add_product_carveouts") @patch("carveouts.logic.carveouts.service_carveout.add_product_carveouts") @patch("carveouts.logic.carveouts.releases", name="get_product") @patch("carveouts.logic.carveouts.country", name="get_country_ids") def test_add_product_carveouts( mock_country: MagicMock, mock_releases: MagicMock, mock_add_product_service_carveouts: MagicMock, mock_add_product_service_country_carveouts: MagicMock, mock_add_product_opt_outs: MagicMock, mock_add_country_carveouts: MagicMock, ) -> None: mock_country.get_country_ids.return_value = [1, 2] mock_releases.get_product.return_value = Product(product_id=123, upc=12341234345) mock_add_product_service_carveouts.return_value = 2 mock_add_product_service_country_carveouts.return_value = 3 mock_add_product_opt_outs.return_value = 3 mock_add_country_carveouts.return_value = 2 payload = CarveoutPayload( service=[ ServiceCarveout( service_id=123, service_name=None, distribution_types=[1], ), ServiceCarveout( service_id=456, service_name=None, distribution_types=[1, 3], ), ], service_country=[ ServiceCountryPayload( service_id=123, country_codes=["US", "CA", "FR"], ) ], country=["US", "CA"], opt_out=[1, 2, 3], updated_by=None, ) carveouts.save_product_carveouts(123, payload) mock_releases.get_product.assert_called_once_with(123) mock_country.get_country_ids.assert_called_once_with(["US", "CA"]) mock_add_product_service_carveouts.assert_called_once_with( product_id=123, upc=12341234345, carveouts=payload.service, updated_by=179, ) mock_add_product_service_country_carveouts.assert_called_once_with( product_id=123, upc=12341234345, carveouts=payload.service_country, ) mock_add_product_opt_outs.assert_called_once_with( product_id=123, upc=12341234345, distribution_types=[1, 2, 3], ) mock_add_country_carveouts.assert_called_once_with( product_id=123, upc=12341234345, country_ids=[1, 2], ) @patch("carveouts.logic.carveouts.service_carveout") def test_create_new_service_carveouts(mock_service_carveout_model: MagicMock) -> None: carveouts.create_new_service_carveouts(123, 101) mock_service_carveout_model.create_account_new_service_carveouts.assert_called_with( 123, 101 ) mock_service_carveout_model.create_product_new_service_carveouts.assert_called_with( 123, 101 ) @patch("carveouts.logic.carveouts.service_carveout.add_account_carveouts") @patch("carveouts.logic.carveouts.country_carveout.update_account_carveouts") def test_save_account_carveouts_with_country( mock_add_account_country_carveout: MagicMock, mock_add_account_service_carveouts: MagicMock, ) -> None: mock_add_account_service_carveouts.return_value = 3 vendor_contract_id = 10 payload = CarveoutPayload( service=[ ServiceCarveout( service_id=123, service_name=None, distribution_types=[1], ), ServiceCarveout( service_id=456, service_name=None, distribution_types=[1, 3], ), ], service_country=[ ServiceCountryPayload( service_id=123, country_codes=["US", "CA", "FR"], ) ], country=["US", "CA"], opt_out=[1, 2, 3], updated_by=None, ) carveouts.save_account_carveouts(vendor_contract_id, payload) mock_add_account_service_carveouts.assert_called_once_with( vendor_contract_id=vendor_contract_id, carveouts=payload.service, updated_by=179, ) mock_add_account_country_carveout.assert_called_once_with( vendor_contract_id=vendor_contract_id, country_codes=["US", "CA"], )