import uuid from typing import Any from unittest.mock import AsyncMock, MagicMock, call, patch import pytest from pydantic import BaseModel from pdp.connectors.redis_client import RedisConnector from pdp.fastapi.schemas.cache import ( IdentityRolesResponseCacheObject, LookupCacheObject, PrincipalPdpCacheObject, ) from pdp.fastapi.schemas.identity import Role, TenantRoles from pdp.logic.cache import ( _get_identity_wildcard, bust_identity_caches, get_object_from_cache, save_cache_object, ) @pytest.fixture def mock_redis_connector() -> RedisConnector: """Mock RedisConnector.""" return AsyncMock(spec=RedisConnector) async def test_bust_identity_caches() -> None: """Test bust_identity_caches.""" identity_uuid1 = uuid.UUID("20212b39-90b3-4486-9ce7-a1323529bc4d") # uuid4 identity_uuid2 = uuid.UUID("1dd9bd3a-e260-11ef-b744-3e17271fba71") # uuid1 mock_redis_connector = AsyncMock(spec=RedisConnector) mock_redis_connector.delete_all_matching_pattern.return_value = [] await bust_identity_caches( [identity_uuid1, identity_uuid2], mock_redis_connector, ) mock_redis_connector.delete_all_matching_pattern.assert_has_calls( [ call("*identity_uuid#20212b39-90b3-4486-9ce7-a1323529bc4d*"), call("*identity_uuid#1dd9bd3a-e260-11ef-b744-3e17271fba71*"), ] ) async def test_bust_identity_caches_empty_list() -> None: """Test bust_identity_caches.""" mock_redis_connector = AsyncMock(spec=RedisConnector) mock_redis_connector.delete_all_matching_pattern.return_value = [] await bust_identity_caches( [], mock_redis_connector, ) mock_redis_connector.delete_all_matching_pattern.assert_not_called() def test__get_identity_wildcard() -> None: """Test _get_identity_wildcard.""" identity_uuid = uuid.UUID("20212b39-90b3-4486-9ce7-a1323529bc4d") actual = _get_identity_wildcard(identity_uuid) assert actual == "*identity_uuid#20212b39-90b3-4486-9ce7-a1323529bc4d*" @pytest.mark.parametrize( "cache_object_is_valid_to_save, expect_set", [ pytest.param( True, True, id="The object is valid to save, so set it on redis", ), pytest.param( False, False, id="The object is valid to save, so set it on redis", ), ], ) @patch("pdp.logic.cache.PydanticSchemaSerializer") async def test_save_cache_object( mock_serializer: MagicMock, cache_object_is_valid_to_save: bool, expect_set: bool, mock_redis_connector: AsyncMock, ) -> None: """Test save_cache_object.""" mock_cache_object = MagicMock(spec=LookupCacheObject) mock_cache_object.is_valid_to_save.return_value = cache_object_is_valid_to_save mock_cache_object.to_cache_key.return_value = "i am a unique cache key" mock_cache_object.get_ttl.return_value = 45 mock_redis_connector.set.return_value = expect_set actual = await save_cache_object( cache_object=mock_cache_object, cache_model_type=BaseModel, redis_connector=mock_redis_connector, ) mock_cache_object.is_valid_to_save.assert_called_once() if expect_set: mock_cache_object.to_cache_key.assert_called_once() mock_cache_object.get_ttl.assert_called_once() mock_redis_connector.set.assert_awaited_once_with( "i am a unique cache key", item=mock_cache_object, serializer=mock_serializer(LookupCacheObject), ttl=45, ) else: mock_cache_object.to_cache_key.assert_not_called() mock_cache_object.get_ttl.assert_not_called() mock_redis_connector.set.assert_not_awaited() assert actual == expect_set @pytest.mark.parametrize( "mock_get_return_value,expected", [ pytest.param(None, None, id="Return None if there is nothing in the cache"), pytest.param({}, None, id="Return None because an empty dict does not have"), pytest.param( PrincipalPdpCacheObject( identity_uuid=uuid.uuid4(), ), None, id="Return None when the cache attribute name is not present", ), pytest.param( PrincipalPdpCacheObject( identity_uuid=uuid.uuid4(), tenant_roles={ uuid.UUID("3ead877d-d0ce-4224-a16d-9dcf042af0d7"): TenantRoles( tenant_type="account", tenant_uuid=uuid.UUID("3ead877d-d0ce-4224-a16d-9dcf042af0d7"), roles=[Role(role="contract_viewer")], ) }, ), { uuid.UUID("3ead877d-d0ce-4224-a16d-9dcf042af0d7"): TenantRoles( tenant_type="account", tenant_uuid=uuid.UUID("3ead877d-d0ce-4224-a16d-9dcf042af0d7"), roles=[Role(role="contract_viewer")], ) }, id="Return the value for the cache attribute name", ), pytest.param( IdentityRolesResponseCacheObject( principal_identity_uuid=uuid.uuid4(), listed_identity_uuid=uuid.uuid4(), ), None, id="Return None - tenant_roles isn't even an attribute", ), ], ) async def test_get_object_from_cache( mock_get_return_value: Any, expected: Any, mock_redis_connector: MagicMock, ) -> None: """Test get_object_from_cache.""" key = "some key" cache_attribute_name = "tenant_roles" mock_serializer = MagicMock() mock_redis_connector.get.return_value = mock_get_return_value actual: Any = await get_object_from_cache( key=key, redis_connector=mock_redis_connector, serializer=mock_serializer, cache_attribute_name=cache_attribute_name, ) mock_redis_connector.get.assert_awaited_once_with( key=key, serializer=mock_serializer, ) assert actual == expected