"""Test Infra Logic.""" import uuid from typing import Any, Dict, List, Optional from unittest.mock import AsyncMock, MagicMock, patch import pytest from pdp.connectors.redis_client import RedisConnector from pdp.constants.constants import CacheEntryType, TenantType from pdp.fastapi.schemas.infra import ( BludgeonCacheRequest, BludgeonCacheResponse, BustCacheRequest, BustCacheResponse, ListCacheRequest, ListCacheResponse, ) from pdp.fastapi.schemas.tenant import ( IdExchangeTenantHierarchy, Tenant, UuidToIdExchangeTenant, ) from pdp.logic.infra import ( WILDCARD, cache_bludgeon, cache_bust, cache_list, gather_id_to_uuid_exchange, gather_uuid_to_id_exchange, ) @pytest.mark.parametrize( "description, list_cache_request, redis_return_value, expected_response", [ ( "cache_list should return a ListCacheResponse with key/value pairs", ListCacheRequest( **{ "keys": [ "not-a-tenant", "tenant_hierarchy_fff741c2-6def-4493-bfdf-c2bcb1128e02", ] } ), [ {}, { "company_brand": { "tenant_type": "account", "tenant_uuid": "d25a4cd1-e820-45f2-be5c-56edcfeb8298", } }, ], ListCacheResponse( items=[ {"key": "not-a-tenant", "value": {}}, { "key": "tenant_hierarchy_fff741c2-6def-4493-bfdf-c2bcb1128e02", "value": { "company_brand": { "tenant_type": "account", "tenant_uuid": "d25a4cd1-e820-45f2-be5c-56edcfeb8298", } }, }, ] ), ), ], ) @patch("pdp.logic.infra.RedisConnector") async def test_cache_list( mock_redis_connector: AsyncMock, description: str, list_cache_request: ListCacheRequest, redis_return_value: Dict[str, Any], expected_response: ListCacheResponse, ) -> None: """Test cache_list.""" mock_redis_connector = AsyncMock(spec=RedisConnector) mock_redis_connector.mget.return_value = redis_return_value response = await cache_list( list_cache_request=list_cache_request, redis_connector=mock_redis_connector, ) assert response == expected_response, description @pytest.mark.parametrize( "description, bust_cache_request, redis_return_value, expected_response", [ ( "cache_bust should return a BustCacheResponse with key/value pairs", BustCacheRequest( **{ "keys": [ "not-a-tenant", "tenant_hierarchy_fff741c2-6def-4493-bfdf-c2bcb1128e02", ] } ), [ None, { "company_brand": { "tenant_type": "account", "tenant_uuid": "d25a4cd1-e820-45f2-be5c-56edcfeb8298", } }, ], BustCacheResponse( items=[ {"key": "not-a-tenant", "value": {}, "deleted": False}, { "key": "tenant_hierarchy_fff741c2-6def-4493-bfdf-c2bcb1128e02", "value": { "company_brand": { "tenant_type": "account", "tenant_uuid": "d25a4cd1-e820-45f2-be5c-56edcfeb8298", } }, "deleted": True, }, ] ), ), ], ) @patch("pdp.logic.infra.RedisConnector") async def test_cache_bust( mock_redis_connector: AsyncMock, description: str, bust_cache_request: BustCacheRequest, redis_return_value: Optional[Dict[str, Any]], expected_response: BustCacheResponse, ) -> None: """Test cache_list.""" mock_redis_connector = AsyncMock(spec=RedisConnector) mock_redis_connector.getdel.return_value = redis_return_value response = await cache_bust( bust_cache_request=bust_cache_request, redis_connector=mock_redis_connector, ) assert response == expected_response, description @pytest.mark.parametrize( "description, bludgeon_cache_request, db_size_response, returned_cache_response, expected_response", # noqa: E501 [ ( "cache_bludgeon should 'delete' when delete = True in the request.", BludgeonCacheRequest( **{ "cache_entry_type": CacheEntryType.CACHE_ENTRY_TENANT_HIERARCHY.value, # noqa: E501 "delete": True, } ), 10, [b"tenant_hierarchy_fff741c2-6def-4493-bfdf-c2bcb1128e02"], BludgeonCacheResponse( **{ "total_rows_before_delete": 10, "total_rows_affected": 1, "performed_delete": True, } ), ), ( "cache_bludgeon should not 'delete' when delete = False in the request.", BludgeonCacheRequest( **{ "cache_entry_type": CacheEntryType.CACHE_ENTRY_TENANT_HIERARCHY.value, # noqa: E501 "delete": False, } ), 10, [b"tenant_hierarchy_fff741c2-6def-4493-bfdf-c2bcb1128e02"], BludgeonCacheResponse( **{ "total_rows_before_delete": 10, "total_rows_affected": 1, "performed_delete": False, } ), ), ( "cache_bludgeon total_rows_affected should be 0 if cache returns 0 entries.", # noqa: E501 BludgeonCacheRequest( **{ "cache_entry_type": CacheEntryType.CACHE_ENTRY_TENANT_HIERARCHY.value, # noqa: E501 "delete": True, } ), 10, [], BludgeonCacheResponse( **{ "total_rows_before_delete": 10, "total_rows_affected": 0, "performed_delete": True, } ), ), ], ) @patch("pdp.logic.infra.RedisConnector") async def test_cache_bludgeon( mock_redis_connector: MagicMock, description: str, bludgeon_cache_request: BludgeonCacheRequest, db_size_response: int, returned_cache_response: List[bytes], expected_response: BludgeonCacheResponse, ) -> None: """Test cache_bludgeon logic function.""" mock_redis_connector.dbsize = AsyncMock(return_value=db_size_response) mock_redis_connector.delete_all_matching_pattern = AsyncMock( return_value=returned_cache_response ) mock_redis_connector.list = AsyncMock(return_value=returned_cache_response) should_delete = bludgeon_cache_request.delete list_pattern = f"{bludgeon_cache_request.cache_entry_type.value}{WILDCARD}" response = await cache_bludgeon( bludgeon_cache_request=bludgeon_cache_request, redis_connector=mock_redis_connector, ) assert response == expected_response, description mock_redis_connector.dbsize.assert_called_once() if should_delete: # Delete mode. mock_redis_connector.delete_all_matching_pattern.assert_called_with( pattern=list_pattern ) mock_redis_connector.list.assert_not_called() else: # Dry-run mode: Should not delete, only list. mock_redis_connector.delete_all_matching_pattern.assert_not_called() mock_redis_connector.list.assert_called_with(pattern=list_pattern) @patch("pdp.logic.infra.IdToUuidExchangeTenantProxy") async def test_gather_id_to_uuid_exchange( mock_proxy: MagicMock, mock_redis_connector: MagicMock, mock_ows_account_client: MagicMock, ) -> None: """Test gather_id_to_uuid_exchange.""" gather_return = { TenantType.TENANT_TYPE_ACCOUNT: { "1": IdExchangeTenantHierarchy( subaccount=None, account=None, company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid=uuid.UUID("fff741c2-6def-4493-bfdf-c2bcb1128e02"), ), tenant_id=1, tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=uuid.UUID("fff741c2-6def-4493-bfdf-c2bcb1128e02"), ), } } mock_proxy.return_value.gather_tenant_exchange = AsyncMock( return_value=gather_return, ) result = await gather_id_to_uuid_exchange( [], mock_ows_account_client, mock_redis_connector, ) assert result == gather_return mock_proxy.assert_called_once_with( tenants=[], redis_connector=mock_redis_connector, ows_account_client=mock_ows_account_client, ) mock_proxy.return_value.gather_tenant_exchange.assert_called_once() @patch("pdp.logic.infra.UuidToIdExchangeTenantProxy") async def test_gather_uuid_to_id_exchange( mock_proxy: MagicMock, mock_redis_connector: MagicMock, mock_ows_account_client: MagicMock, ) -> None: """Test gather_uuid_to_id_exchange.""" gather_return = { uuid.UUID("49989de3-9c85-4dea-8b90-cb055c944b58"): IdExchangeTenantHierarchy( subaccount=None, account=None, company_brand=Tenant( tenant_type=TenantType.TENANT_TYPE_COMPANY_BRAND, tenant_uuid=uuid.UUID("18e62603-c157-433d-85ef-d1adcb1f2375"), ), tenant_id=1, tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=uuid.UUID("49989de3-9c85-4dea-8b90-cb055c944b58"), ), } mock_proxy.return_value.gather_tenant_exchange = AsyncMock( return_value=gather_return, ) tenants = [ UuidToIdExchangeTenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=uuid.UUID("49989de3-9c85-4dea-8b90-cb055c944b58"), ), UuidToIdExchangeTenant( tenant_type=TenantType.TENANT_TYPE_SUBACCOUNT, tenant_uuid=uuid.uuid4(), ), ] result = await gather_uuid_to_id_exchange( tenants, mock_ows_account_client, mock_redis_connector, ) assert result == gather_return mock_proxy.assert_called_once_with( tenants=tenants, redis_connector=mock_redis_connector, ows_account_client=mock_ows_account_client, ) mock_proxy.return_value.gather_tenant_exchange.assert_called_once()