"""Test Abacus State handlers.""" from typing import Any from unittest.mock import MagicMock, patch import pytest from flask import testing from owsresponse import response from werkzeug.exceptions import BadRequest from abacus_state.constants.constants import ACTION_STATUSES, PARENT_TABLE_NAMES from abacus_state.constants.error import ERROR_PREVIOUS_ACTIONS_STATUS from abacus_state.schemas.abacus_state import AbacusStateDetailSchema from abacus_state.utils.format_error import validation_error from tests.utils.factories import AbacusStateFactory @patch('abacus_state.blueprints.abacus_state.logic') def test_create_abacus_states(mock_logic, fixture_client): """Test POST request for abacus states.""" mock_logic.create_abacus_states.return_value = response.Response( message='ok', status=201 ) post_data = [ { 'parent_table_id': 1, 'action_name': 'deliver_sales_files', 'parent_table_name': 'accounting_period', } ] res = fixture_client.post('/abacus-states', json=post_data) assert res.status_code == 201 mock_logic.create_abacus_states.assert_called_once_with(post_data) @patch('abacus_state.blueprints.abacus_state.check_create_permissions_access_checks') @patch('abacus_state.blueprints.abacus_state.logic') def test_create_account_payee_abacus_states_success( mock_logic, mock_perm, fixture_client ): """Test POST request for abacus account_payee states.""" mock_logic.create_abacus_states.return_value = response.Response( message='ok', status=201 ) post_data = [ { 'parent_table_id': 1, 'action_name': 'deliver_sales_files', 'parent_table_name': 'accounting_period', } ] headers = {'Orchard-Profile-Type': 'DocumentsProfile'} res = fixture_client.post('/abacus-states', json=post_data, headers=headers) assert res.status_code == 201 mock_perm.assert_called_once_with(post_data, 'DocumentsProfile') mock_logic.create_abacus_states.assert_called_once_with(post_data) @patch('abacus_state.blueprints.abacus_state.logic') def test_create_account_payee_abacus_states_failure(mock_logic, fixture_client): """Test POST request for abacus account_payee states.""" mock_logic.create_abacus_states.return_value = response.Response( message='ok', status=201 ) post_data = [ { 'parent_table_id': 1, 'action_name': 'deliver_sales_files', 'parent_table_name': 'accounting_period', } ] headers = {'Orchard-Profile-Type': 'DocumentsProfile'} res = fixture_client.post('/abacus-states', json=post_data, headers=headers) assert res.status_code == 400 mock_logic.create_abacus_states.assert_not_called() @patch('abacus_state.blueprints.abacus_state.logic') def test_abacus_states_by_parent_table(mock_logic, fixture_client): """Test POST abacus states for specified parent table.""" mock_logic.create_abacus_states_by_parent_table.return_value = response.Response( message='ok', status=201 ) parent_table_name = 'accounting-period' contract_type = 'distribution' parent_table_id = 123 res = fixture_client.post( '/abacus-states/{0}/{1}?contract_type={2}'.format( parent_table_name, parent_table_id, contract_type ) ) assert res.status_code == 201 mock_logic.create_abacus_states_by_parent_table.assert_called_once_with( parent_table_name.replace('-', '_'), parent_table_id, contract_type ) @patch('abacus_state.blueprints.abacus_state.logic') def test_get_abacus_state_action_list(mock_logic, fixture_client): """Test endpoint passes request body to logic layer.""" res = fixture_client.get('/abacus-state/some-table/10') assert res.status_code == 200 mock_logic.get_action_status_list.assert_called_once_with( parent_table_name='some_table', parent_table_id=10 ) @pytest.mark.parametrize( 'verify_rules_access_standalone_result,' 'authorization_backend_result,' 'expected_status_code,' 'expect_get_action_list_called,' 'expect_is_authorized_called', [ pytest.param( True, False, 200, True, False, id='profile checks authorized request', ), pytest.param( False, False, 403, False, True, id='profile and pp checks failed to authorize request', ), pytest.param( False, True, 200, True, True, id='pp checks authorized request', ), ], ) @patch('abacus_state.blueprints.abacus_state.abacus_state_resource_getter') @patch('abacus_state.blueprints.abacus_state.authorization_backend') @patch('abacus_state.blueprints.abacus_state.flask_request') @patch('abacus_state.blueprints.abacus_state.logic') def test_get_abacus_state_action_list_authorization_checks( mock_logic: MagicMock, mock_flask_request: MagicMock, mock_authorization_backend: MagicMock, mock_abacus_state_resource_getter: MagicMock, verify_rules_access_standalone_result: bool, authorization_backend_result: bool, expected_status_code: int, expect_get_action_list_called: bool, expect_is_authorized_called: bool, fixture_client: Any, ) -> None: """Test endpoint uses authorization checks.""" happy_response = {'the': 'action status list'} mock_logic.get_action_status_list.return_value = happy_response mock_flask_request.verify_rules_access_standalone.return_value = ( verify_rules_access_standalone_result ) mock_authorization_backend.is_authorized.return_value = authorization_backend_result res = fixture_client.get('/abacus-state/some-table/10') assert res.status_code == expected_status_code if res.status_code != 200: res.json == {'code': 'authorization_error', 'message': 'Unauthorized'} else: res.json == happy_response mock_flask_request.verify_rules_access_standalone.assert_called_once() if expect_is_authorized_called: mock_authorization_backend.is_authorized.assert_called_once_with( 'view_some_table_abacus_state', 'some_table_10', 'some_table', mock_abacus_state_resource_getter, parent_table_name='some_table', parent_table_id=10, ) else: mock_authorization_backend.is_authorized.assert_not_called() if expect_get_action_list_called: mock_logic.get_action_status_list.assert_called_once_with( parent_table_name='some_table', parent_table_id=10 ) else: mock_logic.get_action_status_list.assert_not_called() @patch('abacus_state.blueprints.abacus_state.logic') def test_update_abacus_state_success(mock_logic, fixture_client): """Test PUT request for abacus states.""" abacus_state = AbacusStateFactory.create() mock_put_request = {'action_status': 'running', 'message': 'testing'} mock_logic.update_abacus_state.return_value = response.Response( message='ok', status=200 ) res = fixture_client.put( f'/abacus-state/{abacus_state.abacus_state_id}', json=mock_put_request ) assert res.status_code == 200 mock_logic.update_abacus_state.assert_called_once_with( abacus_state, **mock_put_request ) @patch('abacus_state.blueprints.abacus_state.check_update_permissions_access_checks') @patch('abacus_state.blueprints.abacus_state.logic') def test_update_abacus_state_account_payee_success( mock_logic, mock_perm, fixture_client ): """Test PUT request for abacus states.""" abacus_state = AbacusStateFactory.create() mock_put_request = {'action_status': 'running', 'message': 'testing'} headers = {'Orchard-Profile-Type': 'DocumentsProfile'} mock_logic.update_abacus_state.return_value = response.Response( message='ok', status=200 ) res = fixture_client.put( f'/abacus-state/{abacus_state.abacus_state_id}', json=mock_put_request, headers=headers, ) assert res.status_code == 200 mock_perm.assert_called_once_with( abacus_state, mock_put_request, 'DocumentsProfile' ) mock_logic.update_abacus_state.assert_called_once_with( abacus_state, **mock_put_request ) @patch( 'abacus_state.blueprints.abacus_state.check_update_permissions_access_checks', side_effect=BadRequest, ) @patch('abacus_state.blueprints.abacus_state.logic') def test_update_abacus_state_account_payee_failure( mock_logic, mock_perm, fixture_client ): """Test PUT request for abacus states.""" abacus_state = AbacusStateFactory.create() mock_put_request = {'action_status': 'running', 'message': 'testing'} headers = {'Orchard-Profile-Type': 'DocumentsProfile'} mock_logic.update_abacus_state.return_value = response.Response( message='ok', status=200 ) res = fixture_client.put( f'/abacus-state/{abacus_state.abacus_state_id}', json=mock_put_request, headers=headers, ) assert res.status_code == 400 mock_perm.assert_called_once_with( abacus_state, mock_put_request, 'DocumentsProfile' ) assert not mock_logic.update_abacus_state.called @patch('abacus_state.blueprints.abacus_state.logic') def test_update_abacus_state_error(mock_logic, fixture_client): """Test PUT request for abacus states when earlier actions are not completed.""" abacus_state = AbacusStateFactory.create() mock_put_request = {'action_status': 'running', 'message': 'testing'} mock_logic.update_abacus_state.return_value = validation_error( ERROR_PREVIOUS_ACTIONS_STATUS.format(abacus_state.action_name) ) res = fixture_client.put( f'/abacus-state/{abacus_state.abacus_state_id}', json=mock_put_request ) assert res.status_code == 400 @patch('abacus_state.blueprints.abacus_state.logic') def test_bulk_update_abacus_states_by_parent_table(mock_logic, fixture_client): """Test PUT request for updating abacus states in bulk.""" parent_table_name = PARENT_TABLE_NAMES.PAYMENT_GROUP_PAYMENT_ACCOUNT mock_put_request = [ {'action_status': 'running', 'message': 'testing', 'parent_table_id': 1} ] mock_logic.bulk_update_abacus_states_by_parent_table.return_value = ( response.Response(message='ok', status=200) ) res = fixture_client.put( f'/abacus-states/{parent_table_name}', json=mock_put_request ) assert res.status_code == 200 mock_logic.bulk_update_abacus_states_by_parent_table.assert_called_once_with( parent_table_name, mock_put_request ) @patch('abacus_state.blueprints.abacus_state.logic') def test_dataloader_states_by_ids( mock_logic: MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test for the state dataloader handler.""" mock_data = [ { 'action_name': 'deliver_sales_files', 'action_status': 'complete', 'abacus_state_id': 1, 'message': None, } ] mock_response = response.Response( message={ 'items': [ {'data': mock_data}, {'data': None}, ] }, status=200, ) mock_logic.dataload_states_by_ids.return_value = mock_response post_data = [1, 2] res = fixture_client.post('/abacus-state/dataloader', json=post_data) assert res.status_code == 200 mock_logic.dataload_states_by_ids.assert_called_once_with(post_data) @pytest.mark.parametrize( ['standalone_check_result', 'pdp_check_result', 'expected_status'], [ pytest.param(True, None, 200, id='standalone check pass'), pytest.param(False, False, 403, id='pdp check fail'), pytest.param(False, True, 200, id='pdp check pass'), ], ) @patch('abacus_state.blueprints.abacus_state.authorization_backend') @patch('abacus_state.blueprints.abacus_state.authorize_dataloader_states') @patch('abacus_state.blueprints.abacus_state.flask_request') @patch('abacus_state.blueprints.abacus_state.logic') def test_dataloader_states_by_ids_authorization( mock_logic: MagicMock, mock_flask_request: MagicMock, mock_authorize_dataloader_states: MagicMock, mock_authorization_backend: MagicMock, standalone_check_result: bool, pdp_check_result: bool | None, expected_status: int, fixture_client: testing.FlaskClient, ) -> None: """Test for the state dataloader handler.""" mock_flask_request.verify_rules_access_standalone.return_value = ( standalone_check_result ) mock_authorize_dataloader_states.return_value = pdp_check_result mock_data = [ { 'action_name': 'deliver_sales_files', 'action_status': 'complete', 'abacus_state_id': 1, 'message': None, } ] mock_response = response.Response( message={ 'items': [ {'data': mock_data}, {'data': None}, ] }, status=200, ) mock_logic.dataload_states_by_ids.return_value = mock_response post_data = [1, 2] res = fixture_client.post('/abacus-state/dataloader', json=post_data) assert res.status_code == expected_status mock_logic.dataload_states_by_ids.assert_called_once_with(post_data) mock_flask_request.verify_rules_access_standalone.assert_called_once() if standalone_check_result: mock_authorize_dataloader_states.assert_not_called() else: mock_authorize_dataloader_states.assert_called_once_with( mock_authorization_backend, mock_response.message['items'], ) @patch('abacus_state.blueprints.abacus_state.logic') def test_reset_abacus_state_actions(mock_logic, fixture_client): """Test to reset all abacus states for specified table and id.""" parent_table_name = 'statement_period_adjustment_file' parent_table_id = 9999 mock_abacus_states = [ AbacusStateFactory.create( parent_table_name=parent_table_name, parent_table_id=parent_table_id, action_name='upload_file', action_status=ACTION_STATUSES.INIT, ) ] expected_response = AbacusStateDetailSchema(many=True).dump(mock_abacus_states) mock_logic.reset_abacus_state_actions.return_value = response.Response( message=expected_response, status=200 ) res = fixture_client.put( f'/abacus-state/{parent_table_name}/{parent_table_id}/reset' ) assert res.status_code == 200 assert res.json == expected_response mock_logic.reset_abacus_state_actions.assert_called_once_with( parent_table_name=parent_table_name, parent_table_id=parent_table_id )