from typing import Union
from unittest import mock

import graphene
import pytest
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.template.defaultfilters import slugify
from graphene.utils.str_converters import to_camel_case

from saleor.core.taxes import zero_money
from saleor.graphql.core.utils import snake_to_camel_case
from saleor.graphql.product.enums import AttributeTypeEnum, AttributeValueType
from saleor.graphql.product.filters import filter_attributes_by_product_types
from saleor.graphql.product.mutations.attributes import validate_value_is_unique
from saleor.graphql.product.types.attributes import resolve_attribute_value_type
from saleor.product import AttributeInputType
from saleor.product.error_codes import ProductErrorCode
from saleor.product.models import (
    Attribute,
    AttributeProduct,
    AttributeValue,
    AttributeVariant,
    Category,
    Collection,
    Product,
    ProductType,
    ProductVariant,
)
from saleor.product.utils.attributes import associate_attribute_values_to_instance
from tests.api.utils import get_graphql_content


def test_validate_value_is_unique(color_attribute):
    value = color_attribute.values.first()

    # a new value but with existing slug should raise an error
    with pytest.raises(ValidationError):
        validate_value_is_unique(color_attribute, AttributeValue(slug=value.slug))

    # a new value with a new slug should pass
    validate_value_is_unique(
        color_attribute, AttributeValue(slug="spanish-inquisition")
    )

    # value that already belongs to the attribute shouldn't be taken into account
    validate_value_is_unique(color_attribute, value)


def test_get_single_attribute_by_pk(user_api_client, color_attribute_without_values):
    attribute_gql_id = graphene.Node.to_global_id(
        "Attribute", color_attribute_without_values.id
    )
    query = """
    query($id: ID!) {
        attribute(id: $id) {
            id
            slug
        }
    }
    """
    content = get_graphql_content(
        user_api_client.post_graphql(query, {"id": attribute_gql_id})
    )

    assert content["data"]["attribute"], "Should have found an attribute"
    assert content["data"]["attribute"]["id"] == attribute_gql_id
    assert content["data"]["attribute"]["slug"] == color_attribute_without_values.slug


QUERY_ATTRIBUTES = """
    query {
        attributes(first: 20) {
            edges {
                node {
                    id
                    name
                    slug
                    values {
                        id
                        name
                        slug
                    }
                }
            }
        }
    }
"""


def test_attributes_query(user_api_client, product):
    attributes = Attribute.objects
    query = QUERY_ATTRIBUTES
    response = user_api_client.post_graphql(query)
    content = get_graphql_content(response)
    attributes_data = content["data"]["attributes"]["edges"]
    assert attributes_data
    assert len(attributes_data) == attributes.count()


def test_attributes_query_hidden_attribute(user_api_client, product, color_attribute):
    query = QUERY_ATTRIBUTES

    # hide the attribute
    color_attribute.visible_in_storefront = False
    color_attribute.save(update_fields=["visible_in_storefront"])

    attribute_count = Attribute.objects.get_visible_to_user(
        user_api_client.user
    ).count()
    assert attribute_count == 1

    response = user_api_client.post_graphql(query)
    content = get_graphql_content(response)
    attributes_data = content["data"]["attributes"]["edges"]
    assert len(attributes_data) == attribute_count


def test_attributes_query_hidden_attribute_as_staff_user(
    staff_api_client, product, color_attribute, permission_manage_products
):
    query = QUERY_ATTRIBUTES

    # hide the attribute
    color_attribute.visible_in_storefront = False
    color_attribute.save(update_fields=["visible_in_storefront"])

    attribute_count = Attribute.objects.all().count()

    # The user doesn't have the permission yet to manage products,
    # the user shouldn't be able to see the hidden attributes
    assert Attribute.objects.get_visible_to_user(staff_api_client.user).count() == 1

    # The user should now be able to see the attributes
    staff_api_client.user.user_permissions.add(permission_manage_products)

    response = staff_api_client.post_graphql(query)
    content = get_graphql_content(response)
    attributes_data = content["data"]["attributes"]["edges"]
    assert len(attributes_data) == attribute_count


QUERY_PRODUCT_AND_VARIANTS_ATTRIBUTES = """
    {
      products(first: 1) {
        edges {
          node {
            attributes {
              attribute {
                slug
              }
              values {
                slug
              }
              value {
                slug
              }
            }
            variants {
              attributes {
                attribute {
                  slug
                }
                values {
                  slug
                }
                value {
                  slug
                }
              }
            }
          }
        }
      }
    }
"""


@pytest.mark.parametrize("is_staff", (False, True))
def test_resolve_attributes_with_hidden(
    user_api_client,
    product,
    color_attribute,
    size_attribute,
    staff_user,
    is_staff,
    permission_manage_products,
):
    """Ensure non-staff users don't see hidden attributes, and staff users having
    the 'manage product' permission can.
    """
    query = QUERY_PRODUCT_AND_VARIANTS_ATTRIBUTES
    api_client = user_api_client

    variant = product.variants.first()

    product_attribute = color_attribute
    variant_attribute = size_attribute

    expected_product_attribute_count = product.attributes.count() - 1
    expected_variant_attribute_count = variant.attributes.count() - 1

    if is_staff:
        api_client.user = staff_user
        expected_product_attribute_count += 1
        expected_variant_attribute_count += 1
        staff_user.user_permissions.add(permission_manage_products)

    # Hide one product and variant attribute from the storefront
    for attribute in (product_attribute, variant_attribute):
        attribute.visible_in_storefront = False
        attribute.save(update_fields=["visible_in_storefront"])

    product = get_graphql_content(api_client.post_graphql(query))["data"]["products"][
        "edges"
    ][0]["node"]

    assert len(product["attributes"]) == expected_product_attribute_count
    assert len(product["variants"][0]["attributes"]) == expected_variant_attribute_count


def test_resolve_attribute_values(user_api_client, product, staff_user):
    """Ensure the attribute values are properly resolved."""
    query = QUERY_PRODUCT_AND_VARIANTS_ATTRIBUTES
    api_client = user_api_client

    variant = product.variants.first()

    assert product.attributes.count() == 1
    assert variant.attributes.count() == 1

    product_attribute_values = list(
        product.attributes.first().values.values_list("slug", flat=True)
    )
    variant_attribute_values = list(
        variant.attributes.first().values.values_list("slug", flat=True)
    )

    assert len(product_attribute_values) == 1
    assert len(variant_attribute_values) == 1

    product = get_graphql_content(api_client.post_graphql(query))["data"]["products"][
        "edges"
    ][0]["node"]

    product_attributes = product["attributes"]
    variant_attributes = product["variants"][0]["attributes"]

    assert len(product_attributes) == len(product_attribute_values)
    assert len(variant_attributes) == len(variant_attribute_values)

    assert product_attributes[0]["attribute"]["slug"] == "color"
    assert product_attributes[0]["values"][0]["slug"] == product_attribute_values[0]
    assert product_attributes[0]["value"]["slug"] == product_attribute_values[0]

    assert variant_attributes[0]["attribute"]["slug"] == "size"
    assert variant_attributes[0]["values"][0]["slug"] == variant_attribute_values[0]
    assert variant_attributes[0]["value"]["slug"] == variant_attribute_values[0]


def test_resolve_attribute_values_non_assigned_to_node(
    user_api_client, product, staff_user
):
    """Ensure the attribute values are properly resolved when an attribute is part
    of the product type but not of the node (product/variant), thus no values should be
    resolved.
    """
    query = QUERY_PRODUCT_AND_VARIANTS_ATTRIBUTES
    api_client = user_api_client

    variant = product.variants.first()
    product_type = product.product_type

    # Create dummy attributes
    unassigned_product_attribute = Attribute.objects.create(name="P", slug="product")
    unassigned_variant_attribute = Attribute.objects.create(name="V", slug="variant")

    # Create a value for each dummy attribute to ensure they are not returned
    # by the product or variant as they are not associated to them
    AttributeValue.objects.bulk_create(
        [
            AttributeValue(slug="a", name="A", attribute=unassigned_product_attribute),
            AttributeValue(slug="b", name="B", attribute=unassigned_product_attribute),
        ]
    )

    # Assign the dummy attributes to the product type and push them at the top
    # through a sort_order=0 as the other attributes have sort_order=null
    AttributeProduct.objects.create(
        attribute=unassigned_product_attribute, product_type=product_type, sort_order=0
    )
    AttributeVariant.objects.create(
        attribute=unassigned_variant_attribute, product_type=product_type, sort_order=0
    )

    assert product.attributes.count() == 1
    assert variant.attributes.count() == 1

    product = get_graphql_content(api_client.post_graphql(query))["data"]["products"][
        "edges"
    ][0]["node"]

    product_attributes = product["attributes"]
    variant_attributes = product["variants"][0]["attributes"]

    assert len(product_attributes) == 2, "Non-assigned attr from the PT may be missing"
    assert len(variant_attributes) == 2, "Non-assigned attr from the PT may be missing"

    assert product_attributes[0]["attribute"]["slug"] == "product"
    assert product_attributes[0]["values"] == []
    assert variant_attributes[0]["value"] is None

    assert variant_attributes[0]["attribute"]["slug"] == "variant"
    assert variant_attributes[0]["values"] == []
    assert variant_attributes[0]["value"] is None


def test_attributes_filter_by_product_type_with_empty_value():
    """Ensure passing an empty or null value is ignored and the queryset is simply
    returned without any modification.
    """

    qs = Attribute.objects.all()

    assert filter_attributes_by_product_types(qs, "...", "") is qs
    assert filter_attributes_by_product_types(qs, "...", None) is qs


def test_attributes_filter_by_product_type_with_unsupported_field():
    """Ensure using an unknown field to filter attributes by raises a NotImplemented
    exception.
    """

    qs = Attribute.objects.all()

    with pytest.raises(NotImplementedError) as exc:
        filter_attributes_by_product_types(qs, "in_space", "a-value")

    assert exc.value.args == ("Filtering by in_space is unsupported",)


def test_attributes_filter_by_non_existing_category_id():
    """Ensure using a non-existing category ID returns an empty query set."""

    category_id = graphene.Node.to_global_id("Category", -1)
    mocked_qs = mock.MagicMock()
    qs = filter_attributes_by_product_types(mocked_qs, "in_category", category_id)
    assert qs == mocked_qs.none.return_value


@pytest.mark.parametrize("test_deprecated_filter", [True, False])
@pytest.mark.parametrize("tested_field", ["inCategory", "inCollection"])
def test_attributes_in_collection_query(
    user_api_client,
    product_type,
    category,
    collection,
    collection_with_products,
    test_deprecated_filter,
    tested_field,
):
    if "Collection" in tested_field:
        filtered_by_node_id = graphene.Node.to_global_id("Collection", collection.pk)
    elif "Category" in tested_field:
        filtered_by_node_id = graphene.Node.to_global_id("Category", category.pk)
    else:
        raise AssertionError(tested_field)
    expected_qs = Attribute.objects.filter(
        Q(attributeproduct__product_type_id=product_type.pk)
        | Q(attributevariant__product_type_id=product_type.pk)
    )

    # Create another product type and attribute that shouldn't get matched
    other_category = Category.objects.create(name="Other Category", slug="other-cat")
    other_attribute = Attribute.objects.create(name="Other", slug="other")
    other_product_type = ProductType.objects.create(
        name="Other type", has_variants=True, is_shipping_required=True
    )
    other_product_type.product_attributes.add(other_attribute)
    other_product = Product.objects.create(
        name=f"Another Product",
        product_type=other_product_type,
        category=other_category,
        price=zero_money(),
        is_published=True,
    )

    # Create another collection with products but shouldn't get matched
    # as we don't look for this other collection
    other_collection = Collection.objects.create(
        name="Other Collection",
        slug="other-collection",
        is_published=True,
        description="Description",
    )
    other_collection.products.add(other_product)

    query = """
    query($nodeID: ID!) {
        attributes(first: 20, %(filter_input)s) {
            edges {
                node {
                    id
                    name
                    slug
                }
            }
        }
    }
    """

    if test_deprecated_filter:
        query = query % {"filter_input": f"{tested_field}: $nodeID"}
    else:
        query = query % {"filter_input": "filter: { %s: $nodeID }" % tested_field}

    variables = {"nodeID": filtered_by_node_id}
    content = get_graphql_content(user_api_client.post_graphql(query, variables))
    attributes_data = content["data"]["attributes"]["edges"]

    flat_attributes_data = [attr["node"]["slug"] for attr in attributes_data]
    expected_flat_attributes_data = list(expected_qs.values_list("slug", flat=True))

    assert flat_attributes_data == expected_flat_attributes_data


CREATE_ATTRIBUTES_QUERY = """
    mutation createAttribute($name: String!, $values: [AttributeValueCreateInput]) {
        attributeCreate(input: {name: $name, values: $values}) {
            errors {
                field
                message
            }
            productErrors {
                field
                message
                code
            }
            attribute {
                name
                slug
                values {
                    name
                    slug
                }
                productTypes(first: 10) {
                    edges {
                        node {
                            id
                        }
                    }
                }
            }
        }
    }
"""


def test_create_attribute_and_attribute_values(
    staff_api_client, permission_manage_products
):
    query = CREATE_ATTRIBUTES_QUERY

    attribute_name = "Example name"
    name = "Value name"
    variables = {"name": attribute_name, "values": [{"name": name}]}
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    assert not content["data"]["attributeCreate"]["errors"]
    data = content["data"]["attributeCreate"]

    # Check if the attribute was correctly created
    assert data["attribute"]["name"] == attribute_name
    assert data["attribute"]["slug"] == slugify(
        attribute_name
    ), "The default slug should be the slugified name"
    assert (
        data["attribute"]["productTypes"]["edges"] == []
    ), "The attribute should not have been assigned to a product type"

    # Check if the attribute values were correctly created
    assert len(data["attribute"]["values"]) == 1
    assert data["attribute"]["values"][0]["name"] == name
    assert data["attribute"]["values"][0]["slug"] == slugify(name)


@pytest.mark.parametrize(
    "input_slug, expected_slug, expected_error",
    (
        ("my-slug", "my-slug", []),
        (None, "my-name", []),
        (
            "",
            None,
            [{"field": "slug", "message": "The attribute's slug cannot be blank."}],
        ),
    ),
)
def test_create_attribute_with_given_slug(
    staff_api_client,
    permission_manage_products,
    input_slug,
    expected_slug,
    expected_error,
):
    staff_api_client.user.user_permissions.add(permission_manage_products)
    query = """
        mutation createAttribute(
            $name: String!, $slug: String) {
        attributeCreate(input: {name: $name, slug: $slug}) {
            errors {
                field
                message
            }
            attribute {
                slug
            }
        }
    }
    """

    attribute_name = "My Name"
    variables = {"name": attribute_name, "slug": input_slug}
    content = get_graphql_content(staff_api_client.post_graphql(query, variables))

    # Check if the error is as expected: null or something else
    assert content["data"]["attributeCreate"]["errors"] == expected_error

    # Check if the slug was correctly set if no error was expected
    if expected_error is None:
        assert content["data"]["attributeCreate"]["attribute"]["slug"] == expected_slug


@pytest.mark.parametrize(
    "name_1, name_2, error_msg, error_code",
    (
        (
            "Red color",
            "Red color",
            "Provided values are not unique.",
            ProductErrorCode.UNIQUE,
        ),
        (
            "Red color",
            "red color",
            "Provided values are not unique.",
            ProductErrorCode.UNIQUE,
        ),
    ),
)
def test_create_attribute_and_attribute_values_errors(
    staff_api_client,
    name_1,
    name_2,
    error_msg,
    error_code,
    permission_manage_products,
    product_type,
):
    query = CREATE_ATTRIBUTES_QUERY
    variables = {"name": "Example name", "values": [{"name": name_1}, {"name": name_2}]}
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    errors = content["data"]["attributeCreate"]["errors"]
    assert errors
    assert errors[0]["field"] == "values"
    assert errors[0]["message"] == error_msg

    product_errors = content["data"]["attributeCreate"]["productErrors"]
    assert product_errors[0]["code"] == error_code.name


UPDATE_ATTRIBUTE_QUERY = """
    mutation updateAttribute(
        $id: ID!, $name: String!, $addValues: [AttributeValueCreateInput]!,
        $removeValues: [ID]!) {
    attributeUpdate(
            id: $id,
            input: {
                name: $name, addValues: $addValues,
                removeValues: $removeValues}) {
        errors {
            field
            message
        }
        productErrors {
            field
            message
            code
        }
        attribute {
            name
            slug
            values {
                name
                slug
            }
            productTypes(first: 10) {
                edges {
                    node {
                        id
                    }
                }
            }
        }
    }
}
"""


def test_update_attribute_name(
    staff_api_client, color_attribute, permission_manage_products
):
    query = UPDATE_ATTRIBUTE_QUERY
    attribute = color_attribute
    name = "Wings name"
    node_id = graphene.Node.to_global_id("Attribute", attribute.id)
    variables = {"name": name, "id": node_id, "addValues": [], "removeValues": []}
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    attribute.refresh_from_db()
    data = content["data"]["attributeUpdate"]
    assert data["attribute"]["name"] == name == attribute.name
    assert data["attribute"]["productTypes"]["edges"] == []


def test_update_attribute_remove_and_add_values(
    staff_api_client, color_attribute, permission_manage_products
):
    query = UPDATE_ATTRIBUTE_QUERY
    attribute = color_attribute
    name = "Wings name"
    attribute_value_name = "Red Color"
    node_id = graphene.Node.to_global_id("Attribute", attribute.id)
    attribute_value_id = attribute.values.first().id
    value_id = graphene.Node.to_global_id("AttributeValue", attribute_value_id)
    variables = {
        "name": name,
        "id": node_id,
        "addValues": [{"name": attribute_value_name}],
        "removeValues": [value_id],
    }
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    attribute.refresh_from_db()
    data = content["data"]["attributeUpdate"]
    assert not data["errors"]
    assert data["attribute"]["name"] == name == attribute.name
    assert not attribute.values.filter(pk=attribute_value_id).exists()
    assert attribute.values.filter(name=attribute_value_name).exists()


def test_update_empty_attribute_and_add_values(
    staff_api_client, color_attribute_without_values, permission_manage_products
):
    query = UPDATE_ATTRIBUTE_QUERY
    attribute = color_attribute_without_values
    name = "Wings name"
    attribute_value_name = "Yellow Color"
    node_id = graphene.Node.to_global_id("Attribute", attribute.id)
    variables = {
        "name": name,
        "id": node_id,
        "addValues": [{"name": attribute_value_name}],
        "removeValues": [],
    }
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    get_graphql_content(response)
    attribute.refresh_from_db()
    assert attribute.values.count() == 1
    assert attribute.values.filter(name=attribute_value_name).exists()


@pytest.mark.parametrize(
    "name_1, name_2, error_msg, error_code",
    (
        (
            "Red color",
            "Red color",
            "Provided values are not unique.",
            ProductErrorCode.UNIQUE,
        ),
        (
            "Red color",
            "red color",
            "Provided values are not unique.",
            ProductErrorCode.UNIQUE,
        ),
    ),
)
def test_update_attribute_and_add_attribute_values_errors(
    staff_api_client,
    name_1,
    name_2,
    error_msg,
    error_code,
    color_attribute,
    permission_manage_products,
):
    query = UPDATE_ATTRIBUTE_QUERY
    attribute = color_attribute
    node_id = graphene.Node.to_global_id("Attribute", attribute.id)
    variables = {
        "name": "Example name",
        "id": node_id,
        "removeValues": [],
        "addValues": [{"name": name_1}, {"name": name_2}],
    }
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    errors = content["data"]["attributeUpdate"]["errors"]
    assert errors
    assert errors[0]["field"] == "addValues"
    assert errors[0]["message"] == error_msg

    product_errors = content["data"]["attributeUpdate"]["productErrors"]
    assert product_errors[0]["code"] == error_code.name


def test_update_attribute_and_remove_others_attribute_value(
    staff_api_client, color_attribute, size_attribute, permission_manage_products
):
    query = UPDATE_ATTRIBUTE_QUERY
    attribute = color_attribute
    node_id = graphene.Node.to_global_id("Attribute", attribute.id)
    size_attribute = size_attribute.values.first()
    attr_id = graphene.Node.to_global_id("AttributeValue", size_attribute.pk)
    variables = {
        "name": "Example name",
        "id": node_id,
        "slug": "example-slug",
        "addValues": [],
        "removeValues": [attr_id],
    }
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    errors = content["data"]["attributeUpdate"]["errors"]
    assert errors
    assert errors[0]["field"] == "removeValues"
    err_msg = "Value %s does not belong to this attribute." % str(size_attribute)
    assert errors[0]["message"] == err_msg

    product_errors = content["data"]["attributeUpdate"]["productErrors"]
    assert product_errors[0]["code"] == ProductErrorCode.INVALID.name


def test_delete_attribute(
    staff_api_client, color_attribute, permission_manage_products, product_type
):
    attribute = color_attribute
    query = """
    mutation deleteAttribute($id: ID!) {
        attributeDelete(id: $id) {
            errors {
                field
                message
            }
            attribute {
                id
            }
        }
    }
    """
    node_id = graphene.Node.to_global_id("Attribute", attribute.id)
    variables = {"id": node_id}
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    data = content["data"]["attributeDelete"]
    assert data["attribute"]["id"] == variables["id"]
    with pytest.raises(attribute._meta.model.DoesNotExist):
        attribute.refresh_from_db()


CREATE_ATTRIBUTE_VALUE_QUERY = """
    mutation createAttributeValue(
        $attributeId: ID!, $name: String!) {
    attributeValueCreate(
        attribute: $attributeId, input: {name: $name}) {
        productErrors {
            field
            message
            code
        }
        attribute {
            values {
                name
            }
        }
        attributeValue {
            name
            type
            slug
        }
    }
}
"""


def test_create_attribute_value(
    staff_api_client, color_attribute, permission_manage_products
):
    attribute = color_attribute
    query = CREATE_ATTRIBUTE_VALUE_QUERY
    attribute_id = graphene.Node.to_global_id("Attribute", attribute.id)
    name = "test name"
    variables = {"name": name, "attributeId": attribute_id}
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    data = content["data"]["attributeValueCreate"]
    assert not data["productErrors"]

    attr_data = data["attributeValue"]
    assert attr_data["name"] == name
    assert attr_data["slug"] == slugify(name)
    assert attr_data["type"] == "STRING"
    assert name in [value["name"] for value in data["attribute"]["values"]]


def test_create_attribute_value_not_unique_name(
    staff_api_client, color_attribute, permission_manage_products
):
    attribute = color_attribute
    query = CREATE_ATTRIBUTE_VALUE_QUERY
    attribute_id = graphene.Node.to_global_id("Attribute", attribute.id)
    value_name = attribute.values.first().name
    variables = {"name": value_name, "attributeId": attribute_id}
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    data = content["data"]["attributeValueCreate"]
    assert data["productErrors"]
    assert data["productErrors"][0]["code"] == ProductErrorCode.ALREADY_EXISTS.name
    assert data["productErrors"][0]["field"] == "name"


def test_create_attribute_value_capitalized_name(
    staff_api_client, color_attribute, permission_manage_products
):
    attribute = color_attribute
    query = CREATE_ATTRIBUTE_VALUE_QUERY
    attribute_id = graphene.Node.to_global_id("Attribute", attribute.id)
    value_name = attribute.values.first().name
    variables = {"name": value_name.upper(), "attributeId": attribute_id}
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    data = content["data"]["attributeValueCreate"]
    assert data["productErrors"]
    assert data["productErrors"][0]["code"] == ProductErrorCode.ALREADY_EXISTS.name
    assert data["productErrors"][0]["field"] == "name"


UPDATE_ATTRIBUTE_VALUE_QUERY = """
mutation updateChoice(
        $id: ID!, $name: String!) {
    attributeValueUpdate(
    id: $id, input: {name: $name}) {
        errors {
            field
            message
        }
        attributeValue {
            name
            slug
        }
        attribute {
            values {
                name
            }
        }
    }
}
"""


def test_update_attribute_value(
    staff_api_client, pink_attribute_value, permission_manage_products
):
    query = UPDATE_ATTRIBUTE_VALUE_QUERY
    value = pink_attribute_value
    node_id = graphene.Node.to_global_id("AttributeValue", value.id)
    name = "Crimson name"
    variables = {"name": name, "id": node_id}
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    data = content["data"]["attributeValueUpdate"]
    value.refresh_from_db()
    assert data["attributeValue"]["name"] == name == value.name
    assert data["attributeValue"]["slug"] == slugify(name)
    assert name in [value["name"] for value in data["attribute"]["values"]]


def test_update_attribute_value_name_not_unique(
    staff_api_client, pink_attribute_value, permission_manage_products
):
    query = UPDATE_ATTRIBUTE_VALUE_QUERY
    value = pink_attribute_value.attribute.values.create(
        name="Example Name", slug="example-name", value="#RED"
    )
    node_id = graphene.Node.to_global_id("AttributeValue", value.id)
    variables = {"name": pink_attribute_value.name, "id": node_id}
    response = staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    content = get_graphql_content(response)
    data = content["data"]["attributeValueUpdate"]
    assert data["errors"]
    assert data["errors"][0]["message"]
    assert data["errors"][0]["field"] == "name"


def test_delete_attribute_value(
    staff_api_client, color_attribute, pink_attribute_value, permission_manage_products
):
    value = color_attribute.values.get(name="Red")
    query = """
    mutation updateChoice($id: ID!) {
        attributeValueDelete(id: $id) {
            attributeValue {
                name
                slug
            }
        }
    }
    """
    node_id = graphene.Node.to_global_id("AttributeValue", value.id)
    variables = {"id": node_id}
    staff_api_client.post_graphql(
        query, variables, permissions=[permission_manage_products]
    )
    with pytest.raises(value._meta.model.DoesNotExist):
        value.refresh_from_db()


@pytest.mark.parametrize(
    "raw_value, expected_type",
    [
        ("#0000", AttributeValueType.COLOR),
        ("#FF69B4", AttributeValueType.COLOR),
        ("rgb(255, 0, 0)", AttributeValueType.COLOR),
        ("hsl(0, 100%, 50%)", AttributeValueType.COLOR),
        ("hsla(120,  60%, 70%, 0.3)", AttributeValueType.COLOR),
        ("rgba(100%, 255, 0, 0)", AttributeValueType.COLOR),
        ("http://example.com", AttributeValueType.URL),
        ("https://example.com", AttributeValueType.URL),
        ("ftp://example.com", AttributeValueType.URL),
        ("example.com", AttributeValueType.STRING),
        ("Foo", AttributeValueType.STRING),
        ("linear-gradient(red, yellow)", AttributeValueType.GRADIENT),
        ("radial-gradient(#0000, yellow)", AttributeValueType.GRADIENT),
    ],
)
def test_resolve_attribute_value_type(raw_value, expected_type):
    assert resolve_attribute_value_type(raw_value) == expected_type


def test_resolve_assigned_attribute_without_values(api_client, product_type, product):
    """Ensure the attributes assigned to a product type are resolved even if
    the product doesn't provide any value for it or is not directly associated to it.
    """
    # Retrieve the product's variant
    variant = product.variants.get()

    # Remove all attributes and values from the product and its variant
    product.attributesrelated.clear()
    variant.attributesrelated.clear()

    # Retrieve the product and variant's attributes
    products = get_graphql_content(
        api_client.post_graphql(
            """
        {
          products(first: 10) {
            edges {
              node {
                attributes {
                  attribute {
                    slug
                  }
                  values {
                    name
                  }
                }
                variants {
                  attributes {
                    attribute {
                      slug
                    }
                    values {
                      name
                    }
                  }
                }
              }
            }
          }
        }
    """
        )
    )["data"]["products"]["edges"]

    # Ensure we are only working on one product and variant, the ones we are testing
    assert len(products) == 1
    assert len(products[0]["node"]["variants"]) == 1

    # Retrieve the nodes data
    product = products[0]["node"]
    variant = product["variants"][0]

    # Ensure the product attributes values are all None
    assert len(product["attributes"]) == 1
    assert product["attributes"][0]["attribute"]["slug"] == "color"
    assert product["attributes"][0]["values"] == []

    # Ensure the variant attributes values are all None
    assert variant["attributes"][0]["attribute"]["slug"] == "size"
    assert variant["attributes"][0]["values"] == []


ASSIGN_ATTR_QUERY = """
    mutation assign($productTypeId: ID!, $operations: [AttributeAssignInput]!) {
      attributeAssign(productTypeId: $productTypeId, operations: $operations) {
        errors {
          field
          message
        }
        productType {
          id
          productAttributes {
            id
          }
          variantAttributes {
            id
          }
        }
      }
    }
"""


def test_assign_attributes_to_product_type(
    staff_api_client, permission_manage_products, attribute_list
):
    product_type = ProductType.objects.create(name="Default Type", has_variants=True)
    product_type_global_id = graphene.Node.to_global_id("ProductType", product_type.pk)

    query = ASSIGN_ATTR_QUERY
    operations = []
    variables = {"productTypeId": product_type_global_id, "operations": operations}

    product_attributes_ids = {attr.pk for attr in attribute_list[:2]}
    variant_attributes_ids = {attr.pk for attr in attribute_list[2:]}

    for attr_id in product_attributes_ids:
        operations.append(
            {"type": "PRODUCT", "id": graphene.Node.to_global_id("Attribute", attr_id)}
        )

    for attr_id in variant_attributes_ids:
        operations.append(
            {"type": "VARIANT", "id": graphene.Node.to_global_id("Attribute", attr_id)}
        )

    content = get_graphql_content(
        staff_api_client.post_graphql(
            query, variables, permissions=[permission_manage_products]
        )
    )["data"]["attributeAssign"]
    assert not content["errors"], "Should have succeeded"

    assert content["productType"]["id"] == product_type_global_id
    assert len(content["productType"]["productAttributes"]) == len(
        product_attributes_ids
    )
    assert len(content["productType"]["variantAttributes"]) == len(
        variant_attributes_ids
    )

    found_product_attrs_ids = {
        int(graphene.Node.from_global_id(attr["id"])[1])
        for attr in content["productType"]["productAttributes"]
    }
    found_variant_attrs_ids = {
        int(graphene.Node.from_global_id(attr["id"])[1])
        for attr in content["productType"]["variantAttributes"]
    }

    assert found_product_attrs_ids == product_attributes_ids
    assert found_variant_attrs_ids == variant_attributes_ids


def test_assign_variant_attribute_to_product_type_with_disabled_variants(
    staff_api_client,
    permission_manage_products,
    product_type_without_variant,
    color_attribute_without_values,
):
    """The assignAttribute mutation should raise an error when trying
    to add an attribute as a variant attribute when
    the product type doesn't support variants"""

    product_type = product_type_without_variant
    attribute = color_attribute_without_values
    staff_api_client.user.user_permissions.add(permission_manage_products)

    product_type_global_id = graphene.Node.to_global_id("ProductType", product_type.pk)

    query = ASSIGN_ATTR_QUERY
    operations = [
        {"type": "VARIANT", "id": graphene.Node.to_global_id("Attribute", attribute.pk)}
    ]
    variables = {"productTypeId": product_type_global_id, "operations": operations}

    content = get_graphql_content(staff_api_client.post_graphql(query, variables))[
        "data"
    ]["attributeAssign"]
    assert content["errors"] == [
        {
            "field": "operations",
            "message": "Variants are disabled in this product type.",
        }
    ]


def test_assign_variant_attribute_having_unsupported_input_type(
    staff_api_client, permission_manage_products, product_type, size_attribute
):
    """The assignAttribute mutation should raise an error when trying
    to use an attribute as a variant attribute when
    the attribute's input type doesn't support variants"""

    attribute = size_attribute
    attribute.input_type = AttributeInputType.MULTISELECT
    attribute.save(update_fields=["input_type"])
    product_type.variant_attributes.clear()

    staff_api_client.user.user_permissions.add(permission_manage_products)

    product_type_global_id = graphene.Node.to_global_id("ProductType", product_type.pk)

    query = ASSIGN_ATTR_QUERY
    operations = [
        {"type": "VARIANT", "id": graphene.Node.to_global_id("Attribute", attribute.pk)}
    ]
    variables = {"productTypeId": product_type_global_id, "operations": operations}

    content = get_graphql_content(staff_api_client.post_graphql(query, variables))[
        "data"
    ]["attributeAssign"]
    assert content["errors"] == [
        {
            "field": "operations",
            "message": (
                "Attributes having for input types ['multiselect'] cannot be assigned "
                "as variant attributes"
            ),
        }
    ]


@pytest.mark.parametrize(
    "product_type_attribute_type, gql_attribute_type",
    (
        (AttributeTypeEnum.PRODUCT, AttributeTypeEnum.VARIANT),
        (AttributeTypeEnum.VARIANT, AttributeTypeEnum.PRODUCT),
        (AttributeTypeEnum.PRODUCT, AttributeTypeEnum.PRODUCT),
        (AttributeTypeEnum.VARIANT, AttributeTypeEnum.VARIANT),
    ),
)
def test_assign_attribute_to_product_type_having_already_that_attribute(
    staff_api_client,
    permission_manage_products,
    color_attribute_without_values,
    product_type_attribute_type,
    gql_attribute_type,
):
    """The assignAttribute mutation should raise an error when trying
    to add an attribute already contained in the product type."""

    product_type = ProductType.objects.create(name="Type")
    attribute = color_attribute_without_values
    staff_api_client.user.user_permissions.add(permission_manage_products)

    product_type_global_id = graphene.Node.to_global_id("ProductType", product_type.pk)

    if product_type_attribute_type == AttributeTypeEnum.PRODUCT:
        product_type.product_attributes.add(attribute)
    elif product_type_attribute_type == AttributeTypeEnum.VARIANT:
        product_type.variant_attributes.add(attribute)
    else:
        raise ValueError(f"Unknown: {product_type}")

    query = ASSIGN_ATTR_QUERY
    operations = [
        {
            "type": gql_attribute_type.value,
            "id": graphene.Node.to_global_id("Attribute", attribute.pk),
        }
    ]
    variables = {"productTypeId": product_type_global_id, "operations": operations}

    content = get_graphql_content(staff_api_client.post_graphql(query, variables))[
        "data"
    ]["attributeAssign"]
    assert content["errors"] == [
        {
            "field": "operations",
            "message": "Color (color) have already been assigned to this product type.",
        }
    ]


UNASSIGN_ATTR_QUERY = """
    mutation unAssignAttribute(
      $productTypeId: ID!, $attributeIds: [ID]!
    ) {
      attributeUnassign(productTypeId: $productTypeId, attributeIds: $attributeIds) {
        errors {
          field
          message
        }
        productType {
          id
          variantAttributes {
            id
          }
          productAttributes {
            id
          }
        }
      }
    }
"""


def test_unassign_attributes_from_product_type(
    staff_api_client, permission_manage_products, attribute_list
):
    product_type = ProductType.objects.create(name="Type")
    product_type_global_id = graphene.Node.to_global_id("ProductType", product_type.pk)

    variant_attribute, *product_attributes = attribute_list
    product_type.product_attributes.add(*product_attributes)
    product_type.variant_attributes.add(variant_attribute)

    remaining_attribute_global_id = graphene.Node.to_global_id(
        "Attribute", product_attributes[1].pk
    )

    query = UNASSIGN_ATTR_QUERY
    variables = {
        "productTypeId": product_type_global_id,
        "attributeIds": [
            graphene.Node.to_global_id("Attribute", product_attributes[0].pk)
        ],
    }

    content = get_graphql_content(
        staff_api_client.post_graphql(
            query, variables, permissions=[permission_manage_products]
        )
    )["data"]["attributeUnassign"]
    assert not content["errors"]

    assert content["productType"]["id"] == product_type_global_id
    assert len(content["productType"]["productAttributes"]) == 1
    assert len(content["productType"]["variantAttributes"]) == 1

    assert (
        content["productType"]["productAttributes"][0]["id"]
        == remaining_attribute_global_id
    )


def test_unassign_attributes_not_in_product_type(
    staff_api_client, permission_manage_products, color_attribute_without_values
):
    """The unAssignAttribute mutation should not raise any error when trying
    to remove an attribute that is not/no longer in the product type."""

    staff_api_client.user.user_permissions.add(permission_manage_products)

    product_type = ProductType.objects.create(name="Type")
    product_type_global_id = graphene.Node.to_global_id("ProductType", product_type.pk)

    query = UNASSIGN_ATTR_QUERY
    variables = {
        "productTypeId": product_type_global_id,
        "attributeIds": [
            graphene.Node.to_global_id("Attribute", color_attribute_without_values.pk)
        ],
    }

    content = get_graphql_content(staff_api_client.post_graphql(query, variables))[
        "data"
    ]["attributeUnassign"]
    assert not content["errors"]

    assert content["productType"]["id"] == product_type_global_id
    assert len(content["productType"]["productAttributes"]) == 0
    assert len(content["productType"]["variantAttributes"]) == 0


def test_retrieve_product_attributes_input_type(
    staff_api_client, product, permission_manage_products
):
    query = """
        {
          products(first: 10) {
            edges {
              node {
                attributes {
                  values {
                    type
                    inputType
                  }
                }
              }
            }
          }
        }
    """

    found_products = get_graphql_content(
        staff_api_client.post_graphql(query, permissions=[permission_manage_products])
    )["data"]["products"]["edges"]
    assert len(found_products) == 1

    for gql_attr in found_products[0]["node"]["attributes"]:
        assert len(gql_attr["values"]) == 1
        assert gql_attr["values"][0]["type"] == "STRING"
        assert gql_attr["values"][0]["inputType"] == "DROPDOWN"


@pytest.mark.parametrize(
    "attribute, expected_value",
    (
        ("filterable_in_storefront", True),
        ("filterable_in_dashboard", True),
        ("visible_in_storefront", True),
        ("available_in_grid", True),
        ("value_required", False),
        ("storefront_search_position", 0),
    ),
)
def test_retrieving_the_restricted_attributes_restricted(
    staff_api_client,
    color_attribute,
    permission_manage_products,
    attribute,
    expected_value,
):
    """Checks if the attributes are restricted and if their default value
    is the expected one."""

    attribute = to_camel_case(attribute)
    query = (
        """
        {
          attributes(first: 10) {
            edges {
              node {
                %s
              }
            }
          }
        }
    """
        % attribute
    )

    found_attributes = get_graphql_content(
        staff_api_client.post_graphql(query, permissions=[permission_manage_products])
    )["data"]["attributes"]["edges"]

    assert len(found_attributes) == 1
    assert found_attributes[0]["node"][attribute] == expected_value


ATTRIBUTES_RESORT_QUERY = """
    mutation ProductTypeReorderAttributes(
      $productTypeId: ID!
      $moves: [ReorderInput]!
      $type: AttributeTypeEnum!
    ) {
      productTypeReorderAttributes(
        productTypeId: $productTypeId
        moves: $moves
        type: $type
      ) {
        productType {
          id
          variantAttributes {
            id
            slug
          }
          productAttributes {
            id
          }
        }

        errors {
          field
          message
        }
      }
    }
"""


def test_sort_attributes_within_product_type_invalid_product_type(
    staff_api_client, permission_manage_products
):
    """Try to reorder an invalid product type (invalid ID)."""

    product_type_id = graphene.Node.to_global_id("ProductType", -1)
    attribute_id = graphene.Node.to_global_id("Attribute", -1)

    variables = {
        "type": "VARIANT",
        "productTypeId": product_type_id,
        "moves": [{"id": attribute_id, "sortOrder": 1}],
    }

    content = get_graphql_content(
        staff_api_client.post_graphql(
            ATTRIBUTES_RESORT_QUERY, variables, permissions=[permission_manage_products]
        )
    )["data"]["productTypeReorderAttributes"]

    assert content["errors"] == [
        {
            "field": "productTypeId",
            "message": f"Couldn't resolve to a product type: {product_type_id}",
        }
    ]


def test_sort_attributes_within_product_type_invalid_id(
    staff_api_client, permission_manage_products, color_attribute
):
    """Try to reorder an attribute not associated to the given product type."""

    product_type = ProductType.objects.create(name="Dummy Type")
    product_type_id = graphene.Node.to_global_id("ProductType", product_type.id)

    attribute_id = graphene.Node.to_global_id("Attribute", color_attribute.id)

    variables = {
        "type": "VARIANT",
        "productTypeId": product_type_id,
        "moves": [{"id": attribute_id, "sortOrder": 1}],
    }

    content = get_graphql_content(
        staff_api_client.post_graphql(
            ATTRIBUTES_RESORT_QUERY, variables, permissions=[permission_manage_products]
        )
    )["data"]["productTypeReorderAttributes"]

    assert content["errors"] == [
        {
            "field": "moves",
            "message": f"Couldn't resolve to an attribute: {attribute_id}",
        }
    ]


@pytest.mark.parametrize(
    "attribute_type, relation_field, backref_field",
    (
        ("VARIANT", "variant_attributes", "attributevariant"),
        ("PRODUCT", "product_attributes", "attributeproduct"),
    ),
)
def test_sort_attributes_within_product_type(
    staff_api_client,
    attribute_list,
    permission_manage_products,
    attribute_type,
    relation_field,
    backref_field,
):
    attributes = attribute_list
    assert len(attributes) == 3

    staff_api_client.user.user_permissions.add(permission_manage_products)

    product_type = ProductType.objects.create(name="Dummy Type")
    product_type_id = graphene.Node.to_global_id("ProductType", product_type.id)
    m2m_attributes = getattr(product_type, relation_field)
    m2m_attributes.set(attributes)

    sort_method = getattr(m2m_attributes, f"{relation_field}_sorted")
    attributes = list(sort_method())

    assert len(attributes) == 3

    variables = {
        "type": attribute_type,
        "productTypeId": product_type_id,
        "moves": [
            {
                "id": graphene.Node.to_global_id("Attribute", attributes[0].pk),
                "sortOrder": +1,
            },
            {
                "id": graphene.Node.to_global_id("Attribute", attributes[2].pk),
                "sortOrder": -1,
            },
        ],
    }

    expected_order = [attributes[1].pk, attributes[2].pk, attributes[0].pk]

    content = get_graphql_content(
        staff_api_client.post_graphql(ATTRIBUTES_RESORT_QUERY, variables)
    )["data"]["productTypeReorderAttributes"]
    assert not content["errors"]

    assert (
        content["productType"]["id"] == product_type_id
    ), "Did not return the correct product type"

    gql_attributes = content["productType"][snake_to_camel_case(relation_field)]
    assert len(gql_attributes) == len(expected_order)

    for attr, expected_pk in zip(gql_attributes, expected_order):
        gql_type, gql_attr_id = graphene.Node.from_global_id(attr["id"])
        assert gql_type == "Attribute"
        assert int(gql_attr_id) == expected_pk


ATTRIBUTE_VALUES_RESORT_QUERY = """
    mutation attributeReorderValues($attributeId: ID!, $moves: [ReorderInput]!) {
      attributeReorderValues(attributeId: $attributeId, moves: $moves) {
        attribute {
          id
          values {
            id
          }
        }

        errors {
          field
          message
        }
      }
    }
"""


def test_sort_values_within_attribute_invalid_product_type(
    staff_api_client, permission_manage_products
):
    """Try to reorder an invalid attribute (invalid ID)."""

    attribute_id = graphene.Node.to_global_id("Attribute", -1)
    value_id = graphene.Node.to_global_id("AttributeValue", -1)

    variables = {
        "attributeId": attribute_id,
        "moves": [{"id": value_id, "sortOrder": 1}],
    }

    content = get_graphql_content(
        staff_api_client.post_graphql(
            ATTRIBUTE_VALUES_RESORT_QUERY,
            variables,
            permissions=[permission_manage_products],
        )
    )["data"]["attributeReorderValues"]

    assert content["errors"] == [
        {
            "field": "attributeId",
            "message": f"Couldn't resolve to an attribute: {attribute_id}",
        }
    ]


def test_sort_values_within_attribute_invalid_id(
    staff_api_client, permission_manage_products, color_attribute
):
    """Try to reorder a value not associated to the given attribute."""

    attribute_id = graphene.Node.to_global_id("Attribute", color_attribute.id)
    value_id = graphene.Node.to_global_id("AttributeValue", -1)

    variables = {
        "type": "VARIANT",
        "attributeId": attribute_id,
        "moves": [{"id": value_id, "sortOrder": 1}],
    }

    content = get_graphql_content(
        staff_api_client.post_graphql(
            ATTRIBUTE_VALUES_RESORT_QUERY,
            variables,
            permissions=[permission_manage_products],
        )
    )["data"]["attributeReorderValues"]

    assert content["errors"] == [
        {
            "field": "moves",
            "message": f"Couldn't resolve to an attribute value: {value_id}",
        }
    ]


def test_sort_values_within_attribute(
    staff_api_client, color_attribute, permission_manage_products
):
    attribute = color_attribute
    AttributeValue.objects.create(attribute=attribute, name="Green", slug="green")
    values = list(attribute.values.all())
    assert len(values) == 3

    staff_api_client.user.user_permissions.add(permission_manage_products)

    attribute_id = graphene.Node.to_global_id("Attribute", attribute.id)
    m2m_values = attribute.values
    m2m_values.set(values)

    assert values == sorted(
        values, key=lambda o: o.sort_order if o.sort_order is not None else o.pk
    ), "The values are not properly ordered"

    variables = {
        "attributeId": attribute_id,
        "moves": [
            {
                "id": graphene.Node.to_global_id("AttributeValue", values[0].pk),
                "sortOrder": +1,
            },
            {
                "id": graphene.Node.to_global_id("AttributeValue", values[2].pk),
                "sortOrder": -1,
            },
        ],
    }

    expected_order = [values[1].pk, values[2].pk, values[0].pk]

    content = get_graphql_content(
        staff_api_client.post_graphql(ATTRIBUTE_VALUES_RESORT_QUERY, variables)
    )["data"]["attributeReorderValues"]
    assert not content["errors"]

    assert content["attribute"]["id"] == attribute_id

    gql_values = content["attribute"]["values"]
    assert len(gql_values) == len(expected_order)

    actual_order = []

    for attr, expected_pk in zip(gql_values, expected_order):
        gql_type, gql_attr_id = graphene.Node.from_global_id(attr["id"])
        assert gql_type == "AttributeValue"
        actual_order.append(int(gql_attr_id))

    assert actual_order == expected_order


ATTRIBUTES_FILTER_QUERY = """
    query($filters: AttributeFilterInput!) {
      attributes(first: 10, filter: $filters) {
        edges {
          node {
            name
            slug
          }
        }
      }
    }
"""


def test_search_attributes(api_client, color_attribute, size_attribute):
    variables = {"filters": {"search": "color"}}

    attributes = get_graphql_content(
        api_client.post_graphql(ATTRIBUTES_FILTER_QUERY, variables)
    )["data"]["attributes"]["edges"]

    assert len(attributes) == 1
    assert attributes[0]["node"]["slug"] == "color"


def test_filter_attributes_if_filterable_in_dashboard(
    api_client, color_attribute, size_attribute
):
    color_attribute.filterable_in_dashboard = False
    color_attribute.save(update_fields=["filterable_in_dashboard"])

    variables = {"filters": {"filterableInDashboard": True}}

    attributes = get_graphql_content(
        api_client.post_graphql(ATTRIBUTES_FILTER_QUERY, variables)
    )["data"]["attributes"]["edges"]

    assert len(attributes) == 1
    assert attributes[0]["node"]["slug"] == "size"


def test_filter_attributes_if_available_in_grid(
    api_client, color_attribute, size_attribute
):
    color_attribute.available_in_grid = False
    color_attribute.save(update_fields=["available_in_grid"])

    variables = {"filters": {"availableInGrid": True}}

    attributes = get_graphql_content(
        api_client.post_graphql(ATTRIBUTES_FILTER_QUERY, variables)
    )["data"]["attributes"]["edges"]

    assert len(attributes) == 1
    assert attributes[0]["node"]["slug"] == "size"


def test_filter_attributes_by_global_id_list(api_client, attribute_list):
    global_ids = [
        graphene.Node.to_global_id("Attribute", attribute.pk)
        for attribute in attribute_list[:2]
    ]
    variables = {"filters": {"ids": global_ids}}

    expected_slugs = sorted([attribute_list[0].slug, attribute_list[1].slug])

    attributes = get_graphql_content(
        api_client.post_graphql(ATTRIBUTES_FILTER_QUERY, variables)
    )["data"]["attributes"]["edges"]

    assert len(attributes) == 2
    received_slugs = sorted(
        [attributes[0]["node"]["slug"], attributes[1]["node"]["slug"]]
    )

    assert received_slugs == expected_slugs


ATTRIBUTES_SORT_QUERY = """
    query($sortBy: AttributeSortingInput) {
      attributes(first: 10, sortBy: $sortBy) {
        edges {
          node {
            slug
          }
        }
      }
    }
"""


def test_sort_attributes_by_slug(api_client):
    Attribute.objects.bulk_create(
        [
            Attribute(name="MyAttribute", slug="b"),
            Attribute(name="MyAttribute", slug="a"),
        ]
    )

    variables = {"sortBy": {"field": "SLUG", "direction": "ASC"}}

    attributes = get_graphql_content(
        api_client.post_graphql(ATTRIBUTES_SORT_QUERY, variables)
    )["data"]["attributes"]["edges"]

    assert len(attributes) == 2
    assert attributes[0]["node"]["slug"] == "a"
    assert attributes[1]["node"]["slug"] == "b"


@pytest.mark.parametrize(
    "sort_field, m2m_model",
    (
        ("DASHBOARD_VARIANT_POSITION", AttributeVariant),
        ("DASHBOARD_PRODUCT_POSITION", AttributeProduct),
    ),
)
def test_sort_attributes_by_position_in_product_type(
    api_client,
    color_attribute,
    size_attribute,
    sort_field: str,
    m2m_model: Union[AttributeVariant, AttributeProduct],
):
    """Sorts attributes for dashboard custom ordering inside a given product type."""

    product_type = ProductType.objects.create(name="My Product Type")
    m2m_model.objects.create(
        product_type=product_type, attribute=color_attribute, sort_order=0
    )
    m2m_model.objects.create(
        product_type=product_type, attribute=size_attribute, sort_order=1
    )

    variables = {"sortBy": {"field": sort_field, "direction": "DESC"}}

    attributes = get_graphql_content(
        api_client.post_graphql(ATTRIBUTES_SORT_QUERY, variables)
    )["data"]["attributes"]["edges"]

    assert len(attributes) == 2
    assert attributes[0]["node"]["slug"] == "size"
    assert attributes[1]["node"]["slug"] == "color"


def test_sort_attributes_by_default_sorting(api_client):
    """Don't provide any sorting, this should sort by name by default."""
    Attribute.objects.bulk_create(
        [Attribute(name="A", slug="b"), Attribute(name="B", slug="a")]
    )

    attributes = get_graphql_content(
        api_client.post_graphql(ATTRIBUTES_SORT_QUERY, {})
    )["data"]["attributes"]["edges"]

    assert len(attributes) == 2
    assert attributes[0]["node"]["slug"] == "b"
    assert attributes[1]["node"]["slug"] == "a"


@pytest.mark.parametrize("is_variant", (True, False))
def test_attributes_of_products_are_sorted(
    staff_api_client, product, color_attribute, is_variant
):
    """Ensures the attributes of products and variants are sorted."""

    variant = product.variants.first()

    if is_variant:
        query = """
            query($id: ID!) {
              productVariant(id: $id) {
                attributes {
                  attribute {
                    id
                  }
                }
              }
            }
        """
    else:
        query = """
            query($id: ID!) {
              product(id: $id) {
                attributes {
                  attribute {
                    id
                  }
                }
              }
            }
        """

    # Create a dummy attribute with a higher ID
    # This will allow us to make sure it is always the last attribute
    # when sorted by ID. Thus, we are sure the query is actually passing the test.
    other_attribute = Attribute.objects.create(name="Other", slug="other")

    # Add the attribute to the product type
    if is_variant:
        product.product_type.variant_attributes.set([color_attribute, other_attribute])
    else:
        product.product_type.product_attributes.set([color_attribute, other_attribute])

    # Retrieve the M2M object for the attribute vs the product type
    if is_variant:
        m2m_rel_other_attr = other_attribute.attributevariant.last()
    else:
        m2m_rel_other_attr = other_attribute.attributeproduct.last()

    # Push the last attribute to the top and let the others to None
    m2m_rel_other_attr.sort_order = 0
    m2m_rel_other_attr.save(update_fields=["sort_order"])

    # Assign attributes to the product
    node = variant if is_variant else product  # type: Union[Product, ProductVariant]
    node.attributesrelated.clear()
    associate_attribute_values_to_instance(
        node, color_attribute, color_attribute.values.first()
    )

    # Sort the database attributes by their sort order and ID (when None)
    expected_order = [other_attribute.pk, color_attribute.pk]

    # Make the node ID
    if is_variant:
        node_id = graphene.Node.to_global_id("ProductVariant", variant.pk)
    else:
        node_id = graphene.Node.to_global_id("Product", product.pk)

    # Retrieve the attributes
    data = get_graphql_content(staff_api_client.post_graphql(query, {"id": node_id}))[
        "data"
    ]
    attributes = data["productVariant" if is_variant else "product"]["attributes"]
    actual_order = [
        int(graphene.Node.from_global_id(attr["attribute"]["id"])[1])
        for attr in attributes
    ]

    # Compare the received data against our expectations
    assert actual_order == expected_order

[((5264, 5314), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""is_staff"""', '(False, True)'], {}), "('is_staff', (False, True))\n", (5287, 5314), False, 'import pytest\n'), ((11645, 11709), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""test_deprecated_filter"""', '[True, False]'], {}), "('test_deprecated_filter', [True, False])\n", (11668, 11709), False, 'import pytest\n'), ((11711, 11782), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""tested_field"""', "['inCategory', 'inCollection']"], {}), "('tested_field', ['inCategory', 'inCollection'])\n", (11734, 11782), False, 'import pytest\n'), ((16232, 16447), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""input_slug, expected_slug, expected_error"""', '((\'my-slug\', \'my-slug\', []), (None, \'my-name\', []), (\'\', None, [{\'field\':\n    \'slug\', \'message\': "The attribute\'s slug cannot be blank."}]))'], {}), '(\'input_slug, expected_slug, expected_error\', ((\n    \'my-slug\', \'my-slug\', []), (None, \'my-name\', []), (\'\', None, [{\'field\':\n    \'slug\', \'message\': "The attribute\'s slug cannot be blank."}])))\n', (16255, 16447), False, 'import pytest\n'), ((17583, 17838), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""name_1, name_2, error_msg, error_code"""', "(('Red color', 'Red color', 'Provided values are not unique.',\n    ProductErrorCode.UNIQUE), ('Red color', 'red color',\n    'Provided values are not unique.', ProductErrorCode.UNIQUE))"], {}), "('name_1, name_2, error_msg, error_code', ((\n    'Red color', 'Red color', 'Provided values are not unique.',\n    ProductErrorCode.UNIQUE), ('Red color', 'red color',\n    'Provided values are not unique.', ProductErrorCode.UNIQUE)))\n", (17606, 17838), False, 'import pytest\n'), ((22151, 22406), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""name_1, name_2, error_msg, error_code"""', "(('Red color', 'Red color', 'Provided values are not unique.',\n    ProductErrorCode.UNIQUE), ('Red color', 'red color',\n    'Provided values are not unique.', ProductErrorCode.UNIQUE))"], {}), "('name_1, name_2, error_msg, error_code', ((\n    'Red color', 'Red color', 'Provided values are not unique.',\n    ProductErrorCode.UNIQUE), ('Red color', 'red color',\n    'Provided values are not unique.', ProductErrorCode.UNIQUE)))\n", (22174, 22406), False, 'import pytest\n'), ((30923, 31653), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""raw_value, expected_type"""', "[('#0000', AttributeValueType.COLOR), ('#FF69B4', AttributeValueType.COLOR),\n    ('rgb(255, 0, 0)', AttributeValueType.COLOR), ('hsl(0, 100%, 50%)',\n    AttributeValueType.COLOR), ('hsla(120,  60%, 70%, 0.3)',\n    AttributeValueType.COLOR), ('rgba(100%, 255, 0, 0)', AttributeValueType\n    .COLOR), ('http://example.com', AttributeValueType.URL), (\n    'https://example.com', AttributeValueType.URL), ('ftp://example.com',\n    AttributeValueType.URL), ('example.com', AttributeValueType.STRING), (\n    'Foo', AttributeValueType.STRING), ('linear-gradient(red, yellow)',\n    AttributeValueType.GRADIENT), ('radial-gradient(#0000, yellow)',\n    AttributeValueType.GRADIENT)]"], {}), "('raw_value, expected_type', [('#0000',\n    AttributeValueType.COLOR), ('#FF69B4', AttributeValueType.COLOR), (\n    'rgb(255, 0, 0)', AttributeValueType.COLOR), ('hsl(0, 100%, 50%)',\n    AttributeValueType.COLOR), ('hsla(120,  60%, 70%, 0.3)',\n    AttributeValueType.COLOR), ('rgba(100%, 255, 0, 0)', AttributeValueType\n    .COLOR), ('http://example.com', AttributeValueType.URL), (\n    'https://example.com', AttributeValueType.URL), ('ftp://example.com',\n    AttributeValueType.URL), ('example.com', AttributeValueType.STRING), (\n    'Foo', AttributeValueType.STRING), ('linear-gradient(red, yellow)',\n    AttributeValueType.GRADIENT), ('radial-gradient(#0000, yellow)',\n    AttributeValueType.GRADIENT)])\n", (30946, 31653), False, 'import pytest\n'), ((38534, 38853), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""product_type_attribute_type, gql_attribute_type"""', '((AttributeTypeEnum.PRODUCT, AttributeTypeEnum.VARIANT), (AttributeTypeEnum\n    .VARIANT, AttributeTypeEnum.PRODUCT), (AttributeTypeEnum.PRODUCT,\n    AttributeTypeEnum.PRODUCT), (AttributeTypeEnum.VARIANT,\n    AttributeTypeEnum.VARIANT))'], {}), "('product_type_attribute_type, gql_attribute_type',\n    ((AttributeTypeEnum.PRODUCT, AttributeTypeEnum.VARIANT), (\n    AttributeTypeEnum.VARIANT, AttributeTypeEnum.PRODUCT), (\n    AttributeTypeEnum.PRODUCT, AttributeTypeEnum.PRODUCT), (\n    AttributeTypeEnum.VARIANT, AttributeTypeEnum.VARIANT)))\n", (38557, 38853), False, 'import pytest\n'), ((44124, 44388), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""attribute, expected_value"""', "(('filterable_in_storefront', True), ('filterable_in_dashboard', True), (\n    'visible_in_storefront', True), ('available_in_grid', True), (\n    'value_required', False), ('storefront_search_position', 0))"], {}), "('attribute, expected_value', ((\n    'filterable_in_storefront', True), ('filterable_in_dashboard', True), (\n    'visible_in_storefront', True), ('available_in_grid', True), (\n    'value_required', False), ('storefront_search_position', 0)))\n", (44147, 44388), False, 'import pytest\n'), ((47643, 47836), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""attribute_type, relation_field, backref_field"""', "(('VARIANT', 'variant_attributes', 'attributevariant'), ('PRODUCT',\n    'product_attributes', 'attributeproduct'))"], {}), "('attribute_type, relation_field, backref_field', ((\n    'VARIANT', 'variant_attributes', 'attributevariant'), ('PRODUCT',\n    'product_attributes', 'attributeproduct')))\n", (47666, 47836), False, 'import pytest\n'), ((56587, 56747), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""sort_field, m2m_model"""', "(('DASHBOARD_VARIANT_POSITION', AttributeVariant), (\n    'DASHBOARD_PRODUCT_POSITION', AttributeProduct))"], {}), "('sort_field, m2m_model', ((\n    'DASHBOARD_VARIANT_POSITION', AttributeVariant), (\n    'DASHBOARD_PRODUCT_POSITION', AttributeProduct)))\n", (56610, 56747), False, 'import pytest\n'), ((58205, 58257), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""is_variant"""', '(True, False)'], {}), "('is_variant', (True, False))\n", (58228, 58257), False, 'import pytest\n'), ((1643, 1691), 'saleor.graphql.product.mutations.attributes.validate_value_is_unique', 'validate_value_is_unique', (['color_attribute', 'value'], {}), '(color_attribute, value)\n', (1667, 1691), False, 'from saleor.graphql.product.mutations.attributes import validate_value_is_unique\n'), ((1803, 1877), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'color_attribute_without_values.id'], {}), "('Attribute', color_attribute_without_values.id)\n", (1829, 1877), False, 'import graphene\n'), ((2911, 2940), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (2930, 2940), False, 'from tests.api.utils import get_graphql_content\n'), ((3546, 3575), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (3565, 3575), False, 'from tests.api.utils import get_graphql_content\n'), ((4466, 4495), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (4485, 4495), False, 'from tests.api.utils import get_graphql_content\n'), ((8737, 8787), 'saleor.product.models.Attribute.objects.create', 'Attribute.objects.create', ([], {'name': '"""P"""', 'slug': '"""product"""'}), "(name='P', slug='product')\n", (8761, 8787), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((8823, 8873), 'saleor.product.models.Attribute.objects.create', 'Attribute.objects.create', ([], {'name': '"""V"""', 'slug': '"""variant"""'}), "(name='V', slug='variant')\n", (8847, 8873), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((9420, 9536), 'saleor.product.models.AttributeProduct.objects.create', 'AttributeProduct.objects.create', ([], {'attribute': 'unassigned_product_attribute', 'product_type': 'product_type', 'sort_order': '(0)'}), '(attribute=unassigned_product_attribute,\n    product_type=product_type, sort_order=0)\n', (9451, 9536), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((9551, 9667), 'saleor.product.models.AttributeVariant.objects.create', 'AttributeVariant.objects.create', ([], {'attribute': 'unassigned_variant_attribute', 'product_type': 'product_type', 'sort_order': '(0)'}), '(attribute=unassigned_variant_attribute,\n    product_type=product_type, sort_order=0)\n', (9582, 9667), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((10712, 10735), 'saleor.product.models.Attribute.objects.all', 'Attribute.objects.all', ([], {}), '()\n', (10733, 10735), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((11062, 11085), 'saleor.product.models.Attribute.objects.all', 'Attribute.objects.all', ([], {}), '()\n', (11083, 11085), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((11438, 11480), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Category"""', '(-1)'], {}), "('Category', -1)\n", (11464, 11480), False, 'import graphene\n'), ((11497, 11513), 'unittest.mock.MagicMock', 'mock.MagicMock', ([], {}), '()\n', (11511, 11513), False, 'from unittest import mock\n'), ((11523, 11596), 'saleor.graphql.product.filters.filter_attributes_by_product_types', 'filter_attributes_by_product_types', (['mocked_qs', '"""in_category"""', 'category_id'], {}), "(mocked_qs, 'in_category', category_id)\n", (11557, 11596), False, 'from saleor.graphql.product.filters import filter_attributes_by_product_types\n'), ((12538, 12602), 'saleor.product.models.Category.objects.create', 'Category.objects.create', ([], {'name': '"""Other Category"""', 'slug': '"""other-cat"""'}), "(name='Other Category', slug='other-cat')\n", (12561, 12602), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((12625, 12677), 'saleor.product.models.Attribute.objects.create', 'Attribute.objects.create', ([], {'name': '"""Other"""', 'slug': '"""other"""'}), "(name='Other', slug='other')\n", (12649, 12677), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((12703, 12798), 'saleor.product.models.ProductType.objects.create', 'ProductType.objects.create', ([], {'name': '"""Other type"""', 'has_variants': '(True)', 'is_shipping_required': '(True)'}), "(name='Other type', has_variants=True,\n    is_shipping_required=True)\n", (12729, 12798), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((13229, 13354), 'saleor.product.models.Collection.objects.create', 'Collection.objects.create', ([], {'name': '"""Other Collection"""', 'slug': '"""other-collection"""', 'is_published': '(True)', 'description': '"""Description"""'}), "(name='Other Collection', slug='other-collection',\n    is_published=True, description='Description')\n", (13254, 13354), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((15484, 15513), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (15503, 15513), False, 'from tests.api.utils import get_graphql_content\n'), ((18421, 18450), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (18440, 18450), False, 'from tests.api.utils import get_graphql_content\n'), ((19761, 19814), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (19787, 19814), False, 'import graphene\n'), ((20031, 20060), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (20050, 20060), False, 'from tests.api.utils import get_graphql_content\n'), ((20526, 20579), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (20552, 20579), False, 'import graphene\n'), ((20648, 20712), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""AttributeValue"""', 'attribute_value_id'], {}), "('AttributeValue', attribute_value_id)\n", (20674, 20712), False, 'import graphene\n'), ((21006, 21035), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (21025, 21035), False, 'from tests.api.utils import get_graphql_content\n'), ((21645, 21698), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (21671, 21698), False, 'import graphene\n'), ((21974, 22003), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (21993, 22003), False, 'from tests.api.utils import get_graphql_content\n'), ((22819, 22872), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (22845, 22872), False, 'import graphene\n'), ((23172, 23201), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (23191, 23201), False, 'from tests.api.utils import get_graphql_content\n'), ((23727, 23780), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (23753, 23780), False, 'import graphene\n'), ((23846, 23909), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""AttributeValue"""', 'size_attribute.pk'], {}), "('AttributeValue', size_attribute.pk)\n", (23872, 23909), False, 'import graphene\n'), ((24214, 24243), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (24233, 24243), False, 'from tests.api.utils import get_graphql_content\n'), ((25049, 25102), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (25075, 25102), False, 'import graphene\n'), ((25268, 25297), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (25287, 25297), False, 'from tests.api.utils import get_graphql_content\n'), ((26163, 26216), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (26189, 26216), False, 'import graphene\n'), ((26433, 26462), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (26452, 26462), False, 'from tests.api.utils import get_graphql_content\n'), ((27003, 27056), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (27029, 27056), False, 'import graphene\n'), ((27303, 27332), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (27322, 27332), False, 'from tests.api.utils import get_graphql_content\n'), ((27769, 27822), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (27795, 27822), False, 'import graphene\n'), ((28077, 28106), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (28096, 28106), False, 'from tests.api.utils import get_graphql_content\n'), ((28918, 28972), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""AttributeValue"""', 'value.id'], {}), "('AttributeValue', value.id)\n", (28944, 28972), False, 'import graphene\n'), ((29178, 29207), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (29197, 29207), False, 'from tests.api.utils import get_graphql_content\n'), ((29793, 29847), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""AttributeValue"""', 'value.id'], {}), "('AttributeValue', value.id)\n", (29819, 29847), False, 'import graphene\n'), ((30048, 30077), 'tests.api.utils.get_graphql_content', 'get_graphql_content', (['response'], {}), '(response)\n', (30067, 30077), False, 'from tests.api.utils import get_graphql_content\n'), ((30637, 30691), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""AttributeValue"""', 'value.id'], {}), "('AttributeValue', value.id)\n", (30663, 30691), False, 'import graphene\n'), ((34348, 34414), 'saleor.product.models.ProductType.objects.create', 'ProductType.objects.create', ([], {'name': '"""Default Type"""', 'has_variants': '(True)'}), "(name='Default Type', has_variants=True)\n", (34374, 34414), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((34444, 34502), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductType"""', 'product_type.pk'], {}), "('ProductType', product_type.pk)\n", (34470, 34502), False, 'import graphene\n'), ((36658, 36716), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductType"""', 'product_type.pk'], {}), "('ProductType', product_type.pk)\n", (36684, 36716), False, 'import graphene\n'), ((37855, 37913), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductType"""', 'product_type.pk'], {}), "('ProductType', product_type.pk)\n", (37881, 37913), False, 'import graphene\n'), ((39264, 39303), 'saleor.product.models.ProductType.objects.create', 'ProductType.objects.create', ([], {'name': '"""Type"""'}), "(name='Type')\n", (39290, 39303), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((39456, 39514), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductType"""', 'product_type.pk'], {}), "('ProductType', product_type.pk)\n", (39482, 39514), False, 'import graphene\n'), ((40986, 41025), 'saleor.product.models.ProductType.objects.create', 'ProductType.objects.create', ([], {'name': '"""Type"""'}), "(name='Type')\n", (41012, 41025), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((41055, 41113), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductType"""', 'product_type.pk'], {}), "('ProductType', product_type.pk)\n", (41081, 41113), False, 'import graphene\n'), ((41332, 41397), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'product_attributes[1].pk'], {}), "('Attribute', product_attributes[1].pk)\n", (41358, 41397), False, 'import graphene\n'), ((42558, 42597), 'saleor.product.models.ProductType.objects.create', 'ProductType.objects.create', ([], {'name': '"""Type"""'}), "(name='Type')\n", (42584, 42597), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((42627, 42685), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductType"""', 'product_type.pk'], {}), "('ProductType', product_type.pk)\n", (42653, 42685), False, 'import graphene\n'), ((44730, 44754), 'graphene.utils.str_converters.to_camel_case', 'to_camel_case', (['attribute'], {}), '(attribute)\n', (44743, 44754), False, 'from graphene.utils.str_converters import to_camel_case\n'), ((46007, 46052), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductType"""', '(-1)'], {}), "('ProductType', -1)\n", (46033, 46052), False, 'import graphene\n'), ((46072, 46115), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', '(-1)'], {}), "('Attribute', -1)\n", (46098, 46115), False, 'import graphene\n'), ((46895, 46940), 'saleor.product.models.ProductType.objects.create', 'ProductType.objects.create', ([], {'name': '"""Dummy Type"""'}), "(name='Dummy Type')\n", (46921, 46940), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((46963, 47021), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductType"""', 'product_type.id'], {}), "('ProductType', product_type.id)\n", (46989, 47021), False, 'import graphene\n'), ((47042, 47101), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'color_attribute.id'], {}), "('Attribute', color_attribute.id)\n", (47068, 47101), False, 'import graphene\n'), ((48204, 48249), 'saleor.product.models.ProductType.objects.create', 'ProductType.objects.create', ([], {'name': '"""Dummy Type"""'}), "(name='Dummy Type')\n", (48230, 48249), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((48272, 48330), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductType"""', 'product_type.id'], {}), "('ProductType', product_type.id)\n", (48298, 48330), False, 'import graphene\n'), ((50284, 50327), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', '(-1)'], {}), "('Attribute', -1)\n", (50310, 50327), False, 'import graphene\n'), ((50343, 50391), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""AttributeValue"""', '(-1)'], {}), "('AttributeValue', -1)\n", (50369, 50391), False, 'import graphene\n'), ((51138, 51197), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'color_attribute.id'], {}), "('Attribute', color_attribute.id)\n", (51164, 51197), False, 'import graphene\n'), ((51213, 51261), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""AttributeValue"""', '(-1)'], {}), "('AttributeValue', -1)\n", (51239, 51261), False, 'import graphene\n'), ((51964, 52042), 'saleor.product.models.AttributeValue.objects.create', 'AttributeValue.objects.create', ([], {'attribute': 'attribute', 'name': '"""Green"""', 'slug': '"""green"""'}), "(attribute=attribute, name='Green', slug='green')\n", (51993, 52042), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((52209, 52262), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.id'], {}), "('Attribute', attribute.id)\n", (52235, 52262), False, 'import graphene\n'), ((57071, 57121), 'saleor.product.models.ProductType.objects.create', 'ProductType.objects.create', ([], {'name': '"""My Product Type"""'}), "(name='My Product Type')\n", (57097, 57121), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((59225, 59277), 'saleor.product.models.Attribute.objects.create', 'Attribute.objects.create', ([], {'name': '"""Other"""', 'slug': '"""other"""'}), "(name='Other', slug='other')\n", (59249, 59277), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((1290, 1320), 'pytest.raises', 'pytest.raises', (['ValidationError'], {}), '(ValidationError)\n', (1303, 1320), False, 'import pytest\n'), ((1507, 1549), 'saleor.product.models.AttributeValue', 'AttributeValue', ([], {'slug': '"""spanish-inquisition"""'}), "(slug='spanish-inquisition')\n", (1521, 1549), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((10748, 10797), 'saleor.graphql.product.filters.filter_attributes_by_product_types', 'filter_attributes_by_product_types', (['qs', '"""..."""', '""""""'], {}), "(qs, '...', '')\n", (10782, 10797), False, 'from saleor.graphql.product.filters import filter_attributes_by_product_types\n'), ((10815, 10866), 'saleor.graphql.product.filters.filter_attributes_by_product_types', 'filter_attributes_by_product_types', (['qs', '"""..."""', 'None'], {}), "(qs, '...', None)\n", (10849, 10866), False, 'from saleor.graphql.product.filters import filter_attributes_by_product_types\n'), ((11096, 11130), 'pytest.raises', 'pytest.raises', (['NotImplementedError'], {}), '(NotImplementedError)\n', (11109, 11130), False, 'import pytest\n'), ((11147, 11208), 'saleor.graphql.product.filters.filter_attributes_by_product_types', 'filter_attributes_by_product_types', (['qs', '"""in_space"""', '"""a-value"""'], {}), "(qs, 'in_space', 'a-value')\n", (11181, 11208), False, 'from saleor.graphql.product.filters import filter_attributes_by_product_types\n'), ((12039, 12094), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Collection"""', 'collection.pk'], {}), "('Collection', collection.pk)\n", (12065, 12094), False, 'import graphene\n'), ((15767, 15790), 'django.template.defaultfilters.slugify', 'slugify', (['attribute_name'], {}), '(attribute_name)\n', (15774, 15790), False, 'from django.template.defaultfilters import slugify\n'), ((16215, 16228), 'django.template.defaultfilters.slugify', 'slugify', (['name'], {}), '(name)\n', (16222, 16228), False, 'from django.template.defaultfilters import slugify\n'), ((25407, 25456), 'pytest.raises', 'pytest.raises', (['attribute._meta.model.DoesNotExist'], {}), '(attribute._meta.model.DoesNotExist)\n', (25420, 25456), False, 'import pytest\n'), ((26660, 26673), 'django.template.defaultfilters.slugify', 'slugify', (['name'], {}), '(name)\n', (26667, 26673), False, 'from django.template.defaultfilters import slugify\n'), ((29396, 29409), 'django.template.defaultfilters.slugify', 'slugify', (['name'], {}), '(name)\n', (29403, 29409), False, 'from django.template.defaultfilters import slugify\n'), ((30841, 30886), 'pytest.raises', 'pytest.raises', (['value._meta.model.DoesNotExist'], {}), '(value._meta.model.DoesNotExist)\n', (30854, 30886), False, 'import pytest\n'), ((31808, 31847), 'saleor.graphql.product.types.attributes.resolve_attribute_value_type', 'resolve_attribute_value_type', (['raw_value'], {}), '(raw_value)\n', (31836, 31847), False, 'from saleor.graphql.product.types.attributes import resolve_attribute_value_type\n'), ((49601, 49641), 'graphene.Node.from_global_id', 'graphene.Node.from_global_id', (["attr['id']"], {}), "(attr['id'])\n", (49629, 49641), False, 'import graphene\n'), ((53386, 53426), 'graphene.Node.from_global_id', 'graphene.Node.from_global_id', (["attr['id']"], {}), "(attr['id'])\n", (53414, 53426), False, 'import graphene\n'), ((55260, 55313), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.pk'], {}), "('Attribute', attribute.pk)\n", (55286, 55313), False, 'import graphene\n'), ((60400, 60456), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""ProductVariant"""', 'variant.pk'], {}), "('ProductVariant', variant.pk)\n", (60426, 60456), False, 'import graphene\n'), ((60485, 60534), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Product"""', 'product.pk'], {}), "('Product', product.pk)\n", (60511, 60534), False, 'import graphene\n'), ((1372, 1403), 'saleor.product.models.AttributeValue', 'AttributeValue', ([], {'slug': 'value.slug'}), '(slug=value.slug)\n', (1386, 1403), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((3366, 3425), 'saleor.product.models.Attribute.objects.get_visible_to_user', 'Attribute.objects.get_visible_to_user', (['user_api_client.user'], {}), '(user_api_client.user)\n', (3403, 3425), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((4020, 4043), 'saleor.product.models.Attribute.objects.all', 'Attribute.objects.all', ([], {}), '()\n', (4041, 4043), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((9082, 9156), 'saleor.product.models.AttributeValue', 'AttributeValue', ([], {'slug': '"""a"""', 'name': '"""A"""', 'attribute': 'unassigned_product_attribute'}), "(slug='a', name='A', attribute=unassigned_product_attribute)\n", (9096, 9156), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((9170, 9244), 'saleor.product.models.AttributeValue', 'AttributeValue', ([], {'slug': '"""b"""', 'name': '"""B"""', 'attribute': 'unassigned_product_attribute'}), "(slug='b', name='B', attribute=unassigned_product_attribute)\n", (9184, 9244), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((12162, 12213), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Category"""', 'category.pk'], {}), "('Category', category.pk)\n", (12188, 12213), False, 'import graphene\n'), ((12319, 12371), 'django.db.models.Q', 'Q', ([], {'attributeproduct__product_type_id': 'product_type.pk'}), '(attributeproduct__product_type_id=product_type.pk)\n', (12320, 12371), False, 'from django.db.models import Q\n'), ((12382, 12434), 'django.db.models.Q', 'Q', ([], {'attributevariant__product_type_id': 'product_type.pk'}), '(attributevariant__product_type_id=product_type.pk)\n', (12383, 12434), False, 'from django.db.models import Q\n'), ((13037, 13049), 'saleor.core.taxes.zero_money', 'zero_money', ([], {}), '()\n', (13047, 13049), False, 'from saleor.core.taxes import zero_money\n'), ((36801, 36854), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.pk'], {}), "('Attribute', attribute.pk)\n", (36827, 36854), False, 'import graphene\n'), ((37998, 38051), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.pk'], {}), "('Attribute', attribute.pk)\n", (38024, 38051), False, 'import graphene\n'), ((39945, 39998), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attribute.pk'], {}), "('Attribute', attribute.pk)\n", (39971, 39998), False, 'import graphene\n'), ((41550, 41615), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'product_attributes[0].pk'], {}), "('Attribute', product_attributes[0].pk)\n", (41576, 41615), False, 'import graphene\n'), ((42824, 42898), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'color_attribute_without_values.pk'], {}), "('Attribute', color_attribute_without_values.pk)\n", (42850, 42898), False, 'import graphene\n'), ((49411, 49446), 'saleor.graphql.core.utils.snake_to_camel_case', 'snake_to_camel_case', (['relation_field'], {}), '(relation_field)\n', (49430, 49446), False, 'from saleor.graphql.core.utils import snake_to_camel_case\n'), ((56136, 56175), 'saleor.product.models.Attribute', 'Attribute', ([], {'name': '"""MyAttribute"""', 'slug': '"""b"""'}), "(name='MyAttribute', slug='b')\n", (56145, 56175), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((56189, 56228), 'saleor.product.models.Attribute', 'Attribute', ([], {'name': '"""MyAttribute"""', 'slug': '"""a"""'}), "(name='MyAttribute', slug='a')\n", (56198, 56228), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((57870, 57899), 'saleor.product.models.Attribute', 'Attribute', ([], {'name': '"""A"""', 'slug': '"""b"""'}), "(name='A', slug='b')\n", (57879, 57899), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((57901, 57930), 'saleor.product.models.Attribute', 'Attribute', ([], {'name': '"""B"""', 'slug': '"""a"""'}), "(name='B', slug='a')\n", (57910, 57930), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((4193, 4253), 'saleor.product.models.Attribute.objects.get_visible_to_user', 'Attribute.objects.get_visible_to_user', (['staff_api_client.user'], {}), '(staff_api_client.user)\n', (4230, 4253), False, 'from saleor.product.models import Attribute, AttributeProduct, AttributeValue, AttributeVariant, Category, Collection, Product, ProductType, ProductVariant\n'), ((34888, 34936), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attr_id'], {}), "('Attribute', attr_id)\n", (34914, 34936), False, 'import graphene\n'), ((35057, 35105), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attr_id'], {}), "('Attribute', attr_id)\n", (35083, 35105), False, 'import graphene\n'), ((35686, 35726), 'graphene.Node.from_global_id', 'graphene.Node.from_global_id', (["attr['id']"], {}), "(attr['id'])\n", (35714, 35726), False, 'import graphene\n'), ((35845, 35885), 'graphene.Node.from_global_id', 'graphene.Node.from_global_id', (["attr['id']"], {}), "(attr['id'])\n", (35873, 35885), False, 'import graphene\n'), ((48714, 48771), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attributes[0].pk'], {}), "('Attribute', attributes[0].pk)\n", (48740, 48771), False, 'import graphene\n'), ((48857, 48914), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""Attribute"""', 'attributes[2].pk'], {}), "('Attribute', attributes[2].pk)\n", (48883, 48914), False, 'import graphene\n'), ((52591, 52649), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""AttributeValue"""', 'values[0].pk'], {}), "('AttributeValue', values[0].pk)\n", (52617, 52649), False, 'import graphene\n'), ((52735, 52793), 'graphene.Node.to_global_id', 'graphene.Node.to_global_id', (['"""AttributeValue"""', 'values[2].pk'], {}), "('AttributeValue', values[2].pk)\n", (52761, 52793), False, 'import graphene\n'), ((60790, 60843), 'graphene.Node.from_global_id', 'graphene.Node.from_global_id', (["attr['attribute']['id']"], {}), "(attr['attribute']['id'])\n", (60818, 60843), False, 'import graphene\n')]