Skip to content

Commit 37f266b

Browse files
committed
Implementation of ProvidedNonNullArguments
Implement missing test harness complicated args types. Fix failing introspection test now that ProvideNonNullArgs is implemented. Implement all introspection tests.
1 parent 060315c commit 37f266b

File tree

5 files changed

+290
-6
lines changed

5 files changed

+290
-6
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ docs/_build/
5959
# PyBuilder
6060
target/
6161

62+
# IntelliJ
63+
.idea
64+

graphql/core/validation/rules.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from ..utils import type_from_ast
22
from ..error import GraphQLError
3-
from ..type.definition import is_composite_type, is_input_type, is_leaf_type
3+
from ..type.definition import is_composite_type, is_input_type, is_leaf_type, GraphQLNonNull
44
from ..language import ast
55
from ..language.visitor import Visitor
66
from ..language.printer import print_ast
@@ -264,7 +264,52 @@ class ArgumentsOfCorrectType(ValidationRule):
264264

265265

266266
class ProvidedNonNullArguments(ValidationRule):
267-
pass
267+
def leave_Field(self, node, key, parent, path, ancestors):
268+
field_def = self.context.get_field_def()
269+
if not field_def:
270+
return False
271+
272+
errors = []
273+
arg_asts = node.arguments or []
274+
arg_ast_map = {arg.name.value: arg for arg in arg_asts}
275+
276+
for arg_def in field_def.args:
277+
arg_ast = arg_ast_map.get(arg_def.name, None)
278+
if not arg_ast and isinstance(arg_def.type, GraphQLNonNull):
279+
errors.append(GraphQLError(
280+
self.missing_field_arg_message(node.name.value, arg_def.name, arg_def.type),
281+
[node]
282+
))
283+
if errors:
284+
return errors
285+
286+
def leave_Directive(self, node, key, parent, path, ancestors):
287+
directive_def = self.context.get_directive()
288+
if not directive_def:
289+
return False
290+
291+
errors = []
292+
arg_asts = node.arguments or []
293+
arg_ast_map = {arg.name.value: arg for arg in arg_asts}
294+
295+
for arg_def in directive_def.args:
296+
arg_ast = arg_ast_map.get(arg_def.name, None)
297+
if not arg_ast and isinstance(arg_def.type, GraphQLNonNull):
298+
errors.append(GraphQLError(
299+
self.missing_directive_arg_message(node.name.value, arg_def.name, arg_def.type),
300+
[node]
301+
))
302+
303+
if errors:
304+
return errors
305+
306+
@staticmethod
307+
def missing_field_arg_message(name, arg_name, type):
308+
return 'Field "{}" argument "{}" of type "{}" is required but not provided.'.format(name, arg_name, type)
309+
310+
@staticmethod
311+
def missing_directive_arg_message(name, arg_name, type):
312+
return 'Directive "{}" argument "{}" of type "{}" is required but not provided.'.format(name, arg_name, type)
268313

269314

270315
class DefaultValuesOfCorrectType(ValidationRule):

tests/core_type/test_introspection.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import json
22
from graphql.core import graphql
3+
from graphql.core.error import format_error
4+
from graphql.core.language.location import SourceLocation
35
from graphql.core.language.parser import parse
46
from graphql.core.execution import execute
57
from graphql.core.type import (
@@ -14,6 +16,7 @@
1416
GraphQLEnumType,
1517
GraphQLEnumValue,
1618
)
19+
from graphql.core.validation.rules import ProvidedNonNullArguments
1720

1821
introspection_query = '''
1922
query IntrospectionQuery {
@@ -707,10 +710,16 @@ def test_fails_as_expected_on_the_type_root_field_without_an_arg():
707710
'testField': GraphQLField(GraphQLString)
708711
})
709712
schema = GraphQLSchema(TestType)
710-
request = '{ __type { name } }'
713+
request = '''
714+
{
715+
__type {
716+
name
717+
}
718+
}'''
711719
result = graphql(schema, request)
712-
# TODO: change after implementing validation
713-
assert not result.errors
720+
expected_error = {'message': ProvidedNonNullArguments.missing_field_arg_message('__type', 'name', 'String!'),
721+
'locations': [SourceLocation(line=3, column=9)]}
722+
assert (expected_error in [format_error(error) for error in result.errors])
714723

715724

716725
def test_exposes_descriptions_on_types_and_fields():
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
from graphql.core.language.location import SourceLocation
2+
from graphql.core.validation.rules import ProvidedNonNullArguments
3+
from utils import expect_passes_rule, expect_fails_rule
4+
5+
6+
def missing_field_arg(field_name, arg_name, type_name, line, column):
7+
return {
8+
'message': ProvidedNonNullArguments.missing_field_arg_message(field_name, arg_name, type_name),
9+
'locations': [SourceLocation(line, column)]
10+
}
11+
12+
13+
def missing_directive_arg(directive_name, arg_name, type_name, line, column):
14+
return {
15+
'message': ProvidedNonNullArguments.missing_directive_arg_message(directive_name, arg_name, type_name),
16+
'locations': [SourceLocation(line, column)]
17+
}
18+
19+
20+
def test_ignores_unknown_arguments():
21+
expect_passes_rule(ProvidedNonNullArguments, '''
22+
{
23+
dog {
24+
isHousetrained(unknownArgument: true)
25+
}
26+
}''')
27+
28+
29+
def test_no_arg_on_optional_arg():
30+
expect_passes_rule(ProvidedNonNullArguments, '''
31+
{
32+
dog {
33+
isHousetrained
34+
}
35+
}''')
36+
37+
38+
def test_multiple_args():
39+
expect_passes_rule(ProvidedNonNullArguments, '''
40+
{
41+
complicatedArgs {
42+
multipleReqs(req1: 1, req2: 2)
43+
}
44+
}
45+
''')
46+
47+
48+
def test_multiple_args_reverse_order():
49+
expect_passes_rule(ProvidedNonNullArguments, '''
50+
{
51+
complicatedArgs {
52+
multipleReqs(req2: 2, req1: 1)
53+
}
54+
}
55+
''')
56+
57+
58+
def test_no_args_on_multiple_optional():
59+
expect_passes_rule(ProvidedNonNullArguments, '''
60+
{
61+
complicatedArgs {
62+
multipleOpts
63+
}
64+
}
65+
''')
66+
67+
68+
def test_one_arg_on_multiple_optional():
69+
expect_passes_rule(ProvidedNonNullArguments, '''
70+
{
71+
complicatedArgs {
72+
multipleOpts(opt1: 1)
73+
}
74+
}
75+
''')
76+
77+
78+
def test_second_arg_on_multiple_optional():
79+
expect_passes_rule(ProvidedNonNullArguments, '''
80+
{
81+
complicatedArgs {
82+
multipleOpts(opt2: 1)
83+
}
84+
}
85+
''')
86+
87+
88+
def test_multiple_reqs_on_mixed_list():
89+
expect_passes_rule(ProvidedNonNullArguments, '''
90+
{
91+
complicatedArgs {
92+
multipleOptAndReq(req1: 3, req2: 4)
93+
}
94+
}
95+
''')
96+
97+
98+
def test_multiple_reqs_and_one_opt_on_mixed_list():
99+
expect_passes_rule(ProvidedNonNullArguments, '''
100+
{
101+
complicatedArgs {
102+
multipleOptAndReq(req1: 3, req2: 4, opt1: 5)
103+
}
104+
}
105+
''')
106+
107+
108+
def test_all_reqs_and_opts_on_mixed_list():
109+
expect_passes_rule(ProvidedNonNullArguments, '''
110+
{
111+
complicatedArgs {
112+
multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6)
113+
}
114+
}
115+
''')
116+
117+
118+
def test_missing_one_non_nullable_argument():
119+
expect_fails_rule(ProvidedNonNullArguments, '''
120+
{
121+
complicatedArgs {
122+
multipleReqs(req2: 2)
123+
}
124+
}
125+
''', [
126+
missing_field_arg('multipleReqs', 'req1', 'Int!', 4, 13)
127+
])
128+
129+
130+
def test_missing_multiple_non_nullable_arguments():
131+
expect_fails_rule(ProvidedNonNullArguments, '''
132+
{
133+
complicatedArgs {
134+
multipleReqs
135+
}
136+
}
137+
''', [
138+
missing_field_arg('multipleReqs', 'req1', 'Int!', 4, 13),
139+
missing_field_arg('multipleReqs', 'req2', 'Int!', 4, 13)
140+
])
141+
142+
143+
def test_incorrect_value_and_missing_argument():
144+
expect_fails_rule(ProvidedNonNullArguments, '''
145+
{
146+
complicatedArgs {
147+
multipleReqs(req1: "one")
148+
}
149+
}
150+
''', [
151+
missing_field_arg('multipleReqs', 'req2', 'Int!', 4, 13)
152+
])
153+
154+
155+
def test_ignore_unknown_directives():
156+
expect_passes_rule(ProvidedNonNullArguments, '''
157+
{
158+
dog @unknown
159+
}
160+
''')
161+
162+
163+
def test_with_directives_of_valid_type():
164+
expect_passes_rule(ProvidedNonNullArguments, '''
165+
{
166+
dog @include(if: true) {
167+
name
168+
}
169+
human @skip(if: false) {
170+
name
171+
}
172+
}
173+
''')
174+
175+
176+
def test_with_directive_with_missing_types():
177+
expect_fails_rule(ProvidedNonNullArguments, '''
178+
{
179+
dog @include {
180+
name @skip
181+
}
182+
}
183+
''', [
184+
missing_directive_arg('include', 'if', 'Boolean!', 3, 13),
185+
missing_directive_arg('skip', 'if', 'Boolean!', 4, 18),
186+
])

tests/core_validation/utils.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
GraphQLField,
77
GraphQLArgument,
88
GraphQLID,
9+
GraphQLNonNull,
910
GraphQLString,
11+
GraphQLInt,
12+
GraphQLFloat,
1013
GraphQLBoolean,
1114
GraphQLInterfaceType,
1215
GraphQLEnumType,
@@ -64,8 +67,46 @@
6467
})
6568

6669
ComplicatedArgs = GraphQLObjectType('ComplicatedArgs', {
70+
'intArgField': GraphQLField(GraphQLString, {
71+
'intArg': GraphQLArgument(GraphQLInt)
72+
}),
73+
'nonNullIntArgField': GraphQLField(GraphQLString, {
74+
'nonNullIntArg': GraphQLArgument(GraphQLNonNull(GraphQLInt))
75+
}),
76+
'stringArgField': GraphQLField(GraphQLString, {
77+
'stringArg': GraphQLArgument(GraphQLString)
78+
}),
79+
'booleanArgField': GraphQLField(GraphQLString, {
80+
'booleanArg': GraphQLArgument(GraphQLBoolean)
81+
}),
82+
'enumArgField': GraphQLField(GraphQLString, {
83+
'enumArg': GraphQLArgument(FurColor)
84+
}),
85+
'floatArgField': GraphQLField(GraphQLString, {
86+
'floatArg': GraphQLArgument(GraphQLFloat)
87+
}),
88+
'idArgField': GraphQLField(GraphQLString, {
89+
'idArg': GraphQLArgument(GraphQLID)
90+
}),
91+
'stringListArgField': GraphQLField(GraphQLString, {
92+
'stringListArg': GraphQLArgument(GraphQLList(GraphQLString))
93+
}),
6794
'complexArgField': GraphQLField(GraphQLString, {
68-
'complexArg': GraphQLArgument(ComplexInput),
95+
'complexArg': GraphQLArgument(ComplexInput)
96+
}),
97+
'multipleReqs': GraphQLField(GraphQLString, {
98+
'req1': GraphQLArgument(GraphQLNonNull(GraphQLInt)),
99+
'req2': GraphQLArgument(GraphQLNonNull(GraphQLInt)),
100+
}),
101+
'multipleOpts': GraphQLField(GraphQLString, {
102+
'opt1': GraphQLArgument(GraphQLInt, 0),
103+
'opt2': GraphQLArgument(GraphQLInt, 0)
104+
}),
105+
'multipleOptsAndReq': GraphQLField(GraphQLString, {
106+
'req1': GraphQLArgument(GraphQLNonNull(GraphQLInt)),
107+
'req2': GraphQLArgument(GraphQLNonNull(GraphQLInt)),
108+
'opt1': GraphQLArgument(GraphQLInt, 0),
109+
'opt2': GraphQLArgument(GraphQLInt, 0)
69110
})
70111
})
71112

0 commit comments

Comments
 (0)