From 7f1230ff16845b4becd652ddbd9a200e2fbe9bbf Mon Sep 17 00:00:00 2001 From: Juliette Date: Tue, 26 May 2026 18:54:00 -0600 Subject: [PATCH 1/2] implement transfer status update functionality in the Transfer class with corresponding tests. --- cuenca/resources/transfers.py | 31 ++++++++++++++-- cuenca/version.py | 2 +- requirements.txt | 2 +- setup.py | 2 +- .../test_transfers_update_failed.yaml | 35 +++++++++++++++++++ .../test_transfers_update_succeeded.yaml | 35 +++++++++++++++++++ tests/resources/test_transfers.py | 30 ++++++++++++++++ 7 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 tests/resources/cassettes/test_transfers_update_failed.yaml create mode 100644 tests/resources/cassettes/test_transfers_update_succeeded.yaml diff --git a/cuenca/resources/transfers.py b/cuenca/resources/transfers.py index bde76ac3..ddf441ad 100644 --- a/cuenca/resources/transfers.py +++ b/cuenca/resources/transfers.py @@ -1,21 +1,24 @@ import datetime as dt -from typing import ClassVar, Optional, cast +from typing import ClassVar, Literal, Optional, cast from cuenca_validations.types import ( + TransactionStatus, TransferNetwork, TransferQuery, TransferRequest, + UpdateTransferRequest, ) from cuenca_validations.typing import DictStrAny from requests import HTTPError from ..exc import CuencaException +from ..http import Session, session as global_session from .accounts import Account -from .base import Creatable, Transaction +from .base import Creatable, Transaction, Updateable from .resources import retrieve_uri -class Transfer(Transaction, Creatable): +class Transfer(Transaction, Creatable, Updateable): _resource: ClassVar = 'transfers' _query_params: ClassVar = TransferQuery @@ -71,6 +74,28 @@ def create( ) return cls._create(**req.model_dump()) + @classmethod + def update( + cls, + transfer_id: str, + status: Literal[ + TransactionStatus.succeeded, + TransactionStatus.failed, + ], + *, + session: Session = global_session, + ) -> 'Transfer': + """ + Updates the status of a held transfer. + + :param transfer_id: existing transfer_id + :param status: TransactionStatus.succeeded to approve, or + TransactionStatus.failed to reject + :return: Updated transfer object + """ + req = UpdateTransferRequest(status=status) + return cls._update(transfer_id, session=session, **req.model_dump()) + @classmethod def create_many(cls, requests: list[TransferRequest]) -> DictStrAny: transfers: DictStrAny = dict(submitted=[], errors=[]) diff --git a/cuenca/version.py b/cuenca/version.py index 0f7e38a4..cc3d6c7f 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.2.0' +__version__ = '2.1.21' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' diff --git a/requirements.txt b/requirements.txt index ff128c6f..3da97f14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests==2.32.3 -cuenca-validations==2.1.31 +cuenca-validations==2.1.34 pydantic-extra-types==2.10.2 diff --git a/setup.py b/setup.py index ab55a35a..cf4d610e 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ python_requires='>=3.9', install_requires=[ 'requests>=2.32.0', - 'cuenca-validations>=2.1.27', + 'cuenca-validations>=2.1.34', 'pydantic-extra-types>=2.10.0', ], classifiers=[ diff --git a/tests/resources/cassettes/test_transfers_update_failed.yaml b/tests/resources/cassettes/test_transfers_update_failed.yaml new file mode 100644 index 00000000..38e39ecc --- /dev/null +++ b/tests/resources/cassettes/test_transfers_update_failed.yaml @@ -0,0 +1,35 @@ +interactions: +- request: + body: '{"status": "failed"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Content-Length: + - '20' + Content-Type: + - application/json + User-Agent: + - cuenca-python/2.1.21 + X-Cuenca-Api-Version: + - '2020-03-19' + method: PATCH + uri: https://sandbox.cuenca.com/transfers/TR02 + response: + body: + string: '{"id":"TR02","created_at":"2026-05-26T12:00:00.000000","updated_at":"2026-05-26T12:02:00.000000","account_number":"646180157046685645","recipient_name":"Rogelio + Lopez","amount":10000,"descriptor":"held transfer","idempotency_key":"idem-tr02","status":"failed","network":"internal","destination_uri":"/accounts/LA1CVCZVNLR4KM42kPcqhBLV","tracking_key":null,"user_id":"US4PCNV8rLB2wqBfORzIAXUl","reference_number":null}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + status: + code: 200 + message: OK +version: 1 diff --git a/tests/resources/cassettes/test_transfers_update_succeeded.yaml b/tests/resources/cassettes/test_transfers_update_succeeded.yaml new file mode 100644 index 00000000..825a0b63 --- /dev/null +++ b/tests/resources/cassettes/test_transfers_update_succeeded.yaml @@ -0,0 +1,35 @@ +interactions: +- request: + body: '{"status": "succeeded"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Content-Length: + - '23' + Content-Type: + - application/json + User-Agent: + - cuenca-python/2.1.21 + X-Cuenca-Api-Version: + - '2020-03-19' + method: PATCH + uri: https://sandbox.cuenca.com/transfers/TR01 + response: + body: + string: '{"id":"TR01","created_at":"2026-05-26T12:00:00.000000","updated_at":"2026-05-26T12:01:00.000000","account_number":"646180157046685645","recipient_name":"Rogelio + Lopez","amount":10000,"descriptor":"held transfer","idempotency_key":"idem-tr01","status":"succeeded","network":"internal","destination_uri":"/accounts/LA1CVCZVNLR4KM42kPcqhBLV","tracking_key":"tk01","user_id":"US4PCNV8rLB2wqBfORzIAXUl","reference_number":null}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + status: + code: 200 + message: OK +version: 1 diff --git a/tests/resources/test_transfers.py b/tests/resources/test_transfers.py index 327b645d..84fd9dbb 100644 --- a/tests/resources/test_transfers.py +++ b/tests/resources/test_transfers.py @@ -130,3 +130,33 @@ def test_invalid_params(): with pytest.raises(ValidationError) as e: Transfer.one(invalid_param='invalid_param') assert 'Extra inputs are not permitted' in str(e) + + +@pytest.mark.vcr +def test_transfers_update_succeeded(): + transfer = Transfer.update('TR01', status=TransactionStatus.succeeded) + assert transfer.id == 'TR01' + assert transfer.status == TransactionStatus.succeeded + + +@pytest.mark.vcr +def test_transfers_update_failed(): + transfer = Transfer.update('TR02', status=TransactionStatus.failed) + assert transfer.id == 'TR02' + assert transfer.status == TransactionStatus.failed + + +@pytest.mark.parametrize( + 'invalid_status', + [ + TransactionStatus.created, + TransactionStatus.submitted, + TransactionStatus.in_review, + 'cancelled', + 'approve', + 'reject', + ], +) +def test_transfers_update_rejects_invalid_status(invalid_status): + with pytest.raises(ValidationError): + Transfer.update('TR03', status=invalid_status) From 73c72e57a5ebb15459237d30d5774c05804fab51 Mon Sep 17 00:00:00 2001 From: Juliette Date: Wed, 27 May 2026 12:19:16 -0600 Subject: [PATCH 2/2] Update version to 2.2.1 and modify transfer status update method to accept TransactionStatus directly. --- cuenca/resources/transfers.py | 15 ++------------- cuenca/version.py | 2 +- .../cassettes/test_transfers_update_failed.yaml | 2 +- .../test_transfers_update_succeeded.yaml | 2 +- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/cuenca/resources/transfers.py b/cuenca/resources/transfers.py index ddf441ad..2738fcf7 100644 --- a/cuenca/resources/transfers.py +++ b/cuenca/resources/transfers.py @@ -1,5 +1,5 @@ import datetime as dt -from typing import ClassVar, Literal, Optional, cast +from typing import ClassVar, Optional, cast from cuenca_validations.types import ( TransactionStatus, @@ -78,21 +78,10 @@ def create( def update( cls, transfer_id: str, - status: Literal[ - TransactionStatus.succeeded, - TransactionStatus.failed, - ], + status: TransactionStatus, *, session: Session = global_session, ) -> 'Transfer': - """ - Updates the status of a held transfer. - - :param transfer_id: existing transfer_id - :param status: TransactionStatus.succeeded to approve, or - TransactionStatus.failed to reject - :return: Updated transfer object - """ req = UpdateTransferRequest(status=status) return cls._update(transfer_id, session=session, **req.model_dump()) diff --git a/cuenca/version.py b/cuenca/version.py index cc3d6c7f..a1029370 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.1.21' +__version__ = '2.2.1' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' diff --git a/tests/resources/cassettes/test_transfers_update_failed.yaml b/tests/resources/cassettes/test_transfers_update_failed.yaml index 38e39ecc..6dc925ca 100644 --- a/tests/resources/cassettes/test_transfers_update_failed.yaml +++ b/tests/resources/cassettes/test_transfers_update_failed.yaml @@ -15,7 +15,7 @@ interactions: Content-Type: - application/json User-Agent: - - cuenca-python/2.1.21 + - cuenca-python/2.2.1 X-Cuenca-Api-Version: - '2020-03-19' method: PATCH diff --git a/tests/resources/cassettes/test_transfers_update_succeeded.yaml b/tests/resources/cassettes/test_transfers_update_succeeded.yaml index 825a0b63..4b332928 100644 --- a/tests/resources/cassettes/test_transfers_update_succeeded.yaml +++ b/tests/resources/cassettes/test_transfers_update_succeeded.yaml @@ -15,7 +15,7 @@ interactions: Content-Type: - application/json User-Agent: - - cuenca-python/2.1.21 + - cuenca-python/2.2.1 X-Cuenca-Api-Version: - '2020-03-19' method: PATCH