Completed
Push — master ( 6d9275...e17f57 )
by Vladimir
26:45 queued 23:06
created

ValidationTest   F

Complexity

Total Complexity 103

Size/Duplication

Total Lines 2223
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 103
eloc 607
dl 0
loc 2223
rs 1.993
c 0
b 0
f 0

87 Methods

Rating   Name   Duplication   Size   Complexity  
A testAcceptsAnObjectTypeWithFieldsObject() 0 13 1
A testRejectsASchemaWhoseDirectivesAreIncorrectlyTyped() 0 10 1
A testRejectsASchemaWhoseSubscriptionTypeIsAnInputType() 0 41 1
A testRejectsAnObjectTypeWithMissingFields() 0 43 1
A testRejectsASchemaWhoseQueryTypeIsNotAnObjectType() 0 32 1
A testRejectsASchemaWhoseMutationTypeIsAnInputType() 0 41 1
A testRejectsASchemaWithoutAQueryType() 0 28 1
A formatErrors() 0 10 2
A assertEachCallableThrows() 0 8 3
A testAcceptsAnInputObjectTypeWithFields() 0 12 1
A testAcceptsShorthandNotationForFields() 0 12 1
A testRejectsTypesWithoutNames() 0 21 1
A testAcceptsAUnionTypeWithArrayTypes() 0 21 1
A testAcceptsASchemaWhoseQueryAndSubscriptionTypesAreObjectTypes() 0 28 1
A testRejectsFieldArgWithInvalidNames() 0 18 1
A testRejectsAUnionTypeWithNonObjectMembersType() 0 49 2
A assertMatchesValidationMessage() 0 12 4
A tearDown() 0 4 1
A testRejectsAUnionTypeWithEmptyTypes() 0 14 1
A testRejectsAnInputObjectTypeWithMissingFields() 0 14 1
A formatLocations() 0 4 1
A withModifiers() 0 20 1
A testRejectsAUnionTypeWithDuplicatedMemberType() 0 25 1
A schemaWithFieldType() 0 8 1
A testRejectsAnObjectTypeWithIncorrectlyNamedFields() 0 16 1
A testAcceptsASchemaWhoseQueryTypeIsAnObjectType() 0 18 1
A setUp() 0 77 1
A testAcceptsASchemaWhoseQueryAndMutationTypesAreObjectTypes() 0 28 1
A testAcceptsFieldArgsWithValidNames() 0 14 1
A testDoesNotAllowIsDeprecatedWithoutDeprecationReasonOnEnum() 0 11 1
A testAcceptsAnInputTypeAsAFieldArgType() 0 5 2
A testRejectsANonOutputTypeAsAnObjectFieldType() 0 9 2
A testRejectsAnEmptyObjectFieldType() 0 7 1
A testAcceptsAnObjectWhichImplementsAnInterfaceAlongWithMoreFields() 0 20 1
A testRejectsAnEmptyFieldArgType() 0 6 1
A testRejectsInputObjectsWithMultipleNonBreakableCircularReferences() 0 42 1
A testRejectsAnObjectWithBothAnIncorrectlyTypedFieldAndArgument() 0 28 1
A testAcceptsAnObjectWithASubtypedInterfaceFieldForUnion() 0 23 1
A testRejectsANonInputTypeAsAnInputObjectFieldWithLocations() 0 20 1
A testAcceptsAnObjectWithASubsetNonNullInterfaceFieldType() 0 17 1
A testRejectsAnObjectImplementingTheSameInterfaceTwiceDueToExtension() 0 24 1
A testAcceptsAnOutputTypeAsAnInterfaceFieldType() 0 5 2
A testRejectsWithReleventLocationsForANonOutputTypeAsAnObjectFieldType() 0 16 1
A testAcceptsAnInputTypeAsAnInputFieldType() 0 5 2
A testAcceptsAnOutputTypeAsNnObjectFieldType() 0 5 2
A testRejectsAnInputObjectTypeWithIncorrectlyTypedFields() 0 29 1
A testRejectsAnObjectWithAnIncorrectlyTypedInterfaceArgument() 0 22 1
A testRejectsAnObjectMissingAnInterfaceField() 0 22 1
A testAcceptsAnObjectWithASubtypedInterfaceFieldForInterface() 0 17 1
A testRejectsAnEmptyInputFieldType() 0 6 1
A testRejectsAnEnumTypeWithIncorrectlyNamedValues() 0 7 1
A testRejectsANonTypeValueAsAFieldArgType() 0 8 1
A testAcceptsAnObjectWhichImplementsAnInterface() 0 19 1
A invalidEnumValueName() 0 9 1
A testRejectsANonOutputTypeAsAnInterfaceFieldType() 0 10 2
A testRejectsAnEnumTypeWithDuplicateValues() 0 17 1
A testAcceptsAnObjectWhichImplementsAnInterfaceFieldAlongWithAdditionalOptionalArguments() 0 19 1
A testRejectsANonInputTypeAsAnInputFieldType() 0 8 2
A testRejectsAnEnumTypeWithoutValues() 0 14 1
A schemaWithArgOfType() 0 22 1
A testRejectsAnObjectWithAListInterfaceFieldNonListType() 0 22 1
A testRejectsANonTypeValueAsAnObjectFieldType() 0 8 1
A testRejectsAnObjectWithADifferentlyTypedInterfaceField() 0 25 1
A schemaWithObjectFieldOfType() 0 17 1
A testRejectsAAonTypeValueAsAnInputFieldType() 0 8 1
A testRejectsAnObjectWhichImplementsAnInterfaceFieldAlongWithAdditionalRequiredArguments() 0 23 1
A testAcceptsAnObjectWithAnEquivalentlyWrappedInterfaceFieldType() 0 17 1
A testRejectsANonOutputTypeAsAnInterfaceFieldTypeWithLocations() 0 29 1
A testRejectsANonTypeValueAsAnInterfaceFieldType() 0 9 1
A testRejectsAnObjectWithASupersetNullableInterfaceFieldType() 0 22 1
A testRejectsInputObjectsWithNonBreakableCircularReferenceSpreadAcrossThem() 0 28 1
A testRejectsAnInputObjectWithNonBreakableCircularReference() 0 17 1
A schemaWithInputFieldOfType() 0 22 1
A testRejectsAnObjectImplementingANonInterfaceType() 0 20 1
A testRejectsAnObjectWithAnIncorrectlyTypedInterfaceField() 0 22 1
A testANonInputTypeAsAFieldArgWithLocations() 0 16 1
A testRejectsAnObjectWithANonListInterfaceFieldListType() 0 22 1
A testRejectsAnObjectImplementingANonTypeValues() 0 14 1
A testRejectsAnEmptyInterfaceFieldType() 0 8 1
A testRejectsAnInterfaceNotImplementedByAtLeastOneObject() 0 16 1
A testRejectsANonInputTypeAsAFieldArgType() 0 8 2
A testRejectsDifferentInstancesOfTheSameType() 0 27 2
A testRejectsAnObjectImplementingTheSameInterfaceTwice() 0 20 1
A testRejectsAnObjectMissingAnInterfaceArgument() 0 22 1
A schemaWithInterfaceFieldOfType() 0 25 1
A schemaWithEnum() 0 7 1
A testAcceptsAnInputObjectWithBreakableCircularReference() 0 20 1

How to fix   Complexity   

Complex Class

Complex classes like ValidationTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ValidationTest, and based on these observations, apply Extract Interface, too.

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