From 7a21988a5c1ee760bb3e31c4c48e52529e0371ef Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 20 Nov 2023 17:06:45 +0100 Subject: [PATCH] chore: more tests --- server/vbv_lernwelt/shop/services.py | 6 +- .../shop/tests/test_checkout_api.py | 30 ++++++ .../shop/tests/test_datatrans_service.py | 99 +++++++++++++++++++ server/vbv_lernwelt/shop/views.py | 28 ++++-- 4 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 server/vbv_lernwelt/shop/tests/test_datatrans_service.py diff --git a/server/vbv_lernwelt/shop/services.py b/server/vbv_lernwelt/shop/services.py index da6992ba..20a16c46 100644 --- a/server/vbv_lernwelt/shop/services.py +++ b/server/vbv_lernwelt/shop/services.py @@ -12,7 +12,7 @@ from vbv_lernwelt.shop.models import CheckoutState logger = structlog.get_logger(__name__) -class PaymentException(Exception): +class InitTransactionException(Exception): pass @@ -81,7 +81,7 @@ def init_transaction( logger.info("Initiating transaction", payload=payload) response = requests.post( - f"{settings.DATATRANS_API_ENDPOINT}/v1/transactions", + url=f"{settings.DATATRANS_API_ENDPOINT}/v1/transactions", json=payload, headers={ "Authorization": f"Basic {settings.DATATRANS_BASIC_AUTH_KEY}", @@ -94,7 +94,7 @@ def init_transaction( logger.info("Transaction initiated", transaction_id=transaction_id) return transaction_id else: - raise PaymentException( + raise InitTransactionException( "Transaction initiation failed:", response.json().get("error"), ) diff --git a/server/vbv_lernwelt/shop/tests/test_checkout_api.py b/server/vbv_lernwelt/shop/tests/test_checkout_api.py index 1ec7c52b..ccdd42d9 100644 --- a/server/vbv_lernwelt/shop/tests/test_checkout_api.py +++ b/server/vbv_lernwelt/shop/tests/test_checkout_api.py @@ -11,6 +11,7 @@ from vbv_lernwelt.shop.models import ( Product, VV_PRODUCT_SKU, ) +from vbv_lernwelt.shop.services import InitTransactionException USER_USERNAME = "testuser" USER_EMAIL = "test@example.com" @@ -117,3 +118,32 @@ class CheckoutAPITestCase(APITestCase): ) self.assertEqual(expected, response.json()["next_step_url"]) + + @patch("vbv_lernwelt.shop.views.init_transaction") + def test_checkout_init_transaction_exception(self, mock_init_transaction): + # GIVEN + mock_init_transaction.side_effect = InitTransactionException( + "Something went wrong" + ) + + # WHEN + response = self.client.post( + path=reverse("checkout-vv"), + format="json", + data={ + "redirect_url": REDIRECT_URL, + "address": TEST_ADDRESS_DATA, + }, + ) + + # THEN + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + f"{REDIRECT_URL}/onboarding/vv/checkout/address?error", + response.json()["next_step_url"], + ) + + self.assertEqual( + 0, + CheckoutInformation.objects.count(), + ) diff --git a/server/vbv_lernwelt/shop/tests/test_datatrans_service.py b/server/vbv_lernwelt/shop/tests/test_datatrans_service.py new file mode 100644 index 00000000..d6802609 --- /dev/null +++ b/server/vbv_lernwelt/shop/tests/test_datatrans_service.py @@ -0,0 +1,99 @@ +import uuid +from unittest.mock import patch + +from django.test import override_settings, TestCase + +from vbv_lernwelt.core.models import User +from vbv_lernwelt.shop.services import ( + get_payment_url, + init_transaction, + InitTransactionException, +) + +REDIRECT_URL = "http://testserver/redirect-url" + + +class DatatransServiceTest(TestCase): + def setUp(self): + self.user = User.objects.create_user( + username=uuid.uuid4().hex, + email=uuid.uuid4().hex, + password="password", + is_active=True, + ) + + @override_settings(DATATRANS_BASIC_AUTH_KEY="BASIC_AUTH_KEY") + @patch("vbv_lernwelt.shop.services.requests.post") + @patch("vbv_lernwelt.shop.services.uuid.uuid4") + def test_init_transaction_201(self, mock_uuid, mock_post): + # GIVEN + mock_uuid.return_value = uuid.uuid4() + mock_post.return_value.status_code = 201 + mock_post.return_value.json.return_value = { + "transactionId": 1234567890, + } + + self.user.language = "it" + + # WHEN + transaction_id = init_transaction( + user=self.user, + amount_chf_centimes=300_00, + redirect_url_success=f"{REDIRECT_URL}/success", + redirect_url_error=f"{REDIRECT_URL}/error", + redirect_url_cancel=f"{REDIRECT_URL}/cancel", + webhook_url=f"{REDIRECT_URL}/webhook", + ) + + self.assertEqual(1234567890, transaction_id) + + # THEN + mock_post.assert_called_once_with( + url="https://api.sandbox.datatrans.com/v1/transactions", + json={ + "autoSettle": True, + "amount": 300_00, + "currency": "CHF", + "language": self.user.language, + "refno": str(mock_uuid()), + "webhook": {"url": f"{REDIRECT_URL}/webhook"}, + "redirect": { + "successUrl": f"{REDIRECT_URL}/success", + "errorUrl": f"{REDIRECT_URL}/error", + "cancelUrl": f"{REDIRECT_URL}/cancel", + }, + }, + headers={ + "Authorization": "Basic BASIC_AUTH_KEY", + "Content-Type": "application/json", + }, + ) + + @patch("vbv_lernwelt.shop.services.requests.post") + def test_init_transaction_500(self, mock_post): + # GIVEN + mock_post.return_value.status_code = 500 + + # WHEN / THEN + with self.assertRaises(InitTransactionException): + init_transaction( + user=self.user, + amount_chf_centimes=300_00, + redirect_url_success=f"/success", + redirect_url_error=f"/error", + redirect_url_cancel=f"/cancel", + webhook_url=f"/webhook", + ) + + def test_get_payment_url(self): + # GIVEN + transaction_id = "1234567890" + + # WHEN + url = get_payment_url(transaction_id) + + # THEN + self.assertEqual( + url, + f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}", + ) diff --git a/server/vbv_lernwelt/shop/views.py b/server/vbv_lernwelt/shop/views.py index 0a5496b3..e9d1cc59 100644 --- a/server/vbv_lernwelt/shop/views.py +++ b/server/vbv_lernwelt/shop/views.py @@ -18,6 +18,7 @@ from vbv_lernwelt.shop.serializers import BillingAddressSerializer from vbv_lernwelt.shop.services import ( get_payment_url, init_transaction, + InitTransactionException, is_signature_valid, ) @@ -137,16 +138,25 @@ def checkout_vv(request): return JsonResponse({"next_step_url": get_payment_url(checkout.transaction_id)}) # not yet initialized at all, or canceled/failed - transaction_id = init_transaction( - user=request.user, - amount_chf_centimes=product.price, - redirect_url_success=checkout_success_url(base_redirect_url), - redirect_url_error=checkout_error_url(base_redirect_url), - redirect_url_cancel=checkout_cancel_url(base_redirect_url), - webhook_url=webhook_url(base_redirect_url), - ) + # -> create new transaction and checkout + try: + transaction_id = init_transaction( + user=request.user, + amount_chf_centimes=product.price, + redirect_url_success=checkout_success_url(base_redirect_url), + redirect_url_error=checkout_error_url(base_redirect_url), + redirect_url_cancel=checkout_cancel_url(base_redirect_url), + webhook_url=webhook_url(base_redirect_url), + ) + except InitTransactionException as e: + return JsonResponse( + { + "next_step_url": checkout_error_url( + base_url=base_redirect_url, + ) + }, + ) - # immutable snapshot of data at time of purchase CheckoutInformation.objects.create( user=request.user, state="initialized",