Skip to content

Commit 2b13552

Browse files
committed
Improve coverage of the entire codebase
Replicates graphql/graphql-js@4fcef75
1 parent 9b06a20 commit 2b13552

29 files changed

+871
-151
lines changed

src/graphql/execution/execute.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,9 +1048,9 @@ def assert_valid_execution_arguments(
10481048
# Variables, if provided, must be a dictionary.
10491049
if not (raw_variable_values is None or isinstance(raw_variable_values, dict)):
10501050
raise TypeError(
1051-
"Variables must be provided as a dictionary where each property is a"
1052-
" variable value. Perhaps look to see if an unparsed JSON string was"
1053-
" provided."
1051+
"Variable values must be provided as a dictionary"
1052+
" with variable names as keys. Perhaps look to see"
1053+
" if an unparsed JSON string was provided."
10541054
)
10551055

10561056

src/graphql/language/visitor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Any,
44
Callable,
55
Collection,
6+
Dict,
67
List,
78
NamedTuple,
89
Optional,
@@ -23,6 +24,7 @@
2324
"SKIP",
2425
"REMOVE",
2526
"IDLE",
27+
"QUERY_DOCUMENT_KEYS",
2628
]
2729

2830

@@ -32,7 +34,7 @@
3234
BREAK, SKIP, REMOVE, IDLE = True, False, Ellipsis, None
3335

3436
# Default map from visitor kinds to their traversable node attributes:
35-
QUERY_DOCUMENT_KEYS = {
37+
QUERY_DOCUMENT_KEYS: Dict[str, Tuple[str, ...]] = {
3638
"name": (),
3739
"document": ("definitions",),
3840
"operation_definition": (

tests/execution/test_abstract.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,42 @@ def returning_invalid_value_from_resolve_type_yields_useful_error():
372372
],
373373
)
374374

375+
def missing_both_resolve_type_and_is_type_of_yields_useful_error():
376+
foo_interface = GraphQLInterfaceType(
377+
"FooInterface", {"bar": GraphQLField(GraphQLString)}
378+
)
379+
380+
foo_object = GraphQLObjectType(
381+
"FooObject",
382+
{"bar": GraphQLField(GraphQLString)},
383+
interfaces=[foo_interface],
384+
)
385+
386+
schema = GraphQLSchema(
387+
GraphQLObjectType(
388+
"Query",
389+
{"foo": GraphQLField(foo_interface, resolve=lambda *_args: "dummy")},
390+
),
391+
types=[foo_object],
392+
)
393+
394+
result = graphql_sync(schema, "{ foo { bar } }")
395+
396+
assert result == (
397+
{"foo": None},
398+
[
399+
{
400+
"message": "Abstract type 'FooInterface' must resolve to an"
401+
" Object type at runtime for field 'Query.foo' with value 'dummy',"
402+
" received 'None'. Either the 'FooInterface' type should provide"
403+
" a 'resolve_type' function or each possible type"
404+
" should provide an 'is_type_of' function.",
405+
"locations": [(1, 3)],
406+
"path": ["foo"],
407+
}
408+
],
409+
)
410+
375411
def resolve_type_allows_resolving_with_type_name():
376412
PetType = GraphQLInterfaceType(
377413
"Pet",

tests/execution/test_executor.py

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525

2626
def describe_execute_handles_basic_execution_tasks():
27-
2827
# noinspection PyTypeChecker
2928
def throws_if_no_document_is_provided():
3029
schema = GraphQLSchema(
@@ -45,6 +44,39 @@ def throws_if_no_schema_is_provided():
4544

4645
assert str(exc_info.value) == "Expected None to be a GraphQL schema."
4746

47+
def throws_on_invalid_variables():
48+
schema = GraphQLSchema(
49+
GraphQLObjectType(
50+
"Type",
51+
{
52+
"fieldA": GraphQLField(
53+
GraphQLString, args={"argA": GraphQLArgument(GraphQLInt)}
54+
)
55+
},
56+
)
57+
)
58+
document = parse(
59+
"""
60+
query ($a: Int) {
61+
fieldA(argA: $a)
62+
}
63+
"""
64+
)
65+
variable_values = "{'a': 1}"
66+
67+
with raises(TypeError) as exc_info:
68+
assert execute(
69+
schema=schema,
70+
document=document,
71+
variable_values=variable_values, # type: ignore
72+
)
73+
74+
assert str(exc_info.value) == (
75+
"Variable values must be provided as a dictionary"
76+
" with variable names as keys. Perhaps look to see"
77+
" if an unparsed JSON string was provided."
78+
)
79+
4880
def accepts_positional_arguments():
4981
schema = GraphQLSchema(
5082
GraphQLObjectType(
@@ -59,7 +91,6 @@ def accepts_positional_arguments():
5991

6092
@mark.asyncio
6193
async def executes_arbitrary_code():
62-
6394
# noinspection PyMethodMayBeStatic,PyMethodMayBeStatic
6495
class Data:
6596
def a(self, _info):
@@ -80,7 +111,7 @@ def e(self, _info):
80111
f = "Fish"
81112

82113
# Called only by DataType::pic static resolver
83-
def pic(self, _info, size=50):
114+
def pic(self, _info, size):
84115
return f"Pic of size: {size}"
85116

86117
def deep(self, _info):
@@ -766,6 +797,17 @@ class Data:
766797

767798
assert result == ({"a": "b"}, None)
768799

800+
def ignores_missing_sub_selections_on_fields():
801+
some_type = GraphQLObjectType("SomeType", {"b": GraphQLField(GraphQLString)})
802+
schema = GraphQLSchema(
803+
GraphQLObjectType("Query", {"a": GraphQLField(some_type)})
804+
)
805+
document = parse("{ a }")
806+
root_value = {"a": {"b": "c"}}
807+
808+
result = execute(schema, document, root_value)
809+
assert result == ({"a": {}}, None)
810+
769811
def does_not_include_illegal_fields_in_output():
770812
schema = GraphQLSchema(
771813
GraphQLObjectType("Q", {"a": GraphQLField(GraphQLString)})
@@ -804,7 +846,8 @@ def does_not_include_arguments_that_were_not_set():
804846
None,
805847
)
806848

807-
def fails_when_an_is_type_of_check_is_not_met():
849+
@mark.asyncio
850+
async def fails_when_is_type_of_check_is_not_met():
808851
class Special:
809852
value: str
810853

@@ -817,10 +860,20 @@ class NotSpecial:
817860
def __init__(self, value):
818861
self.value = value
819862

863+
def is_type_of_special(obj, _info):
864+
is_special = isinstance(obj, Special)
865+
if not _info.context["async"]:
866+
return is_special
867+
868+
async def async_is_special():
869+
return is_special
870+
871+
return async_is_special()
872+
820873
SpecialType = GraphQLObjectType(
821874
"SpecialType",
822875
{"value": GraphQLField(GraphQLString)},
823-
is_type_of=lambda obj, _info: isinstance(obj, Special),
876+
is_type_of=is_type_of_special,
824877
)
825878

826879
schema = GraphQLSchema(
@@ -832,7 +885,8 @@ def __init__(self, value):
832885
document = parse("{ specials { value } }")
833886
root_value = {"specials": [Special("foo"), NotSpecial("bar")]}
834887

835-
result = execute(schema, document, root_value)
888+
result = execute(schema, document, root_value, {"async": False})
889+
assert not isinstance(result, Awaitable)
836890
assert result == (
837891
{"specials": [{"value": "foo"}, None]},
838892
[
@@ -845,6 +899,11 @@ def __init__(self, value):
845899
],
846900
)
847901

902+
async_result = execute(schema, document, root_value, {"async": True})
903+
assert isinstance(async_result, Awaitable)
904+
awaited_result = await async_result
905+
assert awaited_result == result
906+
848907
def executes_ignoring_invalid_non_executable_definitions():
849908
schema = GraphQLSchema(
850909
GraphQLObjectType("Query", {"foo": GraphQLField(GraphQLString)})

tests/execution/test_mutations.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ async def evaluates_mutations_serially():
133133
None,
134134
)
135135

136+
def does_not_include_illegal_mutation_fields_in_output():
137+
document = parse("mutation { thisIsIllegalDoNotIncludeMe }")
138+
139+
result = execute(schema=schema, document=document)
140+
assert result == ({}, None)
141+
136142
@mark.asyncio
137143
async def evaluates_mutations_correctly_in_presence_of_a_failed_mutation():
138144
document = parse(

tests/execution/test_nonnull.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,8 @@ def describe_handles_non_null_argument():
540540

541541
# noinspection PyPep8Naming
542542
def _resolve(_obj, _info, cannotBeNull):
543-
if isinstance(cannotBeNull, str):
544-
return f"Passed: {cannotBeNull}"
543+
assert isinstance(cannotBeNull, str)
544+
return f"Passed: {cannotBeNull}"
545545

546546
schema_with_non_null_arg = GraphQLSchema(
547547
GraphQLObjectType(

tests/execution/test_union_interface.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,42 @@ def executes_interface_types_with_inline_fragment():
375375
None,
376376
)
377377

378+
def executes_interface_types_with_named_fragments():
379+
document = parse(
380+
"""
381+
{
382+
__typename
383+
name
384+
friends {
385+
__typename
386+
name
387+
...DogBarks
388+
...CatMeows
389+
}
390+
}
391+
392+
fragment DogBarks on Dog {
393+
barks
394+
}
395+
396+
fragment CatMeows on Cat {
397+
meows
398+
}
399+
"""
400+
)
401+
402+
assert execute(schema=schema, document=document, root_value=john) == (
403+
{
404+
"__typename": "Person",
405+
"name": "John",
406+
"friends": [
407+
{"__typename": "Person", "name": "Liz"},
408+
{"__typename": "Dog", "name": "Odie", "barks": True},
409+
],
410+
},
411+
None,
412+
)
413+
378414
def allows_fragment_conditions_to_be_abstract_types():
379415
document = parse(
380416
"""

tests/execution/test_variables.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@
2020
GraphQLString,
2121
)
2222

23+
24+
def parse_serialized_value(value: str) -> str:
25+
assert value == "SerializedValue"
26+
return "DeserializedValue"
27+
28+
2329
TestComplexScalar = GraphQLScalarType(
2430
name="ComplexScalar",
25-
serialize=lambda value: "SerializedValue" if value == "DeserializedValue" else None,
26-
parse_value=lambda value: "DeserializedValue"
27-
if value == "SerializedValue"
28-
else None,
29-
parse_literal=lambda ast, _variables=None: "DeserializedValue"
30-
if ast.value == "SerializedValue"
31-
else None,
31+
parse_value=lambda value: parse_serialized_value(value),
32+
parse_literal=lambda ast, _variables=None: parse_serialized_value(ast.value),
3233
)
3334

3435

tests/fixtures/schema_kitchen_sink.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ extend input InputType @onInputObject
135135
This is a description of the `@skip` directive
136136
"""
137137
directive @skip(
138+
"""This is a description of the `if` argument"""
138139
if: Boolean! @onArgumentDefinition
139140
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
140141

0 commit comments

Comments
 (0)