Repository: saleor/saleor

Issue:
Bug: countryArea filed validation in addressCreate mutattion
### What are you trying to achieve?

addressCreate mutation should validate countryArea like in other mutations and return normalized countryArea

### Steps to reproduce the problem

Request
```
mutation CreateCustomerAddress($id: ID!, $input: AddressInput!) {
  addressCreate(userId: $id, input: $input) {
    errors {
      field
      message
      code
    }
    address {
      id
      countryArea
      postalCode
    }
    user {
      id
    }
  }
}

```
Variables:
```
{
  "id": "VXNlcjoxNjE0",
  "input": {
    "city": "test",
    "cityArea": "test",
    "companyName": "test",
    "country": "US",
    "countryArea": "Indiana",
    "firstName": "test",
    "lastName": "test",
    "phone": "",
    "postalCode": "46802",
    "streetAddress1": "test",
    "streetAddress2": ""
  }
}
```
<img width="1366" alt="Screenshot 2022-12-26 at 14 46 19" src="https://user-images.githubusercontent.com/30683248/209641418-9a45fd04-e795-4751-8799-b9b3a89108e3.png">

<img width="1323" alt="Screenshot 2022-12-27 at 09 54 48" src="https://user-images.githubusercontent.com/30683248/209642566-a1548ad0-4180-40b3-9258-ee432df52091.png">




### What did you expect to happen?

I expect each mutation with addresses to return a normalized country area e.g 
addressUpdate mutation:
<img width="1414" alt="Screenshot 2022-12-26 at 14 46 51" src="https://user-images.githubusercontent.com/30683248/209641473-60861f77-d464-473a-a9a1-524e225df1a6.png">

checkoutCreate
<img width="1741" alt="Screenshot 2022-12-27 at 10 09 09" src="https://user-images.githubusercontent.com/30683248/209642456-785f6309-0829-41fd-afc9-c456efd234ce.png">


### Logs

_No response_

### Environment

Saleor version: core v3.10.0-a
OS and version: …



Discussion:

Resolving patch:
diff --git a/saleor/graphql/account/mutations/staff.py b/saleor/graphql/account/mutations/staff.py
--- a/saleor/graphql/account/mutations/staff.py
+++ b/saleor/graphql/account/mutations/staff.py
@@ -37,6 +37,7 @@
 from ...core.validators.file import clean_image_file
 from ...plugins.dataloaders import get_plugin_manager_promise
 from ...utils.validators import check_for_duplicates
+from ..i18n import I18nMixin
 from ..utils import (
     CustomerDeleteMixin,
     StaffDeleteMixin,
@@ -544,7 +545,7 @@ def perform_mutation(  # type: ignore[override]
         return response
 
 
-class AddressCreate(ModelMutation):
+class AddressCreate(ModelMutation, I18nMixin):
     user = graphene.Field(
         User, description="A user instance for which the address was created."
     )
@@ -569,17 +570,24 @@ class Meta:
     def perform_mutation(cls, root, info: ResolveInfo, /, **data):
         user_id = data["user_id"]
         user = cls.get_node_or_error(info, user_id, field="user_id", only_type=User)
+        instance = cls.get_instance(info, **data)
+        data = data.get("input")
         with traced_atomic_transaction():
-            response = super().perform_mutation(root, info, **data)
-            if not response.errors:
-                manager = get_plugin_manager_promise(info.context).get()
-                address = manager.change_user_address(response.address, None, user)
-                remove_the_oldest_user_address_if_address_limit_is_reached(user)
-                user.addresses.add(address)
-                response.user = user
-                user.search_document = prepare_user_search_document_value(user)
-                user.save(update_fields=["search_document", "updated_at"])
-            return response
+            cleaned_input = cls.clean_input(info, instance, data)
+            instance = cls.validate_address(cleaned_input, instance=instance)
+            cls.clean_instance(info, instance)
+            cls.save(info, instance, cleaned_input)
+            cls.post_save_action(info, instance, cleaned_input)
+            response = cls.success_response(instance)
+            response.user = user
+            manager = get_plugin_manager_promise(info.context).get()
+            address = manager.change_user_address(instance, None, user)
+            remove_the_oldest_user_address_if_address_limit_is_reached(user)
+            user.addresses.add(address)
+            user.search_document = prepare_user_search_document_value(user)
+
+            user.save(update_fields=["search_document", "updated_at"])
+        return response
 
     @classmethod
     def post_save_action(cls, info: ResolveInfo, instance, cleaned_input):


Test patch:
diff --git a/saleor/graphql/account/tests/test_account.py b/saleor/graphql/account/tests/test_account.py
--- a/saleor/graphql/account/tests/test_account.py
+++ b/saleor/graphql/account/tests/test_account.py
@@ -4449,8 +4449,8 @@ def test_password_change_invalid_new_password(user_api_client, settings):
 
 
 ADDRESS_CREATE_MUTATION = """
-    mutation CreateUserAddress($user: ID!, $city: String!, $country: CountryCode!) {
-        addressCreate(userId: $user, input: {city: $city, country: $country}) {
+    mutation CreateUserAddress($user: ID!, $address: AddressInput!) {
+        addressCreate(userId: $user, input: $address) {
             errors {
                 field
                 message
@@ -4471,26 +4471,30 @@ def test_password_change_invalid_new_password(user_api_client, settings):
 
 
 def test_create_address_mutation(
-    staff_api_client, customer_user, permission_manage_users
+    staff_api_client, customer_user, permission_manage_users, graphql_address_data
 ):
+    # given
     query = ADDRESS_CREATE_MUTATION
+    graphql_address_data["city"] = "Dummy"
     user_id = graphene.Node.to_global_id("User", customer_user.id)
-    variables = {"user": user_id, "city": "Dummy", "country": "PL"}
+    variables = {"user": user_id, "address": graphql_address_data}
+    # when
     response = staff_api_client.post_graphql(
         query, variables, permissions=[permission_manage_users]
     )
     content = get_graphql_content(response)
+    # then
     assert content["data"]["addressCreate"]["errors"] == []
     data = content["data"]["addressCreate"]
-    assert data["address"]["city"] == "Dummy"
+    assert data["address"]["city"] == "DUMMY"
     assert data["address"]["country"]["code"] == "PL"
-    address_obj = Address.objects.get(city="Dummy")
+    address_obj = Address.objects.get(city="DUMMY")
     assert address_obj.user_addresses.first() == customer_user
     assert data["user"]["id"] == user_id
 
     customer_user.refresh_from_db()
     for field in ["city", "country"]:
-        assert variables[field].lower() in customer_user.search_document
+        assert variables["address"][field].lower() in customer_user.search_document
 
 
 @freeze_time("2022-05-12 12:00:00")
@@ -4504,13 +4508,14 @@ def test_create_address_mutation_trigger_webhook(
     customer_user,
     permission_manage_users,
     settings,
+    graphql_address_data,
 ):
     # given
     mocked_get_webhooks_for_event.return_value = [any_webhook]
     settings.PLUGINS = ["saleor.plugins.webhook.plugin.WebhookPlugin"]
 
     user_id = graphene.Node.to_global_id("User", customer_user.id)
-    variables = {"user": user_id, "city": "Dummy", "country": "PL"}
+    variables = {"user": user_id, "address": graphql_address_data}
 
     # when
     response = staff_api_client.post_graphql(
@@ -4535,25 +4540,34 @@ def test_create_address_mutation_trigger_webhook(
 
 @override_settings(MAX_USER_ADDRESSES=2)
 def test_create_address_mutation_the_oldest_address_is_deleted(
-    staff_api_client, customer_user, address, permission_manage_users
+    staff_api_client,
+    customer_user,
+    address,
+    permission_manage_users,
+    graphql_address_data,
 ):
+    # given
     same_address = Address.objects.create(**address.as_data())
     customer_user.addresses.set([address, same_address])
 
     user_addresses_count = customer_user.addresses.count()
-
+    graphql_address_data["city"] = "Dummy"
     query = ADDRESS_CREATE_MUTATION
     user_id = graphene.Node.to_global_id("User", customer_user.id)
-    variables = {"user": user_id, "city": "Dummy", "country": "PL"}
+    variables = {"user": user_id, "address": graphql_address_data}
+
+    # when
     response = staff_api_client.post_graphql(
         query, variables, permissions=[permission_manage_users]
     )
     content = get_graphql_content(response)
+
+    # then
     assert content["data"]["addressCreate"]["errors"] == []
     data = content["data"]["addressCreate"]
-    assert data["address"]["city"] == "Dummy"
+    assert data["address"]["city"] == "DUMMY"
     assert data["address"]["country"]["code"] == "PL"
-    address_obj = Address.objects.get(city="Dummy")
+    address_obj = Address.objects.get(city="DUMMY")
     assert address_obj.user_addresses.first() == customer_user
     assert data["user"]["id"] == user_id
 
@@ -4564,6 +4578,33 @@ def test_create_address_mutation_the_oldest_address_is_deleted(
         address.refresh_from_db()
 
 
+def test_create_address_validation_fails(
+    staff_api_client,
+    customer_user,
+    graphql_address_data,
+    permission_manage_users,
+    address,
+):
+    # given
+    query = ADDRESS_CREATE_MUTATION
+    address_data = graphql_address_data
+    user_id = graphene.Node.to_global_id("User", customer_user.id)
+    address_data["postalCode"] = "wrong postal code"
+    variables = {"user": user_id, "address": graphql_address_data}
+
+    # when
+    response = staff_api_client.post_graphql(
+        query, variables, permissions=[permission_manage_users]
+    )
+    content = get_graphql_content(response)
+
+    # then
+    data = content["data"]["addressCreate"]
+    assert len(data["errors"]) == 1
+    assert data["errors"][0]["field"] == "postalCode"
+    assert data["address"] is None
+
+
 ADDRESS_UPDATE_MUTATION = """
     mutation updateUserAddress($addressId: ID!, $address: AddressInput!) {
         addressUpdate(id: $addressId, input: $address) {