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 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
 
  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):
 
  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)
 
  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)
 
  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')
 
  79         response = self.client.post('/paypal/form/', {"amount": "0"})
 
  80         self.assertEqual(response.status_code, 200)
 
  82             len(response.context['form'].errors['amount']),
 
  85     @skip("Changing the flow.")
 
  86     @patch.multiple('paypalrestsdk',
 
  87         BillingPlan=BillingPlanMock,
 
  88         BillingAgreement=BillingAgreementMock,
 
  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)
 
  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)
 
 104         # No BillingAgreement created in our DB yet.
 
 105         self.assertEqual(BillingAgreement.objects.all().count(), 0)
 
 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')
 
 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)
 
 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)
 
 125         # No plan is created yet.
 
 126         self.assertEqual(BillingPlan.objects.all().count(), 0)
 
 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)
 
 134         # But now the plan should be created.
 
 135         self.assertEqual(BillingPlan.objects.all().count(), 1)
 
 137     @skip("Changing the flow.")
 
 138     @patch.multiple('paypalrestsdk',
 
 139         BillingPlan=BillingPlanMock,
 
 140         BillingAgreement=BillingAgreementMock,
 
 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)
 
 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)
 
 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)
 
 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)
 
 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)
 
 169         self.assertTrue(user_is_subscribed(self.user))
 
 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)
 
 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)
 
 187         self.assertTrue(user_is_subscribed(self.user))
 
 189     def test_paypal_return_error(self):
 
 190         self.client.login(username='test', password='test')
 
 191         BillingPlan.objects.create(amount=100)
 
 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)
 
 202         # No agreement created in our DB if not executed successfully.
 
 203         self.assertEqual(BillingAgreement.objects.all().count(), 0)
 
 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)
 
 214         # Now the agreement exists in our DB, but is not active.
 
 215         self.assertEqual([b.active for b in BillingAgreement.objects.all()], [False])
 
 217         with patch('paypalrestsdk.BillingAgreement', Mock(
 
 218                 find=Mock(return_value=Mock(state='Mocked')))):
 
 219             self.assertFalse(user_is_subscribed(self.user))
 
 221     def test_paypal_cancel(self):
 
 222         response = self.client.get('/paypal/cancel/')
 
 223         self.assertEqual(response.status_code, 200)