1 # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
4 from django.contrib.auth.models import User
5 from unittest import skip
6 from unittest.mock import MagicMock, Mock, patch, DEFAULT
7 from catalogue.test_utils import WLTestCase
8 from club.models import Membership, Schedule
9 from .models import BillingAgreement, BillingPlan
10 from paypalrestsdk import ResourceNotFound
13 BillingPlanMock = Mock(
15 id='some-billing-plan-id'
19 BillingAgreementMock = Mock(
20 # BillingAgreement() has a .links[]
21 return_value=MagicMock(
25 href="http://paypal.test/approval/"
29 # BillingAgreement.execute(token)
33 id='some-billing-agreement-id',
37 amount={'value': '100'}
43 # Later we can BillingAgreement.find(...).state == 'Active'
52 class PaypalTests(WLTestCase):
55 cls.user = User(username='test')
56 cls.user.set_password('test')
60 def tearDownClass(cls):
65 s = Schedule.objects.create(
68 membership=Membership.objects.create(
73 @skip("Changing the flow.")
74 def test_paypal_form(self):
75 response = self.client.get('/paypal/form/')
76 self.assertEqual(response.status_code, 200)
78 @skip("Changing the flow.")
79 def test_paypal_form_unauthorized(self):
80 """Legacy flow: only allow payment for logged-in users."""
81 response = self.client.post('/paypal/form/', {"amount": "0"})
82 self.assertEqual(response.status_code, 403)
84 @skip("Changing the flow.")
85 def test_paypal_form_invalid(self):
86 """Paypal form: error on bad input."""
87 self.client.login(username='test', password='test')
89 response = self.client.post('/paypal/form/', {"amount": "0"})
90 self.assertEqual(response.status_code, 200)
92 len(response.context['form'].errors['amount']),
95 @skip("Changing the flow.")
96 @patch.multiple('paypalrestsdk',
97 BillingPlan=BillingPlanMock,
98 BillingAgreement=BillingAgreementMock,
100 def test_paypal_form_valid(self):
101 """PayPal form created a BillingPlan."""
102 self.client.login(username='test', password='test')
103 response = self.client.post('/paypal/form/', {"amount": "100"})
104 self.assertRedirects(response, 'http://paypal.test/approval/',
105 fetch_redirect_response=False)
106 self.assertEqual(BillingPlan.objects.all().count(), 1)
108 # Posting the form a second time does not create another plan.
109 response = self.client.post('/paypal/form/', {"amount": "100"})
110 self.assertRedirects(response, 'http://paypal.test/approval/',
111 fetch_redirect_response=False)
112 self.assertEqual(BillingPlan.objects.all().count(), 1)
114 # No BillingAgreement created in our DB yet.
115 self.assertEqual(BillingAgreement.objects.all().count(), 0)
117 @skip("Changing the flow.")
118 @patch('paypalrestsdk.BillingPlan', BillingPlanMock)
119 def test_paypal_form_error(self):
120 """On PayPal error, plan does not get created."""
121 self.client.login(username='test', password='test')
123 # It can choke on BillingPlan().create().
124 with patch('paypalrestsdk.BillingPlan', Mock(
125 return_value=Mock(create=Mock(return_value=None)))):
126 response = self.client.post('/paypal/form/', {"amount": "100"})
127 self.assertEqual(response.status_code, 200)
129 # Or it can choke on BillingPlan().activate().
130 with patch('paypalrestsdk.BillingPlan', Mock(
131 return_value=Mock(activate=Mock(return_value=None)))):
132 response = self.client.post('/paypal/form/', {"amount": "100"})
133 self.assertEqual(response.status_code, 200)
135 # No plan is created yet.
136 self.assertEqual(BillingPlan.objects.all().count(), 0)
138 # Or it can choke later, on BillingAgreement().create()
139 with patch('paypalrestsdk.BillingAgreement', Mock(
140 return_value=Mock(create=Mock(return_value=None)))):
141 response = self.client.post('/paypal/form/', {"amount": "100"})
142 self.assertEqual(response.status_code, 200)
144 # But now the plan should be created.
145 self.assertEqual(BillingPlan.objects.all().count(), 1)
147 @skip("Changing the flow.")
148 @patch.multiple('paypalrestsdk',
149 BillingPlan=BillingPlanMock,
150 BillingAgreement=BillingAgreementMock,
152 def test_paypal_app_form_valid(self):
153 """App form creates a BillingPlan."""
154 self.client.login(username='test', password='test')
155 response = self.client.post('/paypal/app-form/', {"amount": "100"})
156 self.assertRedirects(response, 'http://paypal.test/approval/',
157 fetch_redirect_response=False)
158 self.assertEqual(BillingPlan.objects.all().count(), 1)
160 @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock)
161 def test_paypal_return(self):
162 self.client.login(username='test', password='test')
163 BillingPlan.objects.create(amount=100)
165 # No token = no agreement.
166 response = self.client.get('/paypal/return/')
167 self.assertEqual(response.status_code, 404)
168 self.assertEqual(BillingAgreement.objects.all().count(), 0)
170 response = self.client.get('/paypal/return/schedule-key/?token=secret-token')
171 self.assertEqual(response.status_code, 302)
172 self.assertEqual(BillingAgreement.objects.all().count(), 1)
174 # Repeated returns will not generate further agreements.
175 response = self.client.get('/paypal/return/schedule-key/?token=secret-token')
176 self.assertEqual(response.status_code, 302)
177 self.assertEqual(BillingAgreement.objects.all().count(), 1)
179 self.assertTrue(Membership.is_active_for(self.user))
181 @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock)
182 def test_paypal_app_return(self):
183 self.client.login(username='test', password='test')
184 BillingPlan.objects.create(amount=100)
185 response = self.client.get('/paypal/app-return/schedule-key/?token=secret-token')
186 self.assertRedirects(
187 response, 'wolnelekturyapp://paypal_return',
188 fetch_redirect_response=False)
190 # Repeated returns will not generate further agreements.
191 response = self.client.get('/paypal/app-return/schedule-key/?token=secret-token')
192 self.assertRedirects(
193 response, 'wolnelekturyapp://paypal_return',
194 fetch_redirect_response=False)
195 self.assertEqual(BillingAgreement.objects.all().count(), 1)
197 self.assertTrue(Membership.is_active_for(self.user))
199 def test_paypal_return_error(self):
200 self.client.login(username='test', password='test')
201 BillingPlan.objects.create(amount=100)
203 # It can choke on BillingAgreement.execute()
204 with patch('paypalrestsdk.BillingAgreement', Mock(
205 execute=Mock(return_value=Mock(id=None)))):
206 self.client.get('/paypal/app-return/?token=secret-token')
207 response = self.client.get('/paypal/app-return/schedule-key/?token=secret-token')
208 self.assertRedirects(
209 response, 'wolnelekturyapp://paypal_error',
210 fetch_redirect_response=False)
212 # No agreement created in our DB if not executed successfully.
213 self.assertEqual(BillingAgreement.objects.all().count(), 0)
215 # It can execute all right, but just not be findable later.
216 with patch('paypalrestsdk.BillingAgreement', Mock(
217 execute=BillingAgreementMock.execute,
218 find=Mock(side_effect=ResourceNotFound(None)))):
219 response = self.client.get('/paypal/app-return/schedule-key/?token=secret-token')
220 self.assertRedirects(
221 response, 'wolnelekturyapp://paypal_return',
222 fetch_redirect_response=False)
224 # Now the agreement exists in our DB, but is not active.
225 self.assertEqual([b.active for b in BillingAgreement.objects.all()], [False])
227 with patch('paypalrestsdk.BillingAgreement', Mock(
228 find=Mock(return_value=Mock(state='Mocked')))):
229 self.assertFalse(Membership.is_active_for(self.user))
231 def test_paypal_cancel(self):
232 response = self.client.get('/paypal/cancel/')
233 self.assertEqual(response.status_code, 200)