Skip to content

Commit 289d79a

Browse files
committed
fix: Duplicated inline fragments that may miss aliases
Ref: #69
1 parent 20cddbb commit 289d79a

File tree

4 files changed

+152
-105
lines changed

4 files changed

+152
-105
lines changed

docs/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Changelog
44
`Unreleased`_ - TBD
55
-------------------
66

7+
**Fixed**
8+
9+
- Duplicated inline fragments that may miss aliases. `#69`_
10+
711
`0.7.1`_ - 2022-04-27
812
---------------------
913

@@ -161,6 +165,7 @@ Invalid queries:
161165
.. _0.3.1: https://github.com/stranger6667/hypothesis-graphql/compare/v0.3.0...v0.3.1
162166
.. _0.3.0: https://github.com/stranger6667/hypothesis-graphql/compare/v0.2.0...v0.3.0
163167

168+
.. _#69: https://github.com/Stranger6667/hypothesis-graphql/69
164169
.. _#57: https://github.com/Stranger6667/hypothesis-graphql/57
165170
.. _#51: https://github.com/Stranger6667/hypothesis-graphql/51
166171
.. _#49: https://github.com/Stranger6667/hypothesis-graphql/49

src/hypothesis_graphql/_strategies/strategy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .validation import maybe_parse_schema, validate_custom_scalars, validate_fields
1919

2020
BY_NAME = operator.attrgetter("name")
21+
EMPTY_LISTS_STRATEGY = st.builds(list)
2122

2223

2324
def instance_cache(key_func: Callable) -> Callable:
@@ -131,7 +132,7 @@ def inline_fragments(self, items: List[graphql.GraphQLObjectType]) -> st.SearchS
131132
# then the resulting query should not have these fields simultaneously
132133
strategies, overlapping_fields = self.collect_fragment_strategies(items)
133134
if overlapping_fields:
134-
return compose_interfaces_with_filter(st.just([]), strategies, self.schema.type_map)
135+
return compose_interfaces_with_filter(EMPTY_LISTS_STRATEGY, strategies, self.schema.type_map)
135136
# No overlapping - safe to choose any subset of fields within the interface itself and any fragment
136137
return st.tuples(*(self.inline_fragment(type_) for type_ in items)).map(list)
137138

test/test_corpus.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22

33
import pytest
4-
from hypothesis import HealthCheck, Phase, Verbosity, given, seed, settings
4+
from hypothesis import HealthCheck, Phase, Verbosity, given, settings
55
from hypothesis import strategies as st
66

77
from hypothesis_graphql import strategies as gql_st

test/test_queries.py

Lines changed: 144 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -205,115 +205,156 @@ def test_no_surrogates(data, validate_operation):
205205
value.encode("utf8")
206206

207207

208+
ALIASES_INTERFACE_TWO_TYPES = """interface Conflict {
209+
id: ID
210+
}
211+
212+
type FloatModel implements Conflict {
213+
id: ID,
214+
query: Float!
215+
}
216+
217+
type StringModel implements Conflict {
218+
id: ID,
219+
query: String!
220+
}
221+
222+
type Query {
223+
getData: Conflict
224+
}"""
225+
ALIASES_MULTIPLE_INTERFACE_OVERLAP = """interface Nullable {
226+
value: Float
227+
}
228+
interface Another {
229+
non_value: Float
230+
}
231+
interface NotNullable {
232+
value: Float!
233+
}
234+
235+
type First implements Nullable {
236+
value: Float
237+
}
238+
239+
type Second implements NotNullable & Another {
240+
value: Float!
241+
non_value: Float
242+
}
243+
244+
union FirstOrSecond = First | Second
245+
246+
type Query {
247+
getData: FirstOrSecond!
248+
}"""
249+
ALIASES_INTERFACE_THREE_TYPES = """interface Conflict {
250+
id: ID!
251+
}
252+
253+
type First implements Conflict {
254+
id: ID!
255+
key: String
256+
}
257+
258+
type Second implements Conflict {
259+
id: ID!
260+
key: String
261+
}
262+
263+
type Third implements Conflict {
264+
id: ID!
265+
key: [String]
266+
}
267+
268+
type Query {
269+
getData: Conflict
270+
}"""
271+
ALIASES_INTERFACE_NESTED_TYPE = """interface Conflict {
272+
keywords: [Keyword!]!
273+
}
274+
275+
type First implements Conflict {
276+
keywords: [Keyword!]!
277+
}
278+
279+
type Keyword {
280+
values(first: Int): String!
281+
}
282+
283+
type Query {
284+
getData(input: Int!): Conflict
285+
}"""
286+
ALIASES_UNION_RETURN_TYPE = """type FloatModel {
287+
query: Float!
288+
}
289+
type StringModel {
290+
query: String!
291+
}
292+
293+
union Conflict = FloatModel | StringModel
294+
295+
type Query {
296+
getData: Conflict
297+
}"""
298+
ALIASES_ARGUMENT_ENUM = """interface Conflict {
299+
query(arg: Arg): String!
300+
}
301+
302+
type FirstModel implements Conflict {
303+
query(arg: Arg): String!
304+
}
305+
306+
type SecondModel implements Conflict {
307+
query(arg: Arg): String!
308+
}
309+
310+
enum Arg {
311+
First
312+
Second
313+
}
314+
315+
type Query {
316+
getConflict(arg: String!): Conflict!
317+
}"""
318+
ALIASES_ARGUMENT_STRING = """interface Conflict {
319+
query(arg: String): String!
320+
}
321+
322+
type FirstModel implements Conflict {
323+
query(arg: String): String!
324+
}
325+
326+
type SecondModel implements Conflict {
327+
query(arg: String): String!
328+
}
329+
330+
type Query {
331+
getConflict(arg: String!): Conflict!
332+
}"""
333+
334+
208335
@pytest.mark.parametrize(
209336
"schema",
210337
(
211-
"""interface Conflict {
212-
id: ID
213-
}
214-
215-
type FloatModel implements Conflict {
216-
id: ID,
217-
query: Float!
218-
}
219-
220-
type StringModel implements Conflict {
221-
id: ID,
222-
query: String!
223-
}
224-
225-
type Query {
226-
getData: Conflict
227-
}""",
228-
"""interface Conflict {
229-
id: ID!
230-
}
231-
232-
type First implements Conflict {
233-
id: ID!
234-
key: String
235-
}
236-
237-
type Second implements Conflict {
238-
id: ID!
239-
key: String
240-
}
241-
242-
type Third implements Conflict {
243-
id: ID!
244-
key: [String]
245-
}
246-
247-
type Query {
248-
getData: Conflict
249-
}""",
250-
"""interface Conflict {
251-
keywords: [Keyword!]!
252-
}
253-
254-
type First implements Conflict {
255-
keywords: [Keyword!]!
256-
}
257-
258-
type Keyword {
259-
values(first: Int): String!
260-
}
261-
262-
type Query {
263-
getData(input: Int!): Conflict
264-
}""",
265-
"""type FloatModel {
266-
query: Float!
267-
}
268-
type StringModel {
269-
query: String!
270-
}
271-
272-
union Conflict = FloatModel | StringModel
273-
274-
type Query {
275-
getData: Conflict
276-
}""",
277-
"""interface Conflict {
278-
query(arg: Arg): String!
279-
}
280-
281-
type FirstModel implements Conflict {
282-
query(arg: Arg): String!
283-
}
284-
285-
type SecondModel implements Conflict {
286-
query(arg: Arg): String!
287-
}
288-
289-
enum Arg {
290-
First
291-
Second
292-
}
293-
294-
type Query {
295-
getConflict(arg: String!): Conflict!
296-
}""",
297-
"""interface Conflict {
298-
query(arg: String): String!
299-
}
300-
301-
type FirstModel implements Conflict {
302-
query(arg: String): String!
303-
}
304-
305-
type SecondModel implements Conflict {
306-
query(arg: String): String!
307-
}
308-
309-
type Query {
310-
getConflict(arg: String!): Conflict!
311-
}""",
338+
ALIASES_INTERFACE_TWO_TYPES,
339+
ALIASES_INTERFACE_THREE_TYPES,
340+
ALIASES_INTERFACE_NESTED_TYPE,
341+
ALIASES_MULTIPLE_INTERFACE_OVERLAP,
342+
ALIASES_UNION_RETURN_TYPE,
343+
ALIASES_ARGUMENT_ENUM,
344+
ALIASES_ARGUMENT_STRING,
345+
),
346+
ids=(
347+
"interface-two-types",
348+
"interface-three-types",
349+
"interface-nested-type",
350+
"non-interface",
351+
"union",
352+
"argument-enum",
353+
"argument-string",
312354
),
313-
ids=("interface", "interface-multiple-types", "interface-sub-type", "union", "arguments-enum", "arguments-string"),
314355
)
315356
@given(data=st.data())
316-
def test_conflicting_field_types(data, validate_operation, schema):
357+
def test_aliases(data, validate_operation, schema):
317358
# See GH-49, GH-57
318359
# When Query contain types on the same level that have fields with the same name but with different types
319360
query = data.draw(gql_st.queries(schema))

0 commit comments

Comments
 (0)