import uuid from typing import Dict, List, Optional, Union from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi import HTTPException from httpx import HTTPStatusError, Request, Response from pdp.connectors import ows_account from pdp.connectors.ows_account import ( LookupCompanyBrandsResponse, LookupSubaccountsResponse, LookupVendorsResponse, OwsAccountClient, ) from pdp.connectors.redis_client import RedisConnector from pdp.constants.constants import TenantType from pdp.fastapi.schemas.tenant import ( IdExchangeTenantHierarchy, IdToUuidExchangeTenant, Tenant, ) from pdp.proxies.id_to_uuid_exchange_tenant_proxy import ( IdToUuidExchangeLookupError, IdToUuidExchangeTenantProxy, ) from pdp.proxies.multi_tenant_proxy import TenantHierarchyLookupError from pdp.utils.tenant_cache import TENANT_HIERARCHY_CACHE_TTL_BY_TENANT_TYPE from tests.unit.fastapi.schemas.test_id_to_uuid_exchange_tenant import ( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1_VARIANT, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ID_TO_UUID_EXCHANGE_TENANT_LP, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT_SAME_ID_AS_ACCOUNT_1, ) from tests.unit.fastapi.schemas.test_tenant import TENANT_ACCOUNT_1 @pytest.fixture def mock_redis_connector() -> RedisConnector: """Mock redis-connector.""" return AsyncMock(spec=RedisConnector) @pytest.fixture def mock_ows_account_client() -> OwsAccountClient: """Mock ows account connector.""" return AsyncMock(spec=OwsAccountClient) ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1 = IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_type, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), ) ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_2 = IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_type, tenant_uuid=uuid.uuid4(), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), ) ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3 = IdToUuidExchangeTenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=33, ) ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_3 = IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_type, tenant_uuid=uuid.uuid4(), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), ) ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_SUBACCOUNT_1 = IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_type, tenant_uuid=uuid.uuid4(), account=Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1.tenant_uuid, ), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), ) @pytest.fixture def list_of_tenants() -> List[IdToUuidExchangeTenant]: """Return test fixture list of tenants.""" return [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1_VARIANT, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3, ] @pytest.fixture def mock_exchange_proxy( mock_redis_connector: RedisConnector, mock_ows_account_client: OwsAccountClient, list_of_tenants: List[IdToUuidExchangeTenant], ) -> IdToUuidExchangeTenantProxy: """Mock IdToUuidExchangeTenantProxy.""" return IdToUuidExchangeTenantProxy( tenants=list_of_tenants, redis_connector=mock_redis_connector, ows_account_client=mock_ows_account_client, ) def test_gathered_tenant_exchanges_property_uninitialized( mock_exchange_proxy: IdToUuidExchangeTenantProxy, ) -> None: """Test gathered_tenant_exchanges property raises when uninitialized.""" with pytest.raises(IdToUuidExchangeLookupError): mock_exchange_proxy.gathered_tenant_exchanges def test_gathered_tenant_exchanges_property( mock_exchange_proxy: IdToUuidExchangeTenantProxy, ) -> None: """Test gathered_tenant_exchanges property.""" # Mock / initialize mock_exchange_proxy._gathered_tenant_exchanges = {} assert mock_exchange_proxy.gathered_tenant_exchanges == {} @pytest.mark.parametrize( "description, tenants, expected", [ ( "A list of one tenant should return that one tenant", [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], ), ( "A list of three tenants with the same tenant should return one tenant", [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1_VARIANT, ], [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], ), ( "A list of multiple tenants should return list of unique tenants", [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ], [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ], ), ( "A list of unsorted tenants should return list of sorted tenants", [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ], [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ], ), ], ) def test__get_unique_tenants( description: str, tenants: List[IdToUuidExchangeTenant], expected: List[IdToUuidExchangeTenant], mock_exchange_proxy: IdToUuidExchangeTenantProxy, ) -> None: """Test _get_unique_tenants.""" unique_tenant_list = mock_exchange_proxy._get_unique_tenants(tenants) assert unique_tenant_list == expected, description @pytest.mark.parametrize( "tenant_list, expected", [ pytest.param( [], { TenantType.TENANT_TYPE_ACCOUNT: [], TenantType.TENANT_TYPE_SUBACCOUNT: [], TenantType.TENANT_TYPE_COMPANY_BRAND: [], }, id="empty list returns dict of empty lists", ), pytest.param( [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], { TenantType.TENANT_TYPE_ACCOUNT: [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], TenantType.TENANT_TYPE_SUBACCOUNT: [], TenantType.TENANT_TYPE_COMPANY_BRAND: [], }, id="List with one account tenant returns dict with only account tenant", ), pytest.param( [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ], { TenantType.TENANT_TYPE_ACCOUNT: [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], TenantType.TENANT_TYPE_SUBACCOUNT: [ ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ], TenantType.TENANT_TYPE_COMPANY_BRAND: [], }, id="List with multiple tenant types return dict with both types", ), pytest.param( [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ], { TenantType.TENANT_TYPE_ACCOUNT: [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], TenantType.TENANT_TYPE_SUBACCOUNT: [ ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ], TenantType.TENANT_TYPE_COMPANY_BRAND: [ ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ], }, id="List with company brand support", ), pytest.param( [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ID_TO_UUID_EXCHANGE_TENANT_LP, ], { TenantType.TENANT_TYPE_ACCOUNT: [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], TenantType.TENANT_TYPE_SUBACCOUNT: [ ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ], TenantType.TENANT_TYPE_COMPANY_BRAND: [ ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ], }, id="List with unsupported tenant types ignores those tenants", ), ], ) def test__init_tenants_by_type( tenant_list: List[IdToUuidExchangeTenant], expected: Dict[TenantType, List[IdToUuidExchangeTenant]], mock_exchange_proxy: IdToUuidExchangeTenantProxy, ) -> None: """Test _init_tenants_by_type.""" mock_exchange_proxy._tenants = tenant_list actual = mock_exchange_proxy._init_tenants_by_type() assert actual == expected async def test_id_to_uuid_exchange_ignores_duplicate_tenants( mock_ows_account_client: OwsAccountClient, mock_redis_connector: RedisConnector, ) -> None: """Test IdToUuidExchangeTenantProxy discards duplicate tenants.""" mock_exchange_proxy = IdToUuidExchangeTenantProxy( tenants=[ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ], redis_connector=mock_redis_connector, ows_account_client=mock_ows_account_client, ) assert len(mock_exchange_proxy._tenants) == 4 assert mock_exchange_proxy._tenants == [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT, ] @pytest.mark.parametrize( "tenant_type, tenant_id, gathered_tenant_exchanges_data, expected_result", [ pytest.param( TenantType.TENANT_TYPE_COMPANY_BRAND, "does not matter what id this is", {}, None, id="Handle when a tenant type isn't in the list", ), pytest.param( TenantType.TENANT_TYPE_ACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, { TenantType.TENANT_TYPE_ACCOUNT: { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1 } }, ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, id="Return results for 'account' tenant type", ), pytest.param( TenantType.TENANT_TYPE_SUBACCOUNT, ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id, { TenantType.TENANT_TYPE_SUBACCOUNT: { str( ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_SUBACCOUNT_1 } }, ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_SUBACCOUNT_1, id="Return results for 'subaccount' tenant type", ), pytest.param( " ", ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id, None, IdToUuidExchangeLookupError(), id="Return exception when invalid tenant type passed", ), pytest.param( TenantType.TENANT_TYPE_SUBACCOUNT, 1, { TenantType.TENANT_TYPE_SUBACCOUNT: { str( ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_SUBACCOUNT_1 } }, None, id="Return None when tenant_id not found", ), ], ) async def test_get_tenant_exchange( mock_exchange_proxy: MagicMock, tenant_type: TenantType, tenant_id: int, gathered_tenant_exchanges_data: Dict[str, IdExchangeTenantHierarchy], expected_result: IdExchangeTenantHierarchy | IdToUuidExchangeLookupError, ) -> None: """Test get_tenant_exchange.""" mock_exchange_proxy._gathered_tenant_exchanges = gathered_tenant_exchanges_data if isinstance(expected_result, IdToUuidExchangeLookupError): with pytest.raises(IdToUuidExchangeLookupError): mock_exchange_proxy.get_tenant_exchange(tenant_type, tenant_id) else: assert expected_result == mock_exchange_proxy.get_tenant_exchange( tenant_type, tenant_id ) @patch("pdp.proxies.id_to_uuid_exchange_tenant_proxy.PydanticSchemaSerializer") async def test__get_tenant_exchange_from_cache( mock_pydantic_schema_serializer: MagicMock, mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_redis_connector: MagicMock, ) -> None: """Test _get_tenant_exchange_from_cache.""" ACCOUNT_2_TENANT_HIERARCHY = IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=22227, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid=uuid.uuid4(), ), ) mock_redis_connector.mget.return_value = [ None, ACCOUNT_2_TENANT_HIERARCHY, None, ] actual = await mock_exchange_proxy._get_tenant_exchange_from_cache( [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, IdToUuidExchangeTenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id="333", ), ] ) assert actual == {"11117": None, "22227": ACCOUNT_2_TENANT_HIERARCHY, "333": None} mock_redis_connector.mget.assert_called_once_with( keys=[ "tenant_hierarchy@tenant_id#11117|tenant_type#account", "tenant_hierarchy@tenant_id#22227|tenant_type#account", "tenant_hierarchy@tenant_id#333|tenant_type#account", ], serializer=mock_pydantic_schema_serializer(IdExchangeTenantHierarchy), ) async def test__get_tenant_exchange_from_cache_empty_tenants( mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_redis_connector: MagicMock, ) -> None: """Test _get_tenant_exchange_from_cache when tenants are empty.""" actual = await mock_exchange_proxy._get_tenant_exchange_from_cache([]) assert actual == {} mock_redis_connector.list.assert_not_called() mock_redis_connector.mget.assert_not_called() @pytest.mark.parametrize( "entries, tenant_type, expected_mset_entries, mset_return, expected", [ pytest.param( [], TenantType.TENANT_TYPE_ACCOUNT, {}, {}, {}, id="Empty entries list returns empty dict", ), pytest.param( [ IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ) ], TenantType.TENANT_TYPE_ACCOUNT, { "tenant_hierarchy@tenant_id#123|tenant_type#account": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), }, { "tenant_hierarchy@tenant_id#123|tenant_type#account": True, "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": True, # noqa: E501 }, { "tenant_hierarchy@tenant_id#123|tenant_type#account": True, "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": True, # noqa: E501 }, id="Two entries saved successfully in cache", ), pytest.param( [ IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), ], TenantType.TENANT_TYPE_ACCOUNT, { "tenant_hierarchy@tenant_id#123|tenant_type#account": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), }, { "tenant_hierarchy@tenant_id#123|tenant_type#account": True, "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": False, # noqa: E501 }, { "tenant_hierarchy@tenant_id#123|tenant_type#account": True, "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": False, # noqa: E501 }, id="mset will return False if failed to save entry", ), pytest.param( [ IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), ], TenantType.TENANT_TYPE_ACCOUNT, { "tenant_hierarchy@tenant_id#123|tenant_type#account": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), }, { "tenant_hierarchy@tenant_id#123|tenant_type#account": True, }, { "tenant_hierarchy@tenant_id#123|tenant_type#account": True, "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": False, # noqa E501 }, id="Entries not returned from mset will default to False in response dict", ), pytest.param( [ IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ) ], TenantType.TENANT_TYPE_ACCOUNT, { "tenant_hierarchy@tenant_id#123|tenant_type#account": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), }, { "tenant_hierarchy@tenant_id#123|tenant_type#account": True, "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": True, # noqa: E501 "tenant_hierarchy@something": False, }, { "tenant_hierarchy@tenant_id#123|tenant_type#account": True, "tenant_hierarchy@tenant_type#account|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": True, # noqa: E501 }, id="Cache keys returned from mset that were not passed in will be ignored in response", # noqa E501 ), ], ) @patch("pdp.proxies.id_to_uuid_exchange_tenant_proxy.PydanticSchemaSerializer") async def test__save_tenant_exchange_to_cache( mock_serializer: MagicMock, mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_redis_connector: MagicMock, entries: List[IdExchangeTenantHierarchy], tenant_type: TenantType, expected_mset_entries: Dict[str, IdExchangeTenantHierarchy], mset_return: Dict[str, bool], expected: Dict[str, bool], ) -> None: """Test _save_tenant_exchange_to_cache.""" mock_redis_connector.mset_with_pipeline.return_value = mset_return actual = await mock_exchange_proxy._save_tenant_exchange_to_cache( entries=entries, tenant_type=tenant_type, ) assert actual == expected if entries: mock_redis_connector.mset.assert_not_called() mock_redis_connector.mset_with_pipeline.assert_awaited_once_with( expected_mset_entries, serializer=mock_serializer(IdExchangeTenantHierarchy), ttl=TENANT_HIERARCHY_CACHE_TTL_BY_TENANT_TYPE[tenant_type], ) else: mock_redis_connector.mset.assert_not_called() mock_redis_connector.mset_with_pipeline.assert_not_called() @pytest.mark.parametrize( "tenant_type", [ pytest.param(TenantType.TENANT_TYPE_ACCOUNT), pytest.param(TenantType.TENANT_TYPE_SUBACCOUNT), pytest.param(TenantType.TENANT_TYPE_COMPANY_BRAND), pytest.param(TenantType.TENANT_TYPE_LABEL_PARTICIPANT), ], ) @patch("pdp.proxies.id_to_uuid_exchange_tenant_proxy.PydanticSchemaSerializer") async def test__save_tenant_exchange_to_cache_ttl( mock_serializer: MagicMock, mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_redis_connector: MagicMock, tenant_type: TenantType, ) -> None: """Test _save_tenant_exchange_to_cache TTL.""" mock_redis_connector.mset_with_pipeline.return_value = {} await mock_exchange_proxy._save_tenant_exchange_to_cache( entries=[ IdExchangeTenantHierarchy( tenant_type=tenant_type, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ) ], tenant_type=tenant_type, ) mock_redis_connector.mset_with_pipeline.assert_awaited_once_with( { f"tenant_hierarchy@tenant_id#123|tenant_type#{tenant_type.value}": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=tenant_type, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), f"tenant_hierarchy@tenant_type#{tenant_type.value}|tenant_uuid#61577c79-d788-4b88-bf6a-2def82e0b4ed": IdExchangeTenantHierarchy( # noqa: E501 tenant_type=tenant_type, tenant_id=123, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="15b8e358-cd42-4fc3-a050-7a1a5c7f3ea7", ), ), }, serializer=mock_serializer(IdExchangeTenantHierarchy), ttl=TENANT_HIERARCHY_CACHE_TTL_BY_TENANT_TYPE[tenant_type], ) async def test__exchange_accounts_from_ows_account( mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_ows_account_client: MagicMock, tenant_1_id: int, tenant_2_id: int, tenant_1_uuid: uuid.UUID, tenant_2_uuid: uuid.UUID, ) -> None: """Test _exchange_accounts_from_ows_account.""" company_brand_uuid = uuid.uuid4() parent_company_uuid_1 = uuid.uuid4() parent_company_uuid_2 = uuid.uuid4() mock_ows_account_client.lookup_vendors_by_ids.return_value = ( ows_account.LookupVendorsResponse( vendors=[ ows_account.LookupVendor( vendor_id=tenant_1_id, uuid=tenant_1_uuid, company_brand_uuid=company_brand_uuid, parent_company_uuid=parent_company_uuid_1, ), ows_account.LookupVendor( vendor_id=tenant_2_id, uuid=tenant_2_uuid, company_brand_uuid=company_brand_uuid, parent_company_uuid=parent_company_uuid_2, ), None, ] ) ) result = await mock_exchange_proxy._exchange_accounts_from_ows_account( tenant_ids=[tenant_1_id, tenant_2_id, 999] ) assert result == { str(tenant_1_id): IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=tenant_1_id, tenant_uuid=tenant_1_uuid, company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid=company_brand_uuid, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid=parent_company_uuid_1, ), ), str(tenant_2_id): IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_id=tenant_2_id, tenant_uuid=tenant_2_uuid, company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid=company_brand_uuid, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid=parent_company_uuid_2, ), ), } async def test__exchange_accounts_from_ows_account__missing_parent_company( mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_ows_account_client: MagicMock, tenant_1_id: int, tenant_2_id: int, tenant_1_uuid: uuid.UUID, tenant_2_uuid: uuid.UUID, ) -> None: """Test _exchange_accounts_from_ows_account with missing parent company.""" company_brand_uuid = uuid.uuid4() parent_company_uuid_1 = uuid.uuid4() mock_ows_account_client.lookup_vendors_by_ids.return_value = ( ows_account.LookupVendorsResponse( vendors=[ ows_account.LookupVendor( vendor_id=tenant_1_id, uuid=tenant_1_uuid, company_brand_uuid=company_brand_uuid, parent_company_uuid=parent_company_uuid_1, ), ows_account.LookupVendor( vendor_id=tenant_2_id, uuid=tenant_2_uuid, company_brand_uuid=company_brand_uuid, parent_company_uuid=None, ), None, ] ) ) with pytest.raises(TenantHierarchyLookupError) as e: _ = await mock_exchange_proxy._exchange_accounts_from_ows_account( tenant_ids=[tenant_1_id, tenant_2_id, 999] ) assert f"tenant_hierarchy missing parent_company_uuid for: '{tenant_2_id}'" == str( e.value ) async def test__exchange_accounts_from_ows_account_empty_result( mock_exchange_proxy: IdToUuidExchangeTenantProxy, ) -> None: """Test _exchange_accounts_from_ows_account vendor_ids is empty.""" result = await mock_exchange_proxy._exchange_accounts_from_ows_account( tenant_ids=[] ) assert result == {} @pytest.mark.parametrize( "side_effect_error, expected_error", [ pytest.param( HTTPStatusError( request=Request("POST", "https://no.such/endpoint"), response=Response(status_code=401), message="Bad request to ows-account for tenant proxy.", ), HTTPException, id="Should raise an HTTPException for a 400 error.", ), pytest.param( HTTPStatusError( request=Request("POST", "https://no.such/endpoint"), response=Response(status_code=500), message="Account lookup by vendor_ids failed for tenant proxy.", ), Exception, id="Should raise an HTTPException for a 500 error.", ), pytest.param( RuntimeError("Unhandled error."), TenantHierarchyLookupError, id="Should raise a TenantHierarchyLookupError for an unhandled error.", ), pytest.param( [ LookupVendorsResponse.model_validate( { "vendors": [ { "vendor_id": 1, "uuid": str(TENANT_ACCOUNT_1.tenant_uuid), "company_brand_uuid": None, }, ] } ), ], TenantHierarchyLookupError, id="Should raise a TenantHierarchyLookupError if company_brand_uuid is null.", # noqa: E501 ), ], ) async def test__exchange_accounts_from_ows_account_fails( mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_ows_account_client: MagicMock, side_effect_error: BaseException, expected_error: type[BaseException], tenant_1_id: int, ) -> None: """Test _exchange_accounts_from_ows_account fails.""" mock_ows_account_client.lookup_vendors_by_ids.side_effect = AsyncMock( side_effect=side_effect_error ) with pytest.raises(expected_error): await mock_exchange_proxy._exchange_accounts_from_ows_account( tenant_ids=[tenant_1_id] ) async def test__exchange_subaccounts_from_ows_account( mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_ows_account_client: MagicMock, tenant_1_id: int, tenant_2_id: int, tenant_1_uuid: uuid.UUID, tenant_2_uuid: uuid.UUID, ) -> None: """Test _exchange_subaccounts_from_ows_account.""" company_brand_uuid = uuid.uuid4() parent_company_uuid_1 = uuid.uuid4() parent_company_uuid_2 = uuid.uuid4() mock_ows_account_client.lookup_subaccounts_by_ids.return_value = ( ows_account.LookupSubaccountsResponse( subaccounts=[ ows_account.LookupSubaccount( subaccount_id=tenant_1_id, uuid=tenant_1_uuid, vendor_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand_uuid=company_brand_uuid, parent_company_uuid=parent_company_uuid_1, ), ows_account.LookupSubaccount( subaccount_id=tenant_2_id, uuid=tenant_2_uuid, vendor_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", company_brand_uuid=company_brand_uuid, parent_company_uuid=parent_company_uuid_2, ), None, ] ) ) result = await mock_exchange_proxy._exchange_subaccounts_from_ows_account( tenant_ids=[tenant_1_id, tenant_2_id, 999] ) assert result == { str(tenant_1_id): IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_SUBACCOUNT, tenant_id=tenant_1_id, tenant_uuid=tenant_1_uuid, account=Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", ), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid=company_brand_uuid, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid=parent_company_uuid_1, ), ), str(tenant_2_id): IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_SUBACCOUNT, tenant_id=tenant_2_id, tenant_uuid=tenant_2_uuid, account=Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", ), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid=company_brand_uuid, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid=parent_company_uuid_2, ), ), } async def test__exchange_subaccounts_from_ows_account_missing_pc( mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_ows_account_client: MagicMock, tenant_1_id: int, tenant_2_id: int, tenant_1_uuid: uuid.UUID, tenant_2_uuid: uuid.UUID, ) -> None: """Test _exchange_subaccounts_from_ows_account when parent_company is missing.""" company_brand_uuid = uuid.uuid4() parent_company_uuid_1 = uuid.uuid4() mock_ows_account_client.lookup_subaccounts_by_ids.return_value = ( ows_account.LookupSubaccountsResponse( subaccounts=[ ows_account.LookupSubaccount( subaccount_id=tenant_1_id, uuid=tenant_1_uuid, vendor_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand_uuid=company_brand_uuid, parent_company_uuid=parent_company_uuid_1, ), ows_account.LookupSubaccount( subaccount_id=tenant_2_id, uuid=tenant_2_uuid, vendor_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", company_brand_uuid=company_brand_uuid, parent_company_uuid=None, ), None, ] ) ) with pytest.raises(TenantHierarchyLookupError) as e: await mock_exchange_proxy._exchange_subaccounts_from_ows_account( tenant_ids=[tenant_1_id, tenant_2_id, 999] ) assert f"tenant_hierarchy missing parent_company_uuid for: '{tenant_2_id}'" == str( e.value ) async def test__exchange_subaccounts_from_ows_account_empty_result( mock_exchange_proxy: IdToUuidExchangeTenantProxy, ) -> None: """Test _exchange_subaccounts_from_ows_account vendor_ids is empty.""" result = await mock_exchange_proxy._exchange_subaccounts_from_ows_account( tenant_ids=[] ) assert result == {} @pytest.mark.parametrize( "side_effect_error, expected_error", [ pytest.param( HTTPStatusError( request=Request("POST", "https://no.such/endpoint"), response=Response(status_code=401), message="Bad request to ows-account for tenant proxy (subaccount).", ), HTTPException, id="Should raise an HTTPException for a 400 error.", ), pytest.param( HTTPStatusError( request=Request("POST", "https://no.such/endpoint"), response=Response(status_code=500), message="Subaccount lookup by vendor_ids failed for tenant proxy.", ), Exception, id="Should raise an HTTPException for a 500 error.", ), pytest.param( RuntimeError("Unhandled error."), TenantHierarchyLookupError, id="Should raise a TenantHierarchyLookupError for an unhandled error.", ), pytest.param( [ LookupSubaccountsResponse.model_validate( { "subaccounts": [ { "subaccount_id": 1, "uuid": str(TENANT_ACCOUNT_1.tenant_uuid), "vendor_uuid": "61577c79-d788-4b88-bf6a-2def82e0b4ed", "company_brand_uuid": None, }, ] } ), ], TenantHierarchyLookupError, id="Should raise a TenantHierarchyLookupError if company_brand_uuid is null.", # noqa: E501 ), ], ) async def test__exchange_subaccounts_from_ows_account_fails( mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_ows_account_client: MagicMock, side_effect_error: BaseException, expected_error: type[BaseException], tenant_1_id: int, ) -> None: """Test _exchange_subaccounts_from_ows_account fails.""" mock_ows_account_client.lookup_subaccounts_by_ids.side_effect = AsyncMock( side_effect=side_effect_error ) with pytest.raises(expected_error): await mock_exchange_proxy._exchange_subaccounts_from_ows_account( tenant_ids=[tenant_1_id] ) async def test__exchange_company_brands_from_ows_account( mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_ows_account_client: MagicMock, tenant_1_id: int, tenant_2_id: int, tenant_1_uuid: uuid.UUID, tenant_2_uuid: uuid.UUID, ) -> None: """Test _exchange_company_brands_from_ows_account.""" parent_company_uuid = uuid.uuid4() mock_ows_account_client.lookup_company_brands_by_ids.return_value = ( ows_account.LookupCompanyBrandsResponse( company_brands=[ ows_account.LookupCompanyBrand( company_brand_id=tenant_1_id, uuid=tenant_1_uuid, parent_company_uuid=parent_company_uuid, ), ows_account.LookupCompanyBrand( company_brand_id=tenant_2_id, uuid=tenant_2_uuid, parent_company_uuid=parent_company_uuid, ), None, ] ) ) result = await mock_exchange_proxy._exchange_company_brands_from_ows_account( tenant_ids=[tenant_1_id, tenant_2_id, 999] ) assert result == { str(tenant_1_id): IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_id=tenant_1_id, tenant_uuid=tenant_1_uuid, company_brand=None, parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid=parent_company_uuid, ), ), str(tenant_2_id): IdExchangeTenantHierarchy( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_id=tenant_2_id, tenant_uuid=tenant_2_uuid, company_brand=None, parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid=parent_company_uuid, ), ), } async def test__exchange_company_brands_from_ows_account_empty_result( mock_exchange_proxy: IdToUuidExchangeTenantProxy, ) -> None: """Test _exchange_company_brands_from_ows_account vendor_ids is empty.""" result = await mock_exchange_proxy._exchange_company_brands_from_ows_account( tenant_ids=[] ) assert result == {} @pytest.mark.parametrize( "side_effect_error, expected_error", [ pytest.param( HTTPStatusError( request=Request("POST", "https://no.such/endpoint"), response=Response(status_code=401), message="Bad request to ows-account for tenant proxy.", ), HTTPException, id="Should raise an HTTPException for a 400 error.", ), pytest.param( HTTPStatusError( request=Request("POST", "https://no.such/endpoint"), response=Response(status_code=500), message="Account lookup by vendor_ids failed for tenant proxy.", ), Exception, id="Should raise an HTTPException for a 500 error.", ), pytest.param( RuntimeError("Unhandled error."), TenantHierarchyLookupError, id="Should raise a TenantHierarchyLookupError for an unhandled error.", ), pytest.param( [ LookupCompanyBrandsResponse.model_validate( { "company_brands": [ { "company_brand_id": 1, "uuid": str(TENANT_ACCOUNT_1.tenant_uuid), "parent_company_uuid": None, }, ] } ), ], TenantHierarchyLookupError, id="Should raise a TenantHierarchyLookupError if company_brand_uuid is null.", # noqa: E501 ), ], ) async def test__exchange_company_brands_from_ows_account_fails( mock_exchange_proxy: IdToUuidExchangeTenantProxy, mock_ows_account_client: MagicMock, side_effect_error: BaseException, expected_error: type[BaseException], tenant_1_id: int, ) -> None: """Test _exchange_company_brands_from_ows_account fails.""" mock_ows_account_client.lookup_company_brands_by_ids.side_effect = AsyncMock( side_effect=side_effect_error ) with pytest.raises(expected_error): await mock_exchange_proxy._exchange_company_brands_from_ows_account( tenant_ids=[tenant_1_id] ) @pytest.mark.parametrize( "tenant_type, tenants_by_type, lookup_response, expected", [ pytest.param( TenantType.TENANT_TYPE_ACCOUNT, {TenantType.TENANT_TYPE_ACCOUNT: [], TenantType.TENANT_TYPE_SUBACCOUNT: []}, {}, {}, id="If proxy has no _tenants_by_type, return empty dict", ), pytest.param( TenantType.TENANT_TYPE_ACCOUNT, { TenantType.TENANT_TYPE_ACCOUNT: [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], TenantType.TENANT_TYPE_SUBACCOUNT: [], }, { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_type, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), ) }, { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_type, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), ) }, id="""Proxy has one _tenants_by_type. Callback returns the IdExchangeTenantHierarchy for that tenant. _do_tenant_exchange should return that tenant in a dict.""", ), pytest.param( TenantType.TENANT_TYPE_SUBACCOUNT, { TenantType.TENANT_TYPE_ACCOUNT: [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], TenantType.TENANT_TYPE_SUBACCOUNT: [], }, {}, {}, id="""Proxy has one account in _tenants_by_type but caller is asking or subaccount tenants. Empty dict returned.""", ), pytest.param( TenantType.TENANT_TYPE_SUBACCOUNT, { TenantType.TENANT_TYPE_ACCOUNT: [ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1], TenantType.TENANT_TYPE_SUBACCOUNT: [ ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT ], }, { str( ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id ): IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_type, tenant_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", account=Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", ), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), ) }, { str( ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id ): IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_type, tenant_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", account=Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", ), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), ) }, id="""Proxy has accounts and subacccount, but caller is asking for subaccount tenants. Returned dict includes only subaccount tenant hierarchies.""", ), ], ) @patch.object(IdToUuidExchangeTenantProxy, "_save_tenant_exchange_to_cache") @patch.object(IdToUuidExchangeTenantProxy, "_get_tenant_exchange_from_cache") async def test__do_tenant_exchange( mock__get_tenant_exchange_from_cache: AsyncMock, mock__save_tenant_exchange_to_cache: AsyncMock, mock_exchange_proxy: IdToUuidExchangeTenantProxy, tenant_type: TenantType, tenants_by_type: Dict[TenantType, List[IdToUuidExchangeTenant]], lookup_response: Dict[str, IdExchangeTenantHierarchy], expected: Dict[str, IdExchangeTenantHierarchy], ) -> None: """Test _do_tenant_exchange.""" mock__get_tenant_exchange_from_cache.return_value = {} mock__save_tenant_exchange_to_cache.return_value = {} mock_exchange_proxy._tenants_by_type = tenants_by_type async def mock_lookup_cb( tenant_ids: List[int | str], ) -> Dict[str, IdExchangeTenantHierarchy]: return lookup_response actual = await mock_exchange_proxy._do_tenant_exchange( tenant_type, mock_lookup_cb, ) assert actual == expected # Only get from cache if there were tenants to get if tenants_by_type[tenant_type]: mock__get_tenant_exchange_from_cache.assert_awaited_once_with( tenants_by_type[tenant_type] ) else: mock__get_tenant_exchange_from_cache.assert_not_awaited() # Only save to cache if the callback lookup returned items if lookup_response: mock__save_tenant_exchange_to_cache.assert_awaited_once_with( entries=list(lookup_response.values()), tenant_type=tenant_type, ) else: mock__save_tenant_exchange_to_cache.assert_not_awaited() @pytest.mark.parametrize( "get_from_cache_result, expected_lookup_cb_ids, lookup_cb_result, expected_save_to_cache, expected_tenant_exchange_result", # noqa: E501 [ pytest.param( { str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id, ], { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_2, str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_3, }, [ ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_2, ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_3, ], { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_2, str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_3, }, id="""3 cache miss, lookup 3 ids, lookup response has 3 non-null items, response should include the 3 items""", ), pytest.param( { str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id, ], { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, [ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1], { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, id="""3 cache miss, lookup 3 ids, lookup response as 1 non-null item, response should include 1 item""", ), pytest.param( { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id, ], { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_2, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, [ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_2], { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_2, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, id="""1 cache hit, lookup 2 ids, lookup response has 1 non-null response, response should include 2 items""", ), pytest.param( { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_2, str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_3, }, [], {}, [], { str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_1, str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_2, str( ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id ): ID_TO_UUID_EXCHANGE_TENANT_HIERARCHY_ACCOUNT_3, }, id="""3 cache hits, lookup not called, response should include 3 items""", ), pytest.param( { str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id, ], { str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, [], { str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id): None, str(ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_3.tenant_id): None, }, id="""3 cache miss, lookup 3 ids, lookup response has 0 non-null responses, response should include 0 items""", ), ], ) async def test__do_tenant_exchange_cache_behavior( get_from_cache_result: Dict[Union[int, str], Optional[IdExchangeTenantHierarchy]], expected_lookup_cb_ids: List[Union[int, str]], lookup_cb_result: Dict[str, Optional[IdExchangeTenantHierarchy]], expected_save_to_cache: List[IdExchangeTenantHierarchy], expected_tenant_exchange_result: Dict[ Union[int, str], Optional[IdExchangeTenantHierarchy] ], mock_exchange_proxy: MagicMock, ) -> None: """Test _do_tenant_exchange on Account tenant type with various cache hit and miss scenarios.""" mock_exchange_proxy._get_tenant_exchange_from_cache = AsyncMock( return_value=get_from_cache_result ) mock_exchange_proxy._save_tenant_exchange_to_cache = AsyncMock(return_value={}) mock_lookup_cb = AsyncMock(return_value=lookup_cb_result) actual = await mock_exchange_proxy._do_tenant_exchange( TenantType.TENANT_TYPE_ACCOUNT, mock_lookup_cb, ) assert actual == expected_tenant_exchange_result mock_exchange_proxy._get_tenant_exchange_from_cache.assert_awaited_once_with( mock_exchange_proxy._tenants_by_type[TenantType.TENANT_TYPE_ACCOUNT] ) if expected_lookup_cb_ids: mock_lookup_cb.assert_awaited_with(expected_lookup_cb_ids) else: mock_lookup_cb.assert_not_called() if expected_save_to_cache: mock_exchange_proxy._save_tenant_exchange_to_cache.assert_awaited_once_with( entries=expected_save_to_cache, tenant_type=TenantType.TENANT_TYPE_ACCOUNT, ) else: mock_exchange_proxy._save_tenant_exchange_to_cache.assert_not_awaited() @pytest.mark.parametrize( "proxy_tenants_by_type," "exchange_accounts_call_args," "exchange_subaccounts_call_args," "exchange_company_brand_call_args," "exchange_accounts_results," "exchange_subaccounts_results," "exchange_company_brands_results," "expected", [ pytest.param( { TenantType.TENANT_TYPE_ACCOUNT: [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ], TenantType.TENANT_TYPE_SUBACCOUNT: [ ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT_SAME_ID_AS_ACCOUNT_1, ], TenantType.TENANT_TYPE_COMPANY_BRAND: [ ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ], }, [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id, ], [ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT_SAME_ID_AS_ACCOUNT_1.tenant_id], [ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_id], { "11117": IdExchangeTenantHierarchy( tenant_id=11117, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), "22227": IdExchangeTenantHierarchy( tenant_id=22227, tenant_uuid="e822e379-5fca-401f-94e9-bfd123762248", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), }, { "11117": IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT_SAME_ID_AS_ACCOUNT_1.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT_SAME_ID_AS_ACCOUNT_1.tenant_type, tenant_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", account=Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", ), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ) }, { "11117": IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_type, tenant_uuid="79aa9c8a-6f20-4643-ab22-3b2ace15df41", company_brand=None, parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ) }, { TenantType.TENANT_TYPE_ACCOUNT: { "11117": IdExchangeTenantHierarchy( tenant_id=11117, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), "22227": IdExchangeTenantHierarchy( tenant_id=22227, tenant_uuid="e822e379-5fca-401f-94e9-bfd123762248", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), }, TenantType.TENANT_TYPE_SUBACCOUNT: { "11117": IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT_SAME_ID_AS_ACCOUNT_1.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT_SAME_ID_AS_ACCOUNT_1.tenant_type, tenant_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", account=Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", ), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), }, TenantType.TENANT_TYPE_COMPANY_BRAND: { "11117": IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_type, tenant_uuid="79aa9c8a-6f20-4643-ab22-3b2ace15df41", company_brand=None, parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_PARENT_COMPANY, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ) }, }, id="An Account, Subaccount, Company Brand with the same tenant_id do not collide", # noqa: E501 ), pytest.param( { TenantType.TENANT_TYPE_ACCOUNT: [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ], TenantType.TENANT_TYPE_SUBACCOUNT: [ ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT ], TenantType.TENANT_TYPE_COMPANY_BRAND: [ ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND, ], }, [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id, ], [ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id], [ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_id], { "11117": IdExchangeTenantHierarchy( tenant_id=11117, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), "22227": IdExchangeTenantHierarchy( tenant_id=22227, tenant_uuid="e822e379-5fca-401f-94e9-bfd123762248", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), }, { "668894": IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_type, tenant_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", account=Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", ), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ) }, { "668894": IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_type, tenant_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", company_brand=None, parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), }, { TenantType.TENANT_TYPE_ACCOUNT: { "11117": IdExchangeTenantHierarchy( tenant_id=11117, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), "22227": IdExchangeTenantHierarchy( tenant_id=22227, tenant_uuid="e822e379-5fca-401f-94e9-bfd123762248", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), }, TenantType.TENANT_TYPE_SUBACCOUNT: { "668894": IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_SUBACCOUNT.tenant_type, tenant_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", account=Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", ), company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="b5b487b6-d8f8-4ebf-8de5-7cc9e7797bda", ), parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), }, TenantType.TENANT_TYPE_COMPANY_BRAND: { "668894": IdExchangeTenantHierarchy( tenant_id=ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_id, tenant_type=ID_TO_UUID_EXCHANGE_TENANT_COMPANY_BRAND.tenant_type, tenant_uuid="71588c79-d788-4b66-bf6a-2def82e0c4ed", company_brand=None, parent_company=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid="0544a73d-d16d-4b11-a813-5e5b10175121", ), ), }, }, id="Tenant exchanges for all supported tenant types are returned", ), pytest.param( { TenantType.TENANT_TYPE_ACCOUNT: [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2, ], TenantType.TENANT_TYPE_SUBACCOUNT: [], TenantType.TENANT_TYPE_COMPANY_BRAND: [], }, [ ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_1.tenant_id, ID_TO_UUID_EXCHANGE_TENANT_ACCOUNT_2.tenant_id, ], None, None, { "11117": IdExchangeTenantHierarchy( tenant_id=11117, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), ), "22227": IdExchangeTenantHierarchy( tenant_id=22227, tenant_uuid="e822e379-5fca-401f-94e9-bfd123762248", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), ), }, {}, {}, { TenantType.TENANT_TYPE_ACCOUNT: { "11117": IdExchangeTenantHierarchy( tenant_id=11117, tenant_uuid="61577c79-d788-4b88-bf6a-2def82e0b4ed", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), ), "22227": IdExchangeTenantHierarchy( tenant_id=22227, tenant_uuid="e822e379-5fca-401f-94e9-bfd123762248", tenant_type=TenantType.TENANT_TYPE_ACCOUNT, company_brand=Tenant( tenant_uuid="26ae66c4-cf27-422b-842a-b2939e12ffba", tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, ), ), }, TenantType.TENANT_TYPE_SUBACCOUNT: {}, TenantType.TENANT_TYPE_COMPANY_BRAND: {}, }, id="If no subaccounts or cb are on proxy, an empty dict is returned", ), ], ) async def test_gather_tenant_exchange( proxy_tenants_by_type: Dict[TenantType, List[IdToUuidExchangeTenant]], exchange_accounts_call_args: Dict[str, IdExchangeTenantHierarchy], exchange_subaccounts_call_args: Dict[str, IdExchangeTenantHierarchy], exchange_company_brand_call_args: Dict[str, IdExchangeTenantHierarchy], exchange_accounts_results: Dict[str, IdExchangeTenantHierarchy], exchange_subaccounts_results: Dict[str, IdExchangeTenantHierarchy], exchange_company_brands_results: Dict[str, IdExchangeTenantHierarchy], expected: Dict[str, IdExchangeTenantHierarchy], mock_exchange_proxy: MagicMock, ) -> None: """Test gather_tenant_exchange.""" mock_exchange_proxy._tenants_by_type = proxy_tenants_by_type mock_exchange_proxy._gathered_tenant_exchanges = {} mock_exchange_proxy._save_tenant_exchange_to_cache = AsyncMock(return_value={}) mock_exchange_proxy._exchange_accounts_from_ows_account = AsyncMock( return_value=exchange_accounts_results ) mock_exchange_proxy._exchange_subaccounts_from_ows_account = AsyncMock( return_value=exchange_subaccounts_results ) mock_exchange_proxy._exchange_company_brands_from_ows_account = AsyncMock( return_value=exchange_company_brands_results ) actual = await mock_exchange_proxy.gather_tenant_exchange() if exchange_accounts_call_args: mock_exchange_proxy._exchange_accounts_from_ows_account.assert_called_with( exchange_accounts_call_args ) else: mock_exchange_proxy._exchange_accounts_from_ows_account.assert_not_called() if exchange_subaccounts_call_args: mock_exchange_proxy._exchange_subaccounts_from_ows_account.assert_called_with( exchange_subaccounts_call_args, ) else: mock_exchange_proxy._exchange_subaccounts_from_ows_account.assert_not_called() if exchange_company_brand_call_args: mock_exchange_proxy._exchange_company_brands_from_ows_account.assert_called_with( exchange_company_brand_call_args, ) assert actual == expected