Преглед на файлове

Check checkout-view skip conditions before pre conditions

master
Joseph Wayodi преди 4 години
родител
ревизия
6a04a96ee3

+ 6
- 6
src/oscar/apps/checkout/session.py Целия файл

@@ -53,6 +53,12 @@ class CheckoutSessionMixin(object):
53 53
         # views.
54 54
         self.checkout_session = CheckoutSessionData(request)
55 55
 
56
+        # Check if this view should be skipped
57
+        try:
58
+            self.check_skip_conditions(request)
59
+        except exceptions.PassedSkipCondition as e:
60
+            return http.HttpResponseRedirect(e.url)
61
+
56 62
         # Enforce any pre-conditions for the view.
57 63
         try:
58 64
             self.check_pre_conditions(request)
@@ -61,12 +67,6 @@ class CheckoutSessionMixin(object):
61 67
                 messages.warning(request, message)
62 68
             return http.HttpResponseRedirect(e.url)
63 69
 
64
-        # Check if this view should be skipped
65
-        try:
66
-            self.check_skip_conditions(request)
67
-        except exceptions.PassedSkipCondition as e:
68
-            return http.HttpResponseRedirect(e.url)
69
-
70 70
         return super().dispatch(
71 71
             request, *args, **kwargs)
72 72
 

+ 3
- 3
src/oscar/apps/checkout/views.py Целия файл

@@ -251,9 +251,9 @@ class ShippingMethodView(CheckoutSessionMixin, generic.FormView):
251 251
         return super().post(request, *args, **kwargs)
252 252
 
253 253
     def get(self, request, *args, **kwargs):
254
-        # These pre-conditions can't easily be factored out into the normal
255
-        # pre-conditions as they do more than run a test and then raise an
256
-        # exception on failure.
254
+        # These skip and pre conditions can't easily be factored out into the
255
+        # normal pre-conditions as they do more than run a test and then raise
256
+        # an exception on failure.
257 257
 
258 258
         # Check that shipping is required at all
259 259
         if not request.basket.is_shipping_required():

+ 494
- 19
tests/functional/checkout/__init__.py Целия файл

@@ -1,13 +1,23 @@
1 1
 from decimal import Decimal as D
2
+from http import client as http_client
3
+from unittest import mock
2 4
 
3 5
 from django.urls import reverse
4 6
 
5
-from oscar.core.loading import get_class, get_model
7
+from oscar.apps.shipping import methods
8
+from oscar.core.loading import get_class, get_classes, get_model
6 9
 from oscar.test import factories
7 10
 
8
-UserAddress = get_model('address', 'UserAddress')
9
-Country = get_model('address', 'Country')
11
+Basket = get_model('basket', 'Basket')
12
+ConditionalOffer = get_model('offer', 'ConditionalOffer')
13
+Order = get_model('order', 'Order')
14
+
15
+FailedPreCondition = get_class('checkout.exceptions', 'FailedPreCondition')
10 16
 GatewayForm = get_class('checkout.forms', 'GatewayForm')
17
+UnableToPlaceOrder = get_class('order.exceptions', 'UnableToPlaceOrder')
18
+RedirectRequired, UnableToTakePayment, PaymentError = get_classes(
19
+    'payment.exceptions', ['RedirectRequired', 'UnableToTakePayment', 'PaymentError'])
20
+NoShippingRequired = get_class('shipping.methods', 'NoShippingRequired')
11 21
 
12 22
 
13 23
 class CheckoutMixin(object):
@@ -20,12 +30,12 @@ class CheckoutMixin(object):
20 30
             num_in_stock=None, price=D('12.00'), product=product)
21 31
         return product
22 32
 
23
-    def add_product_to_basket(self, product=None):
33
+    def add_product_to_basket(self, product=None, **kwargs):
24 34
         if product is None:
25 35
             product = factories.ProductFactory()
26 36
             factories.StockRecordFactory(
27 37
                 num_in_stock=10, price=D('12.00'), product=product)
28
-        detail_page = self.get(product.get_absolute_url())
38
+        detail_page = self.get(product.get_absolute_url(), user=kwargs.get('logged_in_user', self.user))
29 39
         form = detail_page.forms['add_to_basket_form']
30 40
         form.submit()
31 41
 
@@ -39,9 +49,10 @@ class CheckoutMixin(object):
39 49
 
40 50
     def enter_guest_details(self, email='guest@example.com'):
41 51
         index_page = self.get(reverse('checkout:index'))
42
-        index_page.form['username'] = email
43
-        index_page.form.select('options', GatewayForm.GUEST)
44
-        return index_page.form.submit()
52
+        if index_page.status_code == 200:
53
+            index_page.form['username'] = email
54
+            index_page.form.select('options', GatewayForm.GUEST)
55
+            index_page.form.submit()
45 56
 
46 57
     def create_shipping_country(self):
47 58
         return factories.CountryFactory(
@@ -50,13 +61,14 @@ class CheckoutMixin(object):
50 61
     def enter_shipping_address(self):
51 62
         self.create_shipping_country()
52 63
         address_page = self.get(reverse('checkout:shipping-address'))
53
-        form = address_page.forms['new_shipping_address']
54
-        form['first_name'] = 'John'
55
-        form['last_name'] = 'Doe'
56
-        form['line1'] = '1 Egg Road'
57
-        form['line4'] = 'Shell City'
58
-        form['postcode'] = 'N12 9RT'
59
-        form.submit()
64
+        if address_page.status_code == 200:
65
+            form = address_page.forms['new_shipping_address']
66
+            form['first_name'] = 'John'
67
+            form['last_name'] = 'Doe'
68
+            form['line1'] = '1 Egg Road'
69
+            form['line4'] = 'Shell City'
70
+            form['postcode'] = 'N12 9RT'
71
+            form.submit()
60 72
 
61 73
     def enter_shipping_method(self):
62 74
         self.get(reverse('checkout:shipping-method'))
@@ -67,14 +79,477 @@ class CheckoutMixin(object):
67 79
         preview = payment_details.click(linkid="view_preview")
68 80
         return preview.forms['place_order_form'].submit().follow()
69 81
 
70
-    def reach_payment_details_page(self, is_guest=False):
82
+    def reach_payment_details_page(self):
71 83
         self.add_product_to_basket()
72
-        if is_guest:
84
+        if self.is_anonymous:
73 85
             self.enter_guest_details('hello@egg.com')
74 86
         self.enter_shipping_address()
75 87
         return self.get(
76 88
             reverse('checkout:shipping-method')).follow().follow()
77 89
 
78
-    def ready_to_place_an_order(self, is_guest=False):
79
-        payment_details = self.reach_payment_details_page(is_guest)
90
+    def ready_to_place_an_order(self):
91
+        payment_details = self.reach_payment_details_page()
80 92
         return payment_details.click(linkid="view_preview")
93
+
94
+
95
+class IndexViewPreConditionsMixin:
96
+
97
+    view_name = None
98
+
99
+    # Disable skip conditions, so that we do not first get redirected forwards
100
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_payment_is_required')
101
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_basket_requires_shipping')
102
+    def test_check_basket_is_not_empty(
103
+        self,
104
+        mock_skip_unless_basket_requires_shipping,
105
+        mock_skip_unless_payment_is_required,
106
+    ):
107
+        response = self.get(reverse(self.view_name))
108
+        self.assertRedirectsTo(response, 'basket:summary')
109
+
110
+    # Disable skip conditions, so that we do not first get redirected forwards
111
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_payment_is_required')
112
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_basket_requires_shipping')
113
+    def test_check_basket_is_valid(
114
+        self,
115
+        mock_skip_unless_basket_requires_shipping,
116
+        mock_skip_unless_payment_is_required,
117
+    ):
118
+        # Add product to basket but then remove its stock so it is not
119
+        # purchasable.
120
+        product = factories.ProductFactory()
121
+        self.add_product_to_basket(product)
122
+        product.stockrecords.all().update(num_in_stock=0)
123
+        if self.is_anonymous:
124
+            self.enter_guest_details()
125
+
126
+        response = self.get(reverse(self.view_name))
127
+        self.assertRedirectsTo(response, 'basket:summary')
128
+
129
+
130
+class ShippingAddressViewSkipConditionsMixin:
131
+
132
+    view_name = None
133
+    next_view_name = None
134
+
135
+    def test_skip_unless_basket_requires_shipping(self):
136
+        product = self.create_digital_product()
137
+        self.add_product_to_basket(product)
138
+        if self.is_anonymous:
139
+            self.enter_guest_details()
140
+
141
+        response = self.get(reverse(self.view_name))
142
+        self.assertRedirectsTo(response, self.next_view_name)
143
+
144
+
145
+class ShippingAddressViewPreConditionsMixin(IndexViewPreConditionsMixin):
146
+
147
+    view_name = None
148
+
149
+    # Disable skip conditions, so that we do not first get redirected forwards
150
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_payment_is_required')
151
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_basket_requires_shipping')
152
+    def test_check_user_email_is_captured(
153
+        self,
154
+        mock_skip_unless_basket_requires_shipping,
155
+        mock_skip_unless_payment_is_required,
156
+    ):
157
+        if self.is_anonymous:
158
+            self.add_product_to_basket()
159
+            response = self.get(reverse(self.view_name))
160
+            self.assertRedirectsTo(response, 'checkout:index')
161
+
162
+
163
+class ShippingAddressViewMixin(ShippingAddressViewSkipConditionsMixin, ShippingAddressViewPreConditionsMixin):
164
+
165
+    def test_submitting_valid_form_adds_data_to_session(self):
166
+        self.add_product_to_basket()
167
+        if self.is_anonymous:
168
+            self.enter_guest_details()
169
+        self.create_shipping_country()
170
+
171
+        page = self.get(reverse('checkout:shipping-address'))
172
+        form = page.forms['new_shipping_address']
173
+        form['first_name'] = 'Barry'
174
+        form['last_name'] = 'Chuckle'
175
+        form['line1'] = '1 King Street'
176
+        form['line4'] = 'Gotham City'
177
+        form['postcode'] = 'N1 7RR'
178
+        response = form.submit()
179
+        self.assertRedirectsTo(response, 'checkout:shipping-method')
180
+
181
+        session_data = self.app.session['checkout_data']
182
+        session_fields = session_data['shipping']['new_address_fields']
183
+        self.assertEqual('Barry', session_fields['first_name'])
184
+        self.assertEqual('Chuckle', session_fields['last_name'])
185
+        self.assertEqual('1 King Street', session_fields['line1'])
186
+        self.assertEqual('Gotham City', session_fields['line4'])
187
+        self.assertEqual('N1 7RR', session_fields['postcode'])
188
+
189
+    def test_shows_initial_data_if_the_form_has_already_been_submitted(self):
190
+        self.add_product_to_basket()
191
+        if self.is_anonymous:
192
+            self.enter_guest_details()
193
+        self.enter_shipping_address()
194
+        page = self.get(reverse('checkout:shipping-address'), user=self.user)
195
+        form = page.forms['new_shipping_address']
196
+        self.assertEqual('John', form['first_name'].value)
197
+        self.assertEqual('Doe', form['last_name'].value)
198
+        self.assertEqual('1 Egg Road', form['line1'].value)
199
+        self.assertEqual('Shell City', form['line4'].value)
200
+        self.assertEqual('N12 9RT', form['postcode'].value)
201
+
202
+
203
+class ShippingMethodViewSkipConditionsMixin:
204
+
205
+    view_name = None
206
+    next_view_name = None
207
+
208
+    def test_skip_unless_basket_requires_shipping(self):
209
+        # This skip condition is not a "normal" one, but is implemented in the
210
+        # view's "get" method
211
+        product = self.create_digital_product()
212
+        self.add_product_to_basket(product)
213
+        if self.is_anonymous:
214
+            self.enter_guest_details()
215
+
216
+        response = self.get(reverse(self.view_name))
217
+        self.assertRedirectsTo(response, self.next_view_name)
218
+        self.assertEqual(self.app.session['checkout_data']['shipping']['method_code'], NoShippingRequired.code)
219
+
220
+    @mock.patch('oscar.apps.checkout.views.Repository')
221
+    def test_skip_if_single_shipping_method_is_available(self, mock_repo):
222
+        # This skip condition is not a "normal" one, but is implemented in the
223
+        # view's "get" method
224
+        self.add_product_to_basket()
225
+        if self.is_anonymous:
226
+            self.enter_guest_details()
227
+        self.enter_shipping_address()
228
+
229
+        # Ensure one shipping method available
230
+        instance = mock_repo.return_value
231
+        instance.get_shipping_methods.return_value = [methods.Free()]
232
+
233
+        response = self.get(reverse('checkout:shipping-method'))
234
+        self.assertRedirectsTo(response, 'checkout:payment-method')
235
+
236
+
237
+class ShippingMethodViewPreConditionsMixin(ShippingAddressViewPreConditionsMixin):
238
+
239
+    view_name = None
240
+
241
+    # Disable skip conditions, so that we do not first get redirected forwards
242
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_payment_is_required')
243
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_basket_requires_shipping')
244
+    @mock.patch('oscar.apps.checkout.views.Repository')
245
+    def test_check_shipping_methods_are_available(
246
+        self,
247
+        mock_repo,
248
+        mock_skip_unless_basket_requires_shipping,
249
+        mock_skip_unless_payment_is_required,
250
+    ):
251
+        # This pre condition is not a "normal" one, but is implemented in the
252
+        # view's "get" method
253
+        self.add_product_to_basket()
254
+        if self.is_anonymous:
255
+            self.enter_guest_details()
256
+        self.enter_shipping_address()
257
+
258
+        # Ensure no shipping methods available
259
+        instance = mock_repo.return_value
260
+        instance.get_shipping_methods.return_value = []
261
+
262
+        response = self.get(reverse('checkout:shipping-method'))
263
+        self.assertRedirectsTo(response, 'checkout:shipping-address')
264
+
265
+    # Disable skip conditions, so that we do not first get redirected forwards
266
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_payment_is_required')
267
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_basket_requires_shipping')
268
+    def test_check_shipping_data_is_captured(
269
+        self,
270
+        mock_skip_unless_basket_requires_shipping,
271
+        mock_skip_unless_payment_is_required,
272
+    ):
273
+        # This pre condition is not a "normal" one, but is implemented in the
274
+        # view's "get" method
275
+        self.add_product_to_basket()
276
+        if self.is_anonymous:
277
+            self.enter_guest_details()
278
+
279
+        response = self.get(reverse(self.view_name))
280
+        self.assertRedirectsTo(response, 'checkout:shipping-address')
281
+
282
+
283
+class ShippingMethodViewMixin(ShippingMethodViewSkipConditionsMixin, ShippingMethodViewPreConditionsMixin):
284
+
285
+    @mock.patch('oscar.apps.checkout.views.Repository')
286
+    def test_shows_form_when_multiple_shipping_methods_available(self, mock_repo):
287
+        self.add_product_to_basket()
288
+        if self.is_anonymous:
289
+            self.enter_guest_details()
290
+        self.enter_shipping_address()
291
+
292
+        # Ensure multiple shipping methods available
293
+        method = mock.MagicMock()
294
+        method.code = 'm'
295
+        instance = mock_repo.return_value
296
+        instance.get_shipping_methods.return_value = [methods.Free(), method]
297
+        form_page = self.get(reverse('checkout:shipping-method'))
298
+        self.assertIsOk(form_page)
299
+
300
+        response = form_page.forms[0].submit()
301
+        self.assertRedirectsTo(response, 'checkout:payment-method')
302
+
303
+    # Disable skip conditions, so that we do not first get redirected forwards
304
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_payment_is_required')
305
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_basket_requires_shipping')
306
+    @mock.patch('oscar.apps.checkout.views.Repository')
307
+    def test_check_user_can_submit_only_valid_shipping_method(
308
+        self,
309
+        mock_repo,
310
+        mock_skip_unless_basket_requires_shipping,
311
+        mock_skip_unless_payment_is_required,
312
+    ):
313
+        self.add_product_to_basket()
314
+        if self.is_anonymous:
315
+            self.enter_guest_details()
316
+        self.enter_shipping_address()
317
+        method = mock.MagicMock()
318
+        method.code = 'm'
319
+        instance = mock_repo.return_value
320
+        instance.get_shipping_methods.return_value = [methods.Free(), method]
321
+        form_page = self.get(reverse('checkout:shipping-method'))
322
+        # a malicious attempt?
323
+        form_page.forms[0]['method_code'].value = 'super-free-shipping'
324
+        response = form_page.forms[0].submit()
325
+        self.assertIsNotRedirect(response)
326
+        response.mustcontain('Your submitted shipping method is not permitted')
327
+
328
+
329
+class PaymentMethodViewSkipConditionsMixin:
330
+
331
+    @mock.patch('oscar.apps.checkout.session.SurchargeApplicator.get_surcharges')
332
+    def test_skip_unless_payment_is_required(self, mock_get_surcharges):
333
+        mock_get_surcharges.return_value = []
334
+
335
+        product = factories.create_product(price=D('0.00'), num_in_stock=100)
336
+        self.add_product_to_basket(product)
337
+        if self.is_anonymous:
338
+            self.enter_guest_details()
339
+        self.enter_shipping_address()
340
+        # The shipping method is set automatically, as there is only one (free)
341
+        # available
342
+
343
+        response = self.get(reverse('checkout:payment-method'))
344
+        self.assertRedirectsTo(response, 'checkout:preview')
345
+
346
+
347
+class PaymentMethodViewPreConditionsMixin(ShippingMethodViewPreConditionsMixin):
348
+
349
+    view_name = None
350
+
351
+    # Disable skip conditions, so that we do not first get redirected forwards
352
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_payment_is_required')
353
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_basket_requires_shipping')
354
+    def test_check_shipping_data_is_captured(
355
+        self,
356
+        mock_skip_unless_basket_requires_shipping,
357
+        mock_skip_unless_payment_is_required,
358
+    ):
359
+        super().test_check_shipping_data_is_captured()
360
+
361
+        self.enter_shipping_address()
362
+
363
+        response = self.get(reverse(self.view_name))
364
+        self.assertRedirectsTo(response, 'checkout:shipping-method')
365
+
366
+
367
+class PaymentMethodViewMixin(PaymentMethodViewSkipConditionsMixin, PaymentMethodViewPreConditionsMixin):
368
+
369
+    pass
370
+
371
+
372
+class PaymentDetailsViewSkipConditionsMixin:
373
+
374
+    @mock.patch('oscar.apps.checkout.session.SurchargeApplicator.get_surcharges')
375
+    def test_skip_unless_payment_is_required(self, mock_get_surcharges):
376
+        mock_get_surcharges.return_value = []
377
+
378
+        product = factories.create_product(price=D('0.00'), num_in_stock=100)
379
+        self.add_product_to_basket(product)
380
+        if self.is_anonymous:
381
+            self.enter_guest_details()
382
+        self.enter_shipping_address()
383
+        # The shipping method is set automatically, as there is only one (free)
384
+        # available
385
+
386
+        response = self.get(reverse('checkout:payment-details'))
387
+        self.assertRedirectsTo(response, 'checkout:preview')
388
+
389
+
390
+class PaymentDetailsViewPreConditionsMixin(PaymentMethodViewPreConditionsMixin):
391
+    """
392
+    Does not add any new pre conditions.
393
+    """
394
+
395
+
396
+class PaymentDetailsViewMixin(PaymentDetailsViewSkipConditionsMixin, PaymentDetailsViewPreConditionsMixin):
397
+
398
+    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_payment')
399
+    def test_redirects_customers_when_using_bank_gateway(self, mock_method):
400
+
401
+        bank_url = 'https://bank-website.com'
402
+        e = RedirectRequired(url=bank_url)
403
+        mock_method.side_effect = e
404
+        preview = self.ready_to_place_an_order()
405
+        bank_redirect = preview.forms['place_order_form'].submit()
406
+
407
+        assert bank_redirect.status_code == 302
408
+        assert bank_redirect.url == bank_url
409
+
410
+    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_payment')
411
+    def test_handles_anticipated_payments_errors_gracefully(self, mock_method):
412
+        msg = 'Submitted expiration date is wrong'
413
+        e = UnableToTakePayment(msg)
414
+        mock_method.side_effect = e
415
+        preview = self.ready_to_place_an_order()
416
+        response = preview.forms['place_order_form'].submit()
417
+        self.assertIsOk(response)
418
+        # check user is warned
419
+        response.mustcontain(msg)
420
+        # check basket is restored
421
+        basket = Basket.objects.get()
422
+        self.assertEqual(basket.status, Basket.OPEN)
423
+
424
+    @mock.patch('oscar.apps.checkout.views.logger')
425
+    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_payment')
426
+    def test_handles_unexpected_payment_errors_gracefully(
427
+            self, mock_method, mock_logger):
428
+        msg = 'This gateway is down for maintenance'
429
+        e = PaymentError(msg)
430
+        mock_method.side_effect = e
431
+        preview = self.ready_to_place_an_order()
432
+        response = preview.forms['place_order_form'].submit()
433
+        self.assertIsOk(response)
434
+        # check user is warned with a generic error
435
+        response.mustcontain(
436
+            'A problem occurred while processing payment for this order',
437
+            no=[msg])
438
+        # admin should be warned
439
+        self.assertTrue(mock_logger.error.called)
440
+        # check basket is restored
441
+        basket = Basket.objects.get()
442
+        self.assertEqual(basket.status, Basket.OPEN)
443
+
444
+    @mock.patch('oscar.apps.checkout.views.logger')
445
+    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_payment')
446
+    def test_handles_bad_errors_during_payments(
447
+            self, mock_method, mock_logger):
448
+        e = Exception()
449
+        mock_method.side_effect = e
450
+        preview = self.ready_to_place_an_order()
451
+        response = preview.forms['place_order_form'].submit()
452
+        self.assertIsOk(response)
453
+        self.assertTrue(mock_logger.exception.called)
454
+        basket = Basket.objects.get()
455
+        self.assertEqual(basket.status, Basket.OPEN)
456
+
457
+    @mock.patch('oscar.apps.checkout.views.logger')
458
+    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_order_placement')
459
+    def test_handles_unexpected_order_placement_errors_gracefully(
460
+            self, mock_method, mock_logger):
461
+        e = UnableToPlaceOrder()
462
+        mock_method.side_effect = e
463
+        preview = self.ready_to_place_an_order()
464
+        response = preview.forms['place_order_form'].submit()
465
+        self.assertIsOk(response)
466
+        self.assertTrue(mock_logger.error.called)
467
+        basket = Basket.objects.get()
468
+        self.assertEqual(basket.status, Basket.OPEN)
469
+
470
+    @mock.patch('oscar.apps.checkout.views.logger')
471
+    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_order_placement')
472
+    def test_handles_all_other_exceptions_gracefully(self, mock_method, mock_logger):
473
+        mock_method.side_effect = Exception()
474
+        preview = self.ready_to_place_an_order()
475
+        response = preview.forms['place_order_form'].submit()
476
+        self.assertIsOk(response)
477
+        self.assertTrue(mock_logger.exception.called)
478
+        basket = Basket.objects.get()
479
+        self.assertEqual(basket.status, Basket.OPEN)
480
+
481
+
482
+class PaymentDetailsPreviewViewPreConditionsMixin(PaymentDetailsViewPreConditionsMixin):
483
+
484
+    # Disable skip conditions, so that we do not first get redirected forwards
485
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_payment_is_required')
486
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_basket_requires_shipping')
487
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.check_payment_data_is_captured')
488
+    def test_check_payment_data_is_captured(
489
+        self,
490
+        mock_check_payment_data_is_captured,
491
+        mock_skip_unless_basket_requires_shipping,
492
+        mock_skip_unless_payment_is_required,
493
+    ):
494
+        mock_check_payment_data_is_captured.side_effect = FailedPreCondition(url=reverse('checkout:payment-details'))
495
+        response = self.ready_to_place_an_order()
496
+        self.assertRedirectsTo(response, 'checkout:payment-details')
497
+
498
+
499
+class PaymentDetailsPreviewViewMixin(PaymentDetailsPreviewViewPreConditionsMixin):
500
+
501
+    def test_allows_order_to_be_placed(self):
502
+        self.add_product_to_basket()
503
+        if self.is_anonymous:
504
+            self.enter_guest_details()
505
+        self.enter_shipping_address()
506
+
507
+        payment_details = self.get(
508
+            reverse('checkout:shipping-method')).follow().follow()
509
+        preview = payment_details.click(linkid="view_preview")
510
+        preview.forms['place_order_form'].submit().follow()
511
+
512
+        self.assertEqual(1, Order.objects.all().count())
513
+
514
+    def test_payment_form_being_submitted_from_payment_details_view(self):
515
+        payment_details = self.reach_payment_details_page()
516
+        preview = payment_details.forms['sensible_data'].submit()
517
+        self.assertEqual(0, Order.objects.all().count())
518
+        preview.form.submit().follow()
519
+        self.assertEqual(1, Order.objects.all().count())
520
+
521
+    def test_handles_invalid_payment_forms(self):
522
+        payment_details = self.reach_payment_details_page()
523
+        form = payment_details.forms['sensible_data']
524
+        # payment forms should use the preview URL not the payment details URL
525
+        form.action = reverse('checkout:payment-details')
526
+        self.assertEqual(form.submit(status="*").status_code, http_client.BAD_REQUEST)
527
+
528
+    def test_placing_an_order_using_a_voucher_records_use(self):
529
+        self.add_product_to_basket()
530
+        self.add_voucher_to_basket()
531
+        if self.is_anonymous:
532
+            self.enter_guest_details()
533
+        self.enter_shipping_address()
534
+        thankyou = self.place_order()
535
+
536
+        order = thankyou.context['order']
537
+        self.assertEqual(1, order.discounts.all().count())
538
+
539
+        discount = order.discounts.all()[0]
540
+        voucher = discount.voucher
541
+        self.assertEqual(1, voucher.num_orders)
542
+
543
+    def test_placing_an_order_using_an_offer_records_use(self):
544
+        offer = factories.create_offer()
545
+        self.add_product_to_basket()
546
+        if self.is_anonymous:
547
+            self.enter_guest_details()
548
+        self.enter_shipping_address()
549
+        self.place_order()
550
+
551
+        # Reload offer
552
+        offer = ConditionalOffer.objects.get(id=offer.id)
553
+
554
+        self.assertEqual(1, offer.num_orders)
555
+        self.assertEqual(1, offer.num_applications)

+ 40
- 115
tests/functional/checkout/test_customer_checkout.py Целия файл

@@ -1,27 +1,34 @@
1 1
 from django.urls import reverse
2 2
 
3
-from oscar.core.loading import get_class, get_model
3
+from oscar.core.loading import get_model
4 4
 from oscar.test import factories
5 5
 from oscar.test.testcases import WebTestCase
6 6
 
7
-from . import CheckoutMixin
7
+from . import (
8
+    CheckoutMixin, IndexViewPreConditionsMixin, PaymentDetailsPreviewViewMixin,
9
+    PaymentDetailsViewMixin, PaymentMethodViewMixin, ShippingAddressViewMixin,
10
+    ShippingMethodViewMixin)
8 11
 
9 12
 Order = get_model('order', 'Order')
10
-OrderPlacementMixin = get_class('checkout.mixins', 'OrderPlacementMixin')
11
-ConditionalOffer = get_model('offer', 'ConditionalOffer')
12 13
 UserAddress = get_model('address', 'UserAddress')
13
-GatewayForm = get_class('checkout.forms', 'GatewayForm')
14 14
 
15 15
 
16
-class TestIndexView(CheckoutMixin, WebTestCase):
16
+class LoginRequiredMixin:
17
+
18
+    view_name = None
19
+    view_url = None
17 20
 
18 21
     def test_requires_login(self):
19
-        response = self.get(reverse('checkout:index'), user=None)
20
-        self.assertIsRedirect(response)
22
+        response = self.get(self.view_url or reverse(self.view_name), user=None)
23
+        expected_url = '{login_url}?next={forward}'.format(
24
+            login_url=reverse('customer:login'),
25
+            forward=self.view_url or reverse(self.view_name))
26
+        self.assertRedirects(response, expected_url)
21 27
 
22
-    def test_redirects_customers_with_empty_basket(self):
23
-        response = self.get(reverse('checkout:index'))
24
-        self.assertRedirectsTo(response, 'basket:summary')
28
+
29
+class TestIndexView(LoginRequiredMixin, IndexViewPreConditionsMixin, CheckoutMixin, WebTestCase):
30
+
31
+    view_name = 'checkout:index'
25 32
 
26 33
     def test_redirects_customers_to_shipping_address_view(self):
27 34
         self.add_product_to_basket()
@@ -29,36 +36,14 @@ class TestIndexView(CheckoutMixin, WebTestCase):
29 36
         self.assertRedirectsTo(response, 'checkout:shipping-address')
30 37
 
31 38
 
32
-class TestShippingAddressView(CheckoutMixin, WebTestCase):
39
+class TestShippingAddressView(LoginRequiredMixin, ShippingAddressViewMixin, CheckoutMixin, WebTestCase):
40
+
41
+    view_name = 'checkout:shipping-address'
42
+    next_view_name = 'checkout:shipping-method'
33 43
 
34 44
     def setUp(self):
35 45
         super().setUp()
36
-        self.user_address = factories.UserAddressFactory(
37
-            user=self.user, country=self.create_shipping_country())
38
-
39
-    def test_requires_login(self):
40
-        response = self.get(reverse('checkout:shipping-address'), user=None)
41
-        self.assertIsRedirect(response)
42
-
43
-    def test_submitting_valid_form_adds_data_to_session(self):
44
-        self.add_product_to_basket()
45
-        page = self.get(reverse('checkout:shipping-address'))
46
-        form = page.forms['new_shipping_address']
47
-        form['first_name'] = 'Barry'
48
-        form['last_name'] = 'Chuckle'
49
-        form['line1'] = '1 King Street'
50
-        form['line4'] = 'Gotham City'
51
-        form['postcode'] = 'N1 7RR'
52
-        response = form.submit()
53
-        self.assertRedirectsTo(response, 'checkout:shipping-method')
54
-
55
-        session_data = self.app.session['checkout_data']
56
-        session_fields = session_data['shipping']['new_address_fields']
57
-        self.assertEqual('Barry', session_fields['first_name'])
58
-        self.assertEqual('Chuckle', session_fields['last_name'])
59
-        self.assertEqual('1 King Street', session_fields['line1'])
60
-        self.assertEqual('Gotham City', session_fields['line4'])
61
-        self.assertEqual('N1 7RR', session_fields['postcode'])
46
+        self.user_address = factories.UserAddressFactory(user=self.user, country=self.create_shipping_country())
62 47
 
63 48
     def test_only_shipping_addresses_are_shown(self):
64 49
         not_shipping_country = factories.CountryFactory(
@@ -81,28 +66,15 @@ class TestShippingAddressView(CheckoutMixin, WebTestCase):
81 66
         self.assertRedirectsTo(response, 'checkout:shipping-method')
82 67
 
83 68
 
84
-class TestUserAddressUpdateView(CheckoutMixin, WebTestCase):
69
+class TestUserAddressUpdateView(LoginRequiredMixin, CheckoutMixin, WebTestCase):
85 70
 
86 71
     def setUp(self):
87
-        country = self.create_shipping_country()
88 72
         super().setUp()
89
-        self.user_address = factories.UserAddressFactory(
90
-            user=self.user, country=country)
91
-
92
-    def test_requires_login(self):
93
-        response = self.get(
94
-            reverse('checkout:user-address-update',
95
-                    kwargs={'pk': self.user_address.pk}),
96
-            user=None)
97
-        self.assertIsRedirect(response)
73
+        user_address = factories.UserAddressFactory(user=self.user, country=self.create_shipping_country())
74
+        self.view_url = reverse('checkout:user-address-update', kwargs={'pk': user_address.pk})
98 75
 
99 76
     def test_submitting_valid_form_modifies_user_address(self):
100
-        page = self.get(
101
-            reverse(
102
-                'checkout:user-address-update',
103
-                kwargs={'pk': self.user_address.pk}),
104
-            user=self.user)
105
-
77
+        page = self.get(self.view_url, user=self.user)
106 78
         form = page.forms['update_user_address']
107 79
         form['first_name'] = 'Tom'
108 80
         response = form.submit()
@@ -110,40 +82,17 @@ class TestUserAddressUpdateView(CheckoutMixin, WebTestCase):
110 82
         self.assertEqual('Tom', UserAddress.objects.get().first_name)
111 83
 
112 84
 
113
-class TestShippingMethodView(CheckoutMixin, WebTestCase):
114
-
115
-    def test_requires_login(self):
116
-        response = self.get(reverse('checkout:shipping-method'), user=None)
117
-        self.assertIsRedirect(response)
118
-
119
-    def test_redirects_when_only_one_shipping_method(self):
120
-        self.add_product_to_basket()
121
-        self.enter_shipping_address()
122
-        response = self.get(reverse('checkout:shipping-method'))
123
-        self.assertRedirectsTo(response, 'checkout:payment-method')
124
-
125
-
126
-class TestDeleteUserAddressView(CheckoutMixin, WebTestCase):
85
+class TestUserAddressDeleteView(LoginRequiredMixin, CheckoutMixin, WebTestCase):
127 86
 
128 87
     def setUp(self):
129 88
         super().setUp()
130
-        self.country = self.create_shipping_country()
131
-        self.user_address = factories.UserAddressFactory(
132
-            user=self.user, country=self.country)
133
-
134
-    def test_requires_login(self):
135
-        response = self.get(
136
-            reverse('checkout:user-address-delete',
137
-                    kwargs={'pk': self.user_address.pk}),
138
-            user=None)
139
-        self.assertIsRedirect(response)
89
+        self.user_address = factories.UserAddressFactory(user=self.user, country=self.create_shipping_country())
90
+        self.view_url = reverse('checkout:user-address-delete', kwargs={'pk': self.user_address.pk})
140 91
 
141 92
     def test_can_delete_a_user_address_from_shipping_address_page(self):
142 93
         self.add_product_to_basket()
143 94
         page = self.get(reverse('checkout:shipping-address'), user=self.user)
144
-        delete_confirm = page.click(
145
-            href=reverse('checkout:user-address-delete',
146
-                         kwargs={'pk': self.user_address.pk}))
95
+        delete_confirm = page.click(href=self.view_url)
147 96
         form = delete_confirm.forms["delete_address_%s" % self.user_address.id]
148 97
         form.submit()
149 98
 
@@ -152,49 +101,25 @@ class TestDeleteUserAddressView(CheckoutMixin, WebTestCase):
152 101
         self.assertEqual(0, len(user_addresses))
153 102
 
154 103
 
155
-class TestPreviewView(CheckoutMixin, WebTestCase):
156
-
157
-    def test_allows_order_to_be_placed(self):
158
-        self.add_product_to_basket()
159
-        self.enter_shipping_address()
160
-
161
-        payment_details = self.get(
162
-            reverse('checkout:shipping-method')).follow().follow()
163
-        preview = payment_details.click(linkid="view_preview")
164
-        preview.forms['place_order_form'].submit().follow()
165
-
166
-        self.assertEqual(1, Order.objects.all().count())
104
+class TestShippingMethodView(LoginRequiredMixin, ShippingMethodViewMixin, CheckoutMixin, WebTestCase):
167 105
 
106
+    view_name = 'checkout:shipping-method'
107
+    next_view_name = 'checkout:payment-method'
168 108
 
169
-class TestPlacingAnOrderUsingAVoucher(CheckoutMixin, WebTestCase):
170 109
 
171
-    def test_records_use(self):
172
-        self.add_product_to_basket()
173
-        self.add_voucher_to_basket()
174
-        self.enter_shipping_address()
175
-        thankyou = self.place_order()
110
+class TestPaymentMethodView(LoginRequiredMixin, PaymentMethodViewMixin, CheckoutMixin, WebTestCase):
176 111
 
177
-        order = thankyou.context['order']
178
-        self.assertEqual(1, order.discounts.all().count())
112
+    view_name = 'checkout:payment-method'
179 113
 
180
-        discount = order.discounts.all()[0]
181
-        voucher = discount.voucher
182
-        self.assertEqual(1, voucher.num_orders)
183 114
 
115
+class TestPaymentDetailsView(LoginRequiredMixin, PaymentDetailsViewMixin, CheckoutMixin, WebTestCase):
184 116
 
185
-class TestPlacingAnOrderUsingAnOffer(CheckoutMixin, WebTestCase):
117
+    view_name = 'checkout:payment-details'
186 118
 
187
-    def test_records_use(self):
188
-        offer = factories.create_offer()
189
-        self.add_product_to_basket()
190
-        self.enter_shipping_address()
191
-        self.place_order()
192 119
 
193
-        # Reload offer
194
-        offer = ConditionalOffer.objects.get(id=offer.id)
120
+class TestPaymentDetailsPreviewView(LoginRequiredMixin, PaymentDetailsPreviewViewMixin, CheckoutMixin, WebTestCase):
195 121
 
196
-        self.assertEqual(1, offer.num_orders)
197
-        self.assertEqual(1, offer.num_applications)
122
+    view_name = 'checkout:preview'
198 123
 
199 124
 
200 125
 class TestThankYouView(CheckoutMixin, WebTestCase):

+ 44
- 375
tests/functional/checkout/test_guest_checkout.py Целия файл

@@ -1,5 +1,4 @@
1 1
 import sys
2
-from http import client as http_client
3 2
 from importlib import import_module, reload
4 3
 from unittest import mock
5 4
 from urllib.parse import quote
@@ -8,24 +7,16 @@ from django.conf import settings
8 7
 from django.test.utils import override_settings
9 8
 from django.urls import clear_url_caches, reverse
10 9
 
11
-from oscar.apps.shipping import methods
12
-from oscar.core.compat import get_user_model
13
-from oscar.core.loading import get_class, get_classes, get_model
10
+from oscar.core.loading import get_class
14 11
 from oscar.test import factories
15 12
 from oscar.test.testcases import WebTestCase
16 13
 
17
-from . import CheckoutMixin
14
+from . import (
15
+    CheckoutMixin, IndexViewPreConditionsMixin, PaymentDetailsPreviewViewMixin,
16
+    PaymentDetailsViewMixin, PaymentMethodViewMixin, ShippingAddressViewMixin,
17
+    ShippingMethodViewMixin)
18 18
 
19 19
 GatewayForm = get_class('checkout.forms', 'GatewayForm')
20
-CheckoutSessionData = get_class('checkout.utils', 'CheckoutSessionData')
21
-RedirectRequired, UnableToTakePayment, PaymentError = get_classes(
22
-    'payment.exceptions', [
23
-        'RedirectRequired', 'UnableToTakePayment', 'PaymentError'])
24
-UnableToPlaceOrder = get_class('order.exceptions', 'UnableToPlaceOrder')
25
-
26
-Basket = get_model('basket', 'Basket')
27
-Order = get_model('order', 'Order')
28
-User = get_user_model()
29 20
 
30 21
 
31 22
 def reload_url_conf():
@@ -36,27 +27,30 @@ def reload_url_conf():
36 27
     clear_url_caches()
37 28
 
38 29
 
39
-@override_settings(OSCAR_ALLOW_ANON_CHECKOUT=True)
40
-class TestIndexView(CheckoutMixin, WebTestCase):
30
+class AnonymousMixin:
31
+
41 32
     is_anonymous = True
42 33
 
43 34
     def setUp(self):
44 35
         reload_url_conf()
45 36
         super().setUp()
46 37
 
47
-    def test_redirects_customers_with_empty_basket(self):
48
-        response = self.get(reverse('checkout:index'))
38
+    # Disable skip conditions, so that we do not first get redirected forwards
39
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_basket_requires_shipping')
40
+    @mock.patch('oscar.apps.checkout.session.CheckoutSessionMixin.skip_unless_payment_is_required')
41
+    def test_does_not_require_login(
42
+        self,
43
+        mock_skip_unless_payment_is_required,
44
+        mock_skip_unless_basket_requires_shipping,
45
+    ):
46
+        response = self.get(reverse(self.view_name))
49 47
         self.assertRedirectsTo(response, 'basket:summary')
50 48
 
51
-    def test_redirects_customers_with_invalid_basket(self):
52
-        # Add product to basket but then remove its stock so it is not
53
-        # purchasable.
54
-        product = factories.ProductFactory()
55
-        self.add_product_to_basket(product)
56
-        product.stockrecords.all().update(num_in_stock=0)
57 49
 
58
-        response = self.get(reverse('checkout:index'))
59
-        self.assertRedirectsTo(response, 'basket:summary')
50
+@override_settings(OSCAR_ALLOW_ANON_CHECKOUT=True)
51
+class TestIndexView(AnonymousMixin, IndexViewPreConditionsMixin, CheckoutMixin, WebTestCase):
52
+
53
+    view_name = 'checkout:index'
60 54
 
61 55
     def test_redirects_new_customers_to_registration_page(self):
62 56
         self.add_product_to_basket()
@@ -70,25 +64,29 @@ class TestIndexView(CheckoutMixin, WebTestCase):
70 64
 
71 65
         expected_url = '{register_url}?next={forward}&email={email}'.format(
72 66
             register_url=reverse('customer:register'),
73
-            forward='/checkout/shipping-address/',
67
+            forward=reverse('checkout:shipping-address'),
74 68
             email=quote(new_user_email))
75 69
         self.assertRedirects(response, expected_url)
76 70
 
77 71
     def test_redirects_existing_customers_to_shipping_address_page(self):
78
-        existing_user = User.objects.create_user(
79
-            username=self.username, email=self.email, password=self.password)
72
+        password = 'password'
73
+        user = factories.UserFactory(password=password)
80 74
         self.add_product_to_basket()
81 75
         page = self.get(reverse('checkout:index'))
82 76
         form = page.form
83 77
         form.select('options', GatewayForm.EXISTING)
84
-        form['username'].value = existing_user.email
85
-        form['password'].value = self.password
78
+        form['username'].value = user.email
79
+        form['password'].value = password
86 80
         response = form.submit()
87 81
         self.assertRedirectsTo(response, 'checkout:shipping-address')
88 82
 
89 83
     def test_redirects_guest_customers_to_shipping_address_page(self):
90 84
         self.add_product_to_basket()
91
-        response = self.enter_guest_details()
85
+        page = self.get(reverse('checkout:index'))
86
+        form = page.form
87
+        form.select('options', GatewayForm.GUEST)
88
+        form['username'] = 'guest@example.com'
89
+        response = form.submit()
92 90
         self.assertRedirectsTo(response, 'checkout:shipping-address')
93 91
 
94 92
     def test_prefill_form_with_email_for_returning_guest(self):
@@ -100,367 +98,38 @@ class TestIndexView(CheckoutMixin, WebTestCase):
100 98
 
101 99
 
102 100
 @override_settings(OSCAR_ALLOW_ANON_CHECKOUT=True)
103
-class TestShippingAddressView(CheckoutMixin, WebTestCase):
104
-    is_anonymous = True
105
-
106
-    def setUp(self):
107
-        reload_url_conf()
108
-        super().setUp()
109
-
110
-    def test_redirects_customers_with_empty_basket(self):
111
-        response = self.get(reverse('checkout:shipping-address'))
112
-        self.assertRedirectsTo(response, 'basket:summary')
113
-
114
-    def test_redirects_customers_who_have_skipped_guest_form(self):
115
-        self.add_product_to_basket()
116
-        response = self.get(reverse('checkout:shipping-address'))
117
-        self.assertRedirectsTo(response, 'checkout:index')
118
-
119
-    def test_redirects_customers_whose_basket_doesnt_require_shipping(self):
120
-        product = self.create_digital_product()
121
-        self.add_product_to_basket(product)
122
-        self.enter_guest_details()
123
-
124
-        response = self.get(reverse('checkout:shipping-address'))
125
-        self.assertRedirectsTo(response, 'checkout:shipping-method')
126
-
127
-    def test_redirects_customers_with_invalid_basket(self):
128
-        # Add product to basket but then remove its stock so it is not
129
-        # purchasable.
130
-        product = factories.create_product(num_in_stock=1)
131
-        self.add_product_to_basket(product)
132
-        self.enter_guest_details()
133
-
134
-        product.stockrecords.all().update(num_in_stock=0)
135
-
136
-        response = self.get(reverse('checkout:shipping-address'))
137
-        self.assertRedirectsTo(response, 'basket:summary')
138
-
139
-    def test_shows_initial_data_if_the_form_has_already_been_submitted(self):
140
-        self.add_product_to_basket()
141
-        self.enter_guest_details('hello@egg.com')
142
-        self.enter_shipping_address()
143
-        page = self.get(reverse('checkout:shipping-address'), user=self.user)
144
-        self.assertEqual('John', page.form['first_name'].value)
145
-        self.assertEqual('Doe', page.form['last_name'].value)
146
-        self.assertEqual('1 Egg Road', page.form['line1'].value)
147
-        self.assertEqual('Shell City', page.form['line4'].value)
148
-        self.assertEqual('N12 9RT', page.form['postcode'].value)
149
-
150
-
151
-@override_settings(OSCAR_ALLOW_ANON_CHECKOUT=True)
152
-class TestShippingMethodView(CheckoutMixin, WebTestCase):
153
-    is_anonymous = True
154
-
155
-    def setUp(self):
156
-        reload_url_conf()
157
-        super().setUp()
158
-
159
-    def test_redirects_customers_with_empty_basket(self):
160
-        response = self.get(reverse('checkout:shipping-method'))
161
-        self.assertRedirectsTo(response, 'basket:summary')
162
-
163
-    def test_redirects_customers_with_invalid_basket(self):
164
-        product = factories.create_product(num_in_stock=1)
165
-        self.add_product_to_basket(product)
166
-        self.enter_guest_details()
167
-        self.enter_shipping_address()
168
-        product.stockrecords.all().update(num_in_stock=0)
169
-
170
-        response = self.get(reverse('checkout:shipping-method'))
171
-        self.assertRedirectsTo(response, 'basket:summary')
172
-
173
-    def test_redirects_customers_who_have_skipped_guest_form(self):
174
-        self.add_product_to_basket()
175
-
176
-        response = self.get(reverse('checkout:shipping-method'))
177
-        self.assertRedirectsTo(response, 'checkout:index')
178
-
179
-    def test_redirects_customers_whose_basket_doesnt_require_shipping(self):
180
-        product = self.create_digital_product()
181
-        self.add_product_to_basket(product)
182
-        self.enter_guest_details()
183
-
184
-        response = self.get(reverse('checkout:shipping-method'))
185
-        self.assertRedirectsTo(response, 'checkout:payment-method')
186
-
187
-    def test_redirects_customers_who_have_skipped_shipping_address_form(self):
188
-        self.add_product_to_basket()
189
-        self.enter_guest_details()
190
-
191
-        response = self.get(reverse('checkout:shipping-method'))
192
-        self.assertRedirectsTo(response, 'checkout:shipping-address')
193
-
194
-    @mock.patch('oscar.apps.checkout.views.Repository')
195
-    def test_redirects_customers_when_no_shipping_methods_available(
196
-            self, mock_repo):
197
-        self.add_product_to_basket()
198
-        self.enter_guest_details()
199
-        self.enter_shipping_address()
200
-
201
-        # Ensure no shipping methods available
202
-        instance = mock_repo.return_value
203
-        instance.get_shipping_methods.return_value = []
204
-
205
-        response = self.get(reverse('checkout:shipping-method'))
206
-        self.assertRedirectsTo(response, 'checkout:shipping-address')
207
-
208
-    @mock.patch('oscar.apps.checkout.views.Repository')
209
-    def test_redirects_customers_when_only_one_shipping_method_is_available(
210
-            self, mock_repo):
211
-        self.add_product_to_basket()
212
-        self.enter_guest_details()
213
-        self.enter_shipping_address()
214
-
215
-        # Ensure one shipping method available
216
-        instance = mock_repo.return_value
217
-        instance.get_shipping_methods.return_value = [methods.Free()]
101
+class TestShippingAddressView(AnonymousMixin, ShippingAddressViewMixin, CheckoutMixin, WebTestCase):
218 102
 
219
-        response = self.get(reverse('checkout:shipping-method'))
220
-        self.assertRedirectsTo(response, 'checkout:payment-method')
221
-
222
-    @mock.patch('oscar.apps.checkout.views.Repository')
223
-    def test_shows_form_when_multiple_shipping_methods_available(
224
-            self, mock_repo):
225
-        self.add_product_to_basket()
226
-        self.enter_guest_details()
227
-        self.enter_shipping_address()
228
-
229
-        # Ensure multiple shipping methods available
230
-        method = mock.MagicMock()
231
-        method.code = 'm'
232
-        instance = mock_repo.return_value
233
-        instance.get_shipping_methods.return_value = [methods.Free(), method]
234
-        form_page = self.get(reverse('checkout:shipping-method'))
235
-        self.assertIsOk(form_page)
236
-
237
-        response = form_page.forms[0].submit()
238
-        self.assertRedirectsTo(response, 'checkout:payment-method')
239
-
240
-    @mock.patch('oscar.apps.checkout.views.Repository')
241
-    def test_check_user_can_submit_only_valid_shipping_method(self, mock_repo):
242
-        self.add_product_to_basket()
243
-        self.enter_guest_details()
244
-        self.enter_shipping_address()
245
-        method = mock.MagicMock()
246
-        method.code = 'm'
247
-        instance = mock_repo.return_value
248
-        instance.get_shipping_methods.return_value = [methods.Free(), method]
249
-        form_page = self.get(reverse('checkout:shipping-method'))
250
-        # a malicious attempt?
251
-        form_page.forms[0]['method_code'].value = 'super-free-shipping'
252
-        response = form_page.forms[0].submit()
253
-        self.assertIsNotRedirect(response)
254
-        response.mustcontain('Your submitted shipping method is not permitted')
103
+    view_name = 'checkout:shipping-address'
104
+    next_view_name = 'checkout:shipping-method'
255 105
 
256 106
 
257 107
 @override_settings(OSCAR_ALLOW_ANON_CHECKOUT=True)
258
-class TestPaymentMethodView(CheckoutMixin, WebTestCase):
259
-    is_anonymous = True
260
-
261
-    def setUp(self):
262
-        reload_url_conf()
263
-        super().setUp()
108
+class TestShippingMethodView(AnonymousMixin, ShippingMethodViewMixin, CheckoutMixin, WebTestCase):
264 109
 
265
-    def test_redirects_customers_with_empty_basket(self):
266
-        response = self.get(reverse('checkout:payment-method'))
267
-        self.assertRedirectsTo(response, 'basket:summary')
268
-
269
-    def test_redirects_customers_with_invalid_basket(self):
270
-        product = factories.create_product(num_in_stock=1)
271
-        self.add_product_to_basket(product)
272
-        self.enter_guest_details()
273
-        self.enter_shipping_address()
274
-
275
-        product.stockrecords.all().update(num_in_stock=0)
276
-
277
-        response = self.get(reverse('checkout:payment-method'))
278
-        self.assertRedirectsTo(response, 'basket:summary')
279
-
280
-    def test_redirects_customers_who_have_skipped_guest_form(self):
281
-        self.add_product_to_basket()
282
-
283
-        response = self.get(reverse('checkout:payment-method'))
284
-        self.assertRedirectsTo(response, 'checkout:index')
285
-
286
-    def test_redirects_customers_who_have_skipped_shipping_address_form(self):
287
-        self.add_product_to_basket()
288
-        self.enter_guest_details()
289
-
290
-        response = self.get(reverse('checkout:payment-method'))
291
-        self.assertRedirectsTo(response, 'checkout:shipping-address')
292
-
293
-    def test_redirects_customers_who_have_skipped_shipping_method_step(self):
294
-        self.add_product_to_basket()
295
-        self.enter_guest_details()
296
-        self.enter_shipping_address()
297
-
298
-        response = self.get(reverse('checkout:payment-method'))
299
-        self.assertRedirectsTo(response, 'checkout:shipping-method')
110
+    view_name = 'checkout:shipping-method'
111
+    next_view_name = 'checkout:payment-method'
300 112
 
301 113
 
302 114
 @override_settings(OSCAR_ALLOW_ANON_CHECKOUT=True)
303
-class TestPaymentDetailsView(CheckoutMixin, WebTestCase):
304
-    is_anonymous = True
305
-
306
-    def setUp(self):
307
-        reload_url_conf()
308
-        super().setUp()
309
-
310
-    def test_redirects_customers_with_empty_basket(self):
311
-        response = self.get(reverse('checkout:payment-details'))
312
-        self.assertRedirectsTo(response, 'basket:summary')
313
-
314
-    def test_redirects_customers_with_invalid_basket(self):
315
-        product = factories.create_product(num_in_stock=1)
316
-        self.add_product_to_basket(product)
317
-        self.enter_guest_details()
318
-        self.enter_shipping_address()
319
-
320
-        product.stockrecords.all().update(num_in_stock=0)
115
+class TestPaymentMethodView(AnonymousMixin, PaymentMethodViewMixin, CheckoutMixin, WebTestCase):
321 116
 
322
-        response = self.get(reverse('checkout:payment-details'))
323
-        self.assertRedirectsTo(response, 'basket:summary')
324
-
325
-    def test_redirects_customers_who_have_skipped_guest_form(self):
326
-        self.add_product_to_basket()
327
-
328
-        response = self.get(reverse('checkout:payment-details'))
329
-        self.assertRedirectsTo(response, 'checkout:index')
330
-
331
-    def test_redirects_customers_who_have_skipped_shipping_address_form(self):
332
-        self.add_product_to_basket()
333
-        self.enter_guest_details()
334
-
335
-        response = self.get(reverse('checkout:payment-details'))
336
-        self.assertRedirectsTo(response, 'checkout:shipping-address')
337
-
338
-    def test_redirects_customers_who_have_skipped_shipping_method_step(self):
339
-        self.add_product_to_basket()
340
-        self.enter_guest_details()
341
-        self.enter_shipping_address()
342
-
343
-        response = self.get(reverse('checkout:payment-details'))
344
-        self.assertRedirectsTo(response, 'checkout:shipping-method')
345
-
346
-    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_payment')
347
-    def test_redirects_customers_when_using_bank_gateway(self, mock_method):
348
-
349
-        bank_url = 'https://bank-website.com'
350
-        e = RedirectRequired(url=bank_url)
351
-        mock_method.side_effect = e
352
-        preview = self.ready_to_place_an_order(is_guest=True)
353
-        bank_redirect = preview.forms['place_order_form'].submit()
354
-
355
-        assert bank_redirect.status_code == 302
356
-        assert bank_redirect.url == bank_url
357
-
358
-    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_payment')
359
-    def test_handles_anticipated_payments_errors_gracefully(self, mock_method):
360
-        msg = 'Submitted expiration date is wrong'
361
-        e = UnableToTakePayment(msg)
362
-        mock_method.side_effect = e
363
-        preview = self.ready_to_place_an_order(is_guest=True)
364
-        response = preview.forms['place_order_form'].submit()
365
-        self.assertIsOk(response)
366
-        # check user is warned
367
-        response.mustcontain(msg)
368
-        # check basket is restored
369
-        basket = Basket.objects.get()
370
-        self.assertEqual(basket.status, Basket.OPEN)
371
-
372
-    @mock.patch('oscar.apps.checkout.views.logger')
373
-    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_payment')
374
-    def test_handles_unexpected_payment_errors_gracefully(
375
-            self, mock_method, mock_logger):
376
-        msg = 'This gateway is down for maintenance'
377
-        e = PaymentError(msg)
378
-        mock_method.side_effect = e
379
-        preview = self.ready_to_place_an_order(is_guest=True)
380
-        response = preview.forms['place_order_form'].submit()
381
-        self.assertIsOk(response)
382
-        # check user is warned with a generic error
383
-        response.mustcontain(
384
-            'A problem occurred while processing payment for this order',
385
-            no=[msg])
386
-        # admin should be warned
387
-        self.assertTrue(mock_logger.error.called)
388
-        # check basket is restored
389
-        basket = Basket.objects.get()
390
-        self.assertEqual(basket.status, Basket.OPEN)
391
-
392
-    @mock.patch('oscar.apps.checkout.views.logger')
393
-    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_payment')
394
-    def test_handles_bad_errors_during_payments(
395
-            self, mock_method, mock_logger):
396
-        e = Exception()
397
-        mock_method.side_effect = e
398
-        preview = self.ready_to_place_an_order(is_guest=True)
399
-        response = preview.forms['place_order_form'].submit()
400
-        self.assertIsOk(response)
401
-        self.assertTrue(mock_logger.exception.called)
402
-        basket = Basket.objects.get()
403
-        self.assertEqual(basket.status, Basket.OPEN)
404
-
405
-    @mock.patch('oscar.apps.checkout.views.logger')
406
-    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_order_placement')
407
-    def test_handles_unexpected_order_placement_errors_gracefully(
408
-            self, mock_method, mock_logger):
409
-        e = UnableToPlaceOrder()
410
-        mock_method.side_effect = e
411
-        preview = self.ready_to_place_an_order(is_guest=True)
412
-        response = preview.forms['place_order_form'].submit()
413
-        self.assertIsOk(response)
414
-        self.assertTrue(mock_logger.error.called)
415
-        basket = Basket.objects.get()
416
-        self.assertEqual(basket.status, Basket.OPEN)
417
-
418
-    @mock.patch('oscar.apps.checkout.views.logger')
419
-    @mock.patch('oscar.apps.checkout.views.PaymentDetailsView.handle_order_placement')
420
-    def test_handles_all_other_exceptions_gracefully(self, mock_method, mock_logger):
421
-        mock_method.side_effect = Exception()
422
-        preview = self.ready_to_place_an_order(is_guest=True)
423
-        response = preview.forms['place_order_form'].submit()
424
-        self.assertIsOk(response)
425
-        self.assertTrue(mock_logger.exception.called)
426
-        basket = Basket.objects.get()
427
-        self.assertEqual(basket.status, Basket.OPEN)
117
+    view_name = 'checkout:payment-method'
428 118
 
429 119
 
430 120
 @override_settings(OSCAR_ALLOW_ANON_CHECKOUT=True)
431
-class TestPaymentDetailsWithPreview(CheckoutMixin, WebTestCase):
432
-    is_anonymous = True
433
-    csrf_checks = False
121
+class TestPaymentDetailsView(AnonymousMixin, PaymentDetailsViewMixin, CheckoutMixin, WebTestCase):
434 122
 
435
-    def setUp(self):
436
-        reload_url_conf()
437
-        super().setUp()
438
-
439
-    def test_payment_form_being_submitted_from_payment_details_view(self):
440
-        payment_details = self.reach_payment_details_page(is_guest=True)
441
-        preview = payment_details.forms['sensible_data'].submit()
442
-        self.assertEqual(0, Order.objects.all().count())
443
-        preview.form.submit().follow()
444
-        self.assertEqual(1, Order.objects.all().count())
445
-
446
-    def test_handles_invalid_payment_forms(self):
447
-        payment_details = self.reach_payment_details_page(is_guest=True)
448
-        form = payment_details.forms['sensible_data']
449
-        # payment forms should use the preview URL not the payment details URL
450
-        form.action = reverse('checkout:payment-details')
451
-        self.assertEqual(form.submit(status="*").status_code, http_client.BAD_REQUEST)
123
+    view_name = 'checkout:payment-details'
452 124
 
453 125
 
454 126
 @override_settings(OSCAR_ALLOW_ANON_CHECKOUT=True)
455
-class TestPlacingOrder(CheckoutMixin, WebTestCase):
456
-    is_anonymous = True
127
+class TestPaymentDetailsPreviewView(AnonymousMixin, PaymentDetailsPreviewViewMixin, CheckoutMixin, WebTestCase):
457 128
 
458
-    def setUp(self):
459
-        reload_url_conf()
460
-        super().setUp()
129
+    view_name = 'checkout:preview'
461 130
 
462
-    def test_saves_guest_email_with_order(self):
463
-        preview = self.ready_to_place_an_order(is_guest=True)
131
+    def test_placing_order_saves_guest_email_with_order(self):
132
+        preview = self.ready_to_place_an_order()
464 133
         thank_you = preview.forms['place_order_form'].submit().follow()
465 134
         order = thank_you.context['order']
466 135
         self.assertEqual('hello@egg.com', order.guest_email)

Loading…
Отказ
Запис