Repository: mirumee/ariadne

Issue:
Update GraphQL fixing logic
Ariadne should export two utility functions:

- `validate_schema_enum_values(schema: GraphQLSchema)` to raise validation errors on invalid enum members.
- `repair_default_enum_values(schema: GraphQLSchema)` to walk the schema and fix `default_value` on all places where enum arg or input field has default value set, because `graphql-core` sets it to string.

We could also drop `set_default_enum_values_on_schema` from Ariadne because `graphql-core` now defaults to setting enums default values after their members names.

I've already implemented universal tests suite for catching issues in our GraphQL enum handling, I will use it while working on this issue.

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/mirumee/ariadne/pull/1138?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee) Report
Attention: `6 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`010f551`)](https://app.codecov.io/gh/mirumee/ariadne/commit/010f551cb651e7aab4891ad53e41bb79855ab26c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee) 97.85% compared to head [(`7e9a0c2`)](https://app.codecov.io/gh/mirumee/ariadne/pull/1138?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee) 97.71%.
> Report is 11 commits behind head on master.

| [Files](https://app.codecov.io/gh/mirumee/ariadne/pull/1138?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee) | Patch % | Lines |
|---|---|---|
| [ariadne/enums\_values\_visitor.py](https://app.codecov.io/gh/mirumee/ariadne/pull/1138?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee#diff-YXJpYWRuZS9lbnVtc192YWx1ZXNfdmlzaXRvci5weQ==) | 97.17% | [5 Missing :warning: ](https://app.codecov.io/gh/mirumee/ariadne/pull/1138?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee) |
| [ariadne/enums.py](https://app.codecov.io/gh/mirumee/ariadne/pull/1138?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee#diff-YXJpYWRuZS9lbnVtcy5weQ==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/mirumee/ariadne/pull/1138?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1138      +/-   ##
==========================================
- Coverage   97.85%   97.71%   -0.14%     
==========================================
  Files         106      109       +3     
  Lines        7677     7881     +204     
==========================================
+ Hits         7512     7701     +189     
- Misses        165      180      +15     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/mirumee/ariadne/pull/1138?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mirumee).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e416046..c900f64 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # CHANGELOG
 
+## 0.21 (2023-11-08)
+
+- Deprecated `EnumType.bind_to_default_values` method. It will be removed in a future release.
+- Added `repair_schema_default_enum_values` to public API.
+- Removed `validate_schema_enum_values` and introduced `validate_schema_default_enum_values` in its place. This is a breaking change.
+
+
 ## 0.21 (2023-11-08)
 
 - Added Python 3.12 to tested versions.
diff --git a/ariadne/__init__.py b/ariadne/__init__.py
index 78e2ec7..c611897 100644
--- a/ariadne/__init__.py
+++ b/ariadne/__init__.py
@@ -1,7 +1,7 @@
-from .enums import (
-    EnumType,
-    set_default_enum_values_on_schema,
-    validate_schema_enum_values,
+from .enums import EnumType
+from .enums_default_values import (
+    repair_schema_default_enum_values,
+    validate_schema_default_enum_values,
 )
 from .executable_schema import make_executable_schema
 from .extensions import ExtensionManager
@@ -70,12 +70,12 @@ __all__ = [
     "is_default_resolver",
     "load_schema_from_path",
     "make_executable_schema",
+    "repair_schema_default_enum_values",
     "resolve_to",
-    "set_default_enum_values_on_schema",
     "snake_case_fallback_resolvers",
     "subscribe",
     "type_implements_interface",
     "unwrap_graphql_error",
     "upload_scalar",
-    "validate_schema_enum_values",
+    "validate_schema_default_enum_values",
 ]
diff --git a/ariadne/enums.py b/ariadne/enums.py
index 544127a..83c942a 100644
--- a/ariadne/enums.py
+++ b/ariadne/enums.py
@@ -2,49 +2,18 @@ import enum
 from typing import (
     Any,
     Dict,
-    Generator,
-    List,
-    Mapping,
     Optional,
-    Tuple,
     Type,
-    TypeVar,
     Union,
     cast,
 )
-from functools import reduce, singledispatch
-import operator
+import warnings
 
 from graphql.type import GraphQLEnumType, GraphQLNamedType, GraphQLSchema
-from graphql.language.ast import (
-    EnumValueNode,
-    InputValueDefinitionNode,
-    ObjectValueNode,
-)
-from graphql.pyutils.undefined import Undefined
-from graphql.type.definition import (
-    GraphQLArgument,
-    GraphQLField,
-    GraphQLInputField,
-    GraphQLInputObjectType,
-    GraphQLInputType,
-    GraphQLInterfaceType,
-    GraphQLNonNull,
-    GraphQLObjectType,
-    GraphQLScalarType,
-)
 
 from .types import SchemaBindable
 
 
-T = TypeVar("T")
-ArgumentWithKeys = Tuple[str, str, GraphQLArgument, Optional[List["str"]]]
-InputFieldWithKeys = Tuple[str, str, GraphQLInputField, Optional[List["str"]]]
-GraphQLNamedInputType = Union[
-    GraphQLScalarType, GraphQLEnumType, GraphQLInputObjectType
-]
-
-
 class EnumType(SchemaBindable):
     """Bindable mapping Python values to enumeration members in a GraphQL schema.
 
@@ -118,34 +87,27 @@ class EnumType(SchemaBindable):
                 )
             graphql_type.values[key].value = value
 
-    def bind_to_default_values(self, schema: GraphQLSchema) -> None:
+    def bind_to_default_values(self, _schema: GraphQLSchema) -> None:
         """Populates default values of input fields and args in the GraphQL schema.
 
         This step is required because GraphQL query executor doesn't perform a
         lookup for default values defined in schema. Instead it simply pulls the
         value from fields and arguments `default_value` attribute, which is
         `None` by default.
-        """
-        for _, _, arg, key_list in find_enum_values_in_schema(schema):
-            type_ = resolve_null_type(arg.type)
-            type_ = cast(GraphQLNamedInputType, type_)
-
-            if (
-                key_list is None
-                and arg.default_value in self.values
-                and type_.name == self.name
-            ):
-                type_ = resolve_null_type(arg.type)
-                arg.default_value = self.values[arg.default_value]
 
-            elif key_list is not None:
-                enum_value = get_value_from_mapping_value(arg.default_value, key_list)
-                type_ = cast(GraphQLEnumType, track_type_for_nested(arg, key_list))
+        > **Deprecated:** Ariadne versions before 0.22 used
+        `EnumType.bind_to_default_values` method to fix default enum values embedded
+        in the GraphQL schema. Ariadne 0.22 release introduces universal
+        `repair_schema_default_enum_values` utility in its place.
+        """
 
-                if enum_value in self.values and type_.name == self.name:
-                    set_leaf_value_in_mapping(
-                        arg.default_value, key_list, self.values[enum_value]
-                    )
+        warnings.warn(
+            (
+                "'EnumType.bind_to_default_values' was deprecated in Ariadne 0.22 and "
+                "will be removed in a future release."
+            ),
+            DeprecationWarning,
+        )
 
     def validate_graphql_type(self, graphql_type: Optional[GraphQLNamedType]) -> None:
         """Validates that schema's GraphQL type associated with this `EnumType`
@@ -157,206 +119,3 @@ class EnumType(SchemaBindable):
                 "%s is defined in the schema, but it is instance of %s (expected %s)"
                 % (self.name, type(graphql_type).__name__, GraphQLEnumType.__name__)
             )
-
-
-def set_default_enum_values_on_schema(schema: GraphQLSchema):
-    """Sets missing Python values for GraphQL enums in schema.
-
-    Recursively scans GraphQL schema for enums and their values. If `value`
-    attribute is empty, its populated with with a string of its GraphQL name.
-
-    This string is then used to represent enum's value in Python instead of `None`.
-
-    # Requires arguments
-
-    `schema`: a GraphQL schema to set enums default values in.
-    """
-    for type_object in schema.type_map.values():
-        if isinstance(type_object, GraphQLEnumType):
-            set_default_enum_values(type_object)
-
-
-def set_default_enum_values(graphql_type: GraphQLEnumType):
-    for key in graphql_type.values:
-        if graphql_type.values[key].value is None:
-            graphql_type.values[key].value = key
-
-
-def validate_schema_enum_values(schema: GraphQLSchema) -> None:
-    """Raises `ValueError` if GraphQL schema has input fields or arguments with
-    default values that are undefined enum values.
-
-    # Example schema with invalid field argument
-
-    This schema fails to validate because argument `role` on field `users`
-    specifies `REVIEWER` as default value and `REVIEWER` is not a member of
-    the `UserRole` enum:
-
-    ```graphql
-    type Query {
-        users(role: UserRole = REVIEWER): [User!]!
-    }
-
-    enum UserRole {
-        MEMBER
-        MODERATOR
-        ADMIN
-    }
-
-    type User {
-        id: ID!
-    }
-    ```
-
-    # Example schema with invalid input field
-
-    This schema fails to validate because field `role` on input `UserFilters`
-    specifies `REVIEWER` as default value and `REVIEWER` is not a member of
-    the `UserRole` enum:
-
-    ```graphql
-    type Query {
-        users(filter: UserFilters): [User!]!
-    }
-
-    input UserFilters {
-        name: String
-        role: UserRole = REVIEWER
-    }
-
-    enum UserRole {
-        MEMBER
-        MODERATOR
-        ADMIN
-    }
-
-    type User {
-        id: ID!
-    }
-    ```
-    """
-
-    for type_name, field_name, arg, _ in find_enum_values_in_schema(schema):
-        if is_invalid_enum_value(arg):
-            raise ValueError(
-                f"Value for type: <{arg.type}> is invalid. "
-                f"Check InputField/Arguments for <{field_name}> in <{type_name}> "
-                "(Undefined enum value)."
-            )
-
-
-def is_invalid_enum_value(field: Union[GraphQLInputField, GraphQLArgument]) -> bool:
-    if field.ast_node is None:
-        return False
-    return field.default_value is Undefined and field.ast_node.default_value is not None
-
-
-def find_enum_values_in_schema(
-    schema: GraphQLSchema,
-) -> Generator[Union[ArgumentWithKeys, InputFieldWithKeys], None, None]:
-    for name, type_ in schema.type_map.items():
-        result = enum_values_in_types(type_, name)
-        if result is not None:
-            yield from result
-
-
-@singledispatch
-def enum_values_in_types(
-    type_: GraphQLNamedType,  # pylint: disable=unused-argument
-    name: str,  # pylint: disable=unused-argument
-) -> Optional[Generator[Union[ArgumentWithKeys, InputFieldWithKeys], None, None]]:
-    pass
-
-
-@enum_values_in_types.register(GraphQLObjectType)
-@enum_values_in_types.register(GraphQLInterfaceType)
-def enum_values_in_object_type(
-    type_: Union[GraphQLObjectType, GraphQLInterfaceType],
-    field_name: str,
-) -> Generator[ArgumentWithKeys, None, None]:
-    for field in type_.fields.values():
-        yield from enum_values_in_field_args(field_name, field)
-
-
-@enum_values_in_types.register(GraphQLInputObjectType)
-def enum_values_in_input_type(
-    type_: GraphQLInputObjectType,
-    field_name,
-) -> Generator[InputFieldWithKeys, None, None]:
-    yield from _get_field_with_keys(field_name, type_.fields.items())
-
-
-def enum_values_in_field_args(
-    field_name: str,
-    field: GraphQLField,
-) -> Generator[ArgumentWithKeys, None, None]:
-    args = [
-        (name, arg)
-        for name, arg in field.args.items()
-        if isinstance(
-            arg.type, (GraphQLInputObjectType, GraphQLEnumType, GraphQLNonNull)
-        )
-    ]
-
-    yield from _get_field_with_keys(field_name, args)
-
-
-def _get_field_with_keys(field_name, fields):
-    for input_name, field in fields:
-        resolved_type = resolve_null_type(field.type)
-        if isinstance(resolved_type, GraphQLEnumType):
-            yield field_name, input_name, field, None
-
-        if isinstance(resolved_type, GraphQLInputObjectType):
-            if (
-                field.ast_node is not None
-                and field.ast_node.default_value is not None
-                and isinstance(field.ast_node.default_value, ObjectValueNode)
-            ):
-                routes = get_enum_keys_from_ast(field.ast_node)
-                for route in routes:
-                    yield field_name, input_name, field, route
-
-
-def get_enum_keys_from_ast(ast_node: InputValueDefinitionNode) -> List[List["str"]]:
-    routes = []
-    object_node = cast(ObjectValueNode, ast_node.default_value)
-    nodes = [([field.name.value], field) for field in object_node.fields]
-
-    while nodes:
-        key_list, field = nodes.pop()
-        if isinstance(field.value, EnumValueNode):
-            routes.append(key_list)
-
-        if isinstance(field.value, ObjectValueNode):
-            for new_field in field.value.fields:
-                new_route = key_list[:]
-                new_route.append(new_field.name.value)
-                nodes.append((new_route, new_field))
-
-    return routes
-
-
-def get_value_from_mapping_value(mapping: Mapping[T, Any], key_list: List[T]) -> Any:
-    return reduce(operator.getitem, key_list, mapping)
-
-
-def set_leaf_value_in_mapping(
-    mapping: Mapping[T, Any], key_list: List[T], value: Any
-) -> None:
-    get_value_from_mapping_value(mapping, key_list[:-1])[key_list[-1]] = value
-
-
-def track_type_for_nested(
-    arg: Union[GraphQLArgument, GraphQLInputField], key_list: List[str]
-) -> GraphQLInputType:
-    type_ = resolve_null_type(arg.type)
-
-    for elem in key_list:
-        if isinstance(type_, GraphQLInputObjectType):
-            type_ = type_.fields[elem].type
-    return type_
-
-
-def resolve_null_type(type_: GraphQLInputType) -> GraphQLInputType:
-    return type_.of_type if isinstance(type_, GraphQLNonNull) else type_
diff --git a/ariadne/enums_default_values.py b/ariadne/enums_default_values.py
new file mode 100644
index 0000000..555e903
--- /dev/null
+++ b/ariadne/enums_default_values.py
@@ -0,0 +1,144 @@
+from graphql import GraphQLInputField, GraphQLSchema
+
+from .enums_values_visitor import (
+    GraphQLASTEnumDefaultValueLocation,
+    GraphQLASTEnumsValuesVisitor,
+    GraphQLSchemaEnumDefaultValueLocation,
+    GraphQLSchemaEnumsValuesVisitor,
+)
+
+
+__all__ = [
+    "repair_schema_default_enum_values",
+    "validate_schema_default_enum_values",
+]
+
+
+def validate_schema_default_enum_values(schema: GraphQLSchema) -> None:
+    """Raises `ValueError` if GraphQL schema has input fields or arguments with
+    default values that are undefined enum values.
+
+    # Example schema with invalid field argument
+
+    This schema fails to validate because argument `role` on field `users`
+    specifies `REVIEWER` as default value and `REVIEWER` is not a member of
+    the `UserRole` enum:
+
+    ```graphql
+    type Query {
+        users(role: UserRole = REVIEWER): [User!]!
+    }
+
+    enum UserRole {
+        MEMBER
+        MODERATOR
+        ADMIN
+    }
+
+    type User {
+        id: ID!
+    }
+    ```
+
+    # Example schema with invalid input field
+
+    This schema fails to validate because field `role` on input `UserFilters`
+    specifies `REVIEWER` as default value and `REVIEWER` is not a member of
+    the `UserRole` enum:
+
+    ```graphql
+    type Query {
+        users(filter: UserFilters): [User!]!
+    }
+
+    input UserFilters {
+        name: String
+        role: UserRole = REVIEWER
+    }
+
+    enum UserRole {
+        MEMBER
+        MODERATOR
+        ADMIN
+    }
+
+    type User {
+        id: ID!
+    }
+    ```
+
+    # Example schema with invalid default input field argument
+
+    This schema fails to validate because field `field` on input `ChildInput`
+    specifies `INVALID` as default value and `INVALID` is not a member of
+    the `Role` enum:
+
+    ```graphql
+    type Query {
+        field(arg: Input = {field: {field: INVALID}}): String
+    }
+
+    input Input {
+        field: ChildInput
+    }
+
+    input ChildInput {
+        field: Role
+    }
+
+    enum Role {
+        USER
+        ADMIN
+    }
+    ```
+    """
+    GraphQLEnumsValuesValidatorVisitor(schema)
+
+
+class GraphQLEnumsValuesValidatorVisitor(GraphQLASTEnumsValuesVisitor):
+    def visit_ast_enum_default_value(
+        self, location: "GraphQLASTEnumDefaultValueLocation"
+    ):
+        valid_values = self.enum_values[location.enum_name]
+        if location.enum_value not in valid_values:
+            if location.arg_name:
+                raise ValueError(
+                    f"Undefined enum value '{location.enum_value}' for enum "
+                    f"'{location.enum_name}' in a default value of "
+                    f"'{location.arg_name}' argument for '{location.field_name}' "
+                    f"field on '{location.object_name}' type."
+                )
+
+            raise ValueError(
+                f"Undefined enum value '{location.enum_value}' for enum "
+                f"'{location.enum_name}' in a default value of "
+                f"'{location.field_name}' field on '{location.object_name}' type."
+            )
+
+
+def repair_schema_default_enum_values(schema: GraphQLSchema) -> None:
+    """Repairs Python values of default enums embedded in the GraphQL schema.
+
+    Default enum values in the GraphQL schemas are represented as strings with enum
+    member names in Python. Assigning custom Python values to members of the
+    `GraphQLEnumType` doesn't change those defaults.
+
+    This function walks the GraphQL schema, finds default enum values strings and,
+    if this string is a valid GraphQL member name, swaps it out for a valid Python
+    value.
+    """
+    GraphQLSchemaEnumsValuesRepairVisitor(schema)
+
+
+class GraphQLSchemaEnumsValuesRepairVisitor(GraphQLSchemaEnumsValuesVisitor):
+    def visit_schema_enum_default_value(
+        self, location: "GraphQLSchemaEnumDefaultValueLocation"
+    ):
+        valid_values = self.enum_values[location.enum_name]
+        valid_default = valid_values[location.enum_value]
+        if location.default_value_path is not None:
+            location.default_value[location.default_value_path] = valid_default
+        elif location.arg_def:
+            location.arg_def.default_value = valid_default
+        elif isinstance(location.field_def, GraphQLInputField):
+            location.field_def.default_value = valid_default
diff --git a/ariadne/enums_values_visitor.py b/ariadne/enums_values_visitor.py
new file mode 100644
index 0000000..075118a
--- /dev/null
+++ b/ariadne/enums_values_visitor.py
@@ -0,0 +1,561 @@
+from dataclasses import dataclass
+from enum import Enum
+from typing import Any, Dict, Optional, Union, cast
+
+from graphql import (
+    EnumValueNode,
+    GraphQLArgument,
+    GraphQLEnumType,
+    GraphQLField,
+    GraphQLInputField,
+    GraphQLInputObjectType,
+    GraphQLInterfaceType,
+    GraphQLNonNull,
+    GraphQLList,
+    GraphQLObjectType,
+    GraphQLSchema,
+    GraphQLType,
+    InputValueDefinitionNode,
+    ListValueNode,
+    ObjectValueNode,
+)
+
+
+class GraphQLEnumsValuesVisitor:
+    schema: GraphQLSchema
+    enum_values: Dict[str, Dict[str, Any]]
+
+    def __init__(self, schema: GraphQLSchema):
+        self.enum_values = {}
+        self.schema = schema
+
+        self.visit_enum_types()
+        self.visit_schema()
+
+    def visit_enum_types(self) -> None:
+        for type_def in self.schema.type_map.values():
+            if isinstance(type_def, GraphQLEnumType):
+                self.enum_values[type_def.name] = {
+                    key: value.value for key, value in type_def.values.items()
+                }
+
+    def visit_schema(self) -> None:
+        raise NotImplementedError(
+            "GraphQLEnumsValuesVisitor subclasses must implement 'visit_schema'"
+        )
+
+
+class GraphQLSchemaEnumsValuesVisitor(GraphQLEnumsValuesVisitor):
+    def visit_schema(self) -> None:
+        for type_def in self.schema.type_map.values():
+            if type_def.name.startswith("__"):
+                continue  # Skip introspection types
+
+            if isinstance(type_def, (GraphQLObjectType, GraphQLInterfaceType)):
+                self.visit_object(type_def)
+
+            if isinstance(type_def, GraphQLInputObjectType):
+                self.visit_input(type_def)
+
+    def visit_object(
+        self,
+        object_def: Union[GraphQLObjectType, GraphQLInterfaceType],
+    ) -> None:
+        for field_name, field_def in object_def.fields.items():
+            for arg_name, arg_def in field_def.args.items():
+                self.visit_value(
+                    object_def,
+                    field_name,
+                    field_def,
+                    arg_name,
+                    arg_def,
+                )
+
+    def visit_input(
+        self,
+        input_def: GraphQLInputObjectType,
+    ) -> None:
+        for field_name, field_def in input_def.fields.items():
+            self.visit_value(input_def, field_name, field_def)
+
+    def visit_value(
+        self,
+        object_def: Union[
+            GraphQLObjectType, GraphQLInterfaceType, GraphQLInputObjectType
+        ],
+        field_name: str,
+        field_def: Union[GraphQLField, GraphQLInputField],
+        arg_name: Optional[str] = None,
+        arg_def: Optional[GraphQLArgument] = None,
+    ) -> None:
+        src_def: Union[GraphQLInputField, GraphQLArgument]
+        if isinstance(field_def, GraphQLInputField):
+            src_def = field_def
+        elif isinstance(arg_def, GraphQLArgument):
+            src_def = arg_def
+
+        if src_def.default_value:
+            if is_graphql_list(src_def.type) and isinstance(
+                src_def.default_value, list
+            ):
+                self.visit_list_value(
+                    object_def,
+                    field_name,
+                    field_def,
+                    arg_name,
+                    arg_def,
+                    src_def.type,
+                    src_def.default_value,
+                )
+
+            else:
+                src_type = unwrap_type(src_def.type)
+                if isinstance(src_type, GraphQLEnumType) and is_raw_enum_value(
+                    src_def.default_value
+                ):
+                    self.visit_schema_enum_default_value(
+                        GraphQLSchemaEnumDefaultValueLocation(
+                            enum_name=src_type.name,
+                            enum_value=src_def.default_value,
+                            object_name=object_def.name,
+                            object_def=object_def,
+                            field_name=field_name,
+                            field_def=field_def,
+                            arg_name=arg_name,
+                            arg_def=arg_def,
+                            default_value=src_def.default_value,
+                            default_value_path=None,
+                        )
+                    )
+
+                elif isinstance(src_type, GraphQLInputObjectType):
+                    self.visit_input_value(
+                        object_def,
+                        field_name,
+                        field_def,
+                        arg_name,
+                        arg_def,
+                        src_type,
+                        src_def.default_value,
+                    )
+
+    def visit_list_value(
+        self,
+        object_def: Union[
+            GraphQLObjectType, GraphQLInterfaceType, GraphQLInputObjectType
+        ],
+        field_name: str,
+        field_def: Union[GraphQLField, GraphQLInputField],
+        arg_name: Optional[str],
+        arg_def: Optional[GraphQLArgument],
+        value_def: GraphQLType,
+        value: Any,
+    ) -> None:
+        value_type = unwrap_list_type(value_def)
+        if is_graphql_list(value_type):
+            for value_item in value:
+                self.visit_list_value(
+                    object_def,
+                    field_name,
+                    field_def,
+                    arg_name,
+                    arg_def,
+                    value_type,
+                    value_item,
+                )
+
+        elif isinstance(value_type, GraphQLEnumType):
+            for default_value_path, enum_value in enumerate(value):
+                if is_raw_enum_value(enum_value):
+                    self.visit_schema_enum_default_value(
+                        GraphQLSchemaEnumDefaultValueLocation(
+                            enum_name=value_type.name,
+                            enum_value=enum_value,
+                            object_name=object_def.name,
+                            object_def=object_def,
+                            field_name=field_name,
+                            field_def=field_def,
+                            arg_name=arg_name,
+                            arg_def=arg_def,
+                            default_value=value,
+                            default_value_path=default_value_path,
+                        )
+                    )
+
+        elif isinstance(value_type, GraphQLInputObjectType):
+            for value_item in value:
+                if isinstance(value_item, dict):
+                    self.visit_input_value(
+                        object_def,
+                        field_name,
+                        field_def,
+                        arg_name,
+                        arg_def,
+                        value_type,
+                        value_item,
+                    )
+
+    def visit_input_value(
+        self,
+        object_def: Union[
+            GraphQLObjectType, GraphQLInterfaceType, GraphQLInputObjectType
+        ],
+        field_name: str,
+        field_def: Union[GraphQLField, GraphQLInputField],
+        arg_name: Optional[str],
+        arg_def: Optional[GraphQLArgument],
+        value_def: GraphQLInputObjectType,
+        value: dict,
+    ) -> None:
+        for input_field_name, input_field_def in value_def.fields.items():
+            input_field_value = value.get(input_field_name)
+            if input_field_value is None:
+                continue  # Skip field
+
+            input_field_type = unwrap_type(input_field_def.type)
+            if is_graphql_list(input_field_def.type) and isinstance(
+                input_field_value, list
+            ):
+                self.visit_list_value(
+                    object_def,
+                    field_name,
+                    field_def,
+                    arg_name,
+                    arg_def,
+                    input_field_def.type,
+                    input_field_value,
+                )
+
+            elif isinstance(input_field_type, GraphQLEnumType) and is_raw_enum_value(
+                input_field_value
+            ):
+                self.visit_schema_enum_default_value(
+                    GraphQLSchemaEnumDefaultValueLocation(
+                        enum_name=input_field_type.name,
+                        enum_value=input_field_value,
+                        object_name=object_def.name,
+                        object_def=object_def,
+                        field_name=field_name,
+                        field_def=field_def,
+                        arg_name=arg_name,
+                        arg_def=arg_def,
+                        default_value=value,
+                        default_value_path=input_field_name,
+                    )
+                )
+
+            elif isinstance(input_field_type, GraphQLInputObjectType) and isinstance(
+                input_field_value, dict
+            ):
+                self.visit_input_value(
+                    object_def,
+                    field_name,
+                    field_def,
+                    arg_name,
+                    arg_def,
+                    input_field_type,
+                    input_field_value,
+                )
+
+    def visit_schema_enum_default_value(
+        self, location: "GraphQLSchemaEnumDefaultValueLocation"
+    ) -> None:
+        pass
+
+
+@dataclass(frozen=True)
+class GraphQLSchemaEnumDefaultValueLocation:
+    enum_name: str
+    enum_value: Any
+    object_name: str
+    object_def: Union[GraphQLObjectType, GraphQLInterfaceType, GraphQLInputObjectType]
+    field_name: str
+    field_def: Union[GraphQLField, GraphQLInputField]
+    arg_name: Optional[str] = None
+    arg_def: Optional[GraphQLArgument] = None
+    default_value: Any = None
+    default_value_path: Any = None
+
+
+class GraphQLASTEnumsValuesVisitor(GraphQLEnumsValuesVisitor):
+    def visit_schema(self) -> None:
+        for type_def in self.schema.type_map.values():
+            if type_def.name.startswith("__"):
+                continue  # Skip introspection types
+
+            if (
+                isinstance(type_def, (GraphQLObjectType, GraphQLInterfaceType))
+                and type_def.ast_node
+            ):
+                self.visit_object(type_def)
+
+            if isinstance(type_def, GraphQLInputObjectType) and type_def.ast_node:
+                self.visit_input(type_def)
+
+    def visit_object(
+        self,
+        object_def: Union[GraphQLObjectType, GraphQLInterfaceType],
+    ) -> None:
+        for field_name, field_def in object_def.fields.items():
+            for arg_name, arg_def in field_def.args.items():
+                if arg_def.ast_node and arg_def.ast_node.default_value:
+                    self.visit_value(
+                        object_def,
+                        field_name,
+                        field_def,
+                        arg_name,
+                        arg_def,
+                    )
+
+    def visit_input(
+        self,
+        input_def: GraphQLInputObjectType,
+    ) -> None:
+        for field_name, field_def in input_def.fields.items():
+            if field_def.ast_node and field_def.ast_node.default_value:
+                self.visit_value(
+                    input_def,
+                    field_name,
+                    field_def,
+                )
+
+    def visit_value(
+        self,
+        object_def: Union[
+            GraphQLObjectType, GraphQLInterfaceType, GraphQLInputObjectType
+        ],
+        field_name: str,
+        field_def: Union[GraphQLField, GraphQLInputField],
+        arg_name: Optional[str] = None,
+        arg_def: Optional[GraphQLArgument] = None,
+    ) -> None:
+        src_def: Union[GraphQLInputField, GraphQLArgument]
+        if isinstance(field_def, GraphQLInputField):
+            src_def = field_def
+        elif isinstance(arg_def, GraphQLArgument):
+            src_def = arg_def
+
+        ast_node = cast(InputValueDefinitionNode, src_def.ast_node)
+        default_value_ast = ast_node.default_value
+        if is_graphql_list(src_def.type) and isinstance(
+            default_value_ast, ListValueNode
+        ):
+            self.visit_list_value(
+                object_def,
+                field_name,
+                field_def,
+                arg_name,
+                arg_def,
+                src_def.type,
+                default_value_ast,
+            )
+
+        else:
+            src_type = unwrap_type(src_def.type)
+            if isinstance(src_type, GraphQLEnumType) and isinstance(
+                default_value_ast, EnumValueNode
+            ):
+                self.visit_ast_enum_default_value(
+                    GraphQLASTEnumDefaultValueLocation(
+                        enum_name=src_type.name,
+                        enum_value=default_value_ast.value,
+                        object_name=object_def.name,
+                        object_def=object_def,
+                        field_name=field_name,
+                        field_def=field_def,
+                        arg_name=arg_name,
+                        arg_def=arg_def,
+                        ast_node=default_value_ast,
+                        ast_node_path=None,
+                    )
+                )
+
+            elif isinstance(src_type, GraphQLInputObjectType) and isinstance(
+                default_value_ast, ObjectValueNode
+            ):
+                self.visit_input_value(
+                    object_def,
+                    field_name,
+                    field_def,
+                    arg_name,
+                    arg_def,
+                    src_type,
+                    default_value_ast,
+                )
+
+    def visit_list_value(
+        self,
+        object_def: Union[
+            GraphQLObjectType, GraphQLInterfaceType, GraphQLInputObjectType
+        ],
+        field_name: str,
+        field_def: Union[GraphQLField, GraphQLInputField],
+        arg_name: Optional[str],
+        arg_def: Optional[GraphQLArgument],
+        value_def: GraphQLType,
+        value_ast: ListValueNode,
+    ) -> None:
+        value_type = unwrap_list_type(value_def)
+        if is_graphql_list(value_type):
+            for value_item in value_ast.values:
+                self.visit_list_value(
+                    object_def,
+                    field_name,
+                    field_def,
+                    arg_name,
+                    arg_def,
+                    value_type,
+                    cast(ListValueNode, value_item),
+                )
+
+        elif isinstance(value_type, GraphQLEnumType):
+            for default_value_path, enum_value in enumerate(value_ast.values):
+                if isinstance(enum_value, EnumValueNode):
+                    self.visit_ast_enum_default_value(
+                        GraphQLASTEnumDefaultValueLocation(
+                            enum_name=value_type.name,
+                            enum_value=enum_value.value,
+                            object_name=object_def.name,
+                            object_def=object_def,
+                            field_name=field_name,
+                            field_def=field_def,
+                            arg_name=arg_name,
+                            arg_def=arg_def,
+                            ast_node=value_ast,
+                            ast_node_path=default_value_path,
+                        )
+                    )
+
+        elif isinstance(value_type, GraphQLInputObjectType):
+            for value_item in value_ast.values:
+                if isinstance(value_item, ObjectValueNode):
+                    self.visit_input_value(
+                        object_def,
+                        field_name,
+                        field_def,
+                        arg_name,
+                        arg_def,
+                        value_type,
+                        value_item,
+                    )
+
+    def visit_input_value(
+        self,
+        object_def: Union[
+            GraphQLObjectType, GraphQLInterfaceType, GraphQLInputObjectType
+        ],
+        field_name: str,
+        field_def: Union[GraphQLField, GraphQLInputField],
+        arg_name: Optional[str],
+        arg_def: Optional[GraphQLArgument],
+        value_def: GraphQLInputObjectType,
+        value_ast: ObjectValueNode,
+    ) -> None:
+        value: Dict[str, Any] = {
+            field.name.value: field.value for field in value_ast.fields
+        }
+
+        for input_field_name, input_field_def in value_def.fields.items():
+            input_field_value = value.get(input_field_name)
+            if input_field_value is None:
+                continue  # Skip field
+
+            input_field_type = unwrap_type(input_field_def.type)
+            if is_graphql_list(input_field_def.type) and isinstance(
+                input_field_value, ListValueNode
+            ):
+                self.visit_list_value(
+                    object_def,
+                    field_name,
+                    field_def,
+                    arg_name,
+                    arg_def,
+                    input_field_def.type,
+                    input_field_value,
+                )
+
+            elif isinstance(input_field_type, GraphQLEnumType) and isinstance(
+                input_field_value, EnumValueNode
+            ):
+                self.visit_ast_enum_default_value(
+                    GraphQLASTEnumDefaultValueLocation(
+                        enum_name=input_field_type.name,
+                        enum_value=input_field_value.value,
+                        object_name=object_def.name,
+                        object_def=object_def,
+                        field_name=field_name,
+                        field_def=field_def,
+                        arg_name=arg_name,
+                        arg_def=arg_def,
+                        ast_node=value,
+                        ast_node_path=input_field_name,
+                    )
+                )
+
+            elif isinstance(input_field_type, GraphQLInputObjectType) and isinstance(
+                input_field_value, ObjectValueNode
+            ):
+                self.visit_input_value(
+                    object_def,
+                    field_name,
+                    field_def,
+                    arg_name,
+                    arg_def,
+                    input_field_type,
+                    input_field_value,
+                )
+
+    def visit_ast_enum_default_value(
+        self, location: "GraphQLASTEnumDefaultValueLocation"
+    ) -> None:
+        pass
+
+
+@dataclass(frozen=True)
+class GraphQLASTEnumDefaultValueLocation:
+    enum_name: str
+    enum_value: Any
+    object_name: str
+    object_def: Union[GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType]
+    field_name: str
+    field_def: Union[GraphQLField, GraphQLInputField]
+    arg_name: Optional[str] = None
+    arg_def: Optional[GraphQLArgument] = None
+    ast_node: Any = None
+    ast_node_path: Any = None
+
+
+def unwrap_type(graphql_type: GraphQLType) -> GraphQLType:
+    if isinstance(graphql_type, (GraphQLList, GraphQLNonNull)):
+        return unwrap_type(graphql_type.of_type)
+
+    return graphql_type
+
+
+def unwrap_list_type(graphql_type: GraphQLType) -> GraphQLType:
+    if isinstance(graphql_type, GraphQLNonNull):
+        return unwrap_list_type(graphql_type.of_type)
+
+    if isinstance(graphql_type, GraphQLList):
+        return unwrap_nonnull_type(graphql_type.of_type)
+
+    return graphql_type
+
+
+def unwrap_nonnull_type(graphql_type: GraphQLType) -> GraphQLType:
+    if isinstance(graphql_type, GraphQLNonNull):
+        return unwrap_nonnull_type(graphql_type.of_type)
+
+    return graphql_type
+
+
+def is_graphql_list(graphql_type: GraphQLType) -> bool:
+    if isinstance(graphql_type, GraphQLNonNull):
+        return is_graphql_list(graphql_type.of_type)
+
+    return isinstance(graphql_type, GraphQLList)
+
+
+def is_raw_enum_value(value: Any) -> bool:
+    # Raw enum value is a str with key name
+    return isinstance(value, str) and not isinstance(value, Enum)
diff --git a/ariadne/executable_schema.py b/ariadne/executable_schema.py
index 0d99184..138c628 100644
--- a/ariadne/executable_schema.py
+++ b/ariadne/executable_schema.py
@@ -8,10 +8,10 @@ from graphql import (
     parse,
 )
 
-from .enums import (
-    EnumType,
-    set_default_enum_values_on_schema,
-    validate_schema_enum_values,
+from .enums import EnumType
+from .enums_default_values import (
+    repair_schema_default_enum_values,
+    validate_schema_default_enum_values,
 )
 from .schema_names import SchemaNameConverter, convert_schema_names
 from .schema_visitor import SchemaDirectiveVisitor
@@ -345,14 +345,12 @@ def make_executable_schema(
         if isinstance(bindable, SchemaBindable):
             bindable.bind_to_schema(schema)
 
-    set_default_enum_values_on_schema(schema)
-
     if directives:
         SchemaDirectiveVisitor.visit_schema_directives(schema, directives)
 
     assert_valid_schema(schema)
-    validate_schema_enum_values(schema)
-    repair_default_enum_values(schema, normalized_bindables)
+    validate_schema_default_enum_values(schema)
+    repair_schema_default_enum_values(schema)
 
     if convert_names_case:
         convert_schema_names(
@@ -394,11 +392,3 @@ def flatten_bindables(
             new_bindables.append(bindable)
 
     return new_bindables
-
-
-def repair_default_enum_values(
-    schema: GraphQLSchema, bindables: List[SchemaBindable]
-) -> None:
-    for bindable in bindables:
-        if isinstance(bindable, EnumType):
-            bindable.bind_to_default_values(schema)


Test patch:
diff --git a/tests/test_enums.py b/tests/test_enums.py
index 4eeff7b..c41aa82 100644
--- a/tests/test_enums.py
+++ b/tests/test_enums.py
@@ -1,15 +1,9 @@
-import re
 from enum import Enum, IntEnum
 
 import pytest
-from graphql import graphql_sync, build_schema, parse
-from graphql.pyutils.undefined import Undefined
-from graphql.utilities.build_ast_schema import build_ast_schema
+from graphql import graphql_sync, build_schema
 
 from ariadne import EnumType, QueryType, make_executable_schema
-from ariadne import graphql_sync as ariadne_graphql_sync
-from ariadne.executable_schema import join_type_defs
-from ariadne.enums import find_enum_values_in_schema
 
 enum_definition = """
     enum Episode {
@@ -299,183 +293,6 @@ def test_int_enum_input_nested_default_python_value_is_set():
     assert result.data["complex"]
 
 
-def test_input_exc_schema_raises_exception_for_undefined_enum_value_in_flat_input():
-    input_schema = """
-         type Query {
-            complex(i: Test = { role: EMPIRE }): String
-        }
-        input Test {
-            ignore: String
-            role: Episode = TWO_TOWERS  
-        }
-    """
-    with pytest.raises(
-        ValueError,
-        match=re.escape(
-            "Value for type: <Episode> is invalid. "
-            "Check InputField/Arguments for <role> in <Test> (Undefined enum value)."
-        ),
-    ):
-        make_executable_schema([enum_definition, input_schema])
-
-
-def test_input_exc_schema_raises_exception_for_undefined_enum_value_in_nested_object():
-    input_schema = """
-        type Query {
-            complex(i: Test = { role: EMPIRE }): String
-        }
-        input Test {
-            ignore: String 
-            role: Episode = EMPIRE  
-        }
-        input BetterTest {
-            newIgnore: String
-            test: Test = { role: ANDRZEJU }
-        }
-    """
-
-    with pytest.raises(
-        ValueError,
-        match=re.escape(
-            "Value for type: <Test> is invalid. "
-            "Check InputField/Arguments for <test> in <BetterTest> (Undefined enum value)."
-        ),
-    ):
-        make_executable_schema([enum_definition, input_schema])
-
-
-def test_input_exc_schema_raises_exception_for_undefined_enum_value_in_nested_field_arg():
-    input_schema = """
-        type Query {
-            complex(i: BetterTest = { test: { role: TWO_TOWERS } }): String
-        }
-        input Test {
-            ignore: String 
-            role: Episode = EMPIRE  
-        }
-        input BetterTest {
-            newIgnore: String
-            test: Test = { role: NEW_HOPE }
-        }
-    """
-
-    with pytest.raises(
-        ValueError,
-        match=re.escape(
-            "Value for type: <BetterTest> is invalid. "
-            "Check InputField/Arguments for <i> in <Query> (Undefined enum value)."
-        ),
-    ):
-        make_executable_schema([enum_definition, input_schema])
-
-
-def test_find_enum_values_in_schema_for_undefined_and_invalid_values():
-    input_schema = """
-        type Query {
-            complex(
-                hello: Episode = EMPIRE,
-                hi: Test = { role: JEDI,  next_role: ENTERPRISE, ignore: "HI"}, 
-                bonjour: BetterTest = {newIgnore: "Witam", test: { role: NEWHOPE }}
-            ): String
-        }
-    
-        input Test {
-            ignore: String 
-            role: Episode = EMPIRE  
-            next_role: Episode
-        }
-
-        input BetterTest {
-            newIgnore: String
-            test: Test = { role: NEWHOPE }
-        }
-
-        interface Result {
-            hello(r: Episode = ENTERPRISE): String
-        }
-    """
-    query_complex_keys = [["next_role"], ["role"], ["test", "role"]]
-    better_test_complex_keys = [["role"]]
-    number_of_defined_enum_values = 8
-
-    # 2 Undefined because of "ENTERPRISE" invalid value, and next_role in Test input
-    number_of_undefined_default_enum_values = 4
-
-    ast_document = parse(join_type_defs([enum_definition, input_schema]))
-    schema = build_ast_schema(ast_document)
-    enums_entities = list(find_enum_values_in_schema(schema))
-    keys_to_complex_inputs = [keys for *_, keys in enums_entities if keys is not None]
-
-    undefined = [
-        (*_, args, keys)
-        for *_, args, keys in enums_entities
-        if args.default_value is Undefined
-    ]
-
-    assert keys_to_complex_inputs == query_complex_keys + better_test_complex_keys
-    assert len(enums_entities) == number_of_defined_enum_values
-    assert len(undefined) == number_of_undefined_default_enum_values
-
-
-def test_enum_type_is_able_to_represent_enum_default_value_in_schema():
-    #  regression test for: https://github.com/mirumee/ariadne/issues/293
-    #  regression test for: https://github.com/mirumee/ariadne/issues/995
-
-    type_defs = """
-        enum Role {
-            ADMIN
-            USER
-        }
-
-        type Query {
-            hello(r: Role = USER): String
-            results: [Result!]!
-        }
-
-        interface Result {
-            hello(r: Role = USER): String
-        }
-
-        type User implements Result {
-            hello(r: Role = USER): String
-        }
-    """
-
-    class Role(Enum):
-        ADMIN = "admin"
-        USER = "user"
-
-    def resolve_test_enum(*_, r):
-        return r == Role.USER
-
-    RoleGraphQLType = EnumType("Role", Role)
-    QueryGraphQLType = QueryType()
-
-    QueryGraphQLType.set_field("hello", resolve_test_enum)
-
-    schema = make_executable_schema(
-        type_defs,
-        QueryGraphQLType,
-        RoleGraphQLType,
-    )
-
-    query = "{__schema{types{name,fields{name,args{name,defaultValue}}}}}"
-    _, result = ariadne_graphql_sync(schema, {"query": query}, debug=True)
-    assert not result.get("errors")
-
-    types_map = {
-        result_type["name"]: result_type
-        for result_type in result["data"]["__schema"]["types"]
-    }
-    assert schema.type_map["Query"].fields["hello"].args["r"].default_value == Role.USER
-
-    result_hello_query = graphql_sync(schema, "{hello}")
-    assert types_map["Query"]["fields"][0]["args"][0]["defaultValue"] == "USER"
-    assert types_map["User"]["fields"][0]["args"][0]["defaultValue"] == "USER"
-    assert result_hello_query.data["hello"]
-    assert result_hello_query.errors is None
-
-
 def test_python_enums_can_be_passed_directly_to_make_executable_schema():
     class Episode(Enum):
         NEWHOPE = "new-hope"
@@ -515,18 +332,3 @@ def test_error_is_raised_for_python_enum_with_name_not_in_schema():
 
     with pytest.raises(ValueError):
         make_executable_schema([enum_definition, enum_field], query, UnknownEnum)
-
-
-def test_schema_enum_values_fixer_handles_null_input_default():
-    #  regression test for: https://github.com/mirumee/ariadne/issues/1074
-    make_executable_schema(
-        """
-        input SearchInput {
-          name: String!
-        }
-    
-        type Query {
-          search(filters: SearchInput = null): String
-        }
-        """
-    )
diff --git a/tests/test_graphql_enum_fixes.py b/tests/test_graphql_enum_fixes.py
new file mode 100644
index 0000000..dbda413
--- /dev/null
+++ b/tests/test_graphql_enum_fixes.py
@@ -0,0 +1,588 @@
+"""Acceptance tests for fixes to some GraphQL enum gotchas.
+
+GraphQL enum members are represented in Python as strings with their names as default.
+
+For example:
+
+enum Role {
+    ADMIN
+    USER
+}
+
+ADMIN in GraphQL schema and queries will be represented as "ADMIN" string in
+JSON and Python. This is okay until GraphQL enums are paired to other Python types
+using Ariadne's logic.
+
+When either `Enum`, `EnumType` or `GraphQLEnum` is used, Ariadne needs to scan
+entire GraphQL schema for places where `default_value` represents enum member,
+and replace default string value with valid enum member representation.
+
+Other issue is that standard `validate_schema` is not validating when
+`default_value` for field arguments and input fields is a valid enum member.
+
+GitHub issues:
+
+https://github.com/mirumee/ariadne/issues/293
+https://github.com/mirumee/ariadne/issues/995
+https://github.com/mirumee/ariadne/issues/1074
+"""
+
+from enum import Enum
+
+import pytest
+from graphql import GraphQLSchema, graphql_sync
+
+from ariadne import EnumType, make_executable_schema
+
+
+def set_resolver(schema: GraphQLSchema, field: str, resolver):
+    schema.type_map["Query"].fields[field].resolve = resolver
+
+
+def get_arg_type(*_, arg):
+    return type(arg).__name__
+
+
+def get_arg_str(*_, arg):
+    return str(arg)
+
+
+def get_arg_list_type(*_, arg=None):
+    return type(arg[0] if arg is not None else None).__name__
+
+
+def get_arg_list_str(*_, arg=None):
+    return str(arg[0] if arg is not None else None)
+
+
+def get_arg_list_list_type(*_, arg=None):
+    return type(arg[0][0] if arg is not None else None).__name__
+
+
+def get_arg_list_list_str(*_, arg=None):
+    return str(arg[0][0] if arg is not None else None)
+
+
+def get_arg_input_type(*_, arg=None):
+    return type(arg.get("role") if arg is not None else None).__name__
+
+
+def get_arg_input_str(*_, arg=None):
+    return str(arg.get("role") if arg is not None else None)
+
+
+def get_arg_input_list_type(*_, arg=None):
+    return type(arg[0].get("role") if arg is not None else None).__name__
+
+
+def get_arg_input_list_str(*_, arg=None):
+    return str(arg[0].get("role") if arg is not None else None)
+
+
+def get_arg_input_list_def_type(*_, arg=None):
+    return type(arg.get("role")[0] if arg is not None else None).__name__
+
+
+def get_arg_input_list_def_str(*_, arg=None):
+    return str(arg.get("role")[0] if arg is not None else None)
+
+
+def assert_schema_working_defaults(schema: GraphQLSchema):
+    """Fail test if GraphQL default values are not Python-representable."""
+    result = graphql_sync(
+        schema,
+        (
+            """
+            {
+                __schema {
+                    types {
+                        name
+                        fields {
+                            name
+                            args {
+                                name
+                                defaultValue
+                            }
+                        }
+                        inputFields {
+                            name
+                            defaultValue
+                        }
+                    }
+                }
+            }
+            """
+        ),
+    )
+
+    assert not result.errors
+
+
+@pytest.fixture
+def create_schema():
+    def schema_factory(*args):
+        schema = make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            input NoDef {
+                role: Role
+            }
+
+            input HasDef {
+                role: Role = USER
+            }
+
+            input HasListDef {
+                role: [Role] = [USER]
+            }
+
+            type Query {
+                argType(arg: Role): String!
+                argValue(arg: Role): String!
+                argDefType(arg: Role = USER): String!
+                argDefValue(arg: Role = USER): String!
+                argDefNull(arg: Role = null): String!
+                argListDefType(arg: [Role!] = [USER]): String!
+                argListDefValue(arg: [Role!] = [USER]): String!
+                argListListDefType(arg: [[Role!]] = [[USER]]): String!
+                argListListDefValue(arg: [[Role!]] = [[USER]]): String!
+                argInputType(arg: NoDef): String!
+                argInputValue(arg: NoDef): String!
+                argInputObjType(arg: NoDef = {role: USER}): String!
+                argInputObjValue(arg: NoDef = {role: USER}): String!
+                argInputListObjType(arg: [NoDef] = [{role: USER}]): String!
+                argInputListObjValue(arg: [NoDef] = [{role: USER}]): String!
+                argInputDefType(arg: HasDef): String!
+                argInputDefValue(arg: HasDef): String!
+                argInputDefObjType(arg: HasDef = {}): String!
+                argInputDefObjValue(arg: HasDef = {}): String!
+                argInputListDefType(arg: HasListDef = {}): String!
+                argInputListDefValue(arg: HasListDef = {}): String!
+                argInputListDefObjType(arg: HasListDef = {role: [USER]}): String!
+                argInputListDefObjValue(arg: HasListDef = {role: [USER]}): String!
+                pyStr: Role
+                pyInt: Role
+                pyEnum: Role
+            }
+            """,
+            *args,
+        )
+
+        set_resolver(schema, "argType", get_arg_type)
+        set_resolver(schema, "argValue", get_arg_str)
+        set_resolver(schema, "argDefType", get_arg_type)
+        set_resolver(schema, "argDefValue", get_arg_str)
+        set_resolver(schema, "argListDefType", get_arg_list_type)
+        set_resolver(schema, "argListDefValue", get_arg_list_str)
+        set_resolver(schema, "argListListDefType", get_arg_list_list_type)
+        set_resolver(schema, "argListListDefValue", get_arg_list_list_str)
+        set_resolver(schema, "argInputType", get_arg_input_type)
+        set_resolver(schema, "argInputValue", get_arg_input_str)
+        set_resolver(schema, "argInputObjType", get_arg_input_type)
+        set_resolver(schema, "argInputObjValue", get_arg_input_str)
+        set_resolver(schema, "argInputListObjType", get_arg_input_list_type)
+        set_resolver(schema, "argInputListObjValue", get_arg_input_list_str)
+        set_resolver(schema, "argInputDefType", get_arg_input_type)
+        set_resolver(schema, "argInputDefValue", get_arg_input_str)
+        set_resolver(schema, "argInputDefObjType", get_arg_input_type)
+        set_resolver(schema, "argInputDefObjValue", get_arg_input_str)
+        set_resolver(schema, "argInputListDefType", get_arg_input_list_def_type)
+        set_resolver(schema, "argInputListDefValue", get_arg_input_list_def_str)
+        set_resolver(schema, "argInputListDefObjType", get_arg_input_list_def_type)
+        set_resolver(schema, "argInputListDefObjValue", get_arg_input_list_def_str)
+
+        return schema
+
+    return schema_factory
+
+
+query = """
+    {
+        argType(arg: ADMIN)
+        argValue(arg: ADMIN)
+        argDefType
+        argDefValue
+        argListDefType
+        argListDefValue
+        argListListDefType
+        argListListDefValue
+        argInputType(arg: {role: ADMIN})
+        argInputValue(arg: {role: ADMIN})
+        argInputObjType
+        argInputObjValue
+        argInputListObjType
+        argInputListObjValue
+        argInputDefType
+        argInputDefValue
+        argInputDefObjType
+        argInputDefObjValue
+        argInputListDefType
+        argInputListDefValue
+        argInputListDefObjType
+        argInputListDefObjValue
+        pyStr
+        pyInt
+        pyEnum
+    }
+    """
+
+
+def test_enum_with_default_member_names(create_schema):
+    # If no Python values were provided for enum's members,
+    # those enum members will be represented as strings in Python
+    schema = create_schema()
+    set_resolver(schema, "pyStr", lambda *_: "ADMIN")
+
+    result = graphql_sync(schema, query)
+    assert not result.errors
+    assert result.data == {
+        "argType": "str",
+        "argValue": "ADMIN",
+        "argDefType": "str",
+        "argDefValue": "USER",
+        "argListDefType": "str",
+        "argListDefValue": "USER",
+        "argListListDefType": "str",
+        "argListListDefValue": "USER",
+        "argInputType": "str",
+        "argInputValue": "ADMIN",
+        "argInputObjType": "str",
+        "argInputObjValue": "USER",
+        "argInputListObjType": "str",
+        "argInputListObjValue": "USER",
+        "argInputDefType": "NoneType",
+        "argInputDefValue": "None",
+        "argInputDefObjType": "str",
+        "argInputDefObjValue": "USER",
+        "argInputListDefType": "str",
+        "argInputListDefValue": "USER",
+        "argInputListDefObjType": "str",
+        "argInputListDefObjValue": "USER",
+        "pyStr": "ADMIN",
+        "pyInt": None,
+        "pyEnum": None,
+    }
+
+    assert_schema_working_defaults(schema)
+
+
+def test_enum_with_int_values_from_dict(create_schema):
+    schema = create_schema(EnumType("Role", {"USER": 0, "ADMIN": 1}))
+
+    set_resolver(schema, "pyInt", lambda *_: 1)
+
+    result = graphql_sync(schema, query)
+    assert not result.errors
+    assert result.data == {
+        "argType": "int",
+        "argValue": "1",
+        "argDefType": "int",
+        "argDefValue": "0",
+        "argListDefType": "int",
+        "argListDefValue": "0",
+        "argListListDefType": "int",
+        "argListListDefValue": "0",
+        "argInputType": "int",
+        "argInputValue": "1",
+        "argInputObjType": "int",
+        "argInputObjValue": "0",
+        "argInputListObjType": "int",
+        "argInputListObjValue": "0",
+        "argInputDefType": "NoneType",
+        "argInputDefValue": "None",
+        "argInputDefObjType": "int",
+        "argInputDefObjValue": "0",
+        "argInputListDefType": "int",
+        "argInputListDefValue": "0",
+        "argInputListDefObjType": "int",
+        "argInputListDefObjValue": "0",
+        "pyStr": None,
+        "pyInt": "ADMIN",
+        "pyEnum": None,
+    }
+
+    assert_schema_working_defaults(schema)
+
+
+def test_enum_with_int_enum_values(create_schema):
+    class Role(int, Enum):
+        USER = 0
+        ADMIN = 1
+
+    schema = create_schema(Role)
+
+    set_resolver(schema, "pyInt", lambda *_: 1)
+    set_resolver(schema, "pyEnum", lambda *_: Role.ADMIN)
+
+    result = graphql_sync(schema, query)
+    assert not result.errors
+    assert result.data == {
+        "argType": "Role",
+        "argValue": "Role.ADMIN",
+        "argDefType": "Role",
+        "argDefValue": "Role.USER",
+        "argListDefType": "Role",
+        "argListDefValue": "Role.USER",
+        "argListListDefType": "Role",
+        "argListListDefValue": "Role.USER",
+        "argInputType": "Role",
+        "argInputValue": "Role.ADMIN",
+        "argInputObjType": "Role",
+        "argInputObjValue": "Role.USER",
+        "argInputListObjType": "Role",
+        "argInputListObjValue": "Role.USER",
+        "argInputDefType": "NoneType",
+        "argInputDefValue": "None",
+        "argInputDefObjType": "Role",
+        "argInputDefObjValue": "Role.USER",
+        "argInputListDefType": "Role",
+        "argInputListDefValue": "Role.USER",
+        "argInputListDefObjType": "Role",
+        "argInputListDefObjValue": "Role.USER",
+        "pyStr": None,
+        "pyInt": "ADMIN",
+        "pyEnum": "ADMIN",
+    }
+
+    assert_schema_working_defaults(schema)
+
+
+def test_enum_with_str_enum_values(create_schema):
+    class Role(str, Enum):
+        USER = "u"
+        ADMIN = "a"
+
+    schema = create_schema(Role)
+
+    set_resolver(schema, "pyStr", lambda *_: "a")
+    set_resolver(schema, "pyEnum", lambda *_: Role.ADMIN)
+
+    result = graphql_sync(schema, query)
+    assert not result.errors
+    assert result.data == {
+        "argType": "Role",
+        "argValue": "Role.ADMIN",
+        "argDefType": "Role",
+        "argDefValue": "Role.USER",
+        "argListDefType": "Role",
+        "argListDefValue": "Role.USER",
+        "argListListDefType": "Role",
+        "argListListDefValue": "Role.USER",
+        "argInputType": "Role",
+        "argInputValue": "Role.ADMIN",
+        "argInputObjType": "Role",
+        "argInputObjValue": "Role.USER",
+        "argInputListObjType": "Role",
+        "argInputListObjValue": "Role.USER",
+        "argInputDefType": "NoneType",
+        "argInputDefValue": "None",
+        "argInputDefObjType": "Role",
+        "argInputDefObjValue": "Role.USER",
+        "argInputListDefType": "Role",
+        "argInputListDefValue": "Role.USER",
+        "argInputListDefObjType": "Role",
+        "argInputListDefObjValue": "Role.USER",
+        "pyStr": "ADMIN",
+        "pyInt": None,
+        "pyEnum": "ADMIN",
+    }
+
+    assert_schema_working_defaults(schema)
+
+
+def test_invalid_default_field_arg_enum_value_fails_validation():
+    with pytest.raises(ValueError) as exc_info:
+        make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            type Query {
+                field(arg: Role = INVALID): String
+            }
+            """
+        )
+
+    assert "Undefined enum value" in str(exc_info.value)
+
+
+def test_invalid_default_field_arg_enum_list_value_fails_validation():
+    with pytest.raises(ValueError) as exc_info:
+        make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            type Query {
+                field(arg: [Role!] = [USER, INVALID]): String
+            }
+            """
+        )
+
+    assert "Undefined enum value" in str(exc_info.value)
+
+
+def test_invalid_default_field_arg_enum_nested_list_value_fails_validation():
+    with pytest.raises(ValueError) as exc_info:
+        make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            type Query {
+                field(arg: [[Role!]] = [[USER], [INVALID]]): String
+            }
+            """
+        )
+
+    assert "Undefined enum value" in str(exc_info.value)
+
+
+def test_invalid_default_input_field_enum_value_fails_validation():
+    with pytest.raises(ValueError) as exc_info:
+        make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            input Input {
+                field: Role = INVALID
+            }
+
+            type Query {
+                field(arg: Input): String
+            }
+            """
+        )
+
+    assert "Undefined enum value" in str(exc_info.value)
+
+
+def test_invalid_default_field_input_nested_object_enum_value_fails_validation():
+    with pytest.raises(ValueError) as exc_info:
+        make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            input Input {
+                field: ChildInput = {field: INVALID}
+            }
+
+            input ChildInput {
+                field: Role
+            }
+
+            type Query {
+                field(arg: Input): String
+            }
+            """
+        )
+
+    assert "Undefined enum value" in str(exc_info.value)
+
+
+def test_invalid_default_field_input_arg_object_enum_value_fails_validation():
+    with pytest.raises(ValueError) as exc_info:
+        make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            input Input {
+                field: Role
+            }
+
+            type Query {
+                field(arg: Input = {field: INVALID}): String
+            }
+            """
+        )
+
+    assert "Undefined enum value" in str(exc_info.value)
+
+
+def test_invalid_default_field_input_list_arg_object_enum_value_fails_validation():
+    with pytest.raises(ValueError) as exc_info:
+        make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            input Input {
+                field: Role
+            }
+
+            type Query {
+                field(arg: [Input!] = [{field: USER}, {field: INVALID}]): String
+            }
+            """
+        )
+
+    assert "Undefined enum value" in str(exc_info.value)
+
+
+def test_invalid_default_field_input_arg_nested_object_enum_value_fails_validation():
+    with pytest.raises(ValueError) as exc_info:
+        make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            input Input {
+                field: ChildInput
+            }
+
+            input ChildInput {
+                field: Role
+            }
+
+            type Query {
+                field(arg: Input = {field: {field: INVALID}}): String
+            }
+            """
+        )
+
+    assert "Undefined enum value" in str(exc_info.value)
+
+
+def test_invalid_default_field_input_arg_object_enum_list_value_fails_validation():
+    with pytest.raises(ValueError) as exc_info:
+        make_executable_schema(
+            """
+            enum Role {
+                USER
+                ADMIN
+            }
+
+            input Input {
+                field: [Role] = [INVALID]
+            }
+
+            type Query {
+                field(arg: Input! = {}): String
+            }
+            """
+        )
+
+    assert "Undefined enum value" in str(exc_info.value)