"""Tests datasource lifespan methods.""" import re from unittest.mock import AsyncMock, MagicMock, patch import pytest from cerbos.sdk.client import AsyncCerbosClient from fastapi import FastAPI from owsclient import AsyncOwsClient from pdp.connectors.cerbos_policy_parser import PolicyMetadataDatabase from pdp.connectors.dynamo import DynamoDbConnector from pdp.connectors.features import SplitioClient from pdp.connectors.ows_account import OwsAccountClient from pdp.connectors.ows_participant import OwsParticipantClient from pdp.connectors.ows_permissions import OwsPermissionsClient from pdp.connectors.redis_client import RedisConnector, RedisConnectorFactoryError from pdp.constants.constants import CACHE_ENTRY_CERBOS_POLICY_METADATA from pdp.constants.dynamo import LIFESPAN_TEST_IDENTITY_UUID from pdp.fastapi.datasources import ( ASYNC_CERBOS_CLIENT_KEY, ASYNC_OWS_CLIENT_KEY, IDENTITY_DDB_CONNECTOR_KEY, OWS_ACCOUNT_CLIENT_KEY, OWS_PARTICIPANT_CLIENT_KEY, OWS_PERMISSIONS_CLIENT_KEY, REDIS_CONNECTOR_KEY, SPLITIO_CLIENT_KEY, PdpDatasourcesLifespanError, datasources_lifespan, get_async_ows_client, get_boto_connector, get_ows_account_client, get_ows_participant_client, get_ows_permissions_client, get_redis_connector, get_splitio_client, ) from pdp.utils.cerbos_policy_metadata import ( CerbosPolicyDBLookupError, CerbosPolicyDBParseError, get_cerbos_policy_metadata, ) @patch("pdp.fastapi.datasources.get_cerbos_client") @patch("pdp.fastapi.datasources.redis_connector_factory") @patch("pdp.fastapi.datasources.OwsAccountClient") @patch("pdp.fastapi.datasources.OwsParticipantClient") @patch("pdp.fastapi.datasources.OwsPermissionsClient") @patch("pdp.fastapi.datasources.AsyncOwsClient") @patch("pdp.fastapi.datasources.splitio_client_factory") @patch("pdp.fastapi.datasources.DynamoDbConnector") async def test_datasources_lifespan( mock_identity_ddb_connector: MagicMock, mock_splitio_client_factory: MagicMock, mock_async_ows_client: AsyncMock, mock_ows_permissions_client: MagicMock, mock_ows_participant_client: AsyncMock, mock_ows_account_client: MagicMock, mock_redis_connector_factory: AsyncMock, mock_get_cerbos_connector: MagicMock, app: FastAPI, ) -> None: """Tests datasource lifespan methods.""" mock_ddb_instance = mock_identity_ddb_connector.return_value mock_split_client = mock_splitio_client_factory.return_value mock_async_ows_client_instance = AsyncMock(spec=AsyncOwsClient) mock_redis_connector_instance = AsyncMock(spec=RedisConnector) mock_async_cerbos_client_instance = AsyncMock(spec=AsyncCerbosClient) mock_redis_connector_factory.return_value = mock_redis_connector_instance mock_async_ows_client.return_value = mock_async_ows_client_instance mock_get_cerbos_connector.return_value = mock_async_cerbos_client_instance mock_ows_permissions_client_instance = mock_ows_permissions_client.return_value mock_ows_account_client_instance = mock_ows_account_client.return_value mock_ows_participant_client_instance = mock_ows_participant_client.return_value async with datasources_lifespan(app=app) as datasources: assert IDENTITY_DDB_CONNECTOR_KEY in datasources assert datasources[IDENTITY_DDB_CONNECTOR_KEY] == mock_ddb_instance assert datasources[SPLITIO_CLIENT_KEY] == mock_split_client assert datasources[ASYNC_OWS_CLIENT_KEY] == mock_async_ows_client_instance assert ( datasources[OWS_PERMISSIONS_CLIENT_KEY] == mock_ows_permissions_client_instance ) assert datasources[OWS_ACCOUNT_CLIENT_KEY] == mock_ows_account_client_instance assert datasources[REDIS_CONNECTOR_KEY] == mock_redis_connector_instance assert ( datasources[OWS_PARTICIPANT_CLIENT_KEY] == mock_ows_participant_client_instance ) assert datasources[ASYNC_CERBOS_CLIENT_KEY] == mock_async_cerbos_client_instance mock_ddb_instance.query_by_hash_key.assert_called_with( hash_key=LIFESPAN_TEST_IDENTITY_UUID ) mock_get_cerbos_connector.assert_called_once() mock_ddb_instance.client.close.assert_called_once() mock_split_client.destroy.assert_called_once() mock_async_ows_client_instance.close.assert_called_once() mock_redis_connector_instance.client.aclose.assert_called_once() mock_async_cerbos_client_instance.close.assert_called_once() @patch("pdp.fastapi.datasources.redis_connector_factory") @patch("pdp.fastapi.datasources.OwsAccountClient") @patch("pdp.fastapi.datasources.OwsPermissionsClient") @patch("pdp.fastapi.datasources.AsyncOwsClient") @patch("pdp.fastapi.datasources.splitio_client_factory") @patch("pdp.fastapi.datasources.DynamoDbConnector") async def test_datasources_lifespan_redis_failure( mock_identity_ddb_connector: MagicMock, mock_splitio_client_factory: MagicMock, mock_async_ows_client: AsyncMock, mock_ows_permissions_client: MagicMock, mock_ows_account_client: MagicMock, mock_redis_connector_factory: AsyncMock, app: FastAPI, ) -> None: """Tests RedisConnector fails to initialize.""" mock_redis_connector_factory.side_effect = RedisConnectorFactoryError( "fakeredis fallback failed", ) with pytest.raises( PdpDatasourcesLifespanError, match=re.escape("[lifespan] Failed to initialize RedisConnector"), ): async with datasources_lifespan(app=app): pass @pytest.fixture def mock_dynamodb_connector() -> DynamoDbConnector: """Return a mocked dynamodb connector.""" return MagicMock(spec=DynamoDbConnector) def test_get_boto_connector(mock_dynamodb_connector: DynamoDbConnector) -> None: """Verify method returns the dictionary item at key:IDENTITY_DDB_CONNECTOR_KEY.""" with patch.dict( "pdp.fastapi.datasources.DATA_SOURCES", {IDENTITY_DDB_CONNECTOR_KEY: mock_dynamodb_connector}, ): ret = get_boto_connector() assert ret == mock_dynamodb_connector @pytest.fixture def mock_splitio_client() -> SplitioClient: """Return a mocked splitio client.""" return MagicMock(spec=SplitioClient) def test_get_splitio_client(mock_splitio_client: SplitioClient) -> None: """Verify method returns the dictionary item at key:SPLITIO_CLIENT_KEY.""" with patch.dict( "pdp.fastapi.datasources.DATA_SOURCES", {SPLITIO_CLIENT_KEY: mock_splitio_client}, ): ret = get_splitio_client() assert ret == mock_splitio_client def test_get_async_ows_client(mock_async_ows_client: AsyncOwsClient) -> None: """Verify method returns the dictionary item at key:ASYNC_OWS_CLIENT_KEY.""" with patch.dict( "pdp.fastapi.datasources.DATA_SOURCES", {ASYNC_OWS_CLIENT_KEY: mock_async_ows_client}, ): ret = get_async_ows_client() assert ret == mock_async_ows_client def test_get_ows_permissions_client( mock_ows_permissions_client: OwsPermissionsClient, ) -> None: """Verify method returns the dictionary item at key:OWS_PERMISSIONS_CLIENT_KEY.""" with patch.dict( "pdp.fastapi.datasources.DATA_SOURCES", {OWS_PERMISSIONS_CLIENT_KEY: mock_ows_permissions_client}, ): ret = get_ows_permissions_client() assert ret == mock_ows_permissions_client def test_get_ows_participant_client( mock_ows_participant_client: OwsParticipantClient, ) -> None: """Verify method returns the dictionary item at key:OWS_PARTICIPANT_CLIENT_KEY.""" with patch.dict( "pdp.fastapi.datasources.DATA_SOURCES", {OWS_PARTICIPANT_CLIENT_KEY: mock_ows_participant_client}, ): ret = get_ows_participant_client() assert ret == mock_ows_participant_client def test_get_ows_account_client(mock_ows_account_client: OwsAccountClient) -> None: """Verify method returns the dictionary item at key:OWS_ACCOUNT_CLIENT_KEY.""" with patch.dict( "pdp.fastapi.datasources.DATA_SOURCES", {OWS_ACCOUNT_CLIENT_KEY: mock_ows_account_client}, ): ret = get_ows_account_client() assert ret == mock_ows_account_client def test_get_redis_connector(mock_redis_connector: RedisConnector) -> None: """Verify method returns the dictionary item at key:REDIS_CONNECTOR_KEY.""" with patch.dict( "pdp.fastapi.datasources.DATA_SOURCES", {REDIS_CONNECTOR_KEY: mock_redis_connector}, ): ret = get_redis_connector() assert ret == mock_redis_connector async def test_get_cerbos_policy_metadata_found( mock_redis_connector: MagicMock, ) -> None: """Returns a PolicyMetadataDatabase when the Redis key exists.""" db = PolicyMetadataDatabase() raw_json = db.to_json() mock_redis_connector.get = AsyncMock(return_value=raw_json) result = await get_cerbos_policy_metadata(mock_redis_connector) mock_redis_connector.get.assert_called_once() call_kwargs = mock_redis_connector.get.call_args assert call_kwargs.kwargs["key"] == CACHE_ENTRY_CERBOS_POLICY_METADATA assert isinstance(result, PolicyMetadataDatabase) async def test_get_cerbos_policy_metadata_missing( mock_redis_connector: MagicMock, ) -> None: """Raises CerbosPolicyDBLookupError when the Redis key is absent.""" mock_redis_connector.get = AsyncMock(return_value=None) with pytest.raises( CerbosPolicyDBLookupError, match=CACHE_ENTRY_CERBOS_POLICY_METADATA ): await get_cerbos_policy_metadata(mock_redis_connector) async def test_get_cerbos_policy_metadata_parse_error( mock_redis_connector: MagicMock, ) -> None: """Raises CerbosPolicyDBParseError when the cached value cannot be deserialized.""" mock_redis_connector.get = AsyncMock(return_value="not valid json{{{") with pytest.raises( CerbosPolicyDBParseError, match=CACHE_ENTRY_CERBOS_POLICY_METADATA ): await get_cerbos_policy_metadata(mock_redis_connector)