from http import HTTPStatus from unittest.mock import call, Mock, patch from uuid import uuid4 import pytest from payee.config import Config from payee.constants.error import ( ERROR_DONT_HAVE_PERMISSIONS, ERROR_NO_VALID_IDENTITY_IN_CONTEXT, ERROR_NOT_PERMITTED_IDENTITY, ) from payee.logic.exceptions import LogicError from payee.schemas.tax_form import ( TaxFormInfoDetailsOutputSchema, TaxFormInfoOutputSchema, TaxFormOutputSchema, ) from tests.constants import MOCK_TAX_FORM_DETAILS from tests.utils.factories import TaxFormInfoDetailsFactory, TaxFormInfoFactory @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records') def test_get_tax_form_info(mock_logic, mock_perm, fixture_client, mock_headers): """Test successfully retrieving tax from info.""" output = {'items': [TaxFormInfoFactory.build()], 'total_count': 1} mock_logic.return_value = output res = fixture_client.post( f'/account-payee/tax-form-info/bulk', headers=mock_headers, ) assert res.status_code == 200 assert res.json == TaxFormInfoOutputSchema().dump(output) @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records') def test_get_tax_form_info_with_valid_query_params( mock_logic, mock_perm, fixture_client, mock_headers ): """Test successfully retrieving tax from info.""" output = {'items': [TaxFormInfoFactory.build()], 'total_count': 1} mock_logic.return_value = output res = fixture_client.post( f'/account-payee/tax-form-info/bulk?limit=10&offset=0', headers=mock_headers, ) assert res.status_code == 200 assert res.json == TaxFormInfoOutputSchema().dump(output) @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records') def test_get_tax_form_info_with_invalid_query_params( mock_logic, mock_perm, fixture_client, mock_headers ): """Test successfully retrieving tax from info.""" res = fixture_client.post( f'/account-payee/tax-form-info/bulk?limit=305&offset=-1', headers=mock_headers, ) assert res.status_code == 400 assert res.json == { 'code': 'error', 'message': { 'query': { 'limit': [ 'Must be greater than or equal to 1 and less than or equal to 300.' ], 'offset': ['Must be greater than or equal to 0.'], } }, } assert not mock_logic.called @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records') def test_get_tax_form_info_with_valid_json_payload( mock_logic, mock_perm, fixture_client, mock_headers ): """Test successfully retrieving tax from info.""" output = {'items': [TaxFormInfoFactory.build()], 'total_count': 1} mock_logic.return_value = output res = fixture_client.post( f'/account-payee/tax-form-info/bulk?limit=10&offset=0', headers=mock_headers, json={ 'is_active': True, 'expiration_date_start': '2024-11-30', 'expiration_date_end': '2025-11-30', 'account_payee_ids': [1, 2, 3], }, ) assert res.status_code == 200 assert res.json == TaxFormInfoOutputSchema().dump(output) @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records') def test_get_tax_form_info_with_invalid_json_payload( mock_logic, mock_perm, fixture_client, mock_headers ): """Test successfully retrieving tax from info.""" res = fixture_client.post( f'/account-payee/tax-form-info/bulk?limit=10&offset=0', headers=mock_headers, json={ 'is_active': True, 'expiration_date_start': '2027-11-30', 'expiration_date_end': '2025-11-30', 'account_payee_ids': [1, 2, 'a'], }, ) assert res.status_code == 400 assert res.json == { 'code': 'error', 'message': { 'json': { 'account_payee_ids': { '2': ['Must be an integer greater or equal to 0.'] } } }, } assert not mock_logic.called @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records_details') def test_get_tax_form_details_info(mock_logic, mock_perm, fixture_client, mock_headers): """Test successfully retrieving tax from info details.""" output = {'items': [TaxFormInfoDetailsFactory.build()], 'total_count': 1} mock_logic.return_value = output res = fixture_client.post( f'/account-payee/tax-form-info/details/bulk', headers=mock_headers, ) assert res.status_code == 200 assert res.json == TaxFormInfoDetailsOutputSchema().dump(output) @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records_details') def test_get_tax_form_info_details_with_valid_query_params( mock_logic, mock_perm, fixture_client, mock_headers ): """Test successfully retrieving tax from info details.""" output = {'items': [TaxFormInfoDetailsFactory.build()], 'total_count': 1} mock_logic.return_value = output res = fixture_client.post( f'/account-payee/tax-form-info/details/bulk?limit=10&offset=0', headers=mock_headers, ) assert res.status_code == 200 assert res.json == TaxFormInfoDetailsOutputSchema().dump(output) @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records_details') def test_get_tax_form_info_details_with_invalid_query_params( mock_logic, mock_perm, fixture_client, mock_headers ): """Test failure retrieving tax from info details.""" res = fixture_client.post( f'/account-payee/tax-form-info/details/bulk?limit=305&offset=-1', headers=mock_headers, ) assert res.status_code == 400 assert res.json == { 'code': 'error', 'message': { 'query': { 'limit': [ 'Must be greater than or equal to 1 and less than or equal to 300.' ], 'offset': ['Must be greater than or equal to 0.'], } }, } assert not mock_logic.called @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records_details') def test_get_tax_form_info_details_with_valid_json_payload( mock_logic, mock_perm, fixture_client, mock_headers ): """Test successfully retrieving tax from info details.""" output = {'items': [TaxFormInfoDetailsFactory.build()], 'total_count': 1} mock_logic.return_value = output res = fixture_client.post( f'/account-payee/tax-form-info/details/bulk?limit=10&offset=0', headers=mock_headers, json={ 'is_active': True, 'expiration_date_start': '2024-11-30', 'expiration_date_end': '2025-11-30', 'account_payee_ids': [1, 2, 3], }, ) assert res.status_code == 200 assert res.json == TaxFormInfoDetailsOutputSchema().dump(output) @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.logic.tax_form.get_records_details') def test_get_tax_form_info_details_with_unvalid_json_payload( mock_logic, mock_perm, fixture_client, mock_headers ): """Test failure retrieving tax from info details.""" res = fixture_client.post( f'/account-payee/tax-form-info/details/bulk?limit=10&offset=0', headers=mock_headers, json={ 'is_active': True, 'expiration_date_start': '2027-11-30', 'expiration_date_end': '2025-11-30', 'account_payee_ids': [1, 2, 'a'], }, ) assert res.status_code == 400 assert res.json == { 'code': 'error', 'message': { 'json': { 'account_payee_ids': { '2': ['Must be an integer greater or equal to 0.'] } } }, } assert not mock_logic.called @pytest.mark.parametrize( 'tax_form_type, details_fixture', MOCK_TAX_FORM_DETAILS.items() ) @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.blueprints.tax_form.logic') def test_post_tax_form_success( mock_logic, mock_perm, fixture_client, mock_headers, tax_form_type, details_fixture ): """Test successfully posting the tax form.""" mock_details = TaxFormInfoDetailsFactory.build(tax_form_type=tax_form_type) mock_logic.create_or_update_record.return_value = mock_details res = fixture_client.post( f'/account-payee/{mock_details.account_payee_id}/tax-form-info', headers=mock_headers, json={ **details_fixture, 'tax_form_type': mock_details.tax_form_type, 'signed_date': str(mock_details.signed_date), }, ) assert res.status_code == 200, res.text assert res.json == TaxFormOutputSchema().dump(mock_details) assert mock_logic.create_or_update_record.call_args_list == [ call( mock_details.account_payee_id, **details_fixture, tax_form_type=mock_details.tax_form_type, signed_date=mock_details.signed_date, ) ] @pytest.mark.parametrize( 'tax_form_type, details_fixture', MOCK_TAX_FORM_DETAILS.items() ) @patch( 'payee.utils.permissions.authorization_backend.is_authorized', return_value=False ) @patch('payee.blueprints.tax_form.logic') def test_post_tax_form_failure_permissions( mock_logic, mock_perm, fixture_client, mock_headers, tax_form_type, details_fixture ): """Test successfully posting the tax form.""" mock_details = TaxFormInfoDetailsFactory.build(tax_form_type=tax_form_type) mock_logic.create_or_update_record.return_value = mock_details res = fixture_client.post( f'/account-payee/{mock_details.account_payee_id}/tax-form-info', headers=mock_headers, json={ **details_fixture, 'tax_form_type': mock_details.tax_form_type, 'signed_date': str(mock_details.signed_date), }, ) assert res.status_code == 401, res.text assert res.json == { 'error': ', '.join( [ ERROR_NO_VALID_IDENTITY_IN_CONTEXT, ERROR_DONT_HAVE_PERMISSIONS, ] ) } assert not mock_logic.create_or_update_record.called @pytest.mark.parametrize( 'tax_form_type, details_fixture', MOCK_TAX_FORM_DETAILS.items() ) @patch('payee.blueprints.tax_form.logic') def test_post_tax_form_success_m2m( mock_logic, fixture_client, tax_form_type, details_fixture, mock_jwt_auth ): """Test successfully posting the tax form with m2m creds.""" mock_jwt_auth.identity = Config.TAX_FORM_SERVICE_IDENTITIES[0] mock_details = TaxFormInfoDetailsFactory.build(tax_form_type=tax_form_type) mock_logic.create_or_update_record.return_value = mock_details res = fixture_client.post( f'/account-payee/{mock_details.account_payee_id}/tax-form-info', headers=mock_jwt_auth.headers, json={ **details_fixture, 'tax_form_type': mock_details.tax_form_type, 'signed_date': str(mock_details.signed_date), }, ) assert res.status_code == 200, res.text assert res.json == TaxFormOutputSchema().dump(mock_details) assert mock_logic.create_or_update_record.call_args_list == [ call( mock_details.account_payee_id, **details_fixture, tax_form_type=mock_details.tax_form_type, signed_date=mock_details.signed_date, ) ] @pytest.mark.parametrize( 'tax_form_type, details_fixture', MOCK_TAX_FORM_DETAILS.items() ) @patch('payee.blueprints.tax_form.logic') def test_post_tax_form_failures_permissions_m2m( mock_logic, fixture_client, tax_form_type, details_fixture, mock_jwt_auth ): """Test failure posting the tax form with m2m creds.""" mock_jwt_auth.identity = str(uuid4()) mock_details = TaxFormInfoDetailsFactory.build(tax_form_type=tax_form_type) mock_logic.create_or_update_record.return_value = mock_details res = fixture_client.post( f'/account-payee/{mock_details.account_payee_id}/tax-form-info', headers=mock_jwt_auth.headers, json={ **details_fixture, 'tax_form_type': mock_details.tax_form_type, 'signed_date': str(mock_details.signed_date), }, ) assert res.status_code == 401, res.text assert res.json == { 'error': ', '.join( [ ERROR_NOT_PERMITTED_IDENTITY.format( jwt_identity=mock_jwt_auth.identity ), ERROR_DONT_HAVE_PERMISSIONS, ] ) } assert not mock_logic.create_or_update_record.called @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.blueprints.tax_form.logic') def test_post_tax_form_failure_validation( mock_logic, mock_perm, fixture_client, mock_headers, faker ): """Test failure posting the tax form.""" account_payee_id = faker.pyint() res = fixture_client.post( f'/account-payee/{account_payee_id}/tax-form-info', headers=mock_headers, json={}, ) assert res.status_code == 400 assert not mock_logic.create_or_update_record.called @pytest.mark.parametrize( 'tax_form_type, details_fixture', list(MOCK_TAX_FORM_DETAILS.items())[0:1] ) @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.blueprints.tax_form.logic') def test_post_tax_form_failure_logic( mock_logic, mock_perm, fixture_client, mock_headers, tax_form_type, details_fixture ): """Test failure posting the tax form.""" mock_details = TaxFormInfoDetailsFactory.build(tax_form_type=tax_form_type) mock_logic.create_or_update_record.side_effect = LogicError('logic error') res = fixture_client.post( f'/account-payee/{mock_details.account_payee_id}/tax-form-info', headers=mock_headers, json={ **details_fixture, 'tax_form_type': mock_details.tax_form_type, 'signed_date': str(mock_details.signed_date), }, ) assert res.status_code == 400 assert res.json == {'code': 'error', 'message': 'logic error'} assert mock_logic.create_or_update_record.called @patch('payee.utils.permissions.authorization_backend.is_authorized', return_value=True) @patch('payee.blueprints.tax_form.logic') def test_delete_tax_form_success( mock_logic: Mock, _, fixture_client, mock_headers ) -> None: """Test delete_tax_form.""" tax_form_info_id = 5 res = fixture_client.delete( f'/account-payee/tax-form-info/{tax_form_info_id}', headers=mock_headers, ) assert res.status_code == HTTPStatus.NO_CONTENT, res.text mock_logic.delete_record.assert_called_once_with(tax_form_info_id) @patch('payee.blueprints.tax_form.logic') def test_delete_tax_form_success_m2m( mock_logic: Mock, fixture_client, mock_jwt_auth ) -> None: """Test delete_tax_form.""" tax_form_info_id = 5 mock_jwt_auth.identity = Config.TAX_FORM_SERVICE_IDENTITIES[0] res = fixture_client.delete( f'/account-payee/tax-form-info/{tax_form_info_id}', headers=mock_jwt_auth.headers, ) assert res.status_code == HTTPStatus.NO_CONTENT, res.text mock_logic.delete_record.assert_called_once_with(tax_form_info_id) @patch( 'payee.utils.permissions.authorization_backend.is_authorized', return_value=False ) @patch('payee.blueprints.tax_form.logic') def test_delete_tax_form_failure_permissions( mock_logic: Mock, _, fixture_client, mock_headers ) -> None: """Test delete_tax_form.""" tax_form_info_id = 5 res = fixture_client.delete( f'/account-payee/tax-form-info/{tax_form_info_id}', headers=mock_headers, ) assert res.status_code == 401, res.text assert res.json == { 'error': ', '.join( [ ERROR_NO_VALID_IDENTITY_IN_CONTEXT, ERROR_DONT_HAVE_PERMISSIONS, ] ) } assert not mock_logic.delete_record.called @patch('payee.blueprints.tax_form.logic') def test_delete_tax_form_failure_m2m( mock_logic: Mock, fixture_client, mock_jwt_auth ) -> None: """Test delete_tax_form.""" tax_form_info_id = 5 mock_jwt_auth.identity = str(uuid4()) res = fixture_client.delete( f'/account-payee/tax-form-info/{tax_form_info_id}', headers=mock_jwt_auth.headers, ) assert res.status_code == 401, res.text assert res.json == { 'error': ', '.join( [ ERROR_NOT_PERMITTED_IDENTITY.format( jwt_identity=mock_jwt_auth.identity ), ERROR_DONT_HAVE_PERMISSIONS, ] ) } assert not mock_logic.delete_record.called