Skip to content

Commit d7b4747

Browse files
committed
add depth limit validator tests
1 parent a784ef1 commit d7b4747

File tree

4 files changed

+297
-12
lines changed

4 files changed

+297
-12
lines changed

docs/execution/validators.rst

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,26 @@ Example
2020
Here is how you would implement depth-limiting on your schema.
2121

2222
.. code:: python
23+
from graphql import validate
24+
from graphene import ObjectType, Schema, String
2325
from graphene.validation import depth_limit_validator
2426
25-
# The following schema doesn't execute queries
26-
# which have a depth more than 20.
2727
28-
result = schema.execute(
29-
'THE QUERY',
30-
validation_rules=[
28+
class MyQuery(ObjectType):
29+
name = String(required=True)
30+
31+
32+
schema = Schema(query=MyQuery)
33+
34+
# Queries which have a depth more than 20
35+
# will not be executed.
36+
37+
validation_errors = validate(
38+
schema=schema,
39+
document='THE QUERY',
40+
rules=(
3141
depth_limit_validator(
3242
max_depth=20
33-
)
34-
]
43+
),
44+
)
3545
)

graphene/utils/is_introspection_key.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ def is_introspection_key(key):
33
# > All types and directives defined within a schema must not have a name which
44
# > begins with "__" (two underscores), as this is used exclusively
55
# > by GraphQL’s introspection system.
6-
return str(node.name.value).startswith("__")
6+
return str(key).startswith("__")
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
import re
2+
3+
from pytest import raises
4+
from graphql import parse, get_introspection_query, validate
5+
6+
from ...types import Schema, ObjectType, Interface
7+
from ...types import String, Int, List, Field
8+
from ..depth_limit import depth_limit_validator
9+
10+
11+
class PetType(Interface):
12+
name = String(required=True)
13+
14+
class meta:
15+
name = "Pet"
16+
17+
18+
class CatType(ObjectType):
19+
class meta:
20+
name = "Cat"
21+
interfaces = (PetType,)
22+
23+
24+
class DogType(ObjectType):
25+
class meta:
26+
name = "Dog"
27+
interfaces = (PetType,)
28+
29+
30+
class AddressType(ObjectType):
31+
street = String(required=True)
32+
number = Int(required=True)
33+
city = String(required=True)
34+
country = String(required=True)
35+
36+
class Meta:
37+
name = "Address"
38+
39+
40+
class HumanType(ObjectType):
41+
name = String(required=True)
42+
email = String(required=True)
43+
address = Field(AddressType, required=True)
44+
pets = List(PetType, required=True)
45+
46+
class Meta:
47+
name = "Human"
48+
49+
50+
class Query(ObjectType):
51+
user = Field(
52+
HumanType,
53+
required=True,
54+
name=String()
55+
)
56+
version = String(
57+
required=True
58+
)
59+
user1 = Field(
60+
HumanType,
61+
required=True
62+
)
63+
user2 = Field(
64+
HumanType,
65+
required=True
66+
)
67+
user3 = Field(
68+
HumanType,
69+
required=True
70+
)
71+
72+
@staticmethod
73+
def resolve_user(root, info, name=None):
74+
pass
75+
76+
77+
schema = Schema(query=Query)
78+
79+
80+
def run_query(query: str, max_depth: int, ignore=None):
81+
document = parse(query)
82+
83+
result = None
84+
85+
def callback(query_depths):
86+
nonlocal result
87+
result = query_depths
88+
89+
errors = validate(
90+
schema.graphql_schema,
91+
document,
92+
rules=(
93+
depth_limit_validator(
94+
max_depth=max_depth,
95+
ignore=ignore,
96+
callback=callback
97+
),
98+
),
99+
)
100+
101+
return errors, result
102+
103+
104+
def test_should_count_depth_without_fragment():
105+
query = """
106+
query read0 {
107+
version
108+
}
109+
query read1 {
110+
version
111+
user {
112+
name
113+
}
114+
}
115+
query read2 {
116+
matt: user(name: "matt") {
117+
email
118+
}
119+
andy: user(name: "andy") {
120+
email
121+
address {
122+
city
123+
}
124+
}
125+
}
126+
query read3 {
127+
matt: user(name: "matt") {
128+
email
129+
}
130+
andy: user(name: "andy") {
131+
email
132+
address {
133+
city
134+
}
135+
pets {
136+
name
137+
owner {
138+
name
139+
}
140+
}
141+
}
142+
}
143+
"""
144+
145+
expected = {"read0": 0, "read1": 1, "read2": 2, "read3": 3}
146+
147+
errors, result = run_query(query, 10)
148+
assert not errors
149+
assert result == expected
150+
151+
152+
def test_should_count_with_fragments():
153+
query = """
154+
query read0 {
155+
... on Query {
156+
version
157+
}
158+
}
159+
query read1 {
160+
version
161+
user {
162+
... on Human {
163+
name
164+
}
165+
}
166+
}
167+
fragment humanInfo on Human {
168+
email
169+
}
170+
fragment petInfo on Pet {
171+
name
172+
owner {
173+
name
174+
}
175+
}
176+
query read2 {
177+
matt: user(name: "matt") {
178+
...humanInfo
179+
}
180+
andy: user(name: "andy") {
181+
...humanInfo
182+
address {
183+
city
184+
}
185+
}
186+
}
187+
query read3 {
188+
matt: user(name: "matt") {
189+
...humanInfo
190+
}
191+
andy: user(name: "andy") {
192+
... on Human {
193+
email
194+
}
195+
address {
196+
city
197+
}
198+
pets {
199+
...petInfo
200+
}
201+
}
202+
}
203+
"""
204+
205+
expected = {"read0": 0, "read1": 1, "read2": 2, "read3": 3}
206+
207+
errors, result = run_query(query, 10)
208+
assert not errors
209+
assert result == expected
210+
211+
212+
def test_should_ignore_the_introspection_query():
213+
errors, result = run_query(get_introspection_query(), 10)
214+
assert not errors
215+
assert result == {"IntrospectionQuery": 0}
216+
217+
218+
def test_should_catch_very_deep_query():
219+
query = """{
220+
user {
221+
pets {
222+
owner {
223+
pets {
224+
owner {
225+
pets {
226+
name
227+
}
228+
}
229+
}
230+
}
231+
}
232+
}
233+
}
234+
"""
235+
errors, result = run_query(query, 4)
236+
237+
assert len(errors) == 1
238+
assert errors[0].message == "'anonymous' exceeds maximum operation depth of 4"
239+
240+
241+
def test_should_ignore_field():
242+
query = """
243+
query read1 {
244+
user { address { city } }
245+
}
246+
query read2 {
247+
user1 { address { city } }
248+
user2 { address { city } }
249+
user3 { address { city } }
250+
}
251+
"""
252+
253+
errors, result = run_query(
254+
query,
255+
10,
256+
ignore=[
257+
"user1",
258+
re.compile("user2"),
259+
lambda field_name: field_name == "user3",
260+
],
261+
)
262+
263+
expected = {"read1": 2, "read2": 0}
264+
assert not errors
265+
assert result == expected
266+
267+
268+
def test_should_raise_invalid_ignore():
269+
query = """
270+
query read1 {
271+
user { address { city } }
272+
}
273+
"""
274+
with raises(ValueError, match="Invalid ignore option:"):
275+
run_query(
276+
query,
277+
10,
278+
ignore=[True],
279+
)

graphene/validation/tests/test_disable_introspection.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ def run_query(query: str):
1818

1919
result = None
2020

21-
def callback(query_depths):
22-
nonlocal result
23-
result = query_depths
24-
2521
errors = validate(
2622
schema.graphql_schema,
2723
document,

0 commit comments

Comments
 (0)