Skip to content

Commit

Permalink
refactor: retry sending transaction to transaction service layer
Browse files Browse the repository at this point in the history
- Removed the command functionality from the command layer
- Added tests to the command logic
  • Loading branch information
Tiago-Salles committed Nov 5, 2024
1 parent 5373e8d commit 5172d5b
Show file tree
Hide file tree
Showing 4 changed files with 577 additions and 60 deletions.
25 changes: 2 additions & 23 deletions apps/billing/management/commands/retry_sage_transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from django.core.management.base import BaseCommand

from apps.billing.models import SageX3TransactionInformation
from apps.billing.services.transaction_service import TransactionService


Expand Down Expand Up @@ -30,32 +29,12 @@ def add_arguments(self, parser):
"--transaction_id", type=str, required=False, help="The transaction_id to retry to send to SageX3"
)

@staticmethod
def _sagex3_transaction_info_query(transaction_id):
if transaction_id:
return SageX3TransactionInformation.objects.filter(transaction__transaction_id=transaction_id)
else:
return SageX3TransactionInformation.objects.filter(
status__in=[SageX3TransactionInformation.FAILED, SageX3TransactionInformation.PENDING]
)

def handle(self, *args, **kwargs) -> str | None:
transaction_id = kwargs["transaction_id"]
start = time.time()
self.stdout.write("\nGetting failed transactions with Sage X3...\n")
sagex3_to_retry = self.__class__._sagex3_transaction_info_query(transaction_id)
total_count = sagex3_to_retry.count()
counters = {"success": 0, "failed": 0}
try:
for sagex3_failed_transaction in sagex3_to_retry:
if TransactionService(sagex3_failed_transaction.transaction).run_steps_to_send_transaction():
counters["success"] += 1
else:
counters["failed"] += 1
except Exception as e:
self.stdout.write(f"Error while retrying: {e}")
counters["failed"] += 1
counters = TransactionService.retry_sending_transactions(transaction_id=transaction_id)
finish = time.time() - start
self.stdout.write(f"\n----- {total_count} Transactions were retried -----\n")
self.stdout.write(f"\n----- {counters['total_count']} Transactions were retried -----\n")
self.stdout.write(f"\nSUCCESSFULL RETRIES: {counters['success']} FAILED RETRIES: {counters['failed']}\n")
self.stdout.write(f"\nThe time to retry all transactions was {finish}\n")
42 changes: 42 additions & 0 deletions apps/billing/services/transaction_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,45 @@ def run_steps_to_send_transaction(self) -> bool:
transaction=self.transaction,
)
return False

@staticmethod
def sagex3_transaction_info_query(transaction_id: str | None):
"""
This method returns the transactions that need to be retried and if
a `transaction_id` was provided, it will return only the corresponding transaction.
This method must always return a list, regardless of its length.
"""

if transaction_id:
result = SageX3TransactionInformation.objects.filter(transaction__transaction_id=transaction_id)

return list(result)

result = SageX3TransactionInformation.objects.filter(
status__in=[SageX3TransactionInformation.FAILED, SageX3TransactionInformation.PENDING]
)

return list(result)

@staticmethod
def retry_sending_transactions(transaction_id: str | None):
"""
This method retries sending transactions and also is
possible to retry an specific one by providing the `transaction_id`.
"""

sagex3_to_retry = TransactionService.sagex3_transaction_info_query(transaction_id)
counters = {"success": 0, "failed": 0, "total_count": len(sagex3_to_retry)}

for sagex3_failed_transaction in sagex3_to_retry:
try:
if TransactionService(sagex3_failed_transaction.transaction).run_steps_to_send_transaction():
counters["success"] += 1
else:
counters["failed"] += 1
except Exception as e:
print(f"Error while retrying: {e}")
counters["failed"] += 1

return counters
155 changes: 119 additions & 36 deletions apps/billing/tests/test_command_retry_sage_transactions.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,132 @@
from io import StringIO
from unittest import mock

from django.core.management import call_command
from django.test import TestCase
from django.test import TestCase, override_settings

from apps.billing.factories import SageX3TransactionInformationFactory, TransactionFactory, TransactionItemFactory
from apps.billing.mocks import MockResponse
from apps.billing.models import SageX3TransactionInformation
from apps.billing.tests.test_utils import processor_success_response


def create_transaction(status):
transaction = TransactionFactory.build()
transaction.save()
item = TransactionItemFactory.build(transaction=transaction)
item.save()
sageX3TI = SageX3TransactionInformationFactory.build(transaction=transaction, status=status)
sageX3TI.save()
return transaction


@mock.patch("apps.billing.services.transaction_service.TransactionService", autospec=True)
class CommandRetrySageTransactionsTestCase(TestCase):
"""
Test the `retry_sage_transactions` Django command.
"""

def test_command_retry_sage_transactions(self, transaction_service_mock):
"""
Test the command `retry_sage_transactions`.
Unfortunately all this calls needs to be on the same test.
"""
call_command("retry_sage_transactions")
transaction_service_mock.assert_not_called()

# test a pending transaction should be retried
t = create_transaction(SageX3TransactionInformation.PENDING)
for _ in range(5):
create_transaction(SageX3TransactionInformation.SUCCESS)
call_command("retry_sage_transactions")
transaction_service_mock.assert_called_once_with(t)

# test a failed transaction should be retried
f = create_transaction(SageX3TransactionInformation.FAILED)
call_command("retry_sage_transactions")
transaction_service_mock.assert_called_with(f)

# test the force retry of a success transaction
t = create_transaction(SageX3TransactionInformation.SUCCESS)
call_command("retry_sage_transactions", transaction_id=t.transaction_id)
transaction_service_mock.assert_called_with(t)
@override_settings(TRANSACTION_PROCESSOR_URL="http://fake-processor.com", DEFAULT_SERIES="AAA")
@mock.patch("requests.post", side_effect=processor_success_response)
def test_command_retry_sage_transactions_success_PENDIND_status(self, mocked_post):

out = StringIO()
transaction = TransactionFactory.create()
TransactionItemFactory.create(transaction=transaction)

SageX3TransactionInformationFactory.create(
transaction=transaction, status=SageX3TransactionInformation.PENDING
)

call_command("retry_sage_transactions", stdout=out)

message = "----- 1 Transactions were retried -----\n\nSUCCESSFULL RETRIES: 1 FAILED RETRIES: 0"

self.assertTrue(message in out.getvalue())

@override_settings(TRANSACTION_PROCESSOR_URL="http://fake-processor.com", DEFAULT_SERIES="AAA")
@mock.patch("requests.post", side_effect=processor_success_response)
def test_command_retry_sage_transactions_success_FAILED_status(self, mocked_post):

out = StringIO()
transaction = TransactionFactory.create()
TransactionItemFactory.create(transaction=transaction)

SageX3TransactionInformationFactory.create(transaction=transaction, status=SageX3TransactionInformation.FAILED)

call_command("retry_sage_transactions", stdout=out)

message = "----- 1 Transactions were retried -----\n\nSUCCESSFULL RETRIES: 1 FAILED RETRIES: 0"

self.assertTrue(message in out.getvalue())

@override_settings(TRANSACTION_PROCESSOR_URL="http://fake-processor.com", DEFAULT_SERIES="AAA")
@mock.patch("requests.post", side_effect=processor_success_response)
def test_command_retry_sage_transactions_success_without_transaction_id(self, mocked_post):

out = StringIO()

for transaction in TransactionFactory.create_batch(10):
TransactionItemFactory.create(transaction=transaction)
SageX3TransactionInformationFactory.create(
transaction=transaction, status=SageX3TransactionInformation.FAILED
)

for transaction in TransactionFactory.create_batch(10):
TransactionItemFactory.create(transaction=transaction)
SageX3TransactionInformationFactory.create(
transaction=transaction, status=SageX3TransactionInformation.PENDING
)

call_command("retry_sage_transactions", stdout=out)

message = "----- 20 Transactions were retried -----\n\nSUCCESSFULL RETRIES: 20 FAILED RETRIES: 0"

self.assertTrue(message in out.getvalue())

@override_settings(TRANSACTION_PROCESSOR_URL="http://fake-processor.com", DEFAULT_SERIES="AAA")
@mock.patch("requests.post", return_value=MockResponse(status_code=500, data="Some not expected error"))
def test_command_retry_sage_transactions_error_PENDIND_status(self, mocked_post):

out = StringIO()
transaction = TransactionFactory.create()
TransactionItemFactory.create(transaction=transaction)

SageX3TransactionInformationFactory.create(
transaction=transaction, status=SageX3TransactionInformation.PENDING
)

call_command("retry_sage_transactions", stdout=out)

message = "----- 1 Transactions were retried -----\n\nSUCCESSFULL RETRIES: 0 FAILED RETRIES: 1"

self.assertTrue(message in out.getvalue())

@override_settings(TRANSACTION_PROCESSOR_URL="http://fake-processor.com", DEFAULT_SERIES="AAA")
@mock.patch("requests.post", return_value=MockResponse(status_code=500, data="Some not expected error"))
def test_command_retry_sage_transactions_error_FAILED_status(self, mocked_post):

out = StringIO()
transaction = TransactionFactory.create()
TransactionItemFactory.create(transaction=transaction)

SageX3TransactionInformationFactory.create(transaction=transaction, status=SageX3TransactionInformation.FAILED)

call_command("retry_sage_transactions", stdout=out)

message = "----- 1 Transactions were retried -----\n\nSUCCESSFULL RETRIES: 0 FAILED RETRIES: 1"

self.assertTrue(message in out.getvalue())

@override_settings(TRANSACTION_PROCESSOR_URL="http://fake-processor.com", DEFAULT_SERIES="AAA")
@mock.patch("requests.post", return_value=MockResponse(status_code=500, data="Some not expected error"))
def test_command_retry_sage_transactions_error_without_transaction_id(self, mocked_post):

out = StringIO()

for transaction in TransactionFactory.create_batch(10):
TransactionItemFactory.create(transaction=transaction)
SageX3TransactionInformationFactory.create(
transaction=transaction, status=SageX3TransactionInformation.FAILED
)

for transaction in TransactionFactory.create_batch(10):
TransactionItemFactory.create(transaction=transaction)
SageX3TransactionInformationFactory.create(
transaction=transaction, status=SageX3TransactionInformation.PENDING
)

call_command("retry_sage_transactions", stdout=out)

message = "----- 20 Transactions were retried -----\n\nSUCCESSFULL RETRIES: 0 FAILED RETRIES: 20"

self.assertTrue(message in out.getvalue())
Loading

0 comments on commit 5172d5b

Please sign in to comment.