"""Test utils for PP Authorization checks.""" from typing import Any from unittest.mock import MagicMock, patch import pytest from python_pdp_sdk.backends.authorization_backend import ( AuthorizationBackend, ResourceAction, ) from abacus_state.utils.authorization import ( AbacusStateResourceGetter, _build_resource_actions, authorize_dataloader_states, get_action, get_resource_type, ) @pytest.mark.parametrize( 'parent_table_name, expected', [ pytest.param('account_payee', 'account'), pytest.param('contract', 'account'), pytest.param('statement_period', 'statement_period'), ], ) def test_get_resource_type(parent_table_name: str, expected: str) -> None: """Test get_resource_type.""" actual = get_resource_type(parent_table_name) assert actual == expected def test_get_action() -> None: """Test get_action.""" actual = get_action('account_payee', 'view') assert actual == 'view_account_payee_abacus_state' @pytest.fixture() def abacus_state_resource_getter() -> AbacusStateResourceGetter: """Return reusable AbacusStateResourceGetter instance.""" return AbacusStateResourceGetter() @pytest.mark.parametrize( 'kwargs', [ pytest.param({}, id='empty kwargs is bad'), pytest.param( {'parent_table_name': 'account_payee'}, id='needs parent_table_id' ), pytest.param({'parent_table_id': 1}, id='needs parent_table_name'), ], ) def test_asrg_get_attributes_raises_assertions_for_missing_kwargs( abacus_state_resource_getter: AbacusStateResourceGetter, kwargs: dict[str, Any], ) -> None: """Test assertions on kwargs.""" with pytest.raises(AssertionError): abacus_state_resource_getter.get_attributes(**kwargs) @pytest.mark.parametrize( 'kwargs', [ pytest.param( { 'parent_table_name': 'account_payee', 'parent_table_id': 'one', }, id='parent_table_id must be an int', ), ], ) def test_asrg_get_attributes_checks_parent_table_id_is_int( abacus_state_resource_getter: AbacusStateResourceGetter, kwargs: dict[str, Any], ) -> None: """Test enforcement of parent_table_id type on kwargs.""" with pytest.raises(ValueError): abacus_state_resource_getter.get_attributes(**kwargs) @pytest.mark.parametrize( 'parent_table_name', [ pytest.param('contract'), pytest.param('statement_period'), ], ) def test_asrg_get_attributes_returns_empty_dict( abacus_state_resource_getter: AbacusStateResourceGetter, parent_table_name: str, ) -> None: """Test AbacusStateResourceGetter.get_attributes returns empty dict. This is the default behavior unless the parent_table_name has been overridden. """ kwargs = { 'parent_table_name': parent_table_name, 'parent_table_id': 123, } actual = abacus_state_resource_getter.get_attributes(**kwargs) assert actual == {} def mock_account_payee() -> MagicMock: """Return reusable AccountPayee.""" account_payee = MagicMock() account_payee.__setattr__('account_id', 7123) return account_payee @pytest.mark.parametrize( 'get_payees_by_ids_response, expect_exception, expected', [ pytest.param(Exception('some mysql exception'), True, None), pytest.param([], True, None), pytest.param( [mock_account_payee()], False, { 'id_to_uuid_exchange_tenant': { 'tenant_type': 'account', 'tenant_id': 7123, } }, ), ], ) @patch('abacus_state.utils.authorization.AccountPayee') def test_asrg_get_attributes_account_payee( mock_account_payee: MagicMock, abacus_state_resource_getter: AbacusStateResourceGetter, get_payees_by_ids_response: Any, expect_exception: bool, expected: dict[str, Any], ) -> None: """Test AbacusStateResourceGetter.get_attributes handles account_payee.""" kwargs = { 'parent_table_name': 'account_payee', 'parent_table_id': 4321, } mock_account_payee.get_payees_by_ids.side_effect = [ (get_payees_by_ids_response), ] if expect_exception: with pytest.raises(Exception): abacus_state_resource_getter.get_attributes(**kwargs) else: actual = abacus_state_resource_getter.get_attributes(**kwargs) assert actual == expected mock_account_payee.get_payees_by_ids.assert_called_once_with([4321]) @pytest.fixture def account_payee_state() -> dict[str, Any]: """Return reusable account payee state.""" return { 'abacus_state_id': 35, 'parent_table_id': 5, 'parent_table_name': 'account_payee', 'action_name': 'tax_eligibility', 'action_status': 'complete', 'message': 'Details:\ncountryOfTaxResidence: OK -...', 'created_by': 'd5ca8ac3-7e51-4793-8775-50d11282504c', 'created_at': '2022-03-15T15:52:34.000000', 'last_modified_by': '68e1ee6b-b0e8-4e3d-95eb-1bec63a7d3d4', 'last_modified': '2025-05-16T09:27:25.000000', } @pytest.fixture def contract_state() -> dict[str, Any]: """Return reusable contract state.""" return { 'abacus_state_id': 195, 'parent_table_id': 500051, 'parent_table_name': 'contract', 'action_name': 'legacy_sync', 'action_status': 'complete', 'message': None, 'created_by': 'd5ca8ac3-7e51-4793-8775-50d11282504c', 'created_at': '2022-08-02T13:42:08.000000', 'last_modified_by': 'default_user_id', 'last_modified': '2022-08-02T13:42:22.000000', } @pytest.fixture def statement_period_state() -> dict[str, Any]: """Return reusable statement period state.""" return { 'abacus_state_id': 424, 'parent_table_id': 283, 'parent_table_name': 'statement_period', 'action_name': 'statement_period_close', 'action_status': 'complete', 'message': 'autoclose', 'created_by': 'default_user_id', 'created_at': '2022-08-10T21:32:48.000000', 'last_modified_by': 'f0730ba4-e995-451f-8fba-a883d53660a2', 'last_modified': '2022-09-21T14:27:18.000000', } @pytest.fixture def reusable_resource_actions() -> list[ResourceAction]: """Return list of ResourceActions.""" return [ ResourceAction( resource_id=35, attributes={'first': 'dog'}, action='view_account_payee_abacus_state', resource_type='account', ), ResourceAction( resource_id=195, attributes={'second': 'grade'}, action='view_contract_abacus_state', resource_type='account', ), ResourceAction( resource_id=424, attributes={'third': 'base'}, action='view_statement_period_abacus_state', resource_type='statement_period', ), ] @pytest.mark.parametrize( 'is_authorized_many_resources_and_actions_return_value, expected', [ pytest.param([True, True, True], True), pytest.param([True, False, True], False), pytest.param([True], False), pytest.param([True, True, True, True], False), ], ) @patch('abacus_state.utils.authorization._build_resource_actions') def test_authorize_dataloader_states( mock_build_resource_actions: MagicMock, is_authorized_many_resources_and_actions_return_value: list[bool], expected: bool, reusable_resource_actions: list[ResourceAction], account_payee_state: dict[str, Any], contract_state: dict[str, Any], statement_period_state: dict[str, Any], ) -> None: """Test authorize_dataloader_states.""" mock_build_resource_actions.return_value = reusable_resource_actions states: list[dict[str, dict[str, Any] | None]] = [ { 'data': account_payee_state, }, {'data': None}, {'data': contract_state}, {'data': statement_period_state}, ] mock_authorization_backend = MagicMock(spec=AuthorizationBackend) mock_authorization_backend.is_authorized_many_resources_and_actions.return_value = ( is_authorized_many_resources_and_actions_return_value ) actual = authorize_dataloader_states(mock_authorization_backend, states) assert actual == expected mock_build_resource_actions.assert_called_once_with( [ account_payee_state, contract_state, statement_period_state, ] ) mock_authorization_backend.is_authorized_many_resources_and_actions.assert_called_once_with( reusable_resource_actions, ) @patch('abacus_state.utils.authorization._build_resource_actions') def test_authorize_dataloader_states_all_none( mock_build_resource_actions: MagicMock, ) -> None: """Test authorize_dataloader_states for all None states.""" mock_build_resource_actions.return_value = reusable_resource_actions states: list[dict[str, dict[str, Any] | None]] = [ {'data': None}, {'data': None}, {'data': None}, ] mock_authorization_backend = MagicMock(spec=AuthorizationBackend) actual = authorize_dataloader_states(mock_authorization_backend, states) assert actual mock_build_resource_actions.assert_not_called() mock_authorization_backend.is_authorized_many_resources_and_actions.assert_not_called() @patch('abacus_state.utils.authorization.AbacusStateResourceGetter') def test__build_resource_actions( mock_abacus_state_resource_getter: MagicMock, reusable_resource_actions: list[ResourceAction], account_payee_state: dict[str, Any], contract_state: dict[str, Any], statement_period_state: dict[str, Any], ) -> None: """Test _build_resource_actions.""" mock_abacus_state_resource_getter.return_value.get_attributes.side_effect = [ ({'first': 'dog'}), ({'second': 'grade'}), ({'third': 'base'}), ] states = [ account_payee_state, contract_state, statement_period_state, ] resource_actions = _build_resource_actions(states) assert resource_actions == reusable_resource_actions