Repository: saleor/saleor

Issue:
Build breaks on newest django-phonenumber-field release
django-phonenumber-field 2.4.0 was release a few days ago. As their changelog state: Saving or filtering by an invalid phone number will now raise a `ValueError`.

After looking for more information about the previous implementation and for the context, I found the https://github.com/stefanfoulis/django-phonenumber-field/pull/298 pull request. django-phonenumber-field was previously silently failing when we filtered with an invalid phone number, by replacing the phone number value to `""`.

We should probably add or edit the check logic in our back-end handle this case. As we don't handle in (as shown in the tests).

There is also a breakage coming from the newest release of `phonenumberslite` (8.10.11, coming from 8.10.10). They release a new "minor" version which actually introduces breaking changes to the number validation rules. ~~I couldn't tell the reasons of those new changes and their validity as no changelog is provided, the best thing I found was this: https://github.com/matext/python-phonenumbers/pull/21.~~

Edit: I found the changelog.

> Apr 30, 2019: v8.10.11
> Metadata changes:
> - Updated phone metadata for region code(s): CU, EH, MA
> - Updated short number metadata for region code(s): CU
>- Updated geocoding data for country calling code(s): 53 (en)

<details><summary>Test results</summary>

```
number = '+1 (555) 555-5555'
user_api_client = <tests.api.conftest.ApiClient object at 0x7f9a90618550>
checkout_with_item = Checkout(quantity=3)
graphql_address_data = {'city': 'Wrocław', 'companyName': 'Mirumee Software', 'country': 'PL', 'countryArea': '', ...}
    @pytest.mark.parametrize(
        'number', [
            '+48321321888',
            '+1 (555) 555-5555',
            '00 44 (0155 55) 5555'
        ]
    )
    def test_checkout_shipping_address_update_with_phone_country_prefix(
            number, user_api_client, checkout_with_item, graphql_address_data):
        checkout = checkout_with_item
        assert checkout.shipping_address is None
        checkout_id = graphene.Node.to_global_id('Checkout', checkout.pk)
    
        shipping_address = graphql_address_data
        shipping_address['phone'] = number
        variables = {
            'checkoutId': checkout_id,
            'shippingAddress': shipping_address}
    
        response = user_api_client.post_graphql(
            MUTATION_CHECKOUT_SHIPPING_ADDRESS_UPDATE, variables)
>       content = get_graphql_content(response)
tests/api/test_checkout.py:724: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
response = <JsonResponse status_code=200, "application/json">
    def get_graphql_content(response):
        """Get's GraphQL content from the response, and optionally checks if it
        contains any operating-related errors, eg. schema errors or lack of
        permissions.
        """
        content = _get_graphql_content_from_response(response)
>       assert 'errors' not in content, content['errors']
E       AssertionError: [{'message': '“+15555555555” is not a valid phone number.', 'locations': [{'line': 4, 'column': 9}], 'path': ['checkoutShippingAddressUpdate']}]
tests/api/utils.py:18: AssertionError
----------------------------- Captured stderr call -----------------------------
ERROR graphql.execution.utils graphql.error.located_error.GraphQLLocatedError: “+15555555555” is not a valid phone number.
 [PID:5171:MainThread]
------------------------------ Captured log call -------------------------------
utils.py                   151 ERROR    graphql.error.located_error.GraphQLLocatedError: “+15555555555” is not a valid phone number.
_ test_checkout_shipping_address_update_with_phone_country_prefix[00 44 (0155 55) 5555] _
[gw0] linux -- Python 3.6.7 /home/travis/build/mirumee/saleor/.tox/py36-django22/bin/python
number = '00 44 (0155 55) 5555'
user_api_client = <tests.api.conftest.ApiClient object at 0x7f9a9063f588>
checkout_with_item = Checkout(quantity=3)
graphql_address_data = {'city': 'Wrocław', 'companyName': 'Mirumee Software', 'country': 'PL', 'countryArea': '', ...}
    @pytest.mark.parametrize(
        'number', [
            '+48321321888',
            '+1 (555) 555-5555',
            '00 44 (0155 55) 5555'
        ]
    )
    def test_checkout_shipping_address_update_with_phone_country_prefix(
            number, user_api_client, checkout_with_item, graphql_address_data):
        checkout = checkout_with_item
        assert checkout.shipping_address is None
        checkout_id = graphene.Node.to_global_id('Checkout', checkout.pk)
    
        shipping_address = graphql_address_data
        shipping_address['phone'] = number
        variables = {
            'checkoutId': checkout_id,
            'shippingAddress': shipping_address}
    
        response = user_api_client.post_graphql(
            MUTATION_CHECKOUT_SHIPPING_ADDRESS_UPDATE, variables)
>       content = get_graphql_content(response)
tests/api/test_checkout.py:724: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
response = <JsonResponse status_code=200, "application/json">
    def get_graphql_content(response):
        """Get's GraphQL content from the response, and optionally checks if it
        contains any operating-related errors, eg. schema errors or lack of
        permissions.
        """
        content = _get_graphql_content_from_response(response)
>       assert 'errors' not in content, content['errors']
E       AssertionError: [{'message': '“+44155555555” is not a valid phone number.', 'locations': [{'line': 4, 'column': 9}], 'path': ['checkoutShippingAddressUpdate']}]
</details>



Discussion:
I fixed the tests in https://github.com/mirumee/saleor/pull/4072, but it will be nice to have logics handling the exceptions.
@jxltom we definitively have to and we really should test the case where invalid data was sent.

The build succeeds currently, how to reproduce the issue you're talking about @jxltom @NyanKiyoshi?
@maarcingebala I just tried to reproduce again, and managed to. Try upgrading the following:

```
django-phonenumber-field>=2.4.0
phonenumberslite>=8.10.11
```

Resolving patch:
diff --git a/saleor/account/validators.py b/saleor/account/validators.py
--- a/saleor/account/validators.py
+++ b/saleor/account/validators.py
@@ -4,8 +4,8 @@
 from phonenumbers.phonenumberutil import is_possible_number
 
 
-def validate_possible_number(value):
-    phone_number = to_python(value)
+def validate_possible_number(value, country=None):
+    phone_number = to_python(value, country)
     if phone_number and not is_possible_number(phone_number):
         raise ValidationError(
             _("The phone number entered is not valid."), code="invalid_phone_number"
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
@@ -3,6 +3,7 @@
 
 from ...account.forms import get_address_form
 from ...account.models import Address
+from ...account.validators import validate_possible_number
 
 
 class I18nMixin:
@@ -11,7 +12,16 @@ class I18nMixin:
     """
 
     @classmethod
-    def validate_address(cls, address_data, instance=None):
+    def validate_address(cls, address_data: dict, instance=None):
+        phone = address_data.get("phone", None)
+        if phone:
+            try:
+                validate_possible_number(phone, address_data.get("country"))
+            except ValidationError as exc:
+                raise ValidationError(
+                    {"phone": f"'{phone}' is not a valid phone number."}
+                ) from exc
+
         country_code = address_data.get("country")
         if country_code in countries.countries.keys():
             address_form, _ = get_address_form(address_data, address_data["country"])
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
@@ -350,7 +350,7 @@ class Arguments:
         checkout_id = graphene.ID(required=True, description="ID of the Checkout.")
         shipping_address = AddressInput(
             required=True,
-            description=("The mailing address to where the checkout will be shipped."),
+            description="The mailing address to where the checkout will be shipped.",
         )
 
     class Meta:


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
@@ -680,8 +680,31 @@ def test_checkout_shipping_address_update(
     assert checkout.shipping_address.city == shipping_address["city"].upper()
 
 
+def test_checkout_shipping_address_with_invalid_phone_number_returns_error(
+    user_api_client, checkout_with_item, graphql_address_data
+):
+    checkout = checkout_with_item
+    assert checkout.shipping_address is None
+
+    shipping_address = graphql_address_data
+    shipping_address["phone"] = "+33600000"
+
+    response = get_graphql_content(
+        user_api_client.post_graphql(
+            MUTATION_CHECKOUT_SHIPPING_ADDRESS_UPDATE,
+            {
+                "checkoutId": graphene.Node.to_global_id("Checkout", checkout.pk),
+                "shippingAddress": shipping_address,
+            },
+        )
+    )["data"]["checkoutShippingAddressUpdate"]
+    assert response["errors"] == [
+        {"field": "phone", "message": "'+33600000' is not a valid phone number."}
+    ]
+
+
 @pytest.mark.parametrize(
-    "number", ["+48321321888", "+1 (555) 555-5555", "00 44 (0155 55) 5555"]
+    "number", ["+48321321888", "+44 (113) 892-1113", "00 44 (0) 20 7839 1377"]
 )
 def test_checkout_shipping_address_update_with_phone_country_prefix(
     number, user_api_client, checkout_with_item, graphql_address_data
@@ -710,7 +733,7 @@ def test_checkout_shipping_address_update_without_phone_country_prefix(
     checkout_id = graphene.Node.to_global_id("Checkout", checkout.pk)
 
     shipping_address = graphql_address_data
-    shipping_address["phone"] = "321321888"
+    shipping_address["phone"] = "+1-202-555-0132"
     variables = {"checkoutId": checkout_id, "shippingAddress": shipping_address}
 
     response = user_api_client.post_graphql(