"""Test for TransferWise Transaction Persister.""" from datetime import datetime, timezone from decimal import Decimal from unittest.mock import ANY, patch from oto import status import pytest from collaborator.constants import error, transferwise_transfer from collaborator.models.rds.transferwise_transaction_persister import ( TransferwiseTransactionPersister, ) from collaborator.utils import logging from collaborator.utils.error import OwsError from tests.testutils import db @db.test_schema_default_seed @pytest.mark.parametrize( "batch_ids, expected_ids", [([1], [1]), ([1, 2], [1, 2]), ([9999], [])] ) def test_bulk_get_for_transferwise_batch_ids(batch_ids, expected_ids): """Test getting transaction based on batch ID.""" result = TransferwiseTransactionPersister.bulk_get_for_transferwise_batch_ids( batch_ids ) result_ids = [txn["transferwise_batch_id"] for txn in result] assert result_ids == expected_ids @db.test_schema_default_seed @pytest.mark.parametrize("batch_id, expected_ids", [(1, [1]), (2, [2]), (9999, [])]) def test_get_for_transferwise_batch_id(batch_id, expected_ids): """Test getting transaction based on batch ID.""" result = TransferwiseTransactionPersister.get_for_transferwise_batch_id(batch_id) result_ids = [txn["transferwise_batch_id"] for txn in result] assert result_ids == expected_ids @db.test_schema_default_seed def test_save_transactions(): """Test creating a TransferWise Transaction.""" transactions = [ { "profile_id": 1, "recipient_id": 1, "collaborator_id": 1, "transferwise_batch_id": 5, "transfer_id": 134, "quote_id": "81b5e5b0-e7d8-4112-aad9-b9189d53dcb2", "source_currency": "USD", "source_amount": 50, "target_currency": "USD", "target_amount": 50, "wire_fee": 0, "transferwise_fee": 0.5, "status": "COMPLETED", }, { "profile_id": 2, "recipient_id": 2, "collaborator_id": 2, "transferwise_batch_id": 5, "transfer_id": 135, "quote_id": "81b5e5b0-e7d8-4112-aad9-b9189d53dcb2", "source_currency": "USD", "source_amount": 50, "target_currency": "USD", "target_amount": 50, "status": "COMPLETED", }, ] expected_result = [ { "source_amount": Decimal("50.000000"), "target_amount": Decimal("50.000000"), "transferwise_batch_id": 5, "source_currency": "USD", "target_currency": "USD", "profile_id": 1, "quote_id": "81b5e5b0-e7d8-4112-aad9-b9189d53dcb2", "recipient_id": 1, "collaborator_id": 1, "status": "COMPLETED", "id": 20, "transfer_id": 134, "wire_fee": Decimal("0.00"), "transferwise_fee": Decimal("0.50"), "created_date": ANY, "conversion_rate": None, "status_updated_date": ANY, }, { "source_amount": Decimal("50.000000"), "target_amount": Decimal("50.000000"), "transferwise_batch_id": 5, "source_currency": "USD", "target_currency": "USD", "collaborator_id": 2, "profile_id": 2, "quote_id": "81b5e5b0-e7d8-4112-aad9-b9189d53dcb2", "recipient_id": 2, "status": "COMPLETED", "id": 21, "transfer_id": 135, "wire_fee": None, "transferwise_fee": None, "created_date": ANY, "conversion_rate": None, "status_updated_date": ANY, }, ] result = TransferwiseTransactionPersister.save_transactions(transactions) assert result == expected_result @db.test_schema_default_seed @patch.object(logging, "log_event") def test_update_transaction_status_not_found(log_event_mock): """Test updating transaction status when the transaction isn't found.""" with pytest.raises(OwsError) as error_info: TransferwiseTransactionPersister.update_transaction_status( "status", 10, datetime.now() ) assert error_info.value.status == status.NOT_FOUND assert error_info.value.code == error.ERROR_CODE_TRANSACTION_NOT_FOUND assert error_info.value.message == error.ERROR_MESSAGE_TRANSACTION_NOT_FOUND log_event_mock.assert_not_called() @db.test_schema_default_seed @patch.object(logging, "log_event") @pytest.mark.parametrize( "transfer_id, transfer_status, previous_transaction_state, expected_result", [ ( 2, transferwise_transfer.STATUS_OUTGOING_PAYMENT_SENT, { "source_amount": Decimal("40.000000"), "target_amount": Decimal("40.000000"), "transferwise_batch_id": 2, "source_currency": "USD", "target_currency": "USD", "profile_id": 12341235, "quote_id": "22222222-2222-2222-2222-222222222222", "recipient_id": 22223333, "status": "incoming_payment_waiting", "id": 2, "collaborator_id": 1, "transfer_id": 2, "wire_fee": Decimal("0.25"), "transferwise_fee": Decimal("0.25"), "created_date": datetime(2020, 5, 4, 3, 2, 1), "conversion_rate": None, "status_updated_date": datetime(2020, 5, 4, 3, 4, 1), }, { "source_amount": Decimal("40.000000"), "target_amount": Decimal("40.000000"), "transferwise_batch_id": 2, "source_currency": "USD", "target_currency": "USD", "profile_id": 12341235, "quote_id": "22222222-2222-2222-2222-222222222222", "recipient_id": 22223333, "status": transferwise_transfer.STATUS_OUTGOING_PAYMENT_SENT, "id": 2, "collaborator_id": 1, "transfer_id": 2, "wire_fee": Decimal("0.25"), "transferwise_fee": Decimal("0.25"), "created_date": datetime(2020, 5, 4, 3, 2, 1), "conversion_rate": None, "status_updated_date": datetime(2020, 5, 4, 3, 5, 1), }, ) ], ) def test_update_transaction_status( log_event_mock, transfer_id, transfer_status, previous_transaction_state, expected_result, ): """Test updating transaction status.""" occurred_at = datetime(2020, 5, 4, 3, 5, 1, tzinfo=timezone.utc) result = TransferwiseTransactionPersister.update_transaction_status( transfer_status, transfer_id, occurred_at ) assert result == expected_result log_event_mock.assert_called_with( logging.LOG_EVENT_UPDATE, "transferwise_transaction", expected_result["id"], previous_transaction_state, expected_result, None, ) @db.test_schema_default_seed @patch.object(logging, "log_event") @pytest.mark.parametrize( "transfer_id, transfer_status, occurred_at, previous_transaction_state", [ ( 19, transferwise_transfer.STATUS_PROCESSING, datetime.fromisoformat("2023-12-04T05:43:45Z"), { "id": 19, "profile_id": 12341234, "recipient_id": 22223334, "collaborator_id": 10, "transferwise_batch_id": 6, "transfer_id": 19, "quote_id": "19191919-1919-1919-1919-191919191919", "source_currency": "USD", "source_amount": Decimal("40.000000"), "target_currency": "USD", "target_amount": Decimal("40.000000"), "wire_fee": Decimal("0.25"), "transferwise_fee": Decimal("0.25"), "conversion_rate": None, "status": "funds_converted", "created_date": datetime(2023, 12, 4, 5, 40, 30), "status_updated_date": datetime(2023, 12, 4, 5, 43, 49), }, ) ], ) def test_update_transaction_status_desynced_old_event( log_event_mock, transfer_id, transfer_status, occurred_at, previous_transaction_state, ): """Test ignoring old transaction event.""" result = TransferwiseTransactionPersister.update_transaction_status( transfer_status, transfer_id, occurred_at ) assert result == previous_transaction_state log_event_mock.assert_not_called() @db.test_schema_default_seed def test_get_transaction_id_by_transfer_id(): """Test getting a transaction by transfer id.""" expected_result = { "source_amount": Decimal("40.000000"), "target_amount": Decimal("40.000000"), "transferwise_batch_id": 2, "source_currency": "USD", "target_currency": "USD", "profile_id": 12341235, "quote_id": "22222222-2222-2222-2222-222222222222", "recipient_id": 22223333, "status": "incoming_payment_waiting", "id": 2, "collaborator_id": 1, "transfer_id": 2, "wire_fee": Decimal("0.25"), "transferwise_fee": Decimal("0.25"), "created_date": datetime(2020, 5, 4, 3, 2, 1), "conversion_rate": None, "status_updated_date": datetime(2020, 5, 4, 3, 4, 1), } transfer_id = 2 persister = TransferwiseTransactionPersister result = persister.get_by_transfer_id(transfer_id) assert result == expected_result @db.test_schema_default_seed def test_get_transaction_id_by_transfer_id_not_found(): """Test getting a transaction when transaction isn't found.""" transfer_id = 6 persister = TransferwiseTransactionPersister with pytest.raises(OwsError) as error_info: persister.get_by_transfer_id(transfer_id) assert error_info.value.status == status.NOT_FOUND assert error_info.value.code == error.ERROR_CODE_TRANSACTION_NOT_FOUND assert error_info.value.message == error.ERROR_MESSAGE_TRANSACTION_NOT_FOUND @db.test_schema_default_seed def test_get_collaborator_payments_incoming_payment_waiting_status(): """Test getting collaborator transactions with a pending status.""" expected_result = [ { "collaborator_id": 10, "conversion_rate": None, "created_date": "2020-05-04T03:03:01", "id": 17, "profile_id": 12341235, "quote_id": "17171717-1717-1717-1717-171717171717", "recipient_id": 22223333, "source_amount": 40.0, "source_currency": "USD", "status": "incoming_payment_waiting", "status_updated_date": "2020-05-04T03:04:01", "target_amount": 40.0, "target_currency": "USD", "transfer_id": 17, "transferwise_batch_id": 5, "transferwise_fee": 0.25, "wire_fee": 0.25, } ] persister = TransferwiseTransactionPersister result, total_records = persister.get_collaborator_payments( 120594, 10, ["incoming_payment_waiting"], 0, 0, None, None ) assert result == expected_result assert total_records == 1 @db.test_schema_default_seed def test_get_collaborator_payments_cancelled_status(): """Test getting collaborator transactions with cancelled status.""" expected_result = [ { "collaborator_id": 10, "conversion_rate": None, "created_date": "2020-05-04T03:02:01", "id": 18, "profile_id": 12341235, "quote_id": "18181818-1818-1818-1818-181818181818", "recipient_id": 22223333, "source_amount": 40.0, "source_currency": "USD", "status": "cancelled", "status_updated_date": "2020-05-04T03:04:01", "target_amount": 40.0, "target_currency": "USD", "transfer_id": 18, "transferwise_batch_id": 5, "transferwise_fee": 0.25, "wire_fee": 0.25, } ] persister = TransferwiseTransactionPersister result, total_records = persister.get_collaborator_payments( 120594, 10, ["cancelled"], 0, 0, None, None ) assert result == expected_result assert total_records == 1 @db.test_schema_default_seed def test_get_collaborator_payments(): """Test getting collaborator payments.""" expected_result = [ { "collaborator_id": 10, "conversion_rate": None, "created_date": "2020-05-04T03:03:01", "id": 17, "profile_id": 12341235, "quote_id": "17171717-1717-1717-1717-171717171717", "recipient_id": 22223333, "source_amount": 40.0, "source_currency": "USD", "status": "incoming_payment_waiting", "status_updated_date": "2020-05-04T03:04:01", "target_amount": 40.0, "target_currency": "USD", "transfer_id": 17, "transferwise_batch_id": 5, "transferwise_fee": 0.25, "wire_fee": 0.25, }, { "collaborator_id": 10, "conversion_rate": None, "created_date": "2020-05-04T03:02:01", "id": 18, "profile_id": 12341235, "quote_id": "18181818-1818-1818-1818-181818181818", "recipient_id": 22223333, "source_amount": 40.0, "source_currency": "USD", "status": "cancelled", "status_updated_date": "2020-05-04T03:04:01", "target_amount": 40.0, "target_currency": "USD", "transfer_id": 18, "transferwise_batch_id": 5, "transferwise_fee": 0.25, "wire_fee": 0.25, }, ] persister = TransferwiseTransactionPersister result, total_records = persister.get_collaborator_payments( 120594, 10, [], 0, 0, None, None ) assert result == expected_result assert total_records == 2 @db.test_schema_default_seed def test_get_by_quote_ids_for_vendor_profile_returns_match(): """Test returns the transaction when vendor, profile, and quote ID all match.""" result = TransferwiseTransactionPersister.get_by_quote_ids_for_vendor_profile( 24601, 12341235, ["22222222-2222-2222-2222-222222222222"] ) assert len(result) == 1 assert result[0]["quote_id"] == "22222222-2222-2222-2222-222222222222" assert result[0]["transferwise_batch_id"] == 2 @db.test_schema_default_seed def test_get_by_quote_ids_for_vendor_profile_empty_quote_ids(): """Test returns empty list without hitting the DB when quote_ids is empty.""" result = TransferwiseTransactionPersister.get_by_quote_ids_for_vendor_profile( 24601, 12341235, [] ) assert result == [] @db.test_schema_default_seed def test_get_by_quote_ids_for_vendor_profile_wrong_vendor(): """Test does not return a transaction whose batch belongs to a different vendor.""" # quote "17171717-..." belongs to batch 5 (vendor 120594), not vendor 24601 result = TransferwiseTransactionPersister.get_by_quote_ids_for_vendor_profile( 24601, 12341235, ["17171717-1717-1717-1717-171717171717"] ) assert result == [] @db.test_schema_default_seed def test_get_by_quote_ids_for_vendor_profile_wrong_profile(): """Test does not return a transaction when the profile_id does not match.""" result = TransferwiseTransactionPersister.get_by_quote_ids_for_vendor_profile( 24601, 99999, ["22222222-2222-2222-2222-222222222222"] ) assert result == [] @db.test_schema_default_seed def test_get_by_quote_ids_for_vendor_profile_returns_multiple(): """Test returns all transactions when multiple quote IDs match.""" result = TransferwiseTransactionPersister.get_by_quote_ids_for_vendor_profile( 120594, 12341235, [ "17171717-1717-1717-1717-171717171717", "18181818-1818-1818-1818-181818181818", ], ) assert len(result) == 2 returned_quote_ids = {t["quote_id"] for t in result} assert returned_quote_ids == { "17171717-1717-1717-1717-171717171717", "18181818-1818-1818-1818-181818181818", } @db.test_schema_default_seed def test_get_payments_for_vendor(): """Test getting payments for a particular vendor.""" expected_result = [ { "collaborator_id": 4, "conversion_rate": None, "created_date": "2020-05-04T03:02:01", "id": 16, "profile_id": 12341235, "quote_id": "16161616-1616-1616-1616-161616161616", "recipient_id": 22223333, "source_amount": 40.0, "source_currency": "USD", "status": "cancelled", "status_updated_date": "2020-05-04T03:04:01", "target_amount": 40.0, "target_currency": "USD", "transfer_id": 16, "transferwise_batch_id": 4, "transferwise_fee": 0.25, "wire_fee": 0.25, } ] persister = TransferwiseTransactionPersister result, total_records = persister.get_collaborator_payments( 25153, None, [], 0, 0, None, None ) assert result == expected_result assert total_records == 1