"""Functional testcases for EarningsTransfer.""" from datetime import datetime from decimal import Decimal from unittest.mock import patch from abacus_common_logic.constants.constants import SYSTEM_TIMEZONE from freezegun import freeze_time from abacus_contract.tests.utils.factories import ContractFactory from royalties.constants.constants import PAYMENT_SCHEDULES from royalties.constants.error import ( ERROR_INVALID_CRITERIA_DUPLICATE_PAYMENT_ENTITIES, ERROR_INVALID_CRITERIA_DUPLICATE_PAYMENT_SCHEDULES, ERROR_POST_REQUEST_DUPLICATE_RECORDS, ) from royalties.schemas.earnings_transfer import EarningsTransferDetailSchema from royalties.tests.utils.factories import EarningsTransferFactory def test_get_earnings_transfer(mock_earnings_transfer_fixture, fixture_client): """Test to get a list of earnings transfer.""" result = fixture_client.get('/earnings-transfers') assert result.status_code == 200 assert result.json == { 'items': [ { 'from_contract_id': 90001, 'to_contract_id': 90003, 'transfer_type': 'transfer', 'rate_type': 'percent', 'transfer_amount': '128.911000000000', 'input': 'net_revenue', 'negative': False, 'active': False, 'use_static_balance': False, 'comment': 'Test Earnings Transfer', 'created_at': '2026-05-05', 'earnings_transfer_id': result.json['items'][0]['earnings_transfer_id'], }, { 'from_contract_id': 90002, 'to_contract_id': 90004, 'transfer_type': 'override', 'rate_type': 'flat_rate', 'transfer_amount': '99.911000000000', 'input': 'gross_revenue', 'negative': False, 'active': False, 'use_static_balance': False, 'comment': 'Test Earnings Transfer', 'created_at': '2026-05-05', 'earnings_transfer_id': result.json['items'][1]['earnings_transfer_id'], }, ], 'total_count': 2, } def test_filtering_earnings_transfers(mock_earnings_transfer_fixture, fixture_client): """Test to filter earnings transfers.""" result = fixture_client.get( f'/earnings-transfers?reference_payment_entities=1,2,4&payment_schedules=60_days_after_month_end' ) assert result.status_code == 200 assert result.json == { 'items': [ { 'from_contract_id': 90002, 'to_contract_id': 90004, 'transfer_type': 'override', 'rate_type': 'flat_rate', 'transfer_amount': '99.911000000000', 'input': 'gross_revenue', 'negative': False, 'active': False, 'use_static_balance': False, 'comment': 'Test Earnings Transfer', 'created_at': '2026-05-05', 'earnings_transfer_id': result.json['items'][0]['earnings_transfer_id'], }, ], 'total_count': 1, } def test_get_earnings_transfer_with_invalid_payment_entities(fixture_client): """Throws an error to filter out earnings transfers with invalid payment entities.""" result = fixture_client.get( f'/earnings-transfers?reference_payment_entities=1,2,test&payment_schedules=60_days_after_month_end' ) print(vars(result)) assert result.status_code == 400 assert result.json['message'] == str( {'reference_payment_entities': ['Must be a comma-separated list of digits.']} ) def test_get_earnings_transfer_with_invalid_payment_schedules(fixture_client): """Throws an error to filter out earnings transfers with invalid payment schedules.""" result = fixture_client.get( f'/earnings-transfers?reference_payment_entities=1,2&payment_schedules=test' ) assert result.status_code == 400 assert result.json['message'] == str( {'payment_schedules': [f'Must be one of: {", ".join(PAYMENT_SCHEDULES)}']} ) def test_get_earnings_transfer_with_duplicate_payment_entities(fixture_client): """Throws an error to filter out earnings transfers with duplicate payment entities.""" result = fixture_client.get( f'/earnings-transfers?reference_payment_entities=1,2,1,2&payment_schedules=60_days_after_month_end' ) print(vars(result)) assert result.status_code == 400 assert result.json['message'] == str( { 'reference_payment_entities': [ ERROR_INVALID_CRITERIA_DUPLICATE_PAYMENT_ENTITIES ] } ) def test_get_earnings_transfer_with_invalid_payment_schedules(fixture_client): """Throws an error to filter out earnings transfers with duplicate payment schedules.""" result = fixture_client.get( f'/earnings-transfers?reference_payment_entities=1,2&payment_schedules=60_days_after_month_end,60_days_after_month_end' ) assert result.status_code == 400 assert result.json['message'] == str( {'payment_schedules': [ERROR_INVALID_CRITERIA_DUPLICATE_PAYMENT_SCHEDULES]} ) @freeze_time(datetime(2026, 5, 5, 0, 0, 0, tzinfo=SYSTEM_TIMEZONE)) def test_create_one_earnings_transfer(fixture_client): """Test to create a earnings_transfer.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'override', 'rate_type': 'flat_rate', 'transfer_amount': '1.89', 'input': 'closing_balance', 'negative': True, 'active': True, 'use_static_balance': True, 'comment': 'Test comment', } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 201 assert result.json == [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'override', 'rate_type': 'flat_rate', 'transfer_amount': '1.890000000000', 'input': 'closing_balance', 'negative': True, 'active': True, 'use_static_balance': True, 'comment': 'Test comment', 'created_at': '2026-05-05', 'earnings_transfer_id': result.json[0]['earnings_transfer_id'], } ] @freeze_time(datetime(2026, 5, 5, 0, 0, 0, tzinfo=SYSTEM_TIMEZONE)) def test_create_earnings_transfer_in_bulk(fixture_client): """Test to create earnings_transfer in bulk.""" mock_from_contract = ContractFactory.create() mock_to_contract_1 = ContractFactory.create() mock_to_contract_2 = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'flat_rate', 'transfer_amount': '1900.89', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract_2.contract_id, 'transfer_type': 'reclass', 'rate_type': 'percent', 'transfer_amount': '1.89', 'input': 'closing_balance', 'negative': True, 'active': True, 'use_static_balance': True, 'comment': 'Test comment', }, ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 201 assert result.json == [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'flat_rate', 'transfer_amount': '1900.890000000000', 'input': 'closing_balance', 'negative': True, 'active': True, 'use_static_balance': False, 'comment': 'Test comment', 'created_at': '2026-05-05', 'earnings_transfer_id': result.json[0]['earnings_transfer_id'], }, { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract_2.contract_id, 'transfer_type': 'reclass', 'rate_type': 'percent', 'transfer_amount': '1.890000000000', 'input': 'closing_balance', 'negative': True, 'active': True, 'use_static_balance': True, 'comment': 'Test comment', 'created_at': '2026-05-05', 'earnings_transfer_id': result.json[1]['earnings_transfer_id'], }, ] @freeze_time(datetime(2026, 5, 5, 0, 0, 0, tzinfo=SYSTEM_TIMEZONE)) def test_create_earnings_transfer_contract_not_Exist(fixture_client): """Test returns an error if contract doesn't exist.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': 1111, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'override', 'rate_type': 'flat_rate', 'transfer_amount': '1900.89', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': 99999, 'transfer_type': 'reclass', 'rate_type': 'percent', 'transfer_amount': '1.89', 'input': 'gross_revenue', 'negative': True, 'active': True, 'comment': 'Test comment', }, ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert ( result.json['message'] == 'The following contract IDs do not exist: [1111, 99999]' ) @freeze_time(datetime(2026, 5, 5, 0, 0, 0, tzinfo=SYSTEM_TIMEZONE)) def test_create_earnings_transfer_with_required_fields(fixture_client): """Test to create a earnings_transfer with required fields.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'override', 'transfer_amount': '2.167', 'negative': False, } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 201 assert result.json == [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'override', 'rate_type': None, 'transfer_amount': '2.167000000000', 'input': None, 'negative': False, 'active': False, 'use_static_balance': False, 'comment': None, 'created_at': '2026-05-05', 'earnings_transfer_id': result.json[0]['earnings_transfer_id'], } ] def test_create_earnings_transfer_with_same_contracts(fixture_client): """Test throws an error if from/to contracts are same.""" mock_from_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_from_contract.contract_id, 'transfer_type': 'override', 'transfer_amount': '2.167', 'negative': False, } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert result.json['message'] == { '0': {'to_contract_id': ['Source and destination contracts must be different.']} } def test_create_earnings_transfer_with_duplicate_records(fixture_client): """Test throws an error if POST request contain duplicate records.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'override', 'transfer_amount': '2.167', 'negative': False, }, { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'override', 'transfer_amount': '2.167', 'negative': False, }, ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) mock_input[0]['transfer_amount'] = Decimal(mock_input[0]['transfer_amount']) assert result.status_code == 400 assert result.json['message'] == ERROR_POST_REQUEST_DUPLICATE_RECORDS.format( mock_input[0] ) def test_create_earnings_transfer_for_invalid_values(fixture_client): """Test throws an error for invalid values.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'invalid_transfer_type', 'rate_type': 'percent', 'transfer_amount': '1', 'input': 'invalid_gross_revenue', 'negative': True, 'active': True, 'comment': 'Test comment', }, { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'reclass', 'rate_type': 'invalid_flat_rate', 'transfer_amount': '1', 'input': 'invalid_gross_revenue', 'negative': True, 'active': True, 'comment': 'Test comment', }, ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert result.json['message'] == { '0': { 'transfer_type': [ 'Must be one of cross_recoup, reclass, override, transfer, nr_transfer' ], 'input': ['Must be one of closing_balance, gross_revenue, net_revenue'], }, '1': { 'rate_type': ['Must be one of flat_rate, percent'], 'input': ['Must be one of closing_balance, gross_revenue, net_revenue'], }, } def test_create_earnings_transfer_for_percent_rate_type_error(fixture_client): """Test throws an error when rate_type is percent and transfer_amount is not between 0-100.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'cross_recoup', 'rate_type': 'percent', 'transfer_amount': '190.90', 'negative': False, 'active': False, } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert result.json['message'] == { '0': { 'transfer_amount': [ 'Transfer amount must be between 0 and 100 when rate type is percent.' ] } } @freeze_time(datetime(2026, 5, 5, 0, 0, 0, tzinfo=SYSTEM_TIMEZONE)) def test_create_earnings_transfer_for_percent_rate_type_success(fixture_client): """Test creates the earnings_transfer when rate_type is percent and transfer amount is between 0-100.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'cross_recoup', 'rate_type': 'percent', 'transfer_amount': '90.90', 'negative': False, 'active': False, } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 201 assert result.json == [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'cross_recoup', 'rate_type': 'percent', 'transfer_amount': '90.900000000000', 'input': None, 'negative': False, 'active': False, 'use_static_balance': False, 'comment': None, 'created_at': '2026-05-05', 'earnings_transfer_id': result.json[0]['earnings_transfer_id'], } ] def test_create_earnings_transfer_for_flat_rate_type_error(fixture_client): """Test throws an error when rate_type is flat_rate and transfer_amount is negative.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'cross_recoup', 'rate_type': 'flat_rate', 'transfer_amount': '-190.90', 'negative': False, 'active': False, } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert result.json['message'] == { '0': { 'transfer_amount': [ 'Transfer amount must be a positive value when rate type is flat_rate.' ] } } def test_create_earnings_transfer_for_flat_rate_input_error(fixture_client): """Test throws an error when rate_type is flat_rate and input is not closing_balance.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'cross_recoup', 'rate_type': 'flat_rate', 'transfer_amount': '190.90', 'negative': False, 'active': False, 'input': 'gross_revenue', } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert result.json['message'] == { '0': { 'input': [ 'Only closing_balance transfer source is allowed for flat_rate.', ] } } @freeze_time(datetime(2026, 5, 5, 0, 0, 0, tzinfo=SYSTEM_TIMEZONE)) def test_create_earnings_transfer_for_flat_rate_type_success(fixture_client): """Test creates the earnings_transfer when rate_type is flat_rate and transfer_amount is positive.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'cross_recoup', 'rate_type': 'flat_rate', 'transfer_amount': '90.90', 'negative': False, 'active': False, } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 201 assert result.json == [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'cross_recoup', 'rate_type': 'flat_rate', 'transfer_amount': '90.900000000000', 'input': None, 'negative': False, 'active': False, 'use_static_balance': False, 'comment': None, 'created_at': '2026-05-05', 'earnings_transfer_id': result.json[0]['earnings_transfer_id'], } ] @patch( 'royalties.blueprints.earnings_transfer.flask_request.verify_rules_access_standalone' ) def test_create_earnings_transfer_forbidden(mock_verify_rules, fixture_client): """Test that 403 is returned when the user is not authorized to create earnings transfer.""" mock_from_contract = ContractFactory.create() mock_to_contract = ContractFactory.create() mock_input = [ { 'from_contract_id': mock_from_contract.contract_id, 'to_contract_id': mock_to_contract.contract_id, 'transfer_type': 'cross_recoup', 'rate_type': 'flat_rate', 'transfer_amount': '90.90', 'negative': False, 'active': False, } ] mock_verify_rules.return_value = False result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 401 assert result.json['message'] == 'Unauthorized' def test_create_more_than_one_from_to_pair(fixture_client): """Test creating with more than one transfer for the same from/to pair.""" from_contract = ContractFactory.create() to_contract_1 = ContractFactory.create() mock_input = [ { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '10', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '20', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert ( result.json['message'] == 'Contract 1 cannot have more than one transfer of earnings configured for Contract 2' ) def test_create_more_than_one_from_to_pair_existing(fixture_client): """Test creating with more than one transfer for the same from/to pair.""" from_contract = ContractFactory.create() to_contract_1 = ContractFactory.create() mock_input = [ { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '10', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 201 mock_input = [ { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '20', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert ( result.json['message'] == 'Contract 1 cannot have more than one transfer of earnings configured for Contract 2' ) def test_create_total_percentage_above_100(fixture_client): """Test creating for a from_contract with a total percentage above 100.""" from_contract = ContractFactory.create() to_contract_1 = ContractFactory.create() to_contract_2 = ContractFactory.create() mock_input = [ { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '60', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_2.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '60', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert ( result.json['message'] == 'Contract 1 cannot transfer more than 100% of its earnings' ) def test_create_total_percentage_above_100_existing(fixture_client): """Test creating with more than one transfer for the same from/to pair.""" from_contract = ContractFactory.create() to_contract_1 = ContractFactory.create() to_contract_2 = ContractFactory.create() mock_input = [ { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '60', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 201 mock_input = [ { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_2.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '60', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', } ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert ( result.json['message'] == 'Contract 1 cannot transfer more than 100% of its earnings' ) def test_get_earnings_transfers_by_from_contract_id( mock_earnings_transfer_fixture, fixture_client ): """Test to get a list of earnings transfer by from contract_id.""" contract_id = 90002 result = fixture_client.get(f'/contract/{contract_id}/earnings-transfers') assert result.status_code == 200 assert result.json == { 'items': [ { 'from_contract_id': 90002, 'to_contract_id': 90004, 'transfer_type': 'override', 'rate_type': 'flat_rate', 'transfer_amount': '99.911000000000', 'input': 'gross_revenue', 'negative': False, 'active': False, 'use_static_balance': False, 'comment': 'Test Earnings Transfer', 'created_at': '2026-05-05', 'earnings_transfer_id': result.json['items'][0]['earnings_transfer_id'], } ], 'total_count': 1, } def test_get_earnings_transfers_by_to_contract_id( mock_earnings_transfer_fixture, fixture_client ): """Test to get a list of earnings transfer by to contract_id.""" contract_id = 90003 result = fixture_client.get(f'/contract/{contract_id}/earnings-transfers') assert result.status_code == 200 assert result.json == { 'items': [ { 'from_contract_id': 90001, 'to_contract_id': 90003, 'transfer_type': 'transfer', 'rate_type': 'percent', 'transfer_amount': '128.911000000000', 'input': 'net_revenue', 'negative': False, 'active': False, 'use_static_balance': False, 'comment': 'Test Earnings Transfer', 'created_at': '2026-05-05', 'earnings_transfer_id': result.json['items'][0]['earnings_transfer_id'], } ], 'total_count': 1, } def test_get_contract_earnings_transfers_by_transfer_type( mock_earnings_transfer_fixture, fixture_client ): """Test to get a list of earnings transfer by contract_id and transfer_type.""" contract_id = 90001 result = fixture_client.get(f'/contract/{contract_id}/earnings-transfers/transfer') assert result.status_code == 200 assert result.json == { 'items': [ { 'from_contract_id': 90001, 'to_contract_id': 90003, 'transfer_type': 'transfer', 'rate_type': 'percent', 'transfer_amount': '128.911000000000', 'input': 'net_revenue', 'negative': False, 'active': False, 'use_static_balance': False, 'comment': 'Test Earnings Transfer', 'created_at': '2026-05-05', 'earnings_transfer_id': result.json['items'][0]['earnings_transfer_id'], } ], 'total_count': 1, } def test_get_contract_earnings_transfers_by_invalid_transfer_type( mock_earnings_transfer_fixture, fixture_client ): """Test that an error is returned for invalid transfer type.""" contract_id = 90001 result = fixture_client.get(f'/contract/{contract_id}/earnings-transfers/test') assert result.status_code == 400 assert ( result.json['message'] == 'Invalid transfer_type. Must be one of: reclass, override, or transfer.' ) def test_get_earnings_transfer_by_invalid_contract_id( mock_earnings_transfer_fixture, fixture_client ): """Test that an error is returned for invalid transfer type.""" contract_id = 900001 result = fixture_client.get(f'/contract/{contract_id}/earnings-transfers/override') assert result.status_code == 404 assert result.json['message'] == f'Contract {contract_id} does not exist.' @patch( 'royalties.blueprints.earnings_transfer.flask_request.verify_rules_access_standalone' ) def test_get_earnings_transfers_by_contract_id_forbidden( mock_verify_rules, fixture_client ): """Test that 403 is returned when the user is not authorized to get earnings transfers.""" contract_id = 90003 mock_verify_rules.return_value = False result = fixture_client.get(f'/contract/{contract_id}/earnings-transfers') assert result.status_code == 401 assert result.json['message'] == 'Unauthorized' def test_get_empty_earnings_transfers_by_contract_transfer_type( mock_earnings_transfer_fixture, fixture_client ): """Test that an empty list is returned when there are no records for specified contract_id and transfer_type.""" contract_id = 90004 result = fixture_client.get(f'/contract/{contract_id}/earnings-transfers/transfer') assert result.status_code == 200 assert result.json == { 'items': [], 'total_count': 0, } def test_get_contract_earnings_transfers_by_invalid_sort_by( mock_earnings_transfer_fixture, fixture_client ): """Test that an error is returned for invalid sort_by query param.""" contract_id = 90004 result = fixture_client.get( f'/contract/{contract_id}/earnings-transfers/?sort_by=test' ) assert result.status_code == 400 assert result.json['message'] == str( {'sort_by': ['Must be one of earnings_transfer_id']} ) def test_update_earnings_transfer_success(fixture_client): """Test successful update of an earnings transfer.""" mock_earnings_transfer = EarningsTransferFactory.create(transfer_amount=100.0) update_data = [ { 'earnings_transfer_id': mock_earnings_transfer.earnings_transfer_id, 'comment': 'Updated comment', 'negative': True, } ] result = fixture_client.put( '/earnings-transfer/bulk', json=update_data, ) assert result.status_code == 200 assert len(result.json) == 1 assert ( result.json[0]['earnings_transfer_id'] == mock_earnings_transfer.earnings_transfer_id ) assert result.json[0]['comment'] == 'Updated comment' assert result.json[0]['negative'] def test_update_invalid_earnings_transfer_id(fixture_client): """Test update attempt with an invalid earnings transfer ID.""" update_data = [ { 'earnings_transfer_id': 9999, 'comment': 'Test invalid update', } ] invalid_id = 9999 result = fixture_client.put('/earnings-transfer/bulk', json=update_data) assert result.status_code == 400 assert ( result.json['message'] == f'The following earnings transfer IDs do not exist: [{invalid_id}]' ) def test_update_with_invalid_fields(fixture_client): """Test update attempt with invalid field values.""" mock_earnings_transfer = EarningsTransferFactory.create() update_data = [ { 'earnings_transfer_id': mock_earnings_transfer.earnings_transfer_id, 'transfer_amount': '-100.5', # Invalid negative value } ] result = fixture_client.put( '/earnings-transfer/bulk', json=update_data, ) assert result.status_code == 400 assert ( result.json['message'] == "[{'transfer_amount': ['Transfer amount must be between 0 and 100 when rate type is percent.']}]" ) def test_update_more_than_one_from_to_pair(fixture_client): """Test updating with more than one transfer for the same from/to pair.""" from_contract = ContractFactory.create() to_contract_1 = ContractFactory.create() to_contract_2 = ContractFactory.create() mock_input = [ { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '10', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_2.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '20', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) transfer_id = result.json[1]['earnings_transfer_id'] mock_input = [ { 'earnings_transfer_id': transfer_id, 'to_contract_id': to_contract_1.contract_id, }, ] result = fixture_client.put('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert ( result.json['message'] == 'Contract 1 cannot have more than one transfer of earnings configured for Contract 2' ) def test_update_total_percentage_above_100(fixture_client): """Test updating for a from_contract with a total percentage above 100.""" from_contract = ContractFactory.create() to_contract_1 = ContractFactory.create() to_contract_2 = ContractFactory.create() mock_input = [ { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_1.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '10', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, { 'from_contract_id': from_contract.contract_id, 'to_contract_id': to_contract_2.contract_id, 'transfer_type': 'override', 'rate_type': 'percent', 'transfer_amount': '20', 'input': 'closing_balance', 'negative': True, 'active': True, 'comment': 'Test comment', }, ] result = fixture_client.post('/earnings-transfer/bulk', json=mock_input) transfer_id = result.json[0]['earnings_transfer_id'] mock_input = [ { 'earnings_transfer_id': transfer_id, 'transfer_amount': '90', }, ] result = fixture_client.put('/earnings-transfer/bulk', json=mock_input) assert result.status_code == 400 assert ( result.json['message'] == 'Contract 1 cannot transfer more than 100% of its earnings' ) def test_get_earnings_transfer_by_id(fixture_client): """Test getting an earnings transfer by ID.""" mock_earnings_transfer = EarningsTransferFactory.create() transfer_id = mock_earnings_transfer.earnings_transfer_id result = fixture_client.get(f'/earnings-transfer/{transfer_id}') assert result.status_code == 200 assert result.json == EarningsTransferDetailSchema().dump(mock_earnings_transfer)