Failed Conditions
Push — master ( 804daa...8817e8 )
by Vladimir
04:14
created

testAcceptsAnObjectWhichImplementsAnInterfaceFieldAlongWithAdditionalOptionalArguments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 19
c 0
b 0
f 0
rs 10
cc 1
nc 1
nop 0
1
<?php
2
namespace GraphQL\Tests\Type;
3
4
use GraphQL\Error\Error;
5
use GraphQL\Error\InvariantViolation;
6
use GraphQL\Error\Warning;
7
use GraphQL\Type\Schema;
8
use GraphQL\Type\Definition\CustomScalarType;
9
use GraphQL\Type\Definition\EnumType;
10
use GraphQL\Type\Definition\InputObjectType;
11
use GraphQL\Type\Definition\InterfaceType;
12
use GraphQL\Type\Definition\ObjectType;
13
use GraphQL\Type\Definition\Type;
14
use GraphQL\Type\Definition\UnionType;
15
use GraphQL\Utils\BuildSchema;
16
use GraphQL\Utils\Utils;
17
use PHPUnit\Framework\TestCase;
18
19
class ValidationTest extends TestCase
20
{
21
    public $SomeScalarType;
22
23
    public $SomeObjectType;
24
25
    public $SomeUnionType;
26
27
    public $SomeInterfaceType;
28
29
    public $SomeEnumType;
30
31
    public $SomeInputObjectType;
32
33
    public $outputTypes;
34
35
    public $notOutputTypes;
36
37
    public $inputTypes;
38
39
    public $notInputTypes;
40
41
    public $Number;
42
43
    public function setUp()
44
    {
45
        $this->Number = 1;
46
47
        $this->SomeScalarType = new CustomScalarType([
48
            'name' => 'SomeScalar',
49
            'serialize' => function() {},
50
            'parseValue' => function() {},
51
            'parseLiteral' => function() {}
52
        ]);
53
54
        $this->SomeObjectType = new ObjectType([
55
            'name' => 'SomeObject',
56
            'fields' => [ 'f' => [ 'type' => Type::string() ] ],
57
            'interfaces' => function() {return [$this->SomeInterfaceType];}
58
        ]);
59
        $this->SomeUnionType = new UnionType([
60
            'name' => 'SomeUnion',
61
            'types' => [ $this->SomeObjectType ]
62
        ]);
63
64
        $this->SomeInterfaceType = new InterfaceType([
65
            'name' => 'SomeInterface',
66
            'fields' => [ 'f' => ['type' => Type::string() ]]
67
        ]);
68
69
        $this->SomeEnumType = new EnumType([
70
            'name' => 'SomeEnum',
71
            'values' => [
72
                'ONLY' => []
73
            ]
74
        ]);
75
76
        $this->SomeInputObjectType = new InputObjectType([
77
            'name' => 'SomeInputObject',
78
            'fields' => [
79
                'val' => [ 'type' => Type::string(), 'defaultValue' => 'hello' ]
80
            ]
81
        ]);
82
83
        $this->outputTypes = $this->withModifiers([
84
            Type::string(),
85
            $this->SomeScalarType,
86
            $this->SomeEnumType,
87
            $this->SomeObjectType,
88
            $this->SomeUnionType,
89
            $this->SomeInterfaceType
90
        ]);
91
92
        $this->notOutputTypes = $this->withModifiers([
93
          $this->SomeInputObjectType,
94
        ]);
95
        $this->notOutputTypes[] = $this->Number;
96
97
        $this->inputTypes = $this->withModifiers([
98
            Type::string(),
99
            $this->SomeScalarType,
100
            $this->SomeEnumType,
101
            $this->SomeInputObjectType,
102
        ]);
103
104
        $this->notInputTypes = $this->withModifiers([
105
            $this->SomeObjectType,
106
            $this->SomeUnionType,
107
            $this->SomeInterfaceType,
108
        ]);
109
110
        $this->notInputTypes[] = $this->Number;
111
112
        Warning::suppress(Warning::WARNING_NOT_A_TYPE);
113
    }
114
115
    public function tearDown()
116
    {
117
        parent::tearDown();
118
        Warning::enable(Warning::WARNING_NOT_A_TYPE);
119
    }
120
121
    /**
122
     * @param InvariantViolation[]|Error[] $array
123
     * @param array $messages
124
     */
125
    private function assertContainsValidationMessage($array, $messages) {
126
        $this->assertCount(
127
            count($messages),
128
            $array,
129
            'For messages: ' . $messages[0]['message'] . "\n" .
130
            "Received: \n" . join("\n", array_map(function($error) { return $error->getMessage(); }, $array))
131
        );
132
        foreach ($array as $index => $error) {
133
            if(!isset($messages[$index]) || !$error instanceof Error) {
134
                $this->fail('Received unexpected error: ' . $error->getMessage());
135
            }
136
            $this->assertEquals($messages[$index]['message'], $error->getMessage());
137
            $errorLocations = [];
138
            foreach ($error->getLocations() as $location) {
139
                $errorLocations[] = $location->toArray();
140
            }
141
            $this->assertEquals(
142
                isset($messages[$index]['locations']) ? $messages[$index]['locations'] : [],
143
                $errorLocations
144
            );
145
        }
146
    }
147
148
    public function testRejectsTypesWithoutNames()
149
    {
150
        $this->assertEachCallableThrows([
151
            function() {
152
                return new ObjectType([]);
153
            },
154
            function() {
155
                return new EnumType([]);
156
            },
157
            function() {
158
                return new InputObjectType([]);
159
            },
160
            function() {
161
                return new UnionType([]);
162
            },
163
            function() {
164
                return new InterfaceType([]);
165
            }
166
        ], 'Must provide name.');
167
    }
168
169
    // DESCRIBE: Type System: A Schema must have Object root types
170
171
    /**
172
     * @it accepts a Schema whose query type is an object type
173
     */
174
    public function testAcceptsASchemaWhoseQueryTypeIsAnObjectType()
175
    {
176
        $schema = BuildSchema::build('
177
      type Query {
178
        test: String
179
      }
180
        ');
181
        $this->assertEquals([], $schema->validate());
182
183
        $schemaWithDef = BuildSchema::build('
184
      schema {
185
        query: QueryRoot
186
      }
187
      type QueryRoot {
188
        test: String
189
      }
190
    ');
191
        $this->assertEquals([], $schemaWithDef->validate());
192
    }
193
194
    /**
195
     * @it accepts a Schema whose query and mutation types are object types
196
     */
197
    public function testAcceptsASchemaWhoseQueryAndMutationTypesAreObjectTypes()
198
    {
199
        $schema = BuildSchema::build('
200
      type Query {
201
        test: String
202
      }
203
204
      type Mutation {
205
        test: String
206
      }
207
        ');
208
        $this->assertEquals([], $schema->validate());
209
210
        $schema = BuildSchema::build('
211
      schema {
212
        query: QueryRoot
213
        mutation: MutationRoot
214
      }
215
216
      type QueryRoot {
217
        test: String
218
      }
219
220
      type MutationRoot {
221
        test: String
222
      }
223
        ');
224
        $this->assertEquals([], $schema->validate());
225
    }
226
227
    /**
228
     * @it accepts a Schema whose query and subscription types are object types
229
     */
230
    public function testAcceptsASchemaWhoseQueryAndSubscriptionTypesAreObjectTypes()
231
    {
232
        $schema = BuildSchema::build('
233
      type Query {
234
        test: String
235
      }
236
237
      type Subscription {
238
        test: String
239
      }
240
        ');
241
        $this->assertEquals([], $schema->validate());
242
243
        $schema = BuildSchema::build('
244
      schema {
245
        query: QueryRoot
246
        subscription: SubscriptionRoot
247
      }
248
249
      type QueryRoot {
250
        test: String
251
      }
252
253
      type SubscriptionRoot {
254
        test: String
255
      }
256
        ');
257
        $this->assertEquals([], $schema->validate());
258
    }
259
260
    /**
261
     * @it rejects a Schema without a query type
262
     */
263
    public function testRejectsASchemaWithoutAQueryType()
264
    {
265
        $schema = BuildSchema::build('
266
      type Mutation {
267
        test: String
268
      }
269
        ');
270
271
        $this->assertContainsValidationMessage(
272
            $schema->validate(),
273
            [['message' => 'Query root type must be provided.']]
274
        );
275
276
277
        $schemaWithDef = BuildSchema::build('
278
      schema {
279
        mutation: MutationRoot
280
      }
281
282
      type MutationRoot {
283
        test: String
284
      }
285
        ');
286
287
        $this->assertContainsValidationMessage(
288
            $schemaWithDef->validate(),
289
            [[
290
                'message' => 'Query root type must be provided.',
291
                'locations' => [['line' => 2, 'column' => 7]],
292
            ]]
293
        );
294
    }
295
296
    /**
297
     * @it rejects a Schema whose query root type is not an Object type
298
     */
299
    public function testRejectsASchemaWhoseQueryTypeIsNotAnObjectType()
300
    {
301
        $schema = BuildSchema::build('
302
      input Query {
303
        test: String
304
      }
305
        ');
306
307
        $this->assertContainsValidationMessage(
308
            $schema->validate(),
309
            [[
310
                'message' => 'Query root type must be Object type, it cannot be Query.',
311
                'locations' => [['line' => 2, 'column' => 7]],
312
            ]]
313
        );
314
315
316
        $schemaWithDef = BuildSchema::build('
317
      schema {
318
        query: SomeInputObject
319
      }
320
321
      input SomeInputObject {
322
        test: String
323
      }
324
        ');
325
326
        $this->assertContainsValidationMessage(
327
            $schemaWithDef->validate(),
328
            [[
329
                'message' => 'Query root type must be Object type, it cannot be SomeInputObject.',
330
                'locations' => [['line' => 3, 'column' => 16]],
331
            ]]
332
        );
333
    }
334
335
    /**
336
     * @it rejects a Schema whose mutation type is an input type
337
     */
338
    public function testRejectsASchemaWhoseMutationTypeIsAnInputType()
339
    {
340
        $schema = BuildSchema::build('
341
      type Query {
342
        field: String
343
      }
344
345
      input Mutation {
346
        test: String
347
      }
348
        ');
349
350
        $this->assertContainsValidationMessage(
351
            $schema->validate(),
352
            [[
353
                'message' => 'Mutation root type must be Object type if provided, it cannot be Mutation.',
354
                'locations' => [['line' => 6, 'column' => 7]],
355
            ]]
356
        );
357
358
        $schemaWithDef = BuildSchema::build('
359
      schema {
360
        query: Query
361
        mutation: SomeInputObject
362
      }
363
364
      type Query {
365
        field: String
366
      }
367
368
      input SomeInputObject {
369
        test: String
370
      }
371
        ');
372
373
        $this->assertContainsValidationMessage(
374
            $schemaWithDef->validate(),
375
            [[
376
                'message' => 'Mutation root type must be Object type if provided, it cannot be SomeInputObject.',
377
                'locations' => [['line' => 4, 'column' => 19]],
378
            ]]
379
        );
380
    }
381
382
    /**
383
     * @it rejects a Schema whose subscription type is an input type
384
     */
385
    public function testRejectsASchemaWhoseSubscriptionTypeIsAnInputType()
386
    {
387
        $schema = BuildSchema::build('
388
      type Query {
389
        field: String
390
      }
391
392
      input Subscription {
393
        test: String
394
      }
395
        ');
396
397
        $this->assertContainsValidationMessage(
398
            $schema->validate(),
399
            [[
400
                'message' => 'Subscription root type must be Object type if provided, it cannot be Subscription.',
401
                'locations' => [['line' => 6, 'column' => 7]],
402
            ]]
403
        );
404
405
        $schemaWithDef = BuildSchema::build('
406
      schema {
407
        query: Query
408
        subscription: SomeInputObject
409
      }
410
411
      type Query {
412
        field: String
413
      }
414
415
      input SomeInputObject {
416
        test: String
417
      }
418
        ');
419
420
        $this->assertContainsValidationMessage(
421
            $schemaWithDef->validate(),
422
            [[
423
                'message' => 'Subscription root type must be Object type if provided, it cannot be SomeInputObject.',
424
                'locations' => [['line' => 4, 'column' => 23]],
425
            ]]
426
        );
427
428
429
    }
430
431
    /**
432
     * @it rejects a Schema whose directives are incorrectly typed
433
     */
434
    public function testRejectsASchemaWhoseDirectivesAreIncorrectlyTyped()
435
    {
436
        $schema = new Schema([
437
            'query' => $this->SomeObjectType,
438
            'directives' => ['somedirective']
439
        ]);
440
441
        $this->assertContainsValidationMessage(
442
            $schema->validate(),
443
            [['message' => 'Expected directive but got: somedirective.']]
444
        );
445
    }
446
447
    // DESCRIBE: Type System: Objects must have fields
448
449
    /**
450
     * @it accepts an Object type with fields object
451
     */
452
    public function testAcceptsAnObjectTypeWithFieldsObject()
453
    {
454
        $schema = BuildSchema::build('
455
      type Query {
456
        field: SomeObject
457
      }
458
459
      type SomeObject {
460
        field: String
461
      }
462
        ');
463
464
        $this->assertEquals([], $schema->validate());
465
    }
466
467
    /**
468
     * @it rejects an Object type with missing fields
469
     */
470
    public function testRejectsAnObjectTypeWithMissingFields()
471
    {
472
        $schema = BuildSchema::build('
473
      type Query {
474
        test: IncompleteObject
475
      }
476
477
      type IncompleteObject
478
        ');
479
480
        $this->assertContainsValidationMessage(
481
            $schema->validate(),
482
            [[
483
                'message' => 'Type IncompleteObject must define one or more fields.',
484
                'locations' => [['line' => 6, 'column' => 7]],
485
            ]]
486
        );
487
488
        $manualSchema = $this->schemaWithFieldType(
489
            new ObjectType([
490
                'name' => 'IncompleteObject',
491
                'fields' => [],
492
            ])
493
        );
494
495
        $this->assertContainsValidationMessage(
496
            $manualSchema->validate(),
497
            [['message' => 'Type IncompleteObject must define one or more fields.']]
498
        );
499
500
        $manualSchema2 = $this->schemaWithFieldType(
501
            new ObjectType([
502
                'name' => 'IncompleteObject',
503
                'fields' => function () { return []; },
504
            ])
505
        );
506
507
        $this->assertContainsValidationMessage(
508
            $manualSchema2->validate(),
509
            [['message' => 'Type IncompleteObject must define one or more fields.']]
510
        );
511
    }
512
513
    /**
514
     * @it rejects an Object type with incorrectly named fields
515
     */
516
    public function testRejectsAnObjectTypeWithIncorrectlyNamedFields()
517
    {
518
        $schema = $this->schemaWithFieldType(
519
            new ObjectType([
520
                'name' => 'SomeObject',
521
                'fields' => [
522
                  'bad-name-with-dashes' => ['type' => Type::string()]
523
                ],
524
            ])
525
        );
526
527
        $this->assertContainsValidationMessage(
528
            $schema->validate(),
529
            [[
530
                'message' => 'Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but ' .
531
                              '"bad-name-with-dashes" does not.',
532
            ]]
533
        );
534
    }
535
536
    public function testAcceptsShorthandNotationForFields()
537
    {
538
        $this->expectNotToPerformAssertions();
539
        $schema = $this->schemaWithFieldType(
540
            new ObjectType([
541
                'name' => 'SomeObject',
542
                'fields' => [
543
                    'field' => Type::string()
544
                ]
545
            ])
546
        );
547
        $schema->assertValid();
548
    }
549
550
    // DESCRIBE: Type System: Fields args must be properly named
551
552
    /**
553
     * @it accepts field args with valid names
554
     */
555
    public function testAcceptsFieldArgsWithValidNames()
556
    {
557
        $schema = $this->schemaWithFieldType(new ObjectType([
558
            'name' => 'SomeObject',
559
            'fields' => [
560
                'goodField' => [
561
                    'type' => Type::string(),
562
                    'args' => [
563
                        'goodArg' => ['type' => Type::string()]
564
                    ]
565
                ]
566
            ]
567
        ]));
568
        $this->assertEquals([], $schema->validate());
569
    }
570
571
    /**
572
     * @it rejects field arg with invalid names
573
     */
574
    public function testRejectsFieldArgWithInvalidNames()
575
    {
576
        $QueryType = new ObjectType([
577
            'name' => 'SomeObject',
578
            'fields' => [
579
                'badField' => [
580
                    'type' => Type::string(),
581
                    'args' => [
582
                        'bad-name-with-dashes' => ['type' => Type::string()]
583
                    ]
584
                ]
585
            ]
586
        ]);
587
        $schema = new Schema(['query' => $QueryType]);
588
589
        $this->assertContainsValidationMessage(
590
            $schema->validate(),
591
            [['message' => 'Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "bad-name-with-dashes" does not.']]
592
        );
593
    }
594
595
    // DESCRIBE: Type System: Union types must be valid
596
597
    /**
598
     * @it accepts a Union type with member types
599
     */
600
    public function testAcceptsAUnionTypeWithArrayTypes()
601
    {
602
        $schema = BuildSchema::build('
603
      type Query {
604
        test: GoodUnion
605
      }
606
607
      type TypeA {
608
        field: String
609
      }
610
611
      type TypeB {
612
        field: String
613
      }
614
615
      union GoodUnion =
616
        | TypeA
617
        | TypeB
618
        ');
619
620
        $this->assertEquals([], $schema->validate());
621
    }
622
623
    /**
624
     * @it rejects a Union type with empty types
625
     */
626
    public function testRejectsAUnionTypeWithEmptyTypes()
627
    {
628
        $schema = BuildSchema::build('
629
      type Query {
630
        test: BadUnion
631
      }
632
633
      union BadUnion
634
        ');
635
        $this->assertContainsValidationMessage(
636
            $schema->validate(),
637
            [[
638
                'message' => 'Union type BadUnion must define one or more member types.',
639
                'locations' => [['line' => 6, 'column' => 7]],
640
            ]]
641
        );
642
    }
643
644
    /**
645
     * @it rejects a Union type with duplicated member type
646
     */
647
    public function testRejectsAUnionTypeWithDuplicatedMemberType()
648
    {
649
        $schema = BuildSchema::build('
650
      type Query {
651
        test: BadUnion
652
      }
653
654
      type TypeA {
655
        field: String
656
      }
657
658
      type TypeB {
659
        field: String
660
      }
661
662
      union BadUnion =
663
        | TypeA
664
        | TypeB
665
        | TypeA
666
        ');
667
        $this->assertContainsValidationMessage(
668
            $schema->validate(),
669
            [[
670
                'message' => 'Union type BadUnion can only include type TypeA once.',
671
                'locations' => [['line' => 15, 'column' => 11], ['line' => 17, 'column' => 11]],
672
            ]]
673
        );
674
    }
675
676
    /**
677
     * @it rejects a Union type with non-Object members types
678
     */
679
    public function testRejectsAUnionTypeWithNonObjectMembersType()
680
    {
681
        $schema = BuildSchema::build('
682
      type Query {
683
        test: BadUnion
684
      }
685
686
      type TypeA {
687
        field: String
688
      }
689
690
      type TypeB {
691
        field: String
692
      }
693
694
      union BadUnion =
695
        | TypeA
696
        | String
697
        | TypeB
698
        ');
699
        $this->assertContainsValidationMessage(
700
            $schema->validate(),
701
            [[
702
                'message' => 'Union type BadUnion can only include Object types, ' .
703
                             'it cannot include String.',
704
                'locations' => [['line' => 16, 'column' => 11]],
705
            ]]
706
707
        );
708
709
        $badUnionMemberTypes = [
710
            Type::string(),
711
            Type::nonNull($this->SomeObjectType),
712
            Type::listOf($this->SomeObjectType),
713
            $this->SomeInterfaceType,
714
            $this->SomeUnionType,
715
            $this->SomeEnumType,
716
            $this->SomeInputObjectType,
717
        ];
718
719
        foreach($badUnionMemberTypes as $memberType) {
720
            $badSchema = $this->schemaWithFieldType(
721
                new UnionType(['name' => 'BadUnion', 'types' => [$memberType]])
722
            );
723
            $this->assertContainsValidationMessage(
724
                $badSchema->validate(),
725
                [[
726
                    'message' => 'Union type BadUnion can only include Object types, ' .
727
                                 "it cannot include ". Utils::printSafe($memberType) . ".",
728
                ]]
729
            );
730
        }
731
    }
732
733
    // DESCRIBE: Type System: Input Objects must have fields
734
735
    /**
736
     * @it accepts an Input Object type with fields
737
     */
738
    public function testAcceptsAnInputObjectTypeWithFields()
739
    {
740
        $schema = BuildSchema::build('
741
      type Query {
742
        field(arg: SomeInputObject): String
743
      }
744
745
      input SomeInputObject {
746
        field: String
747
      }
748
        ');
749
        $this->assertEquals([], $schema->validate());
750
    }
751
752
    /**
753
     * @it rejects an Input Object type with missing fields
754
     */
755
    public function testRejectsAnInputObjectTypeWithMissingFields()
756
    {
757
        $schema = BuildSchema::build('
758
      type Query {
759
        field(arg: SomeInputObject): String
760
      }
761
762
      input SomeInputObject
763
        ');
764
        $this->assertContainsValidationMessage(
765
            $schema->validate(),
766
            [[
767
                'message' => 'Input Object type SomeInputObject must define one or more fields.',
768
                'locations' => [['line' => 6, 'column' => 7]],
769
            ]]
770
        );
771
    }
772
773
    /**
774
     * @it rejects an Input Object type with incorrectly typed fields
775
     */
776
    public function testRejectsAnInputObjectTypeWithIncorrectlyTypedFields()
777
    {
778
        $schema = BuildSchema::build('
779
      type Query {
780
        field(arg: SomeInputObject): String
781
      }
782
      
783
      type SomeObject {
784
        field: String
785
      }
786
787
      union SomeUnion = SomeObject
788
      
789
      input SomeInputObject {
790
        badObject: SomeObject
791
        badUnion: SomeUnion
792
        goodInputObject: SomeInputObject
793
      }
794
        ');
795
        $this->assertContainsValidationMessage(
796
            $schema->validate(),
797
            [[
798
                'message' => 'The type of SomeInputObject.badObject must be Input Type but got: SomeObject.',
799
                'locations' => [['line' => 13, 'column' => 20]],
800
            ],[
801
                'message' => 'The type of SomeInputObject.badUnion must be Input Type but got: SomeUnion.',
802
                'locations' => [['line' => 14, 'column' => 19]],
803
            ]]
804
        );
805
    }
806
807
    // DESCRIBE: Type System: Enum types must be well defined
808
809
    /**
810
     * @it rejects an Enum type without values
811
     */
812
    public function testRejectsAnEnumTypeWithoutValues()
813
    {
814
        $schema = BuildSchema::build('
815
      type Query {
816
        field: SomeEnum
817
      }
818
      
819
      enum SomeEnum
820
        ');
821
        $this->assertContainsValidationMessage(
822
            $schema->validate(),
823
            [[
824
                'message' => 'Enum type SomeEnum must define one or more values.',
825
                'locations' => [['line' => 6, 'column' => 7]],
826
            ]]
827
        );
828
    }
829
830
    /**
831
     * @it rejects an Enum type with duplicate values
832
     */
833
    public function testRejectsAnEnumTypeWithDuplicateValues()
834
    {
835
        $schema = BuildSchema::build('
836
      type Query {
837
        field: SomeEnum
838
      }
839
      
840
      enum SomeEnum {
841
        SOME_VALUE
842
        SOME_VALUE
843
      }
844
        ');
845
        $this->assertContainsValidationMessage(
846
            $schema->validate(),
847
            [[
848
                'message' => 'Enum type SomeEnum can include value SOME_VALUE only once.',
849
                'locations' => [['line' => 7, 'column' => 9], ['line' => 8, 'column' => 9]],
850
            ]]
851
        );
852
    }
853
854
    public function testDoesNotAllowIsDeprecatedWithoutDeprecationReasonOnEnum()
855
    {
856
        $enum = new EnumType([
857
            'name' => 'SomeEnum',
858
            'values' => [
859
                'value' => ['isDeprecated' => true]
860
            ]
861
        ]);
862
        $this->expectException(InvariantViolation::class);
863
        $this->expectExceptionMessage('SomeEnum.value should provide "deprecationReason" instead of "isDeprecated".');
864
        $enum->assertValid();
865
    }
866
867
    private function schemaWithEnum($name)
868
    {
869
        return $this->schemaWithFieldType(
870
            new EnumType([
871
                'name' => 'SomeEnum',
872
                'values' => [
873
                    $name => []
874
                ]
875
            ])
876
        );
877
    }
878
879
    public function invalidEnumValueName()
880
    {
881
        return [
882
            ['#value', 'Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "#value" does not.'],
883
            ['1value', 'Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "1value" does not.'],
884
            ['KEBAB-CASE', 'Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "KEBAB-CASE" does not.'],
885
            ['false', 'Enum type SomeEnum cannot include value: false.'],
886
            ['true', 'Enum type SomeEnum cannot include value: true.'],
887
            ['null', 'Enum type SomeEnum cannot include value: null.'],
888
        ];
889
    }
890
891
    /**
892
     * @it rejects an Enum type with incorrectly named values
893
     * @dataProvider invalidEnumValueName
894
     */
895
    public function testRejectsAnEnumTypeWithIncorrectlyNamedValues($name, $expectedMessage)
896
    {
897
        $schema = $this->schemaWithEnum($name);
898
899
        $this->assertContainsValidationMessage(
900
            $schema->validate(),
901
            [[
902
                'message' => $expectedMessage,
903
            ]]
904
        );
905
    }
906
907
    // DESCRIBE: Type System: Object fields must have output types
908
909
    /**
910
     * @it accepts an output type as an Object field type
911
     */
912
    public function testAcceptsAnOutputTypeAsNnObjectFieldType()
913
    {
914
        foreach ($this->outputTypes as $type) {
915
            $schema = $this->schemaWithObjectFieldOfType($type);
916
            $this->assertEquals([], $schema->validate());
917
        }
918
    }
919
920
    /**
921
     * @it rejects an empty Object field type
922
     */
923
    public function testRejectsAnEmptyObjectFieldType()
924
    {
925
        $schema = $this->schemaWithObjectFieldOfType(null);
926
927
        $this->assertContainsValidationMessage(
928
            $schema->validate(),
929
            [[
930
                'message' => 'The type of BadObject.badField must be Output Type but got: null.',
931
            ]]
932
        );
933
    }
934
935
    /**
936
     * @it rejects a non-output type as an Object field type
937
     */
938
    public function testRejectsANonOutputTypeAsAnObjectFieldType()
939
    {
940
        foreach ($this->notOutputTypes as $type) {
941
            $schema = $this->schemaWithObjectFieldOfType($type);
942
943
            $this->assertContainsValidationMessage(
944
                $schema->validate(),
945
                [[
946
                    'message' => 'The type of BadObject.badField must be Output Type but got: ' . Utils::printSafe($type) . '.',
947
                ]]
948
            );
949
        }
950
    }
951
952
    /**
953
     * @it rejects with relevant locations for a non-output type as an Object field type
954
     */
955
    public function testRejectsWithReleventLocationsForANonOutputTypeAsAnObjectFieldType()
956
    {
957
        $schema = BuildSchema::build('
958
      type Query {
959
        field: [SomeInputObject]
960
      }
961
      
962
      input SomeInputObject {
963
        field: String
964
      }
965
        ');
966
        $this->assertContainsValidationMessage(
967
            $schema->validate(),
968
            [[
969
                'message' => 'The type of Query.field must be Output Type but got: [SomeInputObject].',
970
                'locations' => [['line' => 3, 'column' => 16]],
971
            ]]
972
        );
973
    }
974
975
    // DESCRIBE: Type System: Objects can only implement unique interfaces
976
977
    /**
978
     * @it rejects an Object implementing a non-type values
979
     */
980
    public function testRejectsAnObjectImplementingANonTypeValues()
981
    {
982
        $schema = new Schema([
983
            'query' => new ObjectType([
984
                'name' => 'BadObject',
985
                'interfaces' => [null],
986
                'fields' => ['a' => Type::string()]
987
            ]),
988
        ]);
989
        $expected = [
990
            'message' => 'Type BadObject must only implement Interface types, it cannot implement null.'
991
        ];
992
993
        $this->assertContainsValidationMessage(
994
            $schema->validate(),
995
            [$expected]
996
        );
997
    }
998
999
    /**
1000
     * @it rejects an Object implementing a non-Interface type
1001
     */
1002
    public function testRejectsAnObjectImplementingANonInterfaceType()
1003
    {
1004
        $schema = BuildSchema::build('
1005
      type Query {
1006
        field: BadObject
1007
      }
1008
      
1009
      input SomeInputObject {
1010
        field: String
1011
      }
1012
      
1013
      type BadObject implements SomeInputObject {
1014
        field: String
1015
      }
1016
        ');
1017
        $this->assertContainsValidationMessage(
1018
            $schema->validate(),
1019
            [[
1020
                'message' => 'Type BadObject must only implement Interface types, it cannot implement SomeInputObject.',
1021
                'locations' => [['line' => 10, 'column' => 33]],
1022
            ]]
1023
        );
1024
    }
1025
1026
    /**
1027
     * @it rejects an Object implementing the same interface twice
1028
     */
1029
    public function testRejectsAnObjectImplementingTheSameInterfaceTwice()
1030
    {
1031
        $schema = BuildSchema::build('
1032
      type Query {
1033
        field: AnotherObject
1034
      }
1035
      
1036
      interface AnotherInterface {
1037
        field: String
1038
      }
1039
      
1040
      type AnotherObject implements AnotherInterface & AnotherInterface {
1041
        field: String
1042
      }
1043
        ');
1044
        $this->assertContainsValidationMessage(
1045
            $schema->validate(),
1046
            [[
1047
                'message' => 'Type AnotherObject can only implement AnotherInterface once.',
1048
                'locations' => [['line' => 10, 'column' => 37], ['line' => 10, 'column' => 56]],
1049
            ]]
1050
        );
1051
    }
1052
1053
    /**
1054
     * @it rejects an Object implementing the same interface twice due to extension
1055
     */
1056
    public function testRejectsAnObjectImplementingTheSameInterfaceTwiceDueToExtension()
1057
    {
1058
        $this->expectNotToPerformAssertions();
1059
        $this->markTestIncomplete('extend does not work this way (yet).');
1060
        $schema = BuildSchema::build('
1061
      type Query {
1062
        field: AnotherObject
1063
      }
1064
      
1065
      interface AnotherInterface {
1066
        field: String
1067
      }
1068
      
1069
      type AnotherObject implements AnotherInterface {
1070
        field: String
1071
      }
1072
      
1073
      extend type AnotherObject implements AnotherInterface
1074
        ');
1075
        $this->assertContainsValidationMessage(
1076
            $schema->validate(),
1077
            [[
1078
                'message' => 'Type AnotherObject can only implement AnotherInterface once.',
1079
                'locations' => [['line' => 10, 'column' => 37], ['line' => 14, 'column' => 38]],
1080
            ]]
1081
        );
1082
    }
1083
1084
    // DESCRIBE: Type System: Interface fields must have output types
1085
1086
    /**
1087
     * @it accepts an output type as an Interface field type
1088
     */
1089
    public function testAcceptsAnOutputTypeAsAnInterfaceFieldType()
1090
    {
1091
        foreach ($this->outputTypes as $type) {
1092
            $schema = $this->schemaWithInterfaceFieldOfType($type);
1093
            $this->assertEquals([], $schema->validate());
1094
        }
1095
    }
1096
1097
    /**
1098
     * @it rejects an empty Interface field type
1099
     */
1100
    public function testRejectsAnEmptyInterfaceFieldType()
1101
    {
1102
        $schema = $this->schemaWithInterfaceFieldOfType(null);
1103
        $this->assertContainsValidationMessage(
1104
            $schema->validate(),
1105
            [[
1106
                'message' => 'The type of BadInterface.badField must be Output Type but got: null.',
1107
            ]]
1108
        );
1109
    }
1110
1111
    /**
1112
     * @it rejects a non-output type as an Interface field type
1113
     */
1114
    public function testRejectsANonOutputTypeAsAnInterfaceFieldType()
1115
    {
1116
        foreach ($this->notOutputTypes as $type) {
1117
            $schema = $this->schemaWithInterfaceFieldOfType($type);
1118
1119
            $this->assertContainsValidationMessage(
1120
                $schema->validate(),
1121
                [[
1122
                    'message' => 'The type of BadInterface.badField must be Output Type but got: ' . Utils::printSafe($type) . '.',
1123
                ]]
1124
            );
1125
        }
1126
    }
1127
1128
    /**
1129
     * @it rejects a non-output type as an Interface field type with locations
1130
     */
1131
    public function testRejectsANonOutputTypeAsAnInterfaceFieldTypeWithLocations()
1132
    {
1133
        $schema = BuildSchema::build('
1134
      type Query {
1135
        field: SomeInterface
1136
      }
1137
      
1138
      interface SomeInterface {
1139
        field: SomeInputObject
1140
      }
1141
      
1142
      input SomeInputObject {
1143
        foo: String
1144
      }
1145
        ');
1146
        $this->assertContainsValidationMessage(
1147
            $schema->validate(),
1148
            [[
1149
                'message' => 'The type of SomeInterface.field must be Output Type but got: SomeInputObject.',
1150
                'locations' => [['line' => 7, 'column' => 16]],
1151
            ]]
1152
        );
1153
    }
1154
1155
    // DESCRIBE: Type System: Field arguments must have input types
1156
1157
    /**
1158
     * @it accepts an input type as a field arg type
1159
     */
1160
    public function testAcceptsAnInputTypeAsAFieldArgType()
1161
    {
1162
        foreach ($this->inputTypes as $type) {
1163
            $schema = $this->schemaWithArgOfType($type);
1164
            $this->assertEquals([], $schema->validate());
1165
        }
1166
    }
1167
1168
    /**
1169
     * @it rejects an empty field arg type
1170
     */
1171
    public function testRejectsAnEmptyFieldArgType()
1172
    {
1173
        $schema = $this->schemaWithArgOfType(null);
1174
        $this->assertContainsValidationMessage(
1175
            $schema->validate(),
1176
            [[
1177
                'message' => 'The type of BadObject.badField(badArg:) must be Input Type but got: null.',
1178
            ]]
1179
        );
1180
    }
1181
1182
    /**
1183
     * @it rejects a non-input type as a field arg type
1184
     */
1185
    public function testRejectsANonInputTypeAsAFieldArgType()
1186
    {
1187
        foreach ($this->notInputTypes as $type) {
1188
            $schema = $this->schemaWithArgOfType($type);
1189
            $this->assertContainsValidationMessage(
1190
                $schema->validate(),
1191
                [[
1192
                    'message' => 'The type of BadObject.badField(badArg:) must be Input Type but got: ' . Utils::printSafe($type) . '.',
1193
                ]]
1194
            );
1195
        }
1196
    }
1197
1198
    /**
1199
     * @it rejects a non-input type as a field arg with locations
1200
     */
1201
    public function testANonInputTypeAsAFieldArgWithLocations()
1202
    {
1203
        $schema = BuildSchema::build('
1204
      type Query {
1205
        test(arg: SomeObject): String
1206
      }
1207
      
1208
      type SomeObject {
1209
        foo: String
1210
      }
1211
        ');
1212
        $this->assertContainsValidationMessage(
1213
            $schema->validate(),
1214
            [[
1215
                'message' => 'The type of Query.test(arg:) must be Input Type but got: SomeObject.',
1216
                'locations' => [['line' => 3, 'column' => 19]],
1217
            ]]
1218
        );
1219
    }
1220
1221
    // DESCRIBE: Type System: Input Object fields must have input types
1222
1223
    /**
1224
     * @it accepts an input type as an input field type
1225
     */
1226
    public function testAcceptsAnInputTypeAsAnInputFieldType()
1227
    {
1228
        foreach ($this->inputTypes as $type) {
1229
            $schema = $this->schemaWithInputFieldOfType($type);
1230
            $this->assertEquals([], $schema->validate());
1231
        }
1232
    }
1233
1234
    /**
1235
     * @it rejects an empty input field type
1236
     */
1237
    public function testRejectsAnEmptyInputFieldType()
1238
    {
1239
        $schema = $this->schemaWithInputFieldOfType(null);
1240
        $this->assertContainsValidationMessage(
1241
            $schema->validate(),
1242
            [[
1243
                'message' => 'The type of BadInputObject.badField must be Input Type but got: null.',
1244
            ]]
1245
        );
1246
    }
1247
1248
    /**
1249
     * @it rejects a non-input type as an input field type
1250
     */
1251
    public function testRejectsANonInputTypeAsAnInputFieldType()
1252
    {
1253
        foreach ($this->notInputTypes as $type) {
1254
            $schema = $this->schemaWithInputFieldOfType($type);
1255
            $this->assertContainsValidationMessage(
1256
                $schema->validate(),
1257
                [[
1258
                    'message' => 'The type of BadInputObject.badField must be Input Type but got: ' . Utils::printSafe($type) . '.',
1259
                ]]
1260
            );
1261
        }
1262
    }
1263
1264
    /**
1265
     * @it rejects a non-input type as an input object field with locations
1266
     */
1267
    public function testRejectsANonInputTypeAsAnInputObjectFieldWithLocations()
1268
    {
1269
        $schema = BuildSchema::build('
1270
      type Query {
1271
        test(arg: SomeInputObject): String
1272
      }
1273
      
1274
      input SomeInputObject {
1275
        foo: SomeObject
1276
      }
1277
      
1278
      type SomeObject {
1279
        bar: String
1280
      }
1281
        ');
1282
        $this->assertContainsValidationMessage(
1283
            $schema->validate(),
1284
            [[
1285
                'message' => 'The type of SomeInputObject.foo must be Input Type but got: SomeObject.',
1286
                'locations' => [['line' => 7, 'column' => 14]],
1287
            ]]
1288
        );
1289
    }
1290
1291
    // DESCRIBE: Objects must adhere to Interface they implement
1292
1293
    /**
1294
     * @it accepts an Object which implements an Interface
1295
     */
1296
    public function testAcceptsAnObjectWhichImplementsAnInterface()
1297
    {
1298
        $schema = BuildSchema::build('
1299
      type Query {
1300
        test: AnotherObject
1301
      }
1302
      
1303
      interface AnotherInterface {
1304
        field(input: String): String
1305
      }
1306
      
1307
      type AnotherObject implements AnotherInterface {
1308
        field(input: String): String
1309
      }
1310
        ');
1311
1312
        $this->assertEquals(
1313
            [],
1314
            $schema->validate()
1315
        );
1316
    }
1317
1318
    /**
1319
     * @it accepts an Object which implements an Interface along with more fields
1320
     */
1321
    public function testAcceptsAnObjectWhichImplementsAnInterfaceAlongWithMoreFields()
1322
    {
1323
        $schema = BuildSchema::build('
1324
      type Query {
1325
        test: AnotherObject
1326
      }
1327
1328
      interface AnotherInterface {
1329
        field(input: String): String
1330
      }
1331
1332
      type AnotherObject implements AnotherInterface {
1333
        field(input: String): String
1334
        anotherField: String
1335
      }
1336
        ');
1337
1338
       $this->assertEquals(
1339
           [],
1340
           $schema->validate()
1341
       );
1342
    }
1343
1344
    /**
1345
     * @it accepts an Object which implements an Interface field along with additional optional arguments
1346
     */
1347
    public function testAcceptsAnObjectWhichImplementsAnInterfaceFieldAlongWithAdditionalOptionalArguments()
1348
    {
1349
        $schema = BuildSchema::build('
1350
      type Query {
1351
        test: AnotherObject
1352
      }
1353
1354
      interface AnotherInterface {
1355
        field(input: String): String
1356
      }
1357
1358
      type AnotherObject implements AnotherInterface {
1359
        field(input: String, anotherInput: String): String
1360
      }
1361
        ');
1362
1363
        $this->assertEquals(
1364
            [],
1365
            $schema->validate()
1366
        );
1367
    }
1368
1369
    /**
1370
     * @it rejects an Object missing an Interface field
1371
     */
1372
    public function testRejectsAnObjectMissingAnInterfaceField()
1373
    {
1374
        $schema = BuildSchema::build('
1375
      type Query {
1376
        test: AnotherObject
1377
      }
1378
1379
      interface AnotherInterface {
1380
        field(input: String): String
1381
      }
1382
1383
      type AnotherObject implements AnotherInterface {
1384
        anotherField: String
1385
      }
1386
        ');
1387
1388
        $this->assertContainsValidationMessage(
1389
            $schema->validate(),
1390
            [[
1391
                'message' => 'Interface field AnotherInterface.field expected but ' .
1392
                             'AnotherObject does not provide it.',
1393
                'locations' => [['line' => 7, 'column' => 9], ['line' => 10, 'column' => 7]],
1394
            ]]
1395
        );
1396
    }
1397
1398
    /**
1399
     * @it rejects an Object with an incorrectly typed Interface field
1400
     */
1401
    public function testRejectsAnObjectWithAnIncorrectlyTypedInterfaceField()
1402
    {
1403
        $schema = BuildSchema::build('
1404
      type Query {
1405
        test: AnotherObject
1406
      }
1407
1408
      interface AnotherInterface {
1409
        field(input: String): String
1410
      }
1411
1412
      type AnotherObject implements AnotherInterface {
1413
        field(input: String): Int
1414
      }
1415
        ');
1416
1417
        $this->assertContainsValidationMessage(
1418
            $schema->validate(),
1419
            [[
1420
                'message' => 'Interface field AnotherInterface.field expects type String but ' .
1421
                    'AnotherObject.field is type Int.',
1422
                'locations' => [['line' => 7, 'column' => 31], ['line' => 11, 'column' => 31]],
1423
            ]]
1424
        );
1425
    }
1426
1427
    /**
1428
     * @it rejects an Object with a differently typed Interface field
1429
     */
1430
    public function testRejectsAnObjectWithADifferentlyTypedInterfaceField()
1431
    {
1432
        $schema = BuildSchema::build('
1433
      type Query {
1434
        test: AnotherObject
1435
      }
1436
1437
      type A { foo: String }
1438
      type B { foo: String }
1439
1440
      interface AnotherInterface {
1441
        field: A
1442
      }
1443
1444
      type AnotherObject implements AnotherInterface {
1445
        field: B
1446
      }
1447
        ');
1448
1449
        $this->assertContainsValidationMessage(
1450
            $schema->validate(),
1451
            [[
1452
                'message' => 'Interface field AnotherInterface.field expects type A but ' .
1453
                    'AnotherObject.field is type B.',
1454
                'locations' => [['line' => 10, 'column' => 16], ['line' => 14, 'column' => 16]],
1455
            ]]
1456
        );
1457
    }
1458
1459
    /**
1460
     * @it accepts an Object with a subtyped Interface field (interface)
1461
     */
1462
    public function testAcceptsAnObjectWithASubtypedInterfaceFieldForInterface()
1463
    {
1464
        $schema = BuildSchema::build('
1465
      type Query {
1466
        test: AnotherObject
1467
      }
1468
1469
      interface AnotherInterface {
1470
        field: AnotherInterface
1471
      }
1472
1473
      type AnotherObject implements AnotherInterface {
1474
        field: AnotherObject
1475
      }
1476
        ');
1477
1478
        $this->assertEquals([], $schema->validate());
1479
    }
1480
1481
    /**
1482
     * @it accepts an Object with a subtyped Interface field (union)
1483
     */
1484
    public function testAcceptsAnObjectWithASubtypedInterfaceFieldForUnion()
1485
    {
1486
        $schema = BuildSchema::build('
1487
      type Query {
1488
        test: AnotherObject
1489
      }
1490
1491
      type SomeObject {
1492
        field: String
1493
      }
1494
1495
      union SomeUnionType = SomeObject
1496
1497
      interface AnotherInterface {
1498
        field: SomeUnionType
1499
      }
1500
1501
      type AnotherObject implements AnotherInterface {
1502
        field: SomeObject
1503
      }
1504
        ');
1505
1506
        $this->assertEquals([],$schema->validate());
1507
    }
1508
1509
    /**
1510
     * @it rejects an Object missing an Interface argument
1511
     */
1512
    public function testRejectsAnObjectMissingAnInterfaceArgument()
1513
    {
1514
        $schema = BuildSchema::build('
1515
      type Query {
1516
        test: AnotherObject
1517
      }
1518
1519
      interface AnotherInterface {
1520
        field(input: String): String
1521
      }
1522
1523
      type AnotherObject implements AnotherInterface {
1524
        field: String
1525
      }
1526
        ');
1527
1528
        $this->assertContainsValidationMessage(
1529
            $schema->validate(),
1530
            [[
1531
                'message' => 'Interface field argument AnotherInterface.field(input:) expected ' .
1532
                             'but AnotherObject.field does not provide it.',
1533
                'locations' => [['line' => 7, 'column' => 15], ['line' => 11, 'column' => 9]],
1534
            ]]
1535
        );
1536
    }
1537
1538
    /**
1539
     * @it rejects an Object with an incorrectly typed Interface argument
1540
     */
1541
    public function testRejectsAnObjectWithAnIncorrectlyTypedInterfaceArgument()
1542
    {
1543
        $schema = BuildSchema::build('
1544
      type Query {
1545
        test: AnotherObject
1546
      }
1547
1548
      interface AnotherInterface {
1549
        field(input: String): String
1550
      }
1551
1552
      type AnotherObject implements AnotherInterface {
1553
        field(input: Int): String
1554
      }
1555
        ');
1556
1557
        $this->assertContainsValidationMessage(
1558
            $schema->validate(),
1559
            [[
1560
                'message' => 'Interface field argument AnotherInterface.field(input:) expects ' .
1561
                             'type String but AnotherObject.field(input:) is type Int.',
1562
                'locations' => [['line' => 7, 'column' => 22], ['line' => 11, 'column' => 22]],
1563
            ]]
1564
        );
1565
    }
1566
1567
    /**
1568
     * @it rejects an Object with both an incorrectly typed field and argument
1569
     */
1570
    public function testRejectsAnObjectWithBothAnIncorrectlyTypedFieldAndArgument()
1571
    {
1572
        $schema = BuildSchema::build('
1573
      type Query {
1574
        test: AnotherObject
1575
      }
1576
1577
      interface AnotherInterface {
1578
        field(input: String): String
1579
      }
1580
1581
      type AnotherObject implements AnotherInterface {
1582
        field(input: Int): Int
1583
      }
1584
        ');
1585
1586
        $this->assertContainsValidationMessage(
1587
            $schema->validate(),
1588
            [[
1589
                'message' => 'Interface field AnotherInterface.field expects type String but ' .
1590
                             'AnotherObject.field is type Int.',
1591
                'locations' => [['line' => 7, 'column' => 31], ['line' => 11, 'column' => 28]],
1592
            ], [
1593
                'message' => 'Interface field argument AnotherInterface.field(input:) expects ' .
1594
                             'type String but AnotherObject.field(input:) is type Int.',
1595
                'locations' => [['line' => 7, 'column' => 22], ['line' => 11, 'column' => 22]],
1596
            ]]
1597
        );
1598
    }
1599
1600
    /**
1601
     * @it rejects an Object which implements an Interface field along with additional required arguments
1602
     */
1603
    public function testRejectsAnObjectWhichImplementsAnInterfaceFieldAlongWithAdditionalRequiredArguments()
1604
    {
1605
        $schema = BuildSchema::build('
1606
      type Query {
1607
        test: AnotherObject
1608
      }
1609
1610
      interface AnotherInterface {
1611
        field(input: String): String
1612
      }
1613
1614
      type AnotherObject implements AnotherInterface {
1615
        field(input: String, anotherInput: String!): String
1616
      }
1617
        ');
1618
1619
        $this->assertContainsValidationMessage(
1620
            $schema->validate(),
1621
            [[
1622
                'message' => 'Object field argument AnotherObject.field(anotherInput:) is of ' .
1623
                             'required type String! but is not also provided by the Interface ' .
1624
                             'field AnotherInterface.field.',
1625
                'locations' => [['line' => 11, 'column' => 44], ['line' => 7, 'column' => 9]],
1626
            ]]
1627
        );
1628
    }
1629
1630
    /**
1631
     * @it accepts an Object with an equivalently wrapped Interface field type
1632
     */
1633
    public function testAcceptsAnObjectWithAnEquivalentlyWrappedInterfaceFieldType()
1634
    {
1635
        $schema = BuildSchema::build('
1636
      type Query {
1637
        test: AnotherObject
1638
      }
1639
1640
      interface AnotherInterface {
1641
        field: [String]!
1642
      }
1643
1644
      type AnotherObject implements AnotherInterface {
1645
        field: [String]!
1646
      }
1647
        ');
1648
1649
        $this->assertEquals([], $schema->validate());
1650
    }
1651
1652
    /**
1653
     * @it rejects an Object with a non-list Interface field list type
1654
     */
1655
    public function testRejectsAnObjectWithANonListInterfaceFieldListType()
1656
    {
1657
        $schema = BuildSchema::build('
1658
      type Query {
1659
        test: AnotherObject
1660
      }
1661
1662
      interface AnotherInterface {
1663
        field: [String]
1664
      }
1665
1666
      type AnotherObject implements AnotherInterface {
1667
        field: String
1668
      }
1669
        ');
1670
1671
        $this->assertContainsValidationMessage(
1672
            $schema->validate(),
1673
            [[
1674
                'message' => 'Interface field AnotherInterface.field expects type [String] ' .
1675
                             'but AnotherObject.field is type String.',
1676
                'locations' => [['line' => 7, 'column' => 16], ['line' => 11, 'column' => 16]],
1677
            ]]
1678
        );
1679
    }
1680
1681
    /**
1682
     * @it rejects an Object with a list Interface field non-list type
1683
     */
1684
    public function testRejectsAnObjectWithAListInterfaceFieldNonListType()
1685
    {
1686
        $schema = BuildSchema::build('
1687
      type Query {
1688
        test: AnotherObject
1689
      }
1690
1691
      interface AnotherInterface {
1692
        field: String
1693
      }
1694
1695
      type AnotherObject implements AnotherInterface {
1696
        field: [String]
1697
      }
1698
        ');
1699
1700
        $this->assertContainsValidationMessage(
1701
            $schema->validate(),
1702
            [[
1703
                'message' => 'Interface field AnotherInterface.field expects type String but ' .
1704
                             'AnotherObject.field is type [String].',
1705
                'locations' => [['line' => 7, 'column' => 16], ['line' => 11, 'column' => 16]],
1706
            ]]
1707
        );
1708
    }
1709
1710
    /**
1711
     * @it accepts an Object with a subset non-null Interface field type
1712
     */
1713
    public function testAcceptsAnObjectWithASubsetNonNullInterfaceFieldType()
1714
    {
1715
        $schema = BuildSchema::build('
1716
      type Query {
1717
        test: AnotherObject
1718
      }
1719
1720
      interface AnotherInterface {
1721
        field: String
1722
      }
1723
1724
      type AnotherObject implements AnotherInterface {
1725
        field: String!
1726
      }
1727
        ');
1728
1729
        $this->assertEquals([], $schema->validate());
1730
    }
1731
1732
    /**
1733
     * @it rejects an Object with a superset nullable Interface field type
1734
     */
1735
    public function testRejectsAnObjectWithASupersetNullableInterfaceFieldType()
1736
    {
1737
        $schema = BuildSchema::build('
1738
      type Query {
1739
        test: AnotherObject
1740
      }
1741
1742
      interface AnotherInterface {
1743
        field: String!
1744
      }
1745
1746
      type AnotherObject implements AnotherInterface {
1747
        field: String
1748
      }
1749
        ');
1750
1751
        $this->assertContainsValidationMessage(
1752
            $schema->validate(),
1753
            [[
1754
                'message' => 'Interface field AnotherInterface.field expects type String! ' .
1755
                             'but AnotherObject.field is type String.',
1756
                'locations' => [['line' => 7, 'column' => 16], ['line' => 11, 'column' => 16]],
1757
            ]]
1758
        );
1759
    }
1760
1761
    public function testRejectsDifferentInstancesOfTheSameType()
1762
    {
1763
        // Invalid: always creates new instance vs returning one from registry
1764
        $typeLoader = function($name) use (&$typeLoader) {
1765
            switch ($name) {
1766
                case 'Query':
1767
                    return new ObjectType([
1768
                        'name' => 'Query',
1769
                        'fields' => [
1770
                            'test' => Type::string()
1771
                        ]
1772
                    ]);
1773
                default:
1774
                    return null;
1775
            }
1776
        };
1777
1778
        $schema = new Schema([
1779
            'query' => $typeLoader('Query'),
1780
            'typeLoader' => $typeLoader
1781
        ]);
1782
        $this->expectException(InvariantViolation::class);
1783
        $this->expectExceptionMessage(
1784
            'Type loader returns different instance for Query than field/argument definitions. '.
1785
            'Make sure you always return the same instance for the same type name.'
1786
        );
1787
        $schema->assertValid();
1788
    }
1789
1790
1791
    private function assertEachCallableThrows($closures, $expectedError)
1792
    {
1793
        foreach ($closures as $index => $factory) {
1794
            try {
1795
                $factory();
1796
                $this->fail('Expected exception not thrown for entry ' . $index);
1797
            } catch (InvariantViolation $e) {
1798
                $this->assertEquals($expectedError, $e->getMessage(), 'Error in callable #' . $index);
1799
            }
1800
        }
1801
    }
1802
1803
    private function schemaWithFieldType($type)
1804
    {
1805
        return new Schema([
1806
            'query' => new ObjectType([
1807
                'name' => 'Query',
1808
                'fields' => ['f' => ['type' => $type]]
1809
            ]),
1810
            'types' => [$type],
1811
        ]);
1812
    }
1813
1814
    private function schemaWithObjectFieldOfType($fieldType)
1815
    {
1816
        $BadObjectType = new ObjectType([
1817
            'name' => 'BadObject',
1818
            'fields' => [
1819
                'badField' => ['type' => $fieldType]
1820
            ]
1821
        ]);
1822
1823
        return new Schema([
1824
            'query' => new ObjectType([
1825
                'name' => 'Query',
1826
                'fields' => [
1827
                    'f' => ['type' => $BadObjectType]
1828
                ]
1829
            ]),
1830
            'types' => [$this->SomeObjectType]
1831
        ]);
1832
    }
1833
1834
    private function withModifiers($types)
1835
    {
1836
        return array_merge(
1837
            $types,
1838
            Utils::map($types, function ($type) {
1839
                return Type::listOf($type);
1840
            }),
1841
            Utils::map($types, function ($type) {
1842
                return Type::nonNull($type);
1843
            }),
1844
            Utils::map($types, function ($type) {
1845
                return Type::nonNull(Type::listOf($type));
1846
            })
1847
        );
1848
    }
1849
1850
    private function schemaWithInterfaceFieldOfType($fieldType)
1851
    {
1852
        $BadInterfaceType = new InterfaceType([
1853
            'name' => 'BadInterface',
1854
            'fields' => [
1855
                'badField' => ['type' => $fieldType]
1856
            ]
1857
        ]);
1858
1859
        return new Schema([
1860
            'query' => new ObjectType([
1861
                'name' => 'Query',
1862
                'fields' => [
1863
                    'f' => ['type' => $BadInterfaceType]
1864
                ]
1865
            ]),
1866
        ]);
1867
    }
1868
1869
    private function schemaWithArgOfType($argType)
1870
    {
1871
        $BadObjectType = new ObjectType([
1872
            'name' => 'BadObject',
1873
            'fields' => [
1874
                'badField' => [
1875
                    'type' => Type::string(),
1876
                    'args' => [
1877
                        'badArg' => ['type' => $argType]
1878
                    ]
1879
                ]
1880
            ]
1881
        ]);
1882
1883
        return new Schema([
1884
            'query' => new ObjectType([
1885
                'name' => 'Query',
1886
                'fields' => [
1887
                    'f' => ['type' => $BadObjectType]
1888
                ]
1889
            ])
1890
        ]);
1891
    }
1892
1893
    private function schemaWithInputFieldOfType($inputFieldType)
1894
    {
1895
        $BadInputObjectType = new InputObjectType([
1896
            'name' => 'BadInputObject',
1897
            'fields' => [
1898
                'badField' => ['type' => $inputFieldType]
1899
            ]
1900
        ]);
1901
1902
        return new Schema([
1903
            'query' => new ObjectType([
1904
                'name' => 'Query',
1905
                'fields' => [
1906
                    'f' => [
1907
                        'type' => Type::string(),
1908
                        'args' => [
1909
                            'badArg' => ['type' => $BadInputObjectType]
1910
                        ]
1911
                    ]
1912
                ]
1913
            ])
1914
        ]);
1915
    }
1916
}
1917