Repository: saleor/saleor

Issue:
No errors returned when empty address set
### What I'm trying to achieve
I am trying to display errors in address form when user skips filling any input within it and I rely on errors returned by backend, but backend does not return any error.

### Steps to reproduce the problem
1. Many possibilities:
- Create checkout with empty object set as shipping address (probably also billing address), or
- Update billing address with empty object set as billing address (probably also shipping address)
2. Response from backend contains checkout set to `undefined` and errors set to empty array

### What I expected to happen
I expect to get in response errors array with errors associated with required, but not properly filled fields or not filled at all.

### Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
<img width="201" alt="Zrzut ekranu 2020-04-30 o 14 27 08" src="https://user-images.githubusercontent.com/9825562/80710173-b7e04800-8aee-11ea-92aa-88d398c6ccd7.png">
<img width="278" alt="Zrzut ekranu 2020-04-30 o 14 25 41" src="https://user-images.githubusercontent.com/9825562/80710180-ba42a200-8aee-11ea-90b6-50a382bd5c05.png">
<img width="201" alt="Zrzut ekranu 2020-04-30 o 14 32 32" src="https://user-images.githubusercontent.com/9825562/80710710-7c924900-8aef-11ea-9cc3-3237c3af5be5.png">
<img width="347" alt="Zrzut ekranu 2020-04-30 o 14 32 41" src="https://user-images.githubusercontent.com/9825562/80710721-8025d000-8aef-11ea-897e-1a61127a6660.png">

**System information**
Operating system: MacOS



Discussion:

Resolving patch:
diff --git a/saleor/graphql/account/i18n.py b/saleor/graphql/account/i18n.py
--- a/saleor/graphql/account/i18n.py
+++ b/saleor/graphql/account/i18n.py
@@ -43,6 +43,10 @@ def validate_address_form(cls, address_data: dict, instance=None):
 
     @classmethod
     def validate_address(cls, address_data: dict, instance=None, info=None):
+        if address_data.get("country") is None:
+            raise ValidationError(
+                {"country": ValidationError("This field is required.", code="required")}
+            )
         address_form = cls.validate_address_form(address_data)
         if not instance:
             instance = Address()
diff --git a/saleor/graphql/checkout/mutations.py b/saleor/graphql/checkout/mutations.py
--- a/saleor/graphql/checkout/mutations.py
+++ b/saleor/graphql/checkout/mutations.py
@@ -205,7 +205,7 @@ def process_checkout_lines(
 
     @classmethod
     def retrieve_shipping_address(cls, user, data: dict) -> Optional[models.Address]:
-        if "shipping_address" in data:
+        if data.get("shipping_address") is not None:
             return cls.validate_address(data["shipping_address"])
         if user.is_authenticated:
             return user.default_shipping_address
@@ -213,7 +213,7 @@ def retrieve_shipping_address(cls, user, data: dict) -> Optional[models.Address]
 
     @classmethod
     def retrieve_billing_address(cls, user, data: dict) -> Optional[models.Address]:
-        if "billing_address" in data:
+        if data.get("billing_address") is not None:
             return cls.validate_address(data["billing_address"])
         if user.is_authenticated:
             return user.default_billing_address


Test patch:
diff --git a/tests/api/test_checkout.py b/tests/api/test_checkout.py
--- a/tests/api/test_checkout.py
+++ b/tests/api/test_checkout.py
@@ -203,6 +203,38 @@ def test_checkout_create_multiple_warehouse(
     assert checkout_line.quantity == 4
 
 
+def test_checkout_create_with_null_as_addresses(api_client, stock):
+    """Create checkout object using GraphQL API."""
+    variant = stock.product_variant
+    variant_id = graphene.Node.to_global_id("ProductVariant", variant.id)
+    test_email = "test@example.com"
+    variables = {
+        "checkoutInput": {
+            "lines": [{"quantity": 1, "variantId": variant_id}],
+            "email": test_email,
+            "shippingAddress": None,
+            "billingAddress": None,
+        }
+    }
+    assert not Checkout.objects.exists()
+    response = api_client.post_graphql(MUTATION_CHECKOUT_CREATE, variables)
+    content = get_graphql_content(response)["data"]["checkoutCreate"]
+
+    # Look at the flag to see whether a new checkout was created or not
+    assert content["created"] is True
+
+    new_checkout = Checkout.objects.first()
+    assert new_checkout is not None
+    checkout_data = content["checkout"]
+    assert checkout_data["token"] == str(new_checkout.token)
+    assert new_checkout.lines.count() == 1
+    checkout_line = new_checkout.lines.first()
+    assert checkout_line.variant == variant
+    assert checkout_line.quantity == 1
+    assert new_checkout.shipping_address is None
+    assert new_checkout.billing_address is None
+
+
 @pytest.mark.parametrize(
     "quantity, expected_error_message, error_code",
     (
@@ -299,6 +331,52 @@ def test_checkout_create_required_email(api_client, stock):
     assert checkout_errors[0]["code"] == CheckoutErrorCode.REQUIRED.name
 
 
+def test_checkout_create_required_country_shipping_address(
+    api_client, stock, graphql_address_data
+):
+    variant = stock.product_variant
+    variant_id = graphene.Node.to_global_id("ProductVariant", variant.id)
+    shipping_address = graphql_address_data
+    del shipping_address["country"]
+    variables = {
+        "checkoutInput": {
+            "lines": [{"quantity": 1, "variantId": variant_id}],
+            "email": "test@example.com",
+            "shippingAddress": shipping_address,
+        }
+    }
+
+    response = api_client.post_graphql(MUTATION_CHECKOUT_CREATE, variables)
+    content = get_graphql_content(response)
+
+    checkout_errors = content["data"]["checkoutCreate"]["checkoutErrors"]
+    assert checkout_errors[0]["field"] == "country"
+    assert checkout_errors[0]["code"] == CheckoutErrorCode.REQUIRED.name
+
+
+def test_checkout_create_required_country_billing_address(
+    api_client, stock, graphql_address_data
+):
+    variant = stock.product_variant
+    variant_id = graphene.Node.to_global_id("ProductVariant", variant.id)
+    billing_address = graphql_address_data
+    del billing_address["country"]
+    variables = {
+        "checkoutInput": {
+            "lines": [{"quantity": 1, "variantId": variant_id}],
+            "email": "test@example.com",
+            "billingAddress": billing_address,
+        }
+    }
+
+    response = api_client.post_graphql(MUTATION_CHECKOUT_CREATE, variables)
+    content = get_graphql_content(response)
+
+    checkout_errors = content["data"]["checkoutCreate"]["checkoutErrors"]
+    assert checkout_errors[0]["field"] == "country"
+    assert checkout_errors[0]["code"] == CheckoutErrorCode.REQUIRED.name
+
+
 def test_checkout_create_default_email_for_logged_in_customer(user_api_client, stock):
     variant = stock.product_variant
     variant_id = graphene.Node.to_global_id("ProductVariant", variant.id)