import json from unittest.mock import AsyncMock, MagicMock, patch import pytest import typer from pdp.cli.commands.cerbos import seed_policy_metadata_cache from pdp.connectors.cerbos_policy_parser import ( PolicyMetadataDatabase, ResourcePolicyMetadata, ) from pdp.connectors.redis_client import JSONSerializer from pdp.constants.constants import CACHE_ENTRY_CERBOS_POLICY_METADATA @patch("pdp.cli.commands.cerbos.CerbosPolicyParser") @patch("pdp.cli.commands.cerbos.RedisConnector") def test_seed_policy_metadata_cache( mock_redis_connector: MagicMock, mock_parser: MagicMock, ) -> None: """The command builds the database and writes it to Redis.""" database = PolicyMetadataDatabase() database.add_policy( ResourcePolicyMetadata( resource_type="account", requires_account_feature_controls=True ) ) database.add_policy( ResourcePolicyMetadata( resource_type="subaccount", requires_account_feature_controls=False ) ) mock_parser.return_value.build_database.return_value = database mock_set = AsyncMock(return_value=True) mock_redis_connector.return_value.set = mock_set seed_policy_metadata_cache(policies_dir="cerbos/policies", verbose=False) mock_parser.assert_called_once_with(policies_dir="cerbos/policies") _, kwargs = mock_set.call_args assert kwargs["key"] == CACHE_ENTRY_CERBOS_POLICY_METADATA assert kwargs["ttl"] is None assert kwargs["item"] == json.loads(database.to_json()) assert isinstance(kwargs["serializer"], JSONSerializer) @patch("pdp.cli.commands.cerbos.typer.echo") @patch("pdp.cli.commands.cerbos.CerbosPolicyParser") @patch("pdp.cli.commands.cerbos.RedisConnector") def test_seed_policy_metadata_cache_verbose( mock_redis_connector: MagicMock, mock_parser: MagicMock, mock_echo: MagicMock, ) -> None: """With --verbose the stored metadata is pretty-printed.""" database = PolicyMetadataDatabase() database.add_policy( ResourcePolicyMetadata( resource_type="account", requires_account_feature_controls=True ) ) mock_parser.return_value.build_database.return_value = database mock_redis_connector.return_value.set = AsyncMock(return_value=True) seed_policy_metadata_cache(policies_dir="cerbos/policies", verbose=True) pretty = json.dumps(json.loads(database.to_json()), indent=2, sort_keys=True) mock_echo.assert_called_once_with( f"Inserting cerbos policy metadata into " f"'{CACHE_ENTRY_CERBOS_POLICY_METADATA}': {pretty}" ) @patch("pdp.cli.commands.cerbos.config.CACHE_USE_REDIS", False) @patch("pdp.cli.commands.cerbos.typer.secho") @patch("pdp.cli.commands.cerbos.CerbosPolicyParser") @patch("pdp.cli.commands.cerbos.RedisConnector") def test_seed_policy_metadata_cache_warns_when_cache_disabled( mock_redis_connector: MagicMock, mock_parser: MagicMock, mock_secho: MagicMock, ) -> None: """A warning is printed when CACHE_USE_REDIS is false (no-op seed).""" database = PolicyMetadataDatabase() database.add_policy( ResourcePolicyMetadata( resource_type="account", requires_account_feature_controls=True ) ) mock_parser.return_value.build_database.return_value = database mock_redis_connector.return_value.set = AsyncMock(return_value=True) seed_policy_metadata_cache(policies_dir="cerbos/policies", verbose=False) warning = mock_secho.call_args_list[0] assert "CACHE_USE_REDIS is false" in warning.args[0] assert warning.kwargs["fg"] == "yellow" @patch("pdp.cli.commands.cerbos.CerbosPolicyParser") @patch("pdp.cli.commands.cerbos.RedisConnector") def test_seed_policy_metadata_cache_empty_db_exits( mock_redis_connector: MagicMock, mock_parser: MagicMock, ) -> None: """An empty policy database exits non-zero without writing to Redis.""" mock_parser.return_value.build_database.return_value = PolicyMetadataDatabase() mock_set = AsyncMock() mock_redis_connector.return_value.set = mock_set with pytest.raises(typer.Exit) as exc_info: seed_policy_metadata_cache(policies_dir="cerbos/policies", verbose=False) assert exc_info.value.exit_code == 1 mock_set.assert_not_called() @patch("pdp.cli.commands.cerbos.CerbosPolicyParser") @patch("pdp.cli.commands.cerbos.RedisConnector") def test_seed_policy_metadata_cache_write_failure_exits( mock_redis_connector: MagicMock, mock_parser: MagicMock, ) -> None: """A failed Redis write (set() returns False) exits non-zero.""" database = PolicyMetadataDatabase() database.add_policy( ResourcePolicyMetadata( resource_type="account", requires_account_feature_controls=True ) ) mock_parser.return_value.build_database.return_value = database mock_redis_connector.return_value.set = AsyncMock(return_value=False) with pytest.raises(typer.Exit) as exc_info: seed_policy_metadata_cache(policies_dir="cerbos/policies", verbose=False) assert exc_info.value.exit_code == 1