1package graphql_test
2
3import (
4	"testing"
5
6	"github.com/graphql-go/graphql"
7	"github.com/graphql-go/graphql/gqlerrors"
8	"github.com/graphql-go/graphql/testutil"
9)
10
11func TestValidate_OverlappingFieldsCanBeMerged_UniqueFields(t *testing.T) {
12	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
13      fragment uniqueFields on Dog {
14        name
15        nickname
16      }
17    `)
18}
19func TestValidate_OverlappingFieldsCanBeMerged_IdenticalFields(t *testing.T) {
20	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
21      fragment mergeIdenticalFields on Dog {
22        name
23        name
24      }
25    `)
26}
27func TestValidate_OverlappingFieldsCanBeMerged_IdenticalFieldsWithIdenticalArgs(t *testing.T) {
28	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
29      fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
30        doesKnowCommand(dogCommand: SIT)
31        doesKnowCommand(dogCommand: SIT)
32      }
33    `)
34}
35func TestValidate_OverlappingFieldsCanBeMerged_IdenticalFieldsWithMultipleIdenticalArgs(t *testing.T) {
36	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
37      fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
38        doesKnowCommand(dogCommand: SIT nextDogCommand: DOWN)
39        doesKnowCommand(dogCommand: SIT nextDogCommand: DOWN)
40      }
41    `)
42}
43func TestValidate_OverlappingFieldsCanBeMerged_IdenticalFieldsWithIdenticalDirectives(t *testing.T) {
44	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
45      fragment mergeSameFieldsWithSameDirectives on Dog {
46        name @include(if: true)
47        name @include(if: true)
48      }
49    `)
50}
51func TestValidate_OverlappingFieldsCanBeMerged_DifferentArgsWithDifferentAliases(t *testing.T) {
52	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
53      fragment differentArgsWithDifferentAliases on Dog {
54        knowsSit: doesKnowCommand(dogCommand: SIT)
55        knowsDown: doesKnowCommand(dogCommand: DOWN)
56      }
57    `)
58}
59func TestValidate_OverlappingFieldsCanBeMerged_DifferentDirectivesWithDifferentAliases(t *testing.T) {
60	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
61      fragment differentDirectivesWithDifferentAliases on Dog {
62        nameIfTrue: name @include(if: true)
63        nameIfFalse: name @include(if: false)
64      }
65    `)
66}
67func TestValidate_OverlappingFieldsCanBeMerged_DifferentSkipIncludeDirectivesAccepted(t *testing.T) {
68	// Note: Differing skip/include directives don't create an ambiguous return
69	// value and are acceptable in conditions where differing runtime values
70	// may have the same desired effect of including or skipping a field.
71	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
72      fragment differentDirectivesWithDifferentAliases on Dog {
73        name @include(if: true)
74        name @include(if: false)
75      }
76    `)
77}
78func TestValidate_OverlappingFieldsCanBeMerged_SameAliasesWithDifferentFieldTargets(t *testing.T) {
79	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
80      fragment sameAliasesWithDifferentFieldTargets on Dog {
81        fido: name
82        fido: nickname
83      }
84    `, []gqlerrors.FormattedError{
85		testutil.RuleError(`Fields "fido" conflict because name and nickname are different fields. `+
86			`Use different aliases on the fields to fetch both if this was intentional.`,
87			3, 9, 4, 9),
88	})
89}
90func TestValidate_OverlappingFieldsCanBeMerged_SameAliasesAllowedOnNonOverlappingFields(t *testing.T) {
91	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
92      fragment sameAliasesWithDifferentFieldTargets on Pet {
93        ... on Dog {
94          name
95        }
96        ... on Cat {
97          name: nickname
98        }
99      }
100    `)
101}
102func TestValidate_OverlappingFieldsCanBeMerged_AliasMaskingDirectFieldAccess(t *testing.T) {
103	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
104      fragment aliasMaskingDirectFieldAccess on Dog {
105        name: nickname
106        name
107      }
108    `, []gqlerrors.FormattedError{
109		testutil.RuleError(`Fields "name" conflict because nickname and name are different fields. `+
110			`Use different aliases on the fields to fetch both if this was intentional.`,
111			3, 9, 4, 9),
112	})
113}
114func TestValidate_OverlappingFieldsCanBeMerged_DifferentArgs_SecondAddsAnArgument(t *testing.T) {
115	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
116      fragment conflictingArgs on Dog {
117        doesKnowCommand
118        doesKnowCommand(dogCommand: HEEL)
119      }
120    `, []gqlerrors.FormattedError{
121		testutil.RuleError(`Fields "doesKnowCommand" conflict because they have differing arguments. `+
122			`Use different aliases on the fields to fetch both if this was intentional.`,
123			3, 9, 4, 9),
124	})
125}
126func TestValidate_OverlappingFieldsCanBeMerged_DifferentArgs_SecondMissingAnArgument(t *testing.T) {
127	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
128      fragment conflictingArgs on Dog {
129        doesKnowCommand(dogCommand: SIT)
130        doesKnowCommand
131      }
132    `, []gqlerrors.FormattedError{
133		testutil.RuleError(`Fields "doesKnowCommand" conflict because they have differing arguments. `+
134			`Use different aliases on the fields to fetch both if this was intentional.`,
135			3, 9, 4, 9),
136	})
137}
138func TestValidate_OverlappingFieldsCanBeMerged_ConflictingArgs(t *testing.T) {
139	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
140      fragment conflictingArgs on Dog {
141        doesKnowCommand(dogCommand: SIT)
142        doesKnowCommand(dogCommand: HEEL)
143      }
144    `, []gqlerrors.FormattedError{
145		testutil.RuleError(`Fields "doesKnowCommand" conflict because they have differing arguments. `+
146			`Use different aliases on the fields to fetch both if this was intentional.`,
147			3, 9, 4, 9),
148	})
149}
150func TestValidate_OverlappingFieldsCanBeMerged_AllowDifferentArgsWhereNoConflictIsPossible(t *testing.T) {
151	// This is valid since no object can be both a "Dog" and a "Cat", thus
152	// these fields can never overlap.
153	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
154      fragment conflictingArgs on Pet {
155        ... on Dog {
156          name(surname: true)
157        }
158        ... on Cat {
159          name
160        }
161      }
162    `)
163}
164func TestValidate_OverlappingFieldsCanBeMerged_EncountersConflictInFragments(t *testing.T) {
165	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
166      {
167        ...A
168        ...B
169      }
170      fragment A on Type {
171        x: a
172      }
173      fragment B on Type {
174        x: b
175      }
176    `, []gqlerrors.FormattedError{
177		testutil.RuleError(`Fields "x" conflict because a and b are different fields. `+
178			`Use different aliases on the fields to fetch both if this was intentional.`,
179			7, 9, 10, 9),
180	})
181}
182func TestValidate_OverlappingFieldsCanBeMerged_ReportsEachConflictOnce(t *testing.T) {
183	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
184      {
185        f1 {
186          ...A
187          ...B
188        }
189        f2 {
190          ...B
191          ...A
192        }
193        f3 {
194          ...A
195          ...B
196          x: c
197        }
198      }
199      fragment A on Type {
200        x: a
201      }
202      fragment B on Type {
203        x: b
204      }
205    `, []gqlerrors.FormattedError{
206		testutil.RuleError(`Fields "x" conflict because a and b are different fields. `+
207			`Use different aliases on the fields to fetch both if this was intentional.`,
208			18, 9, 21, 9),
209		testutil.RuleError(`Fields "x" conflict because c and a are different fields. `+
210			`Use different aliases on the fields to fetch both if this was intentional.`,
211			14, 11, 18, 9),
212		testutil.RuleError(`Fields "x" conflict because c and b are different fields. `+
213			`Use different aliases on the fields to fetch both if this was intentional.`,
214			14, 11, 21, 9),
215	})
216}
217func TestValidate_OverlappingFieldsCanBeMerged_DeepConflict(t *testing.T) {
218	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
219      {
220        field {
221          x: a
222        },
223        field {
224          x: b
225        }
226      }
227    `, []gqlerrors.FormattedError{
228		testutil.RuleError(`Fields "field" conflict because subfields "x" conflict because a and b are different fields. `+
229			`Use different aliases on the fields to fetch both if this was intentional.`,
230			3, 9,
231			4, 11,
232			6, 9,
233			7, 11),
234	})
235}
236func TestValidate_OverlappingFieldsCanBeMerged_DeepConflictWithMultipleIssues(t *testing.T) {
237	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
238      {
239        field {
240          x: a
241          y: c
242        },
243        field {
244          x: b
245          y: d
246        }
247      }
248    `, []gqlerrors.FormattedError{
249		testutil.RuleError(`Fields "field" conflict because subfields "x" conflict because a and b are different fields and `+
250			`subfields "y" conflict because c and d are different fields. `+
251			`Use different aliases on the fields to fetch both if this was intentional.`,
252			3, 9,
253			4, 11,
254			5, 11,
255			7, 9,
256			8, 11,
257			9, 11),
258	})
259}
260func TestValidate_OverlappingFieldsCanBeMerged_VeryDeepConflict(t *testing.T) {
261	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
262      {
263        field {
264          deepField {
265            x: a
266          }
267        },
268        field {
269          deepField {
270            x: b
271          }
272        }
273      }
274    `, []gqlerrors.FormattedError{
275		testutil.RuleError(`Fields "field" conflict because subfields "deepField" conflict because subfields "x" conflict because `+
276			`a and b are different fields. `+
277			`Use different aliases on the fields to fetch both if this was intentional.`,
278			3, 9,
279			4, 11,
280			5, 13,
281			8, 9,
282			9, 11,
283			10, 13),
284	})
285}
286func TestValidate_OverlappingFieldsCanBeMerged_ReportsDeepConflictToNearestCommonAncestor(t *testing.T) {
287	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
288      {
289        field {
290          deepField {
291            x: a
292          }
293          deepField {
294            x: b
295          }
296        },
297        field {
298          deepField {
299            y
300          }
301        }
302      }
303    `, []gqlerrors.FormattedError{
304		testutil.RuleError(`Fields "deepField" conflict because subfields "x" conflict because `+
305			`a and b are different fields. `+
306			`Use different aliases on the fields to fetch both if this was intentional.`,
307			4, 11,
308			5, 13,
309			7, 11,
310			8, 13),
311	})
312}
313func TestValidate_OverlappingFieldsCanBeMerged_ReportsDeepConflictToNearestCommonAncestorInFragments(t *testing.T) {
314	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
315      {
316        field {
317          ...F
318        }
319        field {
320          ...F
321        }
322      }
323      fragment F on T {
324        deepField {
325          deeperField {
326            x: a
327          }
328          deeperField {
329            x: b
330          }
331        },
332        deepField {
333          deeperField {
334            y
335          }
336        }
337      }
338    `, []gqlerrors.FormattedError{
339		testutil.RuleError(`Fields "deeperField" conflict because subfields "x" conflict because `+
340			`a and b are different fields. `+
341			`Use different aliases on the fields to fetch both if this was intentional.`,
342			12, 11,
343			13, 13,
344			15, 11,
345			16, 13),
346	})
347}
348func TestValidate_OverlappingFieldsCanBeMerged_ReportsDeepConflictInNestedFragments(t *testing.T) {
349	testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
350      {
351        field {
352          ...F
353        }
354        field {
355          ...I
356        }
357      }
358      fragment F on T {
359        x: a
360        ...G
361      }
362      fragment G on T {
363        y: c
364      }
365      fragment I on T {
366        y: d
367        ...J
368      }
369      fragment J on T {
370        x: b
371      }
372    `, []gqlerrors.FormattedError{
373		testutil.RuleError(`Fields "field" conflict because `+
374			`subfields "x" conflict because a and b are different fields and `+
375			`subfields "y" conflict because c and d are different fields. `+
376			`Use different aliases on the fields to fetch both if this was intentional.`,
377			3, 9,
378			11, 9,
379			15, 9,
380			6, 9,
381			22, 9,
382			18, 9),
383	})
384}
385func TestValidate_OverlappingFieldsCanBeMerged_IgnoresUnknownFragments(t *testing.T) {
386	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `
387    {
388      field
389      ...Unknown
390      ...Known
391    }
392
393    fragment Known on T {
394      field
395      ...OtherUnknown
396    }
397    `)
398}
399
400var someBoxInterface *graphql.Interface
401var stringBoxObject *graphql.Object
402var intBoxObject *graphql.Object
403var schema graphql.Schema
404
405func init() {
406	someBoxInterface = graphql.NewInterface(graphql.InterfaceConfig{
407		Name: "SomeBox",
408		ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
409			return stringBoxObject
410		},
411		Fields: graphql.FieldsThunk(func() graphql.Fields {
412			return graphql.Fields{
413				"deepBox": &graphql.Field{
414					Type: someBoxInterface,
415				},
416				"unrelatedField": &graphql.Field{
417					Type: graphql.String,
418				},
419			}
420		}),
421	})
422	stringBoxObject = graphql.NewObject(graphql.ObjectConfig{
423		Name: "StringBox",
424		Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface {
425			return []*graphql.Interface{someBoxInterface}
426		}),
427		Fields: graphql.FieldsThunk(func() graphql.Fields {
428			return graphql.Fields{
429				"scalar": &graphql.Field{
430					Type: graphql.String,
431				},
432				"deepBox": &graphql.Field{
433					Type: stringBoxObject,
434				},
435				"unrelatedField": &graphql.Field{
436					Type: graphql.String,
437				},
438				"listStringBox": &graphql.Field{
439					Type: graphql.NewList(stringBoxObject),
440				},
441				"stringBox": &graphql.Field{
442					Type: stringBoxObject,
443				},
444				"intBox": &graphql.Field{
445					Type: intBoxObject,
446				},
447			}
448		}),
449	})
450	intBoxObject = graphql.NewObject(graphql.ObjectConfig{
451		Name: "IntBox",
452		Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface {
453			return []*graphql.Interface{someBoxInterface}
454		}),
455		Fields: graphql.FieldsThunk(func() graphql.Fields {
456			return graphql.Fields{
457				"scalar": &graphql.Field{
458					Type: graphql.Int,
459				},
460				"deepBox": &graphql.Field{
461					Type: someBoxInterface,
462				},
463				"unrelatedField": &graphql.Field{
464					Type: graphql.String,
465				},
466				"listStringBox": &graphql.Field{
467					Type: graphql.NewList(stringBoxObject),
468				},
469				"stringBox": &graphql.Field{
470					Type: stringBoxObject,
471				},
472				"intBox": &graphql.Field{
473					Type: intBoxObject,
474				},
475			}
476		}),
477	})
478	var nonNullStringBox1Interface = graphql.NewInterface(graphql.InterfaceConfig{
479		Name: "NonNullStringBox1",
480		ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
481			return stringBoxObject
482		},
483		Fields: graphql.Fields{
484			"scalar": &graphql.Field{
485				Type: graphql.NewNonNull(graphql.String),
486			},
487		},
488	})
489	NonNullStringBox1Impl := graphql.NewObject(graphql.ObjectConfig{
490		Name: "NonNullStringBox1Impl",
491		Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface {
492			return []*graphql.Interface{someBoxInterface, nonNullStringBox1Interface}
493		}),
494		Fields: graphql.Fields{
495			"scalar": &graphql.Field{
496				Type: graphql.NewNonNull(graphql.String),
497			},
498			"unrelatedField": &graphql.Field{
499				Type: graphql.String,
500			},
501			"deepBox": &graphql.Field{
502				Type: someBoxInterface,
503			},
504		},
505	})
506	var nonNullStringBox2Interface = graphql.NewInterface(graphql.InterfaceConfig{
507		Name: "NonNullStringBox2",
508		ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
509			return stringBoxObject
510		},
511		Fields: graphql.Fields{
512			"scalar": &graphql.Field{
513				Type: graphql.NewNonNull(graphql.String),
514			},
515		},
516	})
517	NonNullStringBox2Impl := graphql.NewObject(graphql.ObjectConfig{
518		Name: "NonNullStringBox2Impl",
519		Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface {
520			return []*graphql.Interface{someBoxInterface, nonNullStringBox2Interface}
521		}),
522		Fields: graphql.Fields{
523			"scalar": &graphql.Field{
524				Type: graphql.NewNonNull(graphql.String),
525			},
526			"unrelatedField": &graphql.Field{
527				Type: graphql.String,
528			},
529			"deepBox": &graphql.Field{
530				Type: someBoxInterface,
531			},
532		},
533	})
534
535	var connectionObject = graphql.NewObject(graphql.ObjectConfig{
536		Name: "Connection",
537		Fields: graphql.Fields{
538			"edges": &graphql.Field{
539				Type: graphql.NewList(graphql.NewObject(graphql.ObjectConfig{
540					Name: "Edge",
541					Fields: graphql.Fields{
542						"node": &graphql.Field{
543							Type: graphql.NewObject(graphql.ObjectConfig{
544								Name: "Node",
545								Fields: graphql.Fields{
546									"id": &graphql.Field{
547										Type: graphql.ID,
548									},
549									"name": &graphql.Field{
550										Type: graphql.String,
551									},
552								},
553							}),
554						},
555					},
556				})),
557			},
558		},
559	})
560	var err error
561	schema, err = graphql.NewSchema(graphql.SchemaConfig{
562		Query: graphql.NewObject(graphql.ObjectConfig{
563			Name: "QueryRoot",
564			Fields: graphql.Fields{
565				"someBox": &graphql.Field{
566					Type: someBoxInterface,
567				},
568				"connection": &graphql.Field{
569					Type: connectionObject,
570				},
571			},
572		}),
573		Types: []graphql.Type{
574			intBoxObject,
575			stringBoxObject,
576			NonNullStringBox1Impl,
577			NonNullStringBox2Impl,
578		},
579	})
580	if err != nil {
581		panic(err)
582	}
583}
584
585func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_ConflictingReturnTypesWhichPotentiallyOverlap(t *testing.T) {
586	// This is invalid since an object could potentially be both the Object
587	// type IntBox and the interface type NonNullStringBox1. While that
588	// condition does not exist in the current schema, the schema could
589	// expand in the future to allow this. Thus it is invalid.
590	testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
591        {
592          someBox {
593            ...on IntBox {
594              scalar
595            }
596            ...on NonNullStringBox1 {
597              scalar
598            }
599          }
600        }
601    `, []gqlerrors.FormattedError{
602		testutil.RuleError(`Fields "scalar" conflict because they return conflicting types Int and String!. `+
603			`Use different aliases on the fields to fetch both if this was intentional.`,
604			5, 15,
605			8, 15),
606	})
607}
608func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_CompatibleReturnShapesOnDifferentReturnTypes(t *testing.T) {
609	// In this case `deepBox` returns `SomeBox` in the first usage, and
610	// `StringBox` in the second usage. These return types are not the same!
611	// however this is valid because the return *shapes* are compatible.
612	testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
613      {
614        someBox {
615          ... on SomeBox {
616            deepBox {
617              unrelatedField
618            }
619          }
620          ... on StringBox {
621            deepBox {
622              unrelatedField
623            }
624          }
625        }
626      }
627    `)
628}
629func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingReturnTypesDespiteNoOverlap(t *testing.T) {
630	testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
631        {
632          someBox {
633            ... on IntBox {
634              scalar
635            }
636            ... on StringBox {
637              scalar
638            }
639          }
640        }
641    `, []gqlerrors.FormattedError{
642		testutil.RuleError(`Fields "scalar" conflict because they return conflicting types Int and String. `+
643			`Use different aliases on the fields to fetch both if this was intentional.`,
644			5, 15,
645			8, 15),
646	})
647}
648func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_ReportsCorrectlyWhenANonExclusiveFollosAnExclusive(t *testing.T) {
649	testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
650        {
651          someBox {
652            ... on IntBox {
653              deepBox {
654                ...X
655              }
656            }
657          }
658          someBox {
659            ... on StringBox {
660              deepBox {
661                ...Y
662              }
663            }
664          }
665          memoed: someBox {
666            ... on IntBox {
667              deepBox {
668                ...X
669              }
670            }
671          }
672          memoed: someBox {
673            ... on StringBox {
674              deepBox {
675                ...Y
676              }
677            }
678          }
679          other: someBox {
680            ...X
681          }
682          other: someBox {
683            ...Y
684          }
685        }
686        fragment X on SomeBox {
687          scalar
688        }
689        fragment Y on SomeBox {
690          scalar: unrelatedField
691        }
692    `, []gqlerrors.FormattedError{
693		testutil.RuleError(`Fields "other" conflict because subfields "scalar" conflict `+
694			`because scalar and unrelatedField are different fields. `+
695			`Use different aliases on the fields to fetch both if this was intentional.`,
696			31, 11,
697			39, 11,
698			34, 11,
699			42, 11),
700	})
701}
702func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingReturnTypeNullabilityDespiteNoOverlap(t *testing.T) {
703	testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
704        {
705          someBox {
706            ... on NonNullStringBox1 {
707              scalar
708            }
709            ... on StringBox {
710              scalar
711            }
712          }
713        }
714    `, []gqlerrors.FormattedError{
715		testutil.RuleError(`Fields "scalar" conflict because they return conflicting types String! and String. `+
716			`Use different aliases on the fields to fetch both if this was intentional.`,
717			5, 15,
718			8, 15),
719	})
720}
721func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingReturnTypeListDespiteNoOverlap(t *testing.T) {
722	testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
723        {
724          someBox {
725            ... on IntBox {
726              box: listStringBox {
727                scalar
728              }
729            }
730            ... on StringBox {
731              box: stringBox {
732                scalar
733              }
734            }
735          }
736        }
737    `, []gqlerrors.FormattedError{
738		testutil.RuleError(`Fields "box" conflict because they return conflicting types [StringBox] and StringBox. `+
739			`Use different aliases on the fields to fetch both if this was intentional.`,
740			5, 15,
741			10, 15),
742	})
743
744	testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
745        {
746          someBox {
747            ... on IntBox {
748              box: stringBox {
749                scalar
750              }
751            }
752            ... on StringBox {
753              box: listStringBox {
754                scalar
755              }
756            }
757          }
758        }
759    `, []gqlerrors.FormattedError{
760		testutil.RuleError(`Fields "box" conflict because they return conflicting types StringBox and [StringBox]. `+
761			`Use different aliases on the fields to fetch both if this was intentional.`,
762			5, 15,
763			10, 15),
764	})
765}
766func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingSubfields(t *testing.T) {
767	testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
768        {
769          someBox {
770            ... on IntBox {
771              box: stringBox {
772                val: scalar
773                val: unrelatedField
774              }
775            }
776            ... on StringBox {
777              box: stringBox {
778                val: scalar
779              }
780            }
781          }
782        }
783    `, []gqlerrors.FormattedError{
784		testutil.RuleError(`Fields "val" conflict because scalar and unrelatedField are different fields. `+
785			`Use different aliases on the fields to fetch both if this was intentional.`,
786			6, 17,
787			7, 17),
788	})
789}
790func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingDeepReturnTypesDespiteNoOverlap(t *testing.T) {
791	testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
792        {
793          someBox {
794            ... on IntBox {
795              box: stringBox {
796                scalar
797              }
798            }
799            ... on StringBox {
800              box: intBox {
801                scalar
802              }
803            }
804          }
805        }
806    `, []gqlerrors.FormattedError{
807		testutil.RuleError(`Fields "box" conflict because subfields "scalar" conflict because they return conflicting types String and Int. `+
808			`Use different aliases on the fields to fetch both if this was intentional.`,
809			5, 15,
810			6, 17,
811			10, 15,
812			11, 17),
813	})
814}
815func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_AllowsNonConflictingOverlappingTypes(t *testing.T) {
816	testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
817        {
818          someBox {
819            ... on IntBox {
820              scalar: unrelatedField
821            }
822            ... on StringBox {
823              scalar
824            }
825          }
826        }
827    `)
828}
829func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_SameWrappedScalarReturnTypes(t *testing.T) {
830	testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
831        {
832          someBox {
833            ...on NonNullStringBox1 {
834              scalar
835            }
836            ...on NonNullStringBox2 {
837              scalar
838            }
839          }
840        }
841    `)
842}
843func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_AllowsInlineTypelessFragments(t *testing.T) {
844	testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
845        {
846          a
847          ... {
848            a
849          }
850        }
851    `)
852}
853func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_ComparesDeepTypesIncludingList(t *testing.T) {
854	testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
855        {
856          connection {
857            ...edgeID
858            edges {
859              node {
860                id: name
861              }
862            }
863          }
864        }
865
866        fragment edgeID on Connection {
867          edges {
868            node {
869              id
870            }
871          }
872        }
873    `, []gqlerrors.FormattedError{
874		testutil.RuleError(`Fields "edges" conflict because subfields "node" conflict because subfields "id" conflict because `+
875			`name and id are different fields. `+
876			`Use different aliases on the fields to fetch both if this was intentional.`,
877			5, 13,
878			6, 15,
879			7, 17,
880			14, 11,
881			15, 13,
882			16, 15),
883	})
884}
885func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_IgnoresUnknownTypes(t *testing.T) {
886	testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, `
887        {
888          someBox {
889            ...on UnknownType {
890              scalar
891            }
892            ...on NonNullStringBox2 {
893              scalar
894            }
895          }
896        }
897    `)
898}
899
900func TestValidate_OverlappingFieldsCanBeMerged_NilCrash(t *testing.T) {
901	testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `subscription {e}`)
902}
903