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