X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/f9b76b3071f108b29a086c14e745da2f3921d82c..98062d2158ebe1f734d811691ab15e6887684281:/src/paypal/tests.py?ds=sidebyside diff --git a/src/paypal/tests.py b/src/paypal/tests.py index 75228422e..0d6aaaeff 100644 --- a/src/paypal/tests.py +++ b/src/paypal/tests.py @@ -1,16 +1,36 @@ -# -*- coding: utf-8 -*- # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django.contrib.auth.models import User -from mock import Mock, patch, DEFAULT +from unittest import skip +from unittest.mock import MagicMock, Mock, patch, DEFAULT from catalogue.test_utils import WLTestCase -from .models import BillingPlan +from club.models import Membership, Schedule +from .models import BillingAgreement, BillingPlan +from paypalrestsdk import ResourceNotFound +BillingPlanMock = Mock( + return_value=Mock( + id='some-billing-plan-id' + ) +) + BillingAgreementMock = Mock( + # BillingAgreement() has a .links[] + return_value=MagicMock( + links=[ + Mock( + rel="approval_url", + href="http://paypal.test/approval/" + ) + ] + ), + # BillingAgreement.execute(token) execute=Mock( return_value=Mock( + error=None, + id='some-billing-agreement-id', plan=Mock( payment_definitions=[ Mock( @@ -19,7 +39,13 @@ BillingAgreementMock = Mock( ] ) ) - ) + ), + # Later we can BillingAgreement.find(...).state == 'Active' + find=Mock( + return_value=Mock( + state='Active' + ) + ), ) @@ -34,15 +60,28 @@ class PaypalTests(WLTestCase): def tearDownClass(cls): cls.user.delete() + def setUp(self): + super().setUp() + s = Schedule.objects.create( + key='schedule-key', + amount=10, + membership=Membership.objects.create( + user=self.user + ) + ) + + @skip("Changing the flow.") def test_paypal_form(self): response = self.client.get('/paypal/form/') self.assertEqual(response.status_code, 200) + @skip("Changing the flow.") def test_paypal_form_unauthorized(self): """Legacy flow: only allow payment for logged-in users.""" response = self.client.post('/paypal/form/', {"amount": "0"}) self.assertEqual(response.status_code, 403) + @skip("Changing the flow.") def test_paypal_form_invalid(self): """Paypal form: error on bad input.""" self.client.login(username='test', password='test') @@ -53,33 +92,141 @@ class PaypalTests(WLTestCase): len(response.context['form'].errors['amount']), 1) + @skip("Changing the flow.") @patch.multiple('paypalrestsdk', - BillingPlan=DEFAULT, - BillingAgreement=DEFAULT + BillingPlan=BillingPlanMock, + BillingAgreement=BillingAgreementMock, ) - def test_paypal_form_valid(self, BillingPlan, BillingAgreement): + def test_paypal_form_valid(self): + """PayPal form created a BillingPlan.""" self.client.login(username='test', password='test') response = self.client.post('/paypal/form/', {"amount": "100"}) - self.assertEqual(response.status_code, 302) - # Assert: BillingPlan created? BillingAgreement created? - # Models created? + self.assertRedirects(response, 'http://paypal.test/approval/', + fetch_redirect_response=False) + self.assertEqual(BillingPlan.objects.all().count(), 1) + # Posting the form a second time does not create another plan. + response = self.client.post('/paypal/form/', {"amount": "100"}) + self.assertRedirects(response, 'http://paypal.test/approval/', + fetch_redirect_response=False) + self.assertEqual(BillingPlan.objects.all().count(), 1) + + # No BillingAgreement created in our DB yet. + self.assertEqual(BillingAgreement.objects.all().count(), 0) + + @skip("Changing the flow.") + @patch('paypalrestsdk.BillingPlan', BillingPlanMock) + def test_paypal_form_error(self): + """On PayPal error, plan does not get created.""" + self.client.login(username='test', password='test') + + # It can choke on BillingPlan().create(). + with patch('paypalrestsdk.BillingPlan', Mock( + return_value=Mock(create=Mock(return_value=None)))): + response = self.client.post('/paypal/form/', {"amount": "100"}) + self.assertEqual(response.status_code, 200) + + # Or it can choke on BillingPlan().activate(). + with patch('paypalrestsdk.BillingPlan', Mock( + return_value=Mock(activate=Mock(return_value=None)))): + response = self.client.post('/paypal/form/', {"amount": "100"}) + self.assertEqual(response.status_code, 200) + + # No plan is created yet. + self.assertEqual(BillingPlan.objects.all().count(), 0) + + # Or it can choke later, on BillingAgreement().create() + with patch('paypalrestsdk.BillingAgreement', Mock( + return_value=Mock(create=Mock(return_value=None)))): + response = self.client.post('/paypal/form/', {"amount": "100"}) + self.assertEqual(response.status_code, 200) + + # But now the plan should be created. + self.assertEqual(BillingPlan.objects.all().count(), 1) + + @skip("Changing the flow.") @patch.multiple('paypalrestsdk', - BillingPlan=DEFAULT, - BillingAgreement=DEFAULT, + BillingPlan=BillingPlanMock, + BillingAgreement=BillingAgreementMock, ) - def test_paypal_form_valid(self, BillingPlan, BillingAgreement): + def test_paypal_app_form_valid(self): + """App form creates a BillingPlan.""" self.client.login(username='test', password='test') response = self.client.post('/paypal/app-form/', {"amount": "100"}) - self.assertEqual(response.status_code, 302) + self.assertRedirects(response, 'http://paypal.test/approval/', + fetch_redirect_response=False) + self.assertEqual(BillingPlan.objects.all().count(), 1) - @patch.multiple('paypalrestsdk', - BillingAgreement=BillingAgreementMock - ) + @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock) def test_paypal_return(self): self.client.login(username='test', password='test') BillingPlan.objects.create(amount=100) - response = self.client.get('/paypal/return/?token=secret-token') + + # No token = no agreement. + response = self.client.get('/paypal/return/') + self.assertEqual(response.status_code, 404) + self.assertEqual(BillingAgreement.objects.all().count(), 0) + + response = self.client.get('/paypal/return/schedule-key/?token=secret-token') + self.assertEqual(response.status_code, 302) + self.assertEqual(BillingAgreement.objects.all().count(), 1) + + # Repeated returns will not generate further agreements. + response = self.client.get('/paypal/return/schedule-key/?token=secret-token') + self.assertEqual(response.status_code, 302) + self.assertEqual(BillingAgreement.objects.all().count(), 1) + + self.assertTrue(Membership.is_active_for(self.user)) + + @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock) + def test_paypal_app_return(self): + self.client.login(username='test', password='test') + BillingPlan.objects.create(amount=100) + response = self.client.get('/paypal/app-return/schedule-key/?token=secret-token') + self.assertRedirects( + response, 'wolnelekturyapp://paypal_return', + fetch_redirect_response=False) + + # Repeated returns will not generate further agreements. + response = self.client.get('/paypal/app-return/schedule-key/?token=secret-token') + self.assertRedirects( + response, 'wolnelekturyapp://paypal_return', + fetch_redirect_response=False) + self.assertEqual(BillingAgreement.objects.all().count(), 1) + + self.assertTrue(Membership.is_active_for(self.user)) + + def test_paypal_return_error(self): + self.client.login(username='test', password='test') + BillingPlan.objects.create(amount=100) + + # It can choke on BillingAgreement.execute() + with patch('paypalrestsdk.BillingAgreement', Mock( + execute=Mock(return_value=Mock(id=None)))): + self.client.get('/paypal/app-return/?token=secret-token') + response = self.client.get('/paypal/app-return/schedule-key/?token=secret-token') + self.assertRedirects( + response, 'wolnelekturyapp://paypal_error', + fetch_redirect_response=False) + + # No agreement created in our DB if not executed successfully. + self.assertEqual(BillingAgreement.objects.all().count(), 0) + + # It can execute all right, but just not be findable later. + with patch('paypalrestsdk.BillingAgreement', Mock( + execute=BillingAgreementMock.execute, + find=Mock(side_effect=ResourceNotFound(None)))): + response = self.client.get('/paypal/app-return/schedule-key/?token=secret-token') + self.assertRedirects( + response, 'wolnelekturyapp://paypal_return', + fetch_redirect_response=False) + + # Now the agreement exists in our DB, but is not active. + self.assertEqual([b.active for b in BillingAgreement.objects.all()], [False]) + + with patch('paypalrestsdk.BillingAgreement', Mock( + find=Mock(return_value=Mock(state='Mocked')))): + self.assertFalse(Membership.is_active_for(self.user)) def test_paypal_cancel(self): response = self.client.get('/paypal/cancel/')