Repository: saleor/saleor

Issue:
Nonexistent voucher code form crashes when invalid
When voucher code for nonexistent voucher is entered on checkout, checkout form crashes, not displaying at all. Here's how it looks like at Saleor's demo:

<img width="454" alt="zrzut ekranu 2018-02-20 o 17 53 36" src="https://user-images.githubusercontent.com/750553/36437343-0277eb9c-1667-11e8-8f2d-87a0eecfe22a.png">

When running Saleor with `DEBUG = True`, Error 500 is produced instead:

<img width="675" alt="zrzut ekranu 2018-02-21 o 10 13 41" src="https://user-images.githubusercontent.com/750553/36471770-f80cdbfc-16ef-11e8-90e8-b583a2740135.png">

Investigating this, I've found that this is caused by the erroneous `default_error_messages` where `pgettext_lazy` is wrapping other `pgettext_lazy`:

```
class VoucherField(forms.ModelChoiceField):

    default_error_messages = {
        'invalid_choice': pgettext_lazy(
            'voucher', pgettext_lazy(
                'Voucher form error', 'Discount code incorrect or expired')),
    }
```

This feels simple fix enough, but the scenario feels important enough for new tests to be added to our tests suite.


Discussion:
Oh that's quite interesting. I tried to reproduce it on January release (on master, not demo) but it doesn't have the issue.
Is there a missing test for this feature? I'm pretty surprised the unit tests didn't catch this bug.

Resolving patch:
diff --git a/saleor/discount/forms.py b/saleor/discount/forms.py
--- a/saleor/discount/forms.py
+++ b/saleor/discount/forms.py
@@ -12,8 +12,7 @@ class VoucherField(forms.ModelChoiceField):
 
     default_error_messages = {
         'invalid_choice': pgettext_lazy(
-            'voucher', pgettext_lazy(
-                'Voucher form error', 'Discount code incorrect or expired')),
+            'Voucher form error', 'Discount code incorrect or expired'),
     }
 
 


Test patch:
diff --git a/tests/test_checkout_integration.py b/tests/test_checkout_integration.py
--- a/tests/test_checkout_integration.py
+++ b/tests/test_checkout_integration.py
@@ -261,6 +261,49 @@ def test_voucher_invalid(
     assert summary_response.context['order'].voucher is None
 
 
+def test_voucher_code_invalid(
+        client, request_cart_with_item, shipping_method):
+    # Enter checkout
+    checkout_index = client.get(reverse('checkout:index'), follow=True)
+    # Checkout index redirects directly to shipping address step
+    shipping_address = client.get(checkout_index.request['PATH_INFO'])
+
+    # Enter shipping address data
+    shipping_data = {
+        'email': 'test@example.com',
+        'first_name': 'John',
+        'last_name': 'Doe',
+        'street_address_1': 'Aleje Jerozolimskie 2',
+        'street_address_2': '',
+        'city': 'Warszawa',
+        'city_area': '',
+        'country_area': '',
+        'postal_code': '00-374',
+        'country': 'PL'}
+    shipping_response = client.post(shipping_address.request['PATH_INFO'],
+                                    data=shipping_data, follow=True)
+
+    # Select shipping method
+    shipping_method_page = client.get(shipping_response.request['PATH_INFO'])
+
+    # Redirect to summary after shipping method selection
+    shipping_method_data = {
+        'method': shipping_method.price_per_country.first().pk}
+    shipping_method_response = client.post(
+        shipping_method_page.request['PATH_INFO'], data=shipping_method_data,
+        follow=True)
+
+    # Summary page asks for Billing address, default is the same as shipping
+    url = shipping_method_response.request['PATH_INFO']
+    discount_data = {'discount-voucher': 'invalid-code'}
+    voucher_response = client.post(
+        '{url}?next={url}'.format(url=url), follow=True, data=discount_data,
+        HTTP_REFERER=url)
+    assert voucher_response.status_code == 200
+    assert 'voucher' in voucher_response.context['voucher_form'].errors
+    assert voucher_response.context['checkout'].voucher_code is None
+
+
 def test_remove_voucher(
         client, request_cart_with_item, shipping_method, voucher):
     # Enter checkout
diff --git a/tests/test_discounts.py b/tests/test_discounts.py
--- a/tests/test_discounts.py
+++ b/tests/test_discounts.py
@@ -183,7 +183,14 @@ def test_category_voucher_checkout_discount_not_applicable(settings,
     assert str(e.value) == 'This offer is only valid for selected items.'
 
 
-def test_invalid_checkout_discount_form(monkeypatch, voucher):
+def test_checkout_discount_form_invalid_voucher_code(monkeypatch):
+    checkout = Mock(cart=Mock())
+    form = CheckoutDiscountForm({'voucher': 'invalid'}, checkout=checkout)
+    assert not form.is_valid()
+    assert 'voucher' in form.errors
+
+
+def test_checkout_discount_form_not_applicable_voucher(monkeypatch, voucher):
     checkout = Mock(cart=Mock())
     form = CheckoutDiscountForm({'voucher': voucher.code}, checkout=checkout)
     monkeypatch.setattr(