1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 from django.contrib.auth.models import User
5 from unittest.mock import MagicMock, Mock, patch, DEFAULT
6 from catalogue.test_utils import WLTestCase
7 from .models import BillingAgreement, BillingPlan
8 from .rest import user_is_subscribed
9 from paypalrestsdk import ResourceNotFound
12 BillingPlanMock = Mock(
14 id='some-billing-plan-id'
18 BillingAgreementMock = Mock(
19 # BillingAgreement() has a .links[]
20 return_value=MagicMock(
24 href="http://paypal.test/approval/"
28 # BillingAgreement.execute(token)
32 id='some-billing-agreement-id',
36 amount={'value': '100'}
42 # Later we can BillingAgreement.find(...).state == 'Active'
51 class PaypalTests(WLTestCase):
54 cls.user = User(username='test')
55 cls.user.set_password('test')
59 def tearDownClass(cls):
62 def test_paypal_form(self):
63 response = self.client.get('/paypal/form/')
64 self.assertEqual(response.status_code, 200)
66 def test_paypal_form_unauthorized(self):
67 """Legacy flow: only allow payment for logged-in users."""
68 response = self.client.post('/paypal/form/', {"amount": "0"})
69 self.assertEqual(response.status_code, 403)
71 def test_paypal_form_invalid(self):
72 """Paypal form: error on bad input."""
73 self.client.login(username='test', password='test')
75 response = self.client.post('/paypal/form/', {"amount": "0"})
76 self.assertEqual(response.status_code, 200)
78 len(response.context['form'].errors['amount']),
81 @patch.multiple('paypalrestsdk',
82 BillingPlan=BillingPlanMock,
83 BillingAgreement=BillingAgreementMock,
85 def test_paypal_form_valid(self):
86 """PayPal form created a BillingPlan."""
87 self.client.login(username='test', password='test')
88 response = self.client.post('/paypal/form/', {"amount": "100"})
89 self.assertRedirects(response, 'http://paypal.test/approval/',
90 fetch_redirect_response=False)
91 self.assertEqual(BillingPlan.objects.all().count(), 1)
93 # Posting the form a second time does not create another plan.
94 response = self.client.post('/paypal/form/', {"amount": "100"})
95 self.assertRedirects(response, 'http://paypal.test/approval/',
96 fetch_redirect_response=False)
97 self.assertEqual(BillingPlan.objects.all().count(), 1)
99 # No BillingAgreement created in our DB yet.
100 self.assertEqual(BillingAgreement.objects.all().count(), 0)
102 @patch('paypalrestsdk.BillingPlan', BillingPlanMock)
103 def test_paypal_form_error(self):
104 """On PayPal error, plan does not get created."""
105 self.client.login(username='test', password='test')
107 # It can choke on BillingPlan().create().
108 with patch('paypalrestsdk.BillingPlan', Mock(
109 return_value=Mock(create=Mock(return_value=None)))):
110 response = self.client.post('/paypal/form/', {"amount": "100"})
111 self.assertEqual(response.status_code, 200)
113 # Or it can choke on BillingPlan().activate().
114 with patch('paypalrestsdk.BillingPlan', Mock(
115 return_value=Mock(activate=Mock(return_value=None)))):
116 response = self.client.post('/paypal/form/', {"amount": "100"})
117 self.assertEqual(response.status_code, 200)
119 # No plan is created yet.
120 self.assertEqual(BillingPlan.objects.all().count(), 0)
122 # Or it can choke later, on BillingAgreement().create()
123 with patch('paypalrestsdk.BillingAgreement', Mock(
124 return_value=Mock(create=Mock(return_value=None)))):
125 response = self.client.post('/paypal/form/', {"amount": "100"})
126 self.assertEqual(response.status_code, 200)
128 # But now the plan should be created.
129 self.assertEqual(BillingPlan.objects.all().count(), 1)
131 @patch.multiple('paypalrestsdk',
132 BillingPlan=BillingPlanMock,
133 BillingAgreement=BillingAgreementMock,
135 def test_paypal_app_form_valid(self):
136 """App form creates a BillingPlan."""
137 self.client.login(username='test', password='test')
138 response = self.client.post('/paypal/app-form/', {"amount": "100"})
139 self.assertRedirects(response, 'http://paypal.test/approval/',
140 fetch_redirect_response=False)
141 self.assertEqual(BillingPlan.objects.all().count(), 1)
143 @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock)
144 def test_paypal_return(self):
145 self.client.login(username='test', password='test')
146 BillingPlan.objects.create(amount=100)
148 # No token = no agreement.
149 response = self.client.get('/paypal/return/')
150 self.assertEqual(response.status_code, 404)
151 self.assertEqual(BillingAgreement.objects.all().count(), 0)
153 response = self.client.get('/paypal/return/?token=secret-token')
154 self.assertEqual(response.status_code, 200)
155 self.assertEqual(BillingAgreement.objects.all().count(), 1)
157 # Repeated returns will not generate further agreements.
158 response = self.client.get('/paypal/return/?token=secret-token')
159 self.assertEqual(response.status_code, 200)
160 self.assertEqual(BillingAgreement.objects.all().count(), 1)
162 self.assertTrue(user_is_subscribed(self.user))
164 @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock)
165 def test_paypal_app_return(self):
166 self.client.login(username='test', password='test')
167 BillingPlan.objects.create(amount=100)
168 response = self.client.get('/paypal/app-return/?token=secret-token')
169 self.assertRedirects(
170 response, 'wolnelekturyapp://paypal_return',
171 fetch_redirect_response=False)
173 # Repeated returns will not generate further agreements.
174 response = self.client.get('/paypal/app-return/?token=secret-token')
175 self.assertRedirects(
176 response, 'wolnelekturyapp://paypal_return',
177 fetch_redirect_response=False)
178 self.assertEqual(BillingAgreement.objects.all().count(), 1)
180 self.assertTrue(user_is_subscribed(self.user))
182 def test_paypal_return_error(self):
183 self.client.login(username='test', password='test')
184 BillingPlan.objects.create(amount=100)
186 # It can choke on BillingAgreement.execute()
187 with patch('paypalrestsdk.BillingAgreement', Mock(
188 execute=Mock(return_value=Mock(id=None)))):
189 self.client.get('/paypal/app-return/?token=secret-token')
190 response = self.client.get('/paypal/app-return/?token=secret-token')
191 self.assertRedirects(
192 response, 'wolnelekturyapp://paypal_error',
193 fetch_redirect_response=False)
195 # No agreement created in our DB if not executed successfully.
196 self.assertEqual(BillingAgreement.objects.all().count(), 0)
198 # It can execute all right, but just not be findable later.
199 with patch('paypalrestsdk.BillingAgreement', Mock(
200 execute=BillingAgreementMock.execute,
201 find=Mock(side_effect=ResourceNotFound(None)))):
202 response = self.client.get('/paypal/app-return/?token=secret-token')
203 self.assertRedirects(
204 response, 'wolnelekturyapp://paypal_return',
205 fetch_redirect_response=False)
207 # Now the agreement exists in our DB, but is not active.
208 self.assertEqual([b.active for b in BillingAgreement.objects.all()], [False])
210 with patch('paypalrestsdk.BillingAgreement', Mock(
211 find=Mock(return_value=Mock(state='Mocked')))):
212 self.assertFalse(user_is_subscribed(self.user))
214 def test_paypal_cancel(self):
215 response = self.client.get('/paypal/cancel/')
216 self.assertEqual(response.status_code, 200)