Repository: saleor/saleor

Issue:
'NoneType' object has no attribute 'lines'
### What I'm trying to achieve
To add an item to cart as an anonymous user.

### Steps to reproduce the problem
1. Use fresh saleor instance with `populatedb`
2. Execute query
```
mutation updateCheckoutLine($checkoutId: ID!, $lines: [CheckoutLineInput]!) {
  checkoutLinesUpdate(checkoutId: $checkoutId, lines: $lines) {
    errors {
      field
      message
    }
  }
}
```

with variables
```
[{"quantity":1,"variantId":"UHJvZHVjdFZhcmlhbnQ6MjIw"}]
```
3. Get an error 
```
{
  locations: [{line: 141, column: 3}]
  message: "'NoneType' object has no attribute 'lines'"
  path: ["checkoutLinesUpdate"]
}
```


Discussion:
It's worth noting that mutation itself works and updates cart.

Resolving patch:
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
@@ -1,7 +1,6 @@
 import graphene
 from django.db import transaction
 
-from ...account.models import Address
 from ...checkout import models
 from ...checkout.utils import (
     add_variant_to_cart, change_billing_address_in_cart,
@@ -66,7 +65,6 @@ def check_lines_quantity(variants, quantities):
     """
     # FIXME Add tests
     errors = []
-
     for variant, quantity in zip(variants, quantities):
         try:
             variant.check_quantity(quantity)
@@ -165,10 +163,13 @@ class Meta:
 
     @classmethod
     def mutate(cls, root, info, checkout_id, lines, replace=False):
-        checkout = graphene.Node.get_node_from_global_id(
-            info, checkout_id, Checkout)
-        variants, quantities = None, None
         errors = []
+        checkout = cls.get_node_or_error(
+            info, checkout_id, errors, 'checkout_id', only_type=Checkout)
+        if not checkout:
+            return CheckoutLinesAdd(errors=errors)
+
+        variants, quantities = None, None
         if lines:
             variant_ids = [line.get('variant_id') for line in lines]
             variants = cls.get_nodes_or_error(
@@ -184,15 +185,14 @@ def mutate(cls, root, info, checkout_id, lines, replace=False):
             for variant, quantity in zip(variants, quantities):
                 add_variant_to_cart(
                     checkout, variant, quantity, replace=replace)
+
         # FIXME test if below function is called
         clean_shipping_method(
-            checkout=checkout,
-            method=checkout.shipping_method,
-            errors=errors,
+            checkout=checkout, method=checkout.shipping_method, errors=errors,
             discounts=info.context.discounts,
             taxes=get_taxes_for_address(checkout.shipping_address))
         if errors:
-            CheckoutLinesAdd(errors=errors)
+            return CheckoutLinesAdd(errors=errors)
 
         return CheckoutLinesAdd(checkout=checkout, errors=errors)
 
@@ -232,13 +232,11 @@ def mutate(cls, root, info, checkout_id, line_id):
 
         # FIXME test if below function is called
         clean_shipping_method(
-            checkout=checkout,
-            method=checkout.shipping_method,
-            errors=errors,
+            checkout=checkout, method=checkout.shipping_method, errors=errors,
             discounts=info.context.discounts,
             taxes=get_taxes_for_address(checkout.shipping_address))
         if errors:
-            CheckoutLineDelete(errors=errors)
+            return CheckoutLineDelete(errors=errors)
 
         return CheckoutLineDelete(checkout=checkout, errors=errors)
 
@@ -280,17 +278,18 @@ class Meta:
     @classmethod
     def mutate(cls, root, info, checkout_id):
         errors = []
-        checkout = graphene.Node.get_node_from_global_id(
-            info, checkout_id, Checkout)
+        checkout = cls.get_node_or_error(
+            info, checkout_id, errors, 'checkout_id', only_type=Checkout)
+        if not checkout:
+            return cls(errors=errors)
         if not checkout.user:
             cls.add_error(
-                errors,
-                field=None,
+                errors, field=None,
                 message='There\'s no customer assigned to this Checkout.')
             return CheckoutCustomerDetach(errors=errors)
-        if checkout:
-            checkout.user = None
-            checkout.save(update_fields=['user'])
+
+        checkout.user = None
+        checkout.save(update_fields=['user'])
         return CheckoutCustomerDetach(checkout=checkout)
 
 
@@ -443,8 +442,7 @@ def mutate(cls, root, info, checkout_id):
             order = create_order(
                 cart=checkout,
                 tracking_code=analytics.get_client_id(info.context),
-                discounts=info.context.discounts,
-                taxes=taxes)
+                discounts=info.context.discounts, taxes=taxes)
         except InsufficientStock:
             order = None
             cls.add_error(
@@ -462,6 +460,7 @@ def mutate(cls, root, info, checkout_id):
             msg = str(exc)
             cls.add_error(field=None, message=msg, errors=errors)
             return CheckoutComplete(order=order, errors=errors)
+
         # capture payment
         try:
             gateway_capture(payment, payment.total)


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
@@ -88,9 +88,7 @@ def test_checkout_lines_add(user_api_client, cart_with_item, variant):
 
     variables = {
         'checkoutId': checkout_id,
-        'lines': [{
-            'variantId': variant_id,
-            'quantity': 1}]}
+        'lines': [{'variantId': variant_id, 'quantity': 1}]}
     response = user_api_client.post_graphql(query, variables)
     content = get_graphql_content(response)
     data = content['data']['checkoutLinesAdd']
@@ -145,6 +143,12 @@ def test_checkout_lines_update(user_api_client, cart_with_item):
     assert line.variant == variant
     assert line.quantity == 1
 
+    # test invalid checkout_id results in error
+    variables = {'checkoutId': 'test', 'lines': []}
+    response = user_api_client.post_graphql(query, variables)
+    content = get_graphql_content(response)
+    assert content['data']['checkoutLinesUpdate']['errors'][0]['field'] == 'checkoutId'
+
 
 def test_checkout_line_delete(user_api_client, cart_with_item):
     cart = cart_with_item