Skip to content

Commit a4b38e2

Browse files
authored
Fix tests to use proper ast node (#254)
- Update test_visitor.py to properly type-annotate the visitor class attribute and add assertion before using selection_set - Update test_schema_parser.py to use more precise types that match GraphQL spec: - NonNullTypeNode's inner type can only be NamedTypeNode or ListTypeNode - Schema definitions use ConstDirectiveNode, not DirectiveNode - Default values use ConstValueNode, not ValueNode - OperationTypeDefinition's type_ must be NamedTypeNode
1 parent 9c3735e commit a4b38e2

File tree

7 files changed

+140
-76
lines changed

7 files changed

+140
-76
lines changed

tests/error/test_graphql_error.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from graphql.error import GraphQLError
66
from graphql.language import (
7-
Node,
7+
NameNode,
88
ObjectTypeDefinitionNode,
99
OperationDefinitionNode,
1010
Source,
@@ -352,7 +352,7 @@ def formats_graphql_error():
352352
extensions = {"ext": None}
353353
error = GraphQLError(
354354
"test message",
355-
Node(),
355+
NameNode(value="stub"),
356356
Source(
357357
"""
358358
query {

tests/language/test_ast.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,22 @@
1010
class SampleTestNode(Node):
1111
__slots__ = "alpha", "beta"
1212

13-
alpha: int
14-
beta: int
13+
alpha: int | Node # Union with Node to support copy tests with nested nodes
14+
beta: int | Node | None
1515

1616

1717
class SampleNamedNode(Node):
1818
__slots__ = "foo", "name"
1919

2020
foo: str
21-
name: str | None
21+
name: NameNode | None
22+
23+
24+
def make_loc(start: int = 1, end: int = 3) -> Location:
25+
"""Create a Location for testing with the given start/end offsets."""
26+
source = Source("test source")
27+
start_token = Token(TokenKind.NAME, start, end, 1, start, "test")
28+
return Location(start_token, start_token, source)
2229

2330

2431
def describe_token_class():
@@ -150,15 +157,21 @@ def can_hash():
150157

151158
def describe_node_class():
152159
def initializes_with_keywords():
153-
node = SampleTestNode(alpha=1, beta=2, loc=0)
160+
node = SampleTestNode(alpha=1, beta=2)
154161
assert node.alpha == 1
155162
assert node.beta == 2
156-
assert node.loc == 0
157-
node = SampleTestNode(alpha=1, loc=None)
158163
assert node.loc is None
164+
165+
def initializes_with_location():
166+
loc = make_loc()
167+
node = SampleTestNode(alpha=1, beta=2, loc=loc)
159168
assert node.alpha == 1
160-
assert node.beta is None
161-
node = SampleTestNode(alpha=1, beta=2, gamma=3)
169+
assert node.beta == 2
170+
assert node.loc is loc
171+
172+
def initializes_with_none_location():
173+
node = SampleTestNode(alpha=1, beta=2, loc=None)
174+
assert node.loc is None
162175
assert node.alpha == 1
163176
assert node.beta == 2
164177
assert not hasattr(node, "gamma")
@@ -174,27 +187,31 @@ def converts_list_to_tuple_on_init():
174187
def has_representation_with_loc():
175188
node = SampleTestNode(alpha=1, beta=2)
176189
assert repr(node) == "SampleTestNode"
177-
node = SampleTestNode(alpha=1, beta=2, loc=3)
178-
assert repr(node) == "SampleTestNode at 3"
190+
loc = make_loc(start=3, end=5)
191+
node = SampleTestNode(alpha=1, beta=2, loc=loc)
192+
assert repr(node) == "SampleTestNode at 3:5"
179193

180194
def has_representation_when_named():
181195
name_node = NameNode(value="baz")
182196
node = SampleNamedNode(foo="bar", name=name_node)
183197
assert repr(node) == "SampleNamedNode(name='baz')"
184-
node = SampleNamedNode(alpha=1, beta=2, name=name_node, loc=3)
185-
assert repr(node) == "SampleNamedNode(name='baz') at 3"
198+
loc = make_loc(start=3, end=5)
199+
node = SampleNamedNode(foo="bar", name=name_node, loc=loc)
200+
assert repr(node) == "SampleNamedNode(name='baz') at 3:5"
186201

187202
def has_representation_when_named_but_name_is_none():
188-
node = SampleNamedNode(alpha=1, beta=2, name=None)
203+
node = SampleNamedNode(foo="bar", name=None)
189204
assert repr(node) == "SampleNamedNode"
190-
node = SampleNamedNode(alpha=1, beta=2, name=None, loc=3)
191-
assert repr(node) == "SampleNamedNode at 3"
205+
loc = make_loc(start=3, end=5)
206+
node = SampleNamedNode(foo="bar", name=None, loc=loc)
207+
assert repr(node) == "SampleNamedNode at 3:5"
192208

193209
def has_special_representation_when_it_is_a_name_node():
194210
node = NameNode(value="foo")
195211
assert repr(node) == "NameNode('foo')"
196-
node = NameNode(value="foo", loc=3)
197-
assert repr(node) == "NameNode('foo') at 3"
212+
loc = make_loc(start=3, end=5)
213+
node = NameNode(value="foo", loc=loc)
214+
assert repr(node) == "NameNode('foo') at 3:5"
198215

199216
def can_check_equality():
200217
node = SampleTestNode(alpha=1, beta=2)

tests/language/test_schema_parser.py

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
import pickle
44
from copy import deepcopy
55
from textwrap import dedent
6-
from typing import Optional, Tuple
76

87
import pytest
98

109
from graphql.error import GraphQLSyntaxError
1110
from graphql.language import (
1211
ArgumentNode,
1312
BooleanValueNode,
13+
ConstDirectiveNode,
14+
ConstValueNode,
1415
DirectiveDefinitionNode,
1516
DirectiveNode,
1617
DocumentNode,
@@ -22,6 +23,7 @@
2223
InterfaceTypeDefinitionNode,
2324
InterfaceTypeExtensionNode,
2425
ListTypeNode,
26+
Location,
2527
NamedTypeNode,
2628
NameNode,
2729
NonNullTypeNode,
@@ -32,25 +34,30 @@
3234
ScalarTypeDefinitionNode,
3335
SchemaDefinitionNode,
3436
SchemaExtensionNode,
37+
Source,
3538
StringValueNode,
39+
Token,
40+
TokenKind,
3641
TypeNode,
3742
UnionTypeDefinitionNode,
38-
ValueNode,
3943
parse,
4044
)
4145

4246
from ..fixtures import kitchen_sink_sdl # noqa: F401
4347

44-
try:
45-
from typing import TypeAlias
46-
except ImportError: # Python < 3.10
47-
from typing_extensions import TypeAlias
4848

49-
50-
Location: TypeAlias = Optional[Tuple[int, int]]
49+
def make_loc(position: tuple[int, int]) -> Location:
50+
"""Create a Location for testing with the given (start, end) offsets."""
51+
source = Source(body="")
52+
token = Token(
53+
kind=TokenKind.NAME, start=position[0], end=position[1], line=1, column=1
54+
)
55+
return Location(start_token=token, end_token=token, source=source)
5156

5257

53-
def assert_syntax_error(text: str, message: str, location: Location) -> None:
58+
def assert_syntax_error(
59+
text: str, message: str, location: tuple[int, int] | None
60+
) -> None:
5461
with pytest.raises(GraphQLSyntaxError) as exc_info:
5562
parse(text)
5663
error = exc_info.value
@@ -59,85 +66,104 @@ def assert_syntax_error(text: str, message: str, location: Location) -> None:
5966
assert error.locations == [location]
6067

6168

62-
def assert_definitions(body: str, loc: Location, num=1):
69+
def assert_definitions(body: str, position: tuple[int, int] | None, num: int = 1):
6370
doc = parse(body)
6471
assert isinstance(doc, DocumentNode)
65-
assert doc.loc == loc
72+
assert doc.loc == position
6673
definitions = doc.definitions
6774
assert isinstance(definitions, tuple)
6875
assert len(definitions) == num
6976
return definitions[0] if num == 1 else definitions
7077

7178

72-
def type_node(name: str, loc: Location):
73-
return NamedTypeNode(name=name_node(name, loc), loc=loc)
79+
def type_node(name: str, position: tuple[int, int]):
80+
return NamedTypeNode(name=name_node(name, position), loc=make_loc(position))
7481

7582

76-
def name_node(name: str, loc: Location):
77-
return NameNode(value=name, loc=loc)
83+
def name_node(name: str, position: tuple[int, int]):
84+
return NameNode(value=name, loc=make_loc(position))
7885

7986

80-
def field_node(name: NameNode, type_: TypeNode, loc: Location):
81-
return field_node_with_args(name, type_, (), loc)
87+
def field_node(name: NameNode, type_: TypeNode, position: tuple[int, int]):
88+
return field_node_with_args(name, type_, (), position)
8289

8390

84-
def field_node_with_args(name: NameNode, type_: TypeNode, args: tuple, loc: Location):
91+
def field_node_with_args(
92+
name: NameNode, type_: TypeNode, args: tuple, position: tuple[int, int]
93+
):
8594
return FieldDefinitionNode(
86-
name=name, arguments=args, type=type_, directives=(), loc=loc, description=None
95+
name=name,
96+
arguments=args,
97+
type=type_,
98+
directives=(),
99+
loc=make_loc(position),
100+
description=None,
87101
)
88102

89103

90-
def non_null_type(type_: TypeNode, loc: Location):
91-
return NonNullTypeNode(type=type_, loc=loc)
104+
def non_null_type(type_: NamedTypeNode | ListTypeNode, position: tuple[int, int]):
105+
return NonNullTypeNode(type=type_, loc=make_loc(position))
92106

93107

94-
def enum_value_node(name: str, loc: Location):
108+
def enum_value_node(name: str, position: tuple[int, int]):
95109
return EnumValueDefinitionNode(
96-
name=name_node(name, loc), directives=(), loc=loc, description=None
110+
name=name_node(name, position),
111+
directives=(),
112+
loc=make_loc(position),
113+
description=None,
97114
)
98115

99116

100117
def input_value_node(
101-
name: NameNode, type_: TypeNode, default_value: ValueNode | None, loc: Location
118+
name: NameNode,
119+
type_: TypeNode,
120+
default_value: ConstValueNode | None,
121+
position: tuple[int, int],
102122
):
103123
return InputValueDefinitionNode(
104124
name=name,
105125
type=type_,
106126
default_value=default_value,
107127
directives=(),
108-
loc=loc,
128+
loc=make_loc(position),
109129
description=None,
110130
)
111131

112132

113-
def boolean_value_node(value: bool, loc: Location):
114-
return BooleanValueNode(value=value, loc=loc)
133+
def boolean_value_node(value: bool, position: tuple[int, int]):
134+
return BooleanValueNode(value=value, loc=make_loc(position))
115135

116136

117-
def string_value_node(value: str, block: bool | None, loc: Location):
118-
return StringValueNode(value=value, block=block, loc=loc)
137+
def string_value_node(value: str, block: bool | None, position: tuple[int, int]):
138+
return StringValueNode(value=value, block=block, loc=make_loc(position))
119139

120140

121-
def list_type_node(type_: TypeNode, loc: Location):
122-
return ListTypeNode(type=type_, loc=loc)
141+
def list_type_node(type_: TypeNode, position: tuple[int, int]):
142+
return ListTypeNode(type=type_, loc=make_loc(position))
123143

124144

125145
def schema_extension_node(
126-
directives: tuple[DirectiveNode, ...],
146+
directives: tuple[ConstDirectiveNode, ...],
127147
operation_types: tuple[OperationTypeDefinitionNode, ...],
128-
loc: Location,
148+
position: tuple[int, int],
129149
):
130150
return SchemaExtensionNode(
131-
directives=directives, operation_types=operation_types, loc=loc
151+
directives=directives, operation_types=operation_types, loc=make_loc(position)
132152
)
133153

134154

135-
def operation_type_definition(operation: OperationType, type_: TypeNode, loc: Location):
136-
return OperationTypeDefinitionNode(operation=operation, type=type_, loc=loc)
155+
def operation_type_definition(
156+
operation: OperationType, type_: NamedTypeNode, position: tuple[int, int]
157+
):
158+
return OperationTypeDefinitionNode(
159+
operation=operation, type=type_, loc=make_loc(position)
160+
)
137161

138162

139-
def directive_node(name: NameNode, arguments: tuple[ArgumentNode, ...], loc: Location):
140-
return DirectiveNode(name=name, arguments=arguments, loc=loc)
163+
def directive_node(
164+
name: NameNode, arguments: tuple[ArgumentNode, ...], position: tuple[int, int]
165+
):
166+
return DirectiveNode(name=name, arguments=arguments, loc=make_loc(position))
141167

142168

143169
def describe_schema_parser():

tests/language/test_visitor.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ def allows_editing_a_node_both_on_enter_and_on_leave():
308308
visited = []
309309

310310
class TestVisitor(Visitor):
311-
selection_set = None
311+
selection_set: SelectionSetNode | None = None
312312

313313
def enter_operation_definition(self, *args):
314314
check_visitor_fn_args(ast, *args)
@@ -330,6 +330,7 @@ def leave_operation_definition(self, *args):
330330
check_visitor_fn_args_edited(ast, *args)
331331
node = args[0]
332332
assert not node.selection_set.selections
333+
assert self.selection_set is not None
333334
# Create new node with original selection set (immutable pattern)
334335
new_node = OperationDefinitionNode(
335336
operation=node.operation,

0 commit comments

Comments
 (0)