52c57473e80dacc92c18252e4bb9c850800ea90a
[wolnelektury.git] / src / paypal / tests.py
1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
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 .models import BillingAgreement, BillingPlan
9 from .rest import user_is_subscribed
10 from paypalrestsdk import ResourceNotFound
11
12
13 BillingPlanMock = Mock(
14     return_value=Mock(
15         id='some-billing-plan-id'
16     )
17 )
18
19 BillingAgreementMock = Mock(
20     # BillingAgreement() has a .links[]
21     return_value=MagicMock(
22         links=[
23             Mock(
24                 rel="approval_url",
25                 href="http://paypal.test/approval/"
26             )
27         ]
28     ),
29     # BillingAgreement.execute(token)
30     execute=Mock(
31         return_value=Mock(
32             error=None,
33             id='some-billing-agreement-id',
34             plan=Mock(
35                 payment_definitions=[
36                     Mock(
37                         amount={'value': '100'}
38                     )
39                 ]
40             )
41         )
42     ),
43     # Later we can BillingAgreement.find(...).state == 'Active'
44     find=Mock(
45         return_value=Mock(
46             state='Active'
47         )
48     ),
49 )
50
51
52 class PaypalTests(WLTestCase):
53     @classmethod
54     def setUpClass(cls):
55         cls.user = User(username='test')
56         cls.user.set_password('test')
57         cls.user.save()
58
59     @classmethod
60     def tearDownClass(cls):
61         cls.user.delete()
62
63     @skip("Changing the flow.")
64     def test_paypal_form(self):
65         response = self.client.get('/paypal/form/')
66         self.assertEqual(response.status_code, 200)
67
68     @skip("Changing the flow.")
69     def test_paypal_form_unauthorized(self):
70         """Legacy flow: only allow payment for logged-in users."""
71         response = self.client.post('/paypal/form/', {"amount": "0"})
72         self.assertEqual(response.status_code, 403)
73
74     @skip("Changing the flow.")
75     def test_paypal_form_invalid(self):
76         """Paypal form: error on bad input."""
77         self.client.login(username='test', password='test')
78
79         response = self.client.post('/paypal/form/', {"amount": "0"})
80         self.assertEqual(response.status_code, 200)
81         self.assertEqual(
82             len(response.context['form'].errors['amount']),
83             1)
84
85     @skip("Changing the flow.")
86     @patch.multiple('paypalrestsdk',
87         BillingPlan=BillingPlanMock,
88         BillingAgreement=BillingAgreementMock,
89     )
90     def test_paypal_form_valid(self):
91         """PayPal form created a BillingPlan."""
92         self.client.login(username='test', password='test')
93         response = self.client.post('/paypal/form/', {"amount": "100"})
94         self.assertRedirects(response, 'http://paypal.test/approval/',
95             fetch_redirect_response=False)
96         self.assertEqual(BillingPlan.objects.all().count(), 1)
97
98         # Posting the form a second time does not create another plan.
99         response = self.client.post('/paypal/form/', {"amount": "100"})
100         self.assertRedirects(response, 'http://paypal.test/approval/',
101             fetch_redirect_response=False)
102         self.assertEqual(BillingPlan.objects.all().count(), 1)
103
104         # No BillingAgreement created in our DB yet.
105         self.assertEqual(BillingAgreement.objects.all().count(), 0)
106
107     @skip("Changing the flow.")
108     @patch('paypalrestsdk.BillingPlan', BillingPlanMock)
109     def test_paypal_form_error(self):
110         """On PayPal error, plan does not get created."""
111         self.client.login(username='test', password='test')
112
113         # It can choke on BillingPlan().create().
114         with patch('paypalrestsdk.BillingPlan', Mock(
115                 return_value=Mock(create=Mock(return_value=None)))):
116             response = self.client.post('/paypal/form/', {"amount": "100"})
117             self.assertEqual(response.status_code, 200)
118
119         # Or it can choke on BillingPlan().activate().
120         with patch('paypalrestsdk.BillingPlan', Mock(
121                 return_value=Mock(activate=Mock(return_value=None)))):
122             response = self.client.post('/paypal/form/', {"amount": "100"})
123             self.assertEqual(response.status_code, 200)
124
125         # No plan is created yet.
126         self.assertEqual(BillingPlan.objects.all().count(), 0)
127
128         # Or it can choke later, on BillingAgreement().create()
129         with patch('paypalrestsdk.BillingAgreement', Mock(
130                 return_value=Mock(create=Mock(return_value=None)))):
131             response = self.client.post('/paypal/form/', {"amount": "100"})
132             self.assertEqual(response.status_code, 200)
133
134         # But now the plan should be created.
135         self.assertEqual(BillingPlan.objects.all().count(), 1)
136
137     @skip("Changing the flow.")
138     @patch.multiple('paypalrestsdk',
139         BillingPlan=BillingPlanMock,
140         BillingAgreement=BillingAgreementMock,
141     )
142     def test_paypal_app_form_valid(self):
143         """App form creates a BillingPlan."""
144         self.client.login(username='test', password='test')
145         response = self.client.post('/paypal/app-form/', {"amount": "100"})
146         self.assertRedirects(response, 'http://paypal.test/approval/',
147             fetch_redirect_response=False)
148         self.assertEqual(BillingPlan.objects.all().count(), 1)
149
150     @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock)
151     def test_paypal_return(self):
152         self.client.login(username='test', password='test')
153         BillingPlan.objects.create(amount=100)
154
155         # No token = no agreement.
156         response = self.client.get('/paypal/return/')
157         self.assertEqual(response.status_code, 404)
158         self.assertEqual(BillingAgreement.objects.all().count(), 0)
159
160         response = self.client.get('/paypal/return/?token=secret-token')
161         self.assertEqual(response.status_code, 200)
162         self.assertEqual(BillingAgreement.objects.all().count(), 1)
163
164         # Repeated returns will not generate further agreements.
165         response = self.client.get('/paypal/return/?token=secret-token')
166         self.assertEqual(response.status_code, 200)
167         self.assertEqual(BillingAgreement.objects.all().count(), 1)
168
169         self.assertTrue(user_is_subscribed(self.user))
170
171     @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock)
172     def test_paypal_app_return(self):
173         self.client.login(username='test', password='test')
174         BillingPlan.objects.create(amount=100)
175         response = self.client.get('/paypal/app-return/?token=secret-token')
176         self.assertRedirects(
177             response, 'wolnelekturyapp://paypal_return',
178             fetch_redirect_response=False)
179
180         # Repeated returns will not generate further agreements.
181         response = self.client.get('/paypal/app-return/?token=secret-token')
182         self.assertRedirects(
183             response, 'wolnelekturyapp://paypal_return',
184             fetch_redirect_response=False)
185         self.assertEqual(BillingAgreement.objects.all().count(), 1)
186
187         self.assertTrue(user_is_subscribed(self.user))
188
189     def test_paypal_return_error(self):
190         self.client.login(username='test', password='test')
191         BillingPlan.objects.create(amount=100)
192
193         # It can choke on BillingAgreement.execute()
194         with patch('paypalrestsdk.BillingAgreement', Mock(
195                 execute=Mock(return_value=Mock(id=None)))):
196             self.client.get('/paypal/app-return/?token=secret-token')
197             response = self.client.get('/paypal/app-return/?token=secret-token')
198             self.assertRedirects(
199                 response, 'wolnelekturyapp://paypal_error',
200                 fetch_redirect_response=False)
201
202         # No agreement created in our DB if not executed successfully.
203         self.assertEqual(BillingAgreement.objects.all().count(), 0)
204
205         # It can execute all right, but just not be findable later.
206         with patch('paypalrestsdk.BillingAgreement', Mock(
207                 execute=BillingAgreementMock.execute,
208                 find=Mock(side_effect=ResourceNotFound(None)))):
209             response = self.client.get('/paypal/app-return/?token=secret-token')
210             self.assertRedirects(
211                 response, 'wolnelekturyapp://paypal_return',
212                 fetch_redirect_response=False)
213
214         # Now the agreement exists in our DB, but is not active.
215         self.assertEqual([b.active for b in BillingAgreement.objects.all()], [False])
216
217         with patch('paypalrestsdk.BillingAgreement', Mock(
218                 find=Mock(return_value=Mock(state='Mocked')))):
219             self.assertFalse(user_is_subscribed(self.user))
220
221     def test_paypal_cancel(self):
222         response = self.client.get('/paypal/cancel/')
223         self.assertEqual(response.status_code, 200)