diff --git a/commerce_coordinator/apps/paypal/tests/test_views.py b/commerce_coordinator/apps/paypal/tests/test_views.py new file mode 100644 index 00000000..59543672 --- /dev/null +++ b/commerce_coordinator/apps/paypal/tests/test_views.py @@ -0,0 +1,114 @@ +import base64 +import zlib +from unittest.mock import patch, MagicMock + +from django.conf import settings +from django.urls import reverse +from rest_framework.test import APITestCase + +from commerce_coordinator.apps.paypal.views import PayPalWebhookView +from commerce_coordinator.apps.paypal.models import KeyValueCache + + +class PayPalWebhookViewTests(APITestCase): + """ Tests for PayPalWebhookView """ + + def setUp(self): + super().setUp() + self.url = reverse('paypal:paypal_webhook') + self.headers = { + 'paypal-transmission-id': 'test-transmission-id', + 'paypal-transmission-time': '2023-01-01T00:00:00Z', + 'paypal-transmission-sig': base64.b64encode(b'test-signature').decode('utf-8'), + 'paypal-cert-url': 'https://www.paypal.com/cert.pem', + } + self.body = b'test-body' + self.crc = zlib.crc32(self.body) + self.message = f"{self.headers['paypal-transmission-id']}|{self.headers['paypal-transmission-time']}|{settings.PAYPAL_WEBHOOK_ID}|{self.crc}" + + @patch('requests.get') + @patch('commerce_coordinator.apps.paypal.views.x509.load_pem_x509_certificate') + @patch('commerce_coordinator.apps.paypal.views.CommercetoolsAPIClient') + def test_post_refund_event(self, mock_commercetools_client, mock_load_cert, mock_requests_get): + mock_requests_get.return_value.text = 'test-cert' + mock_load_cert.return_value.public_key.return_value.verify = MagicMock() + + mock_commercetools_client.return_value.get_payment_by_transaction_interaction_id.return_value = MagicMock(key='test-paypal-order-id') + + data = { + "event_type": "PAYMENT.CAPTURE.REFUNDED", + "resource": { + "id": "test-refund-id", + "create_time": "2023-01-01T00:00:00Z", + "status": "COMPLETED", + "amount": { + "value": "100.00", + "currency_code": "USD" + }, + "invoice_id": "test-order-number", + "links": [ + {"rel": "up", "href": "https://api.paypal.com/v2/payments/captures/test-capture-id"} + ] + } + } + + response = self.client.post(self.url, data, format='json', **self.headers) + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + @patch('commerce_coordinator.apps.paypal.views.x509.load_pem_x509_certificate') + def test_post_invalid_signature(self, mock_load_cert, mock_requests_get): + mock_requests_get.return_value.text = 'test-cert' + mock_load_cert.return_value.public_key.return_value.verify.side_effect = Exception("Invalid signature") + + data = { + "event_type": "PAYMENT.CAPTURE.REFUNDED", + "resource": {} + } + + response = self.client.post(self.url, data, format='json', **self.headers) + self.assertEqual(response.status_code, 400) + + @patch('requests.get') + def test_get_certificate_from_cache(self, mock_requests_get): + KeyValueCache.objects.create(cache_key=self.headers['paypal-cert-url'], cache_value='cached-cert') + view = PayPalWebhookView() + cert = view._get_certificate(self.headers['paypal-cert-url']) + self.assertEqual(cert, 'cached-cert') + mock_requests_get.assert_not_called() + + @patch('requests.get') + def test_get_certificate_from_url(self, mock_requests_get): + mock_requests_get.return_value.text = 'test-cert' + view = PayPalWebhookView() + cert = view._get_certificate(self.headers['paypal-cert-url']) + self.assertEqual(cert, 'test-cert') + mock_requests_get.assert_called_once_with(self.headers['paypal-cert-url']) + + def test_is_valid_url(self): + view = PayPalWebhookView() + self.assertTrue(view._is_valid_url('https://www.paypal.com/cert.pem')) + self.assertFalse(view._is_valid_url('ftp://www.paypal.com/cert.pem')) + self.assertFalse(view._is_valid_url('https://www.untrusted.com/cert.pem')) + + def test_missing_headers(self): + data = { + "event_type": "PAYMENT.CAPTURE.REFUNDED", + "resource": {} + } + response = self.client.post(self.url, data, format='json') + self.assertEqual(response.status_code, 400) + + @patch('requests.get') + @patch('commerce_coordinator.apps.paypal.views.x509.load_pem_x509_certificate') + def test_invalid_event_type(self, mock_load_cert, mock_requests_get): + mock_requests_get.return_value.text = 'test-cert' + mock_load_cert.return_value.public_key.return_value.verify = MagicMock() + + data = { + "event_type": "INVALID.EVENT.TYPE", + "resource": {} + } + + response = self.client.post(self.url, data, format='json', **self.headers) + self.assertEqual(response.status_code, 200) diff --git a/commerce_coordinator/apps/paypal/views.py b/commerce_coordinator/apps/paypal/views.py index fc0c0a30..1f9a8fc5 100644 --- a/commerce_coordinator/apps/paypal/views.py +++ b/commerce_coordinator/apps/paypal/views.py @@ -43,7 +43,7 @@ def _get_certificate(self, url): raise ValueError("Invalid or untrusted URL provided") try: cache = KeyValueCache.objects.get(cache_key=url) - return cache.value + return cache.cache_value except KeyValueCache.DoesNotExist: r = requests.get(url) # pylint: disable=missing-timeout KeyValueCache.objects.create(cache_key=url, cache_value=r.text)