"""Test reserve payout flow tasks.""" import decimal import itertools import json from unittest import mock import pytest from accounting.flows.reserve_payouts import tasks @pytest.mark.parametrize('validate_only, expected_validate_only', [ (None, False), (False, False), (True, True), ]) @pytest.mark.parametrize( 'skip_prepare_label_data, expected_skip_prepare_label_data', [ (None, False), (True, True), (False, False), ]) @pytest.mark.parametrize('period_id', [227, 228]) @mock.patch('accounting.flows.reserve_payouts.tasks.notify_status') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb') @mock.patch('accounting.flows.reserve_payouts.tasks.sqs') @mock.patch('accounting.flows.reserve_payouts.tasks.mysql') @mock.patch('accounting.flows.reserve_payouts.tasks.ows_manual_adjustment') @mock.patch('accounting.flows.reserve_payouts.tasks.setting') def test_bootstrap( setting, ows_manual_adjustment, mysql, sqs, dynamodb, notify_status, period_id, skip_prepare_label_data, expected_skip_prepare_label_data, validate_only, expected_validate_only): """Test bootstrap task.""" kwargs = {} if skip_prepare_label_data is not None: kwargs['skip_prepare_label_data'] = skip_prepare_label_data if validate_only is not None: kwargs['validate_only'] = validate_only test_db = 'test_db' test_db2 = 'test_db2' setting.DATABASES = [test_db, test_db2] activity = mock.MagicMock() response = tasks.bootstrap(activity, period_id, **kwargs) expected_response = { 'period_id': period_id, 'skip_prepare_label_data': expected_skip_prepare_label_data, 'validate_only': expected_validate_only, } assert response == expected_response notify_status.assert_not_called() for connector in (ows_manual_adjustment, sqs, dynamodb): health_check = getattr(connector, 'health_check') health_check.assert_called_once() mysql_calls = [mock.call(test_db), mock.call(test_db2)] mysql.health_check.assert_has_calls(mysql_calls, any_order=True) def test_bootstrap_no_period_id(): """Test that assert raises if the period id is None.""" activity = mock.MagicMock() with pytest.raises(AssertionError): tasks.bootstrap(activity, None) @mock.patch('accounting.flows.reserve_payouts.tasks.notify_status') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb') @mock.patch('accounting.flows.reserve_payouts.tasks.sqs') @mock.patch('accounting.flows.reserve_payouts.tasks.mysql') @mock.patch('accounting.flows.reserve_payouts.tasks.ows_manual_adjustment') @mock.patch('accounting.flows.reserve_payouts.tasks.setting') def test_bootstrap_healt_check_fail( setting, ows_manual_adjustment, mysql, sqs, dynamodb, notify_status): """Test health_check failure invokes notify_status.""" setting.DATABASES = ['test_db'] expected_message = 'ows-manual-adjustment has failed.' health_check_failure = mock.MagicMock(bool=False, message=expected_message) sqs.health_check.return_value = health_check_failure expected_response = {'stop': True, 'error_message': expected_message} activity = mock.MagicMock() response = tasks.bootstrap(activity, 222) assert response == expected_response notify_status.assert_called_with('error', 'bootstrap', expected_message) @pytest.mark.parametrize('period_id', [217, 218, 219]) @mock.patch('accounting.flows.reserve_payouts.tasks.mysql') def test_get_vendor_transactions_and_contract_terms( mysql, period_id): """Test get_vendor_transactions_and_contract_terms function.""" physical_transactions = mock.MagicMock() mysql.get_physical_transactions_sum.return_value = physical_transactions physical_transactions_dict = { period_id: {'some_key': 'some_value'} } contract_details = mock.MagicMock() mysql.get_vendor_contracts.return_value = contract_details contract_details_dict = { period_id: {'another_key': 'another_value'} } mysql.vendor_query_result_to_dict.side_effect = [ physical_transactions_dict, contract_details_dict] expected_result = { period_id: { 'some_key': 'some_value', 'another_key': 'another_value', } } actual_result = tasks.get_vendor_transactions_and_contract_terms( period_id) assert actual_result == expected_result mysql.get_physical_transactions_sum.assert_called_with(period_id) mysql.get_vendor_contracts.assert_called_with( period_id, expected_result.keys()) vendor_query_result_to_dict_calls = [ mock.call(physical_transactions), mock.call(contract_details)] mysql.vendor_query_result_to_dict.assert_has_calls( vendor_query_result_to_dict_calls, any_order=True) @mock.patch('accounting.flows.reserve_payouts.tasks.mysql') def test_get_vendor_transactions_and_contract_terms_no_label_data(mysql): """Test get_vendor_transactions_and_contract_terms function.""" physical_transactions = mock.MagicMock() mysql.get_physical_transactions_sum.return_value = physical_transactions mysql.vendor_query_result_to_dict.return_value = {} period_id = 222 actual_result = tasks.get_vendor_transactions_and_contract_terms( period_id) assert actual_result == {} mysql.get_physical_transactions_sum.assert_called_with(period_id) mysql.get_vendor_contracts.assert_not_called() @pytest.mark.parametrize('dynamodb_item_count, sqs_item_count', [ (0, 0), (1, 0), (4242, 4242), (42, 4242) ]) @mock.patch('accounting.flows.reserve_payouts.tasks.notify_status') @mock.patch('accounting.flows.reserve_payouts.tasks.sqs') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb') def test_check_dynamodb_sqs_item_count( dynamodb, sqs, notify_status, dynamodb_item_count, sqs_item_count): """Test check_dynamodb_sqs_item_count funciton.""" task_name = 'prepare_label_data' dynamodb.get_table_items_count.return_value = dynamodb_item_count sqs.get_queue_message_count.return_value = sqs_item_count msg = ( 'DynamoDB item count and SQS message count mismatch, ' 'DynamoDB: {dynamodb_count} SQS: {sqs_count}') msg = msg.format( dynamodb_count=dynamodb_item_count, sqs_count=sqs_item_count) result = tasks.check_dynamodb_sqs_item_count(task_name) assert result['dynamodb_count'] == dynamodb_item_count assert result['sqs_count'] == sqs_item_count if dynamodb_item_count == sqs_item_count: notify_status.assert_not_called() assert 'stop' not in result else: notify_status.assert_called_with( 'error', task_name, msg) assert result['stop'] @pytest.mark.parametrize('period_id', [222, 4242]) @mock.patch('accounting.flows.reserve_payouts.tasks.notify_status') @mock.patch( 'accounting.flows.reserve_payouts.tasks.sqs.check_dynamodb_sqs_item_count') @mock.patch('accounting.flows.reserve_payouts.tasks.sqs') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb') @mock.patch( 'accounting.flows.reserve_payouts.tasks' '.get_vendor_transactions_and_contract_terms') def test_prepare_label_data( get_vendor_transactions_and_contract_terms, dynamodb, sqs, check_dynamodb_sqs_item_count, notify_status, period_id): """Test prepare_label_data task.""" activity = mock.MagicMock() vendor_data = mock.MagicMock() vendor_1_data = mock.MagicMock() vendor_2_data = mock.MagicMock() vendor_message_list = [vendor_1_data, vendor_2_data] len_data = len(vendor_message_list) vendor_data.__len__.return_value = len_data vendor_data.values.return_value = iter([vendor_1_data, vendor_2_data]) sqs.construct_vendor_message.side_effect = vendor_message_list get_vendor_transactions_and_contract_terms.return_value = vendor_data check_result = {} check_dynamodb_sqs_item_count.return_value = check_result # execute the task result = tasks.prepare_label_data(activity, period_id) assert result['vendr_items_count'] == len_data get_vendor_transactions_and_contract_terms.assert_called_once_with( period_id) dynamodb.batch_write_vendor_data.assert_called_once_with(vendor_data) vendor_data.values.assert_called_once() expected_construct_message_calls = [ mock.call(vendor_1_data), mock.call(vendor_2_data), ] sqs.construct_vendor_message.assert_has_calls( expected_construct_message_calls, any_order=True) sqs.batch_vendor_send_messages.assert_called_with(vendor_message_list) @mock.patch( 'accounting.flows.reserve_payouts.tasks.sqs.check_dynamodb_sqs_item_count') @mock.patch('accounting.flows.reserve_payouts.tasks.sqs') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb') @mock.patch( 'accounting.flows.reserve_payouts.tasks' '.get_vendor_transactions_and_contract_terms') def test_prepare_label_data_no_transactions( get_vendor_transactions_and_contract_terms, dynamodb, sqs, check_dynamodb_sqs_item_count): """Test prepare_label_data task.""" activity = mock.MagicMock() get_vendor_transactions_and_contract_terms.return_value = [] # execute the task period_id = 222 result = tasks.prepare_label_data(activity, period_id) assert result['stop'] assert result['reason'] == 'No data.' get_vendor_transactions_and_contract_terms.assert_called_once_with( period_id) dynamodb.batch_write_vendor_data.assert_not_called() sqs.construct_vendor_message.assert_not_called() sqs.batch_vendor_send_messages.assert_not_called() @mock.patch('accounting.flows.reserve_payouts.tasks.task_constants') @mock.patch('accounting.flows.reserve_payouts.tasks.notify_status') @mock.patch('accounting.flows.reserve_payouts.tasks.sqs') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb') def test_validate_reserve_payout_calculation( dynamodb, sqs, notify_status, task_constants): """Test validate_reserve_payout_calculation task.""" expected_success_message = 'expected success message' task_constants.SUCCESS_FLOW_COMPLETION_MESSAGE = expected_success_message activity = mock.MagicMock() sqs.get_queue_message_count.return_value = 0 dynamodb.validate_successful_items_count.return_value = True actual_response = tasks.validate_reserve_payout_calculation(activity) assert actual_response == {} sqs.get_queue_message_count.assert_called_once() dynamodb.validate_successful_items_count.assert_called_once() notify_status.assert_called_once_with( 'success', 'validate_reserve_payout_calculation', expected_success_message) @pytest.mark.parametrize('sqs_count, dynamodb_validation, expected_message', [ (0, False, 'Validation failure, DynamoDB items have other than SUCCESS status.'), (1, True, 'Validation failure, not all SQS messages were consumed.'), ]) @mock.patch('accounting.flows.reserve_payouts.tasks.notify_status') @mock.patch('accounting.flows.reserve_payouts.tasks.sqs') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb') def test_validate_reserve_payout_calculation_failure( dynamodb, sqs, notify_status, sqs_count, dynamodb_validation, expected_message): """Test validate_reserve_payout_calculation task.""" activity = mock.MagicMock() sqs.get_queue_message_count.return_value = sqs_count dynamodb.validate_successful_items_count.return_value = dynamodb_validation expected_result = { 'stop': True, 'validation_error_message': expected_message} result = tasks.validate_reserve_payout_calculation(activity) assert result == expected_result sqs.get_queue_message_count.assert_called_once() dynamodb.validate_successful_items_count.assert_called_once() notify_status.assert_called_with( 'error', 'validate_reserve_payout_calculation', expected_message) @pytest.mark.parametrize('data, expected_held, expected_payouts', [ # case with no reserve and no payout ( { 'amount_in_original_currency': decimal.Decimal('60.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'exchange_rate': 1, 'reserve_rate': decimal.Decimal('0.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'number_of_months_before_payout': 1, 'number_of_installments': 1, 'period_id': 223 }, None, [] ), # case with 1 payout ( { 'amount_in_original_currency': decimal.Decimal('60.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'exchange_rate': 1, 'reserve_rate': decimal.Decimal('0.2').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'number_of_months_before_payout': 1, 'number_of_installments': 1, 'period_id': 223 }, { 'amount': decimal.Decimal('-12').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('-12.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 223, 'apply_to_period_id': 223, }, [{ 'amount': decimal.Decimal('12').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('12').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 223, 'apply_to_period_id': 224, }] ), # case with 3 payouts ( { 'amount_in_original_currency': decimal.Decimal('60.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'exchange_rate': 1, 'reserve_rate': decimal.Decimal('0.2').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'number_of_months_before_payout': 1, 'number_of_installments': 3, 'period_id': 225 }, { 'amount': decimal.Decimal('-12').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('-12.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 225, }, [ { 'amount': decimal.Decimal('4').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('4').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 226, }, { 'amount': decimal.Decimal('4').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('4').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 227, }, { 'amount': decimal.Decimal('4').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('4').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 228, } ] ), # two payouts and the delay ( { 'amount_in_original_currency': decimal.Decimal('100.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'exchange_rate': 1, 'reserve_rate': decimal.Decimal('0.3').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'number_of_months_before_payout': 3, 'number_of_installments': 2, 'period_id': 225 }, { 'amount': decimal.Decimal('-30').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('-30.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 225, }, [ { 'amount': decimal.Decimal('15').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('15').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 228, }, { 'amount': decimal.Decimal('15').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('15').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 229, }, ] ), # two payouts and exchange rate ( { 'amount_in_original_currency': decimal.Decimal('100.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'exchange_rate': decimal.Decimal('1.2').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'reserve_rate': decimal.Decimal('0.3').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'number_of_months_before_payout': 3, 'number_of_installments': 2, 'period_id': 225 }, { 'amount': decimal.Decimal('-36').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('-30.0').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 225, }, [ { 'amount': decimal.Decimal('18').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('15').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 228, }, { 'amount': decimal.Decimal('18').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'amount_in_original_currency': decimal.Decimal('15').quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP), 'adjust_for_period_id': 225, 'apply_to_period_id': 229, }, ] ), ]) @pytest.mark.parametrize('vendor_id, currencies_id', [ (1234, 1), (2345, 2), ]) @mock.patch( 'accounting.flows.reserve_payouts.tasks.ows_manual_adjustment_constants') def test_calculate_reserve_and_payout_values( ows_manual_adjustment_constants, vendor_id, currencies_id, data, expected_held, expected_payouts): """Test calculate_reserve_payout function.""" reserve_held = 'reserve held' reserve_released = 'reserve_released' ows_manual_adjustment_constants.RESERVE_HELD = reserve_held ows_manual_adjustment_constants.RESERVE_RELEASED = reserve_released data['vendor_id'] = vendor_id data['currencies_id'] = currencies_id if expected_held: expected_held.update({ 'category_id': reserve_held, 'parent_id': vendor_id, 'currencies_id': currencies_id, }) for payout in expected_payouts: payout.update({ 'category_id': reserve_released, 'parent_id': vendor_id, 'currencies_id': currencies_id}) if expected_held: expected_held_list = [expected_held] else: expected_held_list = [] expected_result = expected_held_list + expected_payouts period_id = data['period_id'] result = tasks.calculate_reserve_and_payout_values(period_id, data) assert result == expected_result @mock.patch( 'accounting.flows.reserve_payouts.tasks' '.calculate_reserve_and_payout_values') @mock.patch( 'accounting.flows.reserve_payouts.tasks.ows_manual_adjustment') def test_process_reserve_payout( ows_manual_adjustment, calculate_reserve_and_payout_values): """Test process_reserve_payout helper function.""" data = {1: 1, 2: 2} period_id = 4242 calculate_reserve_and_payout_values.return_value = [1, 2] post_response = mock.MagicMock(status_code=200) ows_manual_adjustment.post_manual_adjustment.return_value = post_response num_adjustments, failed_adjustments = tasks.process_reserve_payout( period_id, data) assert num_adjustments == 2 assert failed_adjustments == [] calculate_reserve_and_payout_values.assert_called_once_with( period_id, data) expected_post_manual_adjustment_calls = [ mock.call(1), mock.call(2) ] ows_manual_adjustment.post_manual_adjustment.assert_has_calls( expected_post_manual_adjustment_calls) @mock.patch( 'accounting.flows.reserve_payouts.tasks' '.calculate_reserve_and_payout_values') @mock.patch( 'accounting.flows.reserve_payouts.tasks.ows_manual_adjustment') def test_process_reserve_payout_raises( ows_manual_adjustment, calculate_reserve_and_payout_values): """Test process_reserve_payout helper function.""" data = {1: 1} period_id = 4242 calculate_reserve_and_payout_values.return_value = [{'category_id': 69}] ows_manual_adjustment.post_manual_adjustment.side_effect = Exception() with pytest.raises(Exception): tasks.process_reserve_payout(period_id, data) @pytest.mark.parametrize( 'adjustments, status_codes, expected_failed', [ # one success and one failure ( ({'category_id': 42}, {'category_id': 42}), (200, 400), [{ 'category': 'Reserve held', 'manual_adjustment': {'category_id': 42}, 'response_status_code': 400, 'response_text': 'test', }] ), # two failures ( ({'category_id': 42}, {'category_id': 42}), (400, 400), [ { 'category': 'Reserve held', 'manual_adjustment': {'category_id': 42}, 'response_status_code': 400, 'response_text': 'test', }, { 'category': 'Reserve held', 'manual_adjustment': {'category_id': 42}, 'response_status_code': 400, 'response_text': 'test', } ] ), # two failures different categories ( ({'category_id': 41}, {'category_id': 42}), (400, 400), [ { 'category': 'Reserve released', 'manual_adjustment': {'category_id': 41}, 'response_status_code': 400, 'response_text': 'test', }, { 'category': 'Reserve held', 'manual_adjustment': {'category_id': 42}, 'response_status_code': 400, 'response_text': 'test', } ] ), ]) @mock.patch( 'accounting.flows.reserve_payouts.tasks' '.calculate_reserve_and_payout_values') @mock.patch( 'accounting.flows.reserve_payouts.tasks.ows_manual_adjustment_constants') @mock.patch( 'accounting.flows.reserve_payouts.tasks.ows_manual_adjustment') def test_process_reserve_payout_failed_adjustments( ows_manual_adjustment, ows_manual_adjustment_constants, calculate_reserve_and_payout_values, adjustments, status_codes, expected_failed): """Test process_reserve_payout helper function with failed adjustments.""" period_id = 4141 ows_manual_adjustment_constants.RESERVE_HELD = 42 ows_manual_adjustment_constants.RESERVE_RELEASED = 41 dummy_data = {1: 1} calculate_reserve_and_payout_values.return_value = adjustments post_responses = [mock.MagicMock(status_code=status_code, text='test') for status_code in status_codes] ows_manual_adjustment.post_manual_adjustment.side_effect = post_responses num_adjustments, failed_adjustments = tasks.process_reserve_payout( period_id, dummy_data) assert num_adjustments == len(adjustments) assert failed_adjustments == expected_failed calculate_reserve_and_payout_values.assert_called_once_with( period_id, dummy_data) expected_post_manual_adjustment_calls = [ mock.call(adjustment) for adjustment in adjustments ] ows_manual_adjustment.post_manual_adjustment.assert_has_calls( expected_post_manual_adjustment_calls) @pytest.mark.parametrize( 'vendor_id, failures, total_adjustments, expected_failure_msg', [ (4141, [{'category': 'Reserve held'}, {'category': 'Reserve released'}], 2, '4141 Reserve held, Reserve released 2 2'), (4242, [{'category': 'Reserve held'}], 5, '4242 Reserve held 1 5'), ]) @mock.patch('accounting.flows.reserve_payouts.tasks.task_constants') def test_get_calculation_failure_msg( task_constants, vendor_id, failures, total_adjustments, expected_failure_msg): """Test get_calculation_failure_msg helper function.""" msg_template = ( '{vendor_id} {failed_categories} {num_failed} {num_total}') task_constants.VENDOR_CALCULATION_FAIL_MSG_TEMPLATE = msg_template result = tasks.get_calculation_failure_msg( vendor_id, total_adjustments, failures) assert result == expected_failure_msg @pytest.mark.parametrize('period_id', [222, 333]) @mock.patch('accounting.flows.reserve_payouts.tasks.notify_status') @mock.patch('accounting.flows.reserve_payouts.tasks.sqs') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb_constants') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb') @mock.patch('accounting.flows.reserve_payouts.tasks.process_reserve_payout') def test_calculate_reserve_payouts( process_reserve_payout, dynamodb, dynamodb_constants, sqs, notify_status, period_id): """Test calculate_reserve_payouts task.""" processing_status = 'TEST PROCESSING' success_status = 'TEST SUCCESS' dynamodb_constants.PROCESSING = processing_status dynamodb_constants.SUCCESS = success_status message = mock.MagicMock(body='{"vendor_id": 4242, "category_id": 69}') sqs.get_message.side_effect = [message, None] process_reserve_payout.return_value = (1, []) acivity = mock.MagicMock() result = tasks.calculate_reserve_payouts(acivity, period_id) assert result == {} process_reserve_payout.assert_called_with( period_id, {'vendor_id': 4242, 'category_id': 69}) message.delete.assert_has_calls([mock.call()]) notify_status.assert_not_called() expected_update_status_calls = [ mock.call(4242, processing_status), mock.call(4242, success_status) ] dynamodb.update_vendor_status.assert_has_calls( expected_update_status_calls) @pytest.mark.parametrize('messages, failures', [ ([ {'vendor_id': 4141}, {'vendor_id': 4242}], [{'vendor_id': 4242}]), ([ {'vendor_id': 4141}, {'vendor_id': 4242}], [{'vendor_id': 4141}, {'vendor_id': 4242}]), ]) @mock.patch( 'accounting.flows.reserve_payouts.tasks.get_calculation_failure_msg') @mock.patch('accounting.flows.reserve_payouts.tasks.notify_status') @mock.patch('accounting.flows.reserve_payouts.tasks.sqs') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb_constants') @mock.patch('accounting.flows.reserve_payouts.tasks.dynamodb') @mock.patch('accounting.flows.reserve_payouts.tasks.process_reserve_payout') def test_calculate_reserve_payouts_failed_adjustments( process_reserve_payout, dynamodb, dynamodb_constants, sqs, notify_status, get_calculation_failure_msg, messages, failures): """Test calculate_reserve_payouts task with failed adjustments.""" period_id = 222 processing_status = 'TEST PROCESSING' success_status = 'TEST SUCCESS' error_status = 'TEST ERROR' dynamodb_constants.PROCESSING = processing_status dynamodb_constants.SUCCESS = success_status dynamodb_constants.ERROR = error_status expected_failure_msg = 'FAILURES MESSAGE' get_calculation_failure_msg.return_value = expected_failure_msg for message in itertools.chain(messages, failures): message['category_id'] = 69 mock_messages = [mock.MagicMock(body=json.dumps(message)) for message in messages] sqs.get_message.side_effect = mock_messages + [None] process_return_values = [] for message in messages: if message not in failures: process_return_values.append((len(messages), [])) else: process_return_values.append((len(messages), [message])) process_reserve_payout.side_effect = process_return_values acivity = mock.MagicMock() result = tasks.calculate_reserve_payouts(acivity, period_id) expected_result = { 'failed_adjustments': [expected_failure_msg for _ in failures], 'failed_adjustments_details': failures, } assert result == expected_result expected_process_reserve_payout_calls = [ mock.call(period_id, message) for message in messages] process_reserve_payout.assert_has_calls( expected_process_reserve_payout_calls) for message in mock_messages: message.delete.assert_has_calls([mock.call()]) expected_notify_status_msg = '\n'.join( [expected_failure_msg for _ in failures]) notify_status.assert_called_once_with( 'error', 'calculate_reserve_payouts', expected_notify_status_msg) expected_update_status_calls = [] for message in messages: expected_update_status_calls.append( mock.call(message['vendor_id'], processing_status)) if message in failures: expected_update_status_calls.append( mock.call(message['vendor_id'], error_status)) else: expected_update_status_calls.append( mock.call(message['vendor_id'], success_status)) dynamodb.update_vendor_status.assert_has_calls( expected_update_status_calls) @pytest.mark.parametrize('status', ['error, success']) @pytest.mark.parametrize('task, message, expected_message', [ ('test_task', 'error message', 'test_task: error message'), ('another task', 'another error', 'another task: another error') ]) @mock.patch('accounting.flows.reserve_payouts.tasks.sns') def test_notify_status(sns, task, message, expected_message, status): """Test notify_status helper function.""" tasks.notify_status(status, task, message) sns.send_status_message.assert_called_with(status, expected_message) @pytest.mark.parametrize('rowcount, expected_stop', [ (0, True), (1, False), (4242, False), ]) @mock.patch('accounting.flows.reserve_payouts.tasks.logging') @mock.patch('accounting.flows.reserve_payouts.tasks.mysql') def test_prepare_temp_table(mysql, logging, rowcount, expected_stop): """Test prepare_temp_table task.""" period_id = 4242 mock_activity = mock.MagicMock() mysql.populate_reserves_temp_table.return_value = rowcount expected_response = { 'stop': expected_stop, 'rowcount': rowcount, } response = tasks.prepare_temp_table(mock_activity, period_id) assert response == expected_response mysql.truncate_reserves_temp_table.assert_has_calls([mock.call()]) mysql.populate_reserves_temp_table.assert_called_once_with_(period_id)