Merge commit 'afb3cc28'
[wolnelektury.git] / src / paypal / tests.py
1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 #
5 from django.contrib.auth.models import User
6 from 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     def test_paypal_form(self):
64         response = self.client.get('/paypal/form/')
65         self.assertEqual(response.status_code, 200)
66
67     def test_paypal_form_unauthorized(self):
68         """Legacy flow: only allow payment for logged-in users."""
69         response = self.client.post('/paypal/form/', {"amount": "0"})
70         self.assertEqual(response.status_code, 403)
71
72     def test_paypal_form_invalid(self):
73         """Paypal form: error on bad input."""
74         self.client.login(username='test', password='test')
75
76         response = self.client.post('/paypal/form/', {"amount": "0"})
77         self.assertEqual(response.status_code, 200)
78         self.assertEqual(
79             len(response.context['form'].errors['amount']),
80             1)
81
82     @patch.multiple('paypalrestsdk',
83         BillingPlan=BillingPlanMock,
84         BillingAgreement=BillingAgreementMock,
85     )
86     def test_paypal_form_valid(self):
87         """PayPal form created a BillingPlan."""
88         self.client.login(username='test', password='test')
89         response = self.client.post('/paypal/form/', {"amount": "100"})
90         self.assertRedirects(response, 'http://paypal.test/approval/',
91             fetch_redirect_response=False)
92         self.assertEqual(BillingPlan.objects.all().count(), 1)
93
94         # Posting the form a second time does not create another plan.
95         response = self.client.post('/paypal/form/', {"amount": "100"})
96         self.assertRedirects(response, 'http://paypal.test/approval/',
97             fetch_redirect_response=False)
98         self.assertEqual(BillingPlan.objects.all().count(), 1)
99
100         # No BillingAgreement created in our DB yet.
101         self.assertEqual(BillingAgreement.objects.all().count(), 0)
102
103     @patch('paypalrestsdk.BillingPlan', BillingPlanMock)
104     def test_paypal_form_error(self):
105         """On PayPal error, plan does not get created."""
106         self.client.login(username='test', password='test')
107
108         # It can choke on BillingPlan().create().
109         with patch('paypalrestsdk.BillingPlan', Mock(
110                 return_value=Mock(create=Mock(return_value=None)))):
111             response = self.client.post('/paypal/form/', {"amount": "100"})
112             self.assertEqual(response.status_code, 200)
113
114         # Or it can choke on BillingPlan().activate().
115         with patch('paypalrestsdk.BillingPlan', Mock(
116                 return_value=Mock(activate=Mock(return_value=None)))):
117             response = self.client.post('/paypal/form/', {"amount": "100"})
118             self.assertEqual(response.status_code, 200)
119
120         # No plan is created yet.
121         self.assertEqual(BillingPlan.objects.all().count(), 0)
122
123         # Or it can choke later, on BillingAgreement().create()
124         with patch('paypalrestsdk.BillingAgreement', 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)
128
129         # But now the plan should be created.
130         self.assertEqual(BillingPlan.objects.all().count(), 1)
131
132     @patch.multiple('paypalrestsdk',
133         BillingPlan=BillingPlanMock,
134         BillingAgreement=BillingAgreementMock,
135     )
136     def test_paypal_app_form_valid(self):
137         """App form creates a BillingPlan."""
138         self.client.login(username='test', password='test')
139         response = self.client.post('/paypal/app-form/', {"amount": "100"})
140         self.assertRedirects(response, 'http://paypal.test/approval/',
141             fetch_redirect_response=False)
142         self.assertEqual(BillingPlan.objects.all().count(), 1)
143
144     @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock)
145     def test_paypal_return(self):
146         self.client.login(username='test', password='test')
147         BillingPlan.objects.create(amount=100)
148
149         # No token = no agreement.
150         response = self.client.get('/paypal/return/')
151         self.assertEqual(response.status_code, 404)
152         self.assertEqual(BillingAgreement.objects.all().count(), 0)
153
154         response = self.client.get('/paypal/return/?token=secret-token')
155         self.assertEqual(response.status_code, 200)
156         self.assertEqual(BillingAgreement.objects.all().count(), 1)
157
158         # Repeated returns will not generate further agreements.
159         response = self.client.get('/paypal/return/?token=secret-token')
160         self.assertEqual(response.status_code, 200)
161         self.assertEqual(BillingAgreement.objects.all().count(), 1)
162
163         self.assertTrue(user_is_subscribed(self.user))
164
165     @patch('paypalrestsdk.BillingAgreement', BillingAgreementMock)
166     def test_paypal_app_return(self):
167         self.client.login(username='test', password='test')
168         BillingPlan.objects.create(amount=100)
169         response = self.client.get('/paypal/app-return/?token=secret-token')
170         self.assertRedirects(response, 'wolnelekturyapp://paypal_return')
171
172         # Repeated returns will not generate further agreements.
173         response = self.client.get('/paypal/app-return/?token=secret-token')
174         self.assertRedirects(response, 'wolnelekturyapp://paypal_return')
175         self.assertEqual(BillingAgreement.objects.all().count(), 1)
176
177         self.assertTrue(user_is_subscribed(self.user))
178
179     def test_paypal_return_error(self):
180         self.client.login(username='test', password='test')
181         BillingPlan.objects.create(amount=100)
182
183         # It can choke on BillingAgreement.execute()
184         with patch('paypalrestsdk.BillingAgreement', Mock(
185                 execute=Mock(return_value=Mock(id=None)))):
186             self.client.get('/paypal/app-return/?token=secret-token')
187             response = self.client.get('/paypal/app-return/?token=secret-token')
188             self.assertRedirects(response, 'wolnelekturyapp://paypal_error')
189
190         # No agreement created in our DB if not executed successfully.
191         self.assertEqual(BillingAgreement.objects.all().count(), 0)
192
193         # It can execute all right, but just not be findable later.
194         with patch('paypalrestsdk.BillingAgreement', Mock(
195                 execute=BillingAgreementMock.execute,
196                 find=Mock(side_effect=ResourceNotFound(None)))):
197             response = self.client.get('/paypal/app-return/?token=secret-token')
198             self.assertRedirects(response, 'wolnelekturyapp://paypal_return')
199
200         # Now the agreement exists in our DB, but is not active.
201         self.assertEqual([b.active for b in BillingAgreement.objects.all()], [False])
202
203         with patch('paypalrestsdk.BillingAgreement', Mock(
204                 find=Mock(return_value=Mock(state='Mocked')))):
205             self.assertFalse(user_is_subscribed(self.user))
206
207     def test_paypal_cancel(self):
208         response = self.client.get('/paypal/cancel/')
209         self.assertEqual(response.status_code, 200)