Completed
Push — master ( ce0272...4e43a2 )
by Vladimir
06:13 queued 03:53
created

DefinitionTest::objectWithIsTypeOf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Tests\Type;
6
7
use GraphQL\Error\InvariantViolation;
8
use GraphQL\Tests\Type\TestClasses\MyCustomType;
9
use GraphQL\Tests\Type\TestClasses\OtherCustom;
10
use GraphQL\Type\Definition\CustomScalarType;
11
use GraphQL\Type\Definition\EnumType;
12
use GraphQL\Type\Definition\InputObjectType;
13
use GraphQL\Type\Definition\InterfaceType;
14
use GraphQL\Type\Definition\ListOfType;
15
use GraphQL\Type\Definition\NonNull;
16
use GraphQL\Type\Definition\ObjectType;
17
use GraphQL\Type\Definition\Type;
18
use GraphQL\Type\Definition\UnionType;
19
use GraphQL\Type\Schema;
20
use GraphQL\Utils\Utils;
21
use PHPUnit\Framework\TestCase;
22
use function count;
23
use function get_class;
24
use function json_encode;
25
use function sprintf;
26
27
class DefinitionTest extends TestCase
28
{
29
    /** @var ObjectType */
30
    public $blogImage;
31
32
    /** @var ObjectType */
33
    public $blogArticle;
34
35
    /** @var ObjectType */
36
    public $blogAuthor;
37
38
    /** @var ObjectType */
39
    public $blogMutation;
40
41
    /** @var ObjectType */
42
    public $blogQuery;
43
44
    /** @var ObjectType */
45
    public $blogSubscription;
46
47
    /** @var ObjectType */
48
    public $objectType;
49
50
    /** @var ObjectType */
51
    public $objectWithIsTypeOf;
52
53
    /** @var InterfaceType */
54
    public $interfaceType;
55
56
    /** @var UnionType */
57
    public $unionType;
58
59
    /** @var EnumType */
60
    public $enumType;
61
62
    /** @var InputObjectType */
63
    public $inputObjectType;
64
65
    /** @var CustomScalarType */
66
    public $scalarType;
67
68
    public function setUp()
69
    {
70
        $this->objectType      = new ObjectType(['name' => 'Object', 'fields' => ['tmp' => Type::string()]]);
71
        $this->interfaceType   = new InterfaceType(['name' => 'Interface']);
72
        $this->unionType       = new UnionType(['name' => 'Union', 'types' => [$this->objectType]]);
73
        $this->enumType        = new EnumType(['name' => 'Enum']);
74
        $this->inputObjectType = new InputObjectType(['name' => 'InputObject']);
75
76
        $this->objectWithIsTypeOf = new ObjectType([
77
            'name'   => 'ObjectWithIsTypeOf',
78
            'fields' => ['f' => ['type' => Type::string()]],
79
        ]);
80
81
        $this->scalarType = new CustomScalarType([
82
            'name'         => 'Scalar',
83
            'serialize'    => function () {
84
            },
85
            'parseValue'   => function () {
86
            },
87
            'parseLiteral' => function () {
88
            },
89
        ]);
90
91
        $this->blogImage = new ObjectType([
92
            'name'   => 'Image',
93
            'fields' => [
94
                'url'    => ['type' => Type::string()],
95
                'width'  => ['type' => Type::int()],
96
                'height' => ['type' => Type::int()],
97
            ],
98
        ]);
99
100
        $this->blogAuthor = new ObjectType([
101
            'name'   => 'Author',
102
            'fields' => function () {
103
                return [
104
                    'id'            => ['type' => Type::string()],
105
                    'name'          => ['type' => Type::string()],
106
                    'pic'           => [
107
                        'type' => $this->blogImage,
108
                        'args' => [
109
                            'width'  => ['type' => Type::int()],
110
                            'height' => ['type' => Type::int()],
111
                        ],
112
                    ],
113
                    'recentArticle' => $this->blogArticle,
114
                ];
115
            },
116
        ]);
117
118
        $this->blogArticle = new ObjectType([
119
            'name'   => 'Article',
120
            'fields' => [
121
                'id'          => ['type' => Type::string()],
122
                'isPublished' => ['type' => Type::boolean()],
123
                'author'      => ['type' => $this->blogAuthor],
124
                'title'       => ['type' => Type::string()],
125
                'body'        => ['type' => Type::string()],
126
            ],
127
        ]);
128
129
        $this->blogQuery = new ObjectType([
130
            'name'   => 'Query',
131
            'fields' => [
132
                'article' => [
133
                    'type' => $this->blogArticle,
134
                    'args' => [
135
                        'id' => ['type' => Type::string()],
136
                    ],
137
                ],
138
                'feed'    => ['type' => new ListOfType($this->blogArticle)],
139
            ],
140
        ]);
141
142
        $this->blogMutation = new ObjectType([
143
            'name'   => 'Mutation',
144
            'fields' => [
145
                'writeArticle' => ['type' => $this->blogArticle],
146
            ],
147
        ]);
148
149
        $this->blogSubscription = new ObjectType([
150
            'name'   => 'Subscription',
151
            'fields' => [
152
                'articleSubscribe' => [
153
                    'args' => ['id' => ['type' => Type::string()]],
154
                    'type' => $this->blogArticle,
155
                ],
156
            ],
157
        ]);
158
    }
159
160
    // Type System: Example
161
162
    /**
163
     * @see it('defines a query only schema')
164
     */
165
    public function testDefinesAQueryOnlySchema() : void
166
    {
167
        $blogSchema = new Schema([
168
            'query' => $this->blogQuery,
169
        ]);
170
171
        $this->assertSame($blogSchema->getQueryType(), $this->blogQuery);
172
173
        $articleField = $this->blogQuery->getField('article');
174
        $this->assertSame($articleField->getType(), $this->blogArticle);
175
        $this->assertSame($articleField->getType()->name, 'Article');
176
        $this->assertSame($articleField->name, 'article');
177
178
        /** @var ObjectType $articleFieldType */
179
        $articleFieldType = $articleField->getType();
180
        $titleField       = $articleFieldType->getField('title');
181
182
        $this->assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $titleField);
183
        $this->assertSame('title', $titleField->name);
184
        $this->assertSame(Type::string(), $titleField->getType());
185
186
        $authorField = $articleFieldType->getField('author');
187
        $this->assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $authorField);
188
189
        /** @var ObjectType $authorFieldType */
190
        $authorFieldType = $authorField->getType();
191
        $this->assertSame($this->blogAuthor, $authorFieldType);
192
193
        $recentArticleField = $authorFieldType->getField('recentArticle');
194
        $this->assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $recentArticleField);
195
        $this->assertSame($this->blogArticle, $recentArticleField->getType());
196
197
        $feedField = $this->blogQuery->getField('feed');
198
        $this->assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $feedField);
199
200
        /** @var ListOfType $feedFieldType */
201
        $feedFieldType = $feedField->getType();
202
        $this->assertInstanceOf('GraphQL\Type\Definition\ListOfType', $feedFieldType);
203
        $this->assertSame($this->blogArticle, $feedFieldType->getWrappedType());
204
    }
205
206
    /**
207
     * @see it('defines a mutation schema')
208
     */
209
    public function testDefinesAMutationSchema() : void
210
    {
211
        $schema = new Schema([
212
            'query'    => $this->blogQuery,
213
            'mutation' => $this->blogMutation,
214
        ]);
215
216
        $this->assertSame($this->blogMutation, $schema->getMutationType());
217
        $writeMutation = $this->blogMutation->getField('writeArticle');
218
219
        $this->assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $writeMutation);
220
        $this->assertSame($this->blogArticle, $writeMutation->getType());
221
        $this->assertSame('Article', $writeMutation->getType()->name);
222
        $this->assertSame('writeArticle', $writeMutation->name);
223
    }
224
225
    /**
226
     * @see it('defines a subscription schema')
227
     */
228
    public function testDefinesSubscriptionSchema() : void
229
    {
230
        $schema = new Schema([
231
            'query'        => $this->blogQuery,
232
            'subscription' => $this->blogSubscription,
233
        ]);
234
235
        $this->assertEquals($this->blogSubscription, $schema->getSubscriptionType());
236
237
        $sub = $this->blogSubscription->getField('articleSubscribe');
238
        $this->assertEquals($sub->getType(), $this->blogArticle);
239
        $this->assertEquals($sub->getType()->name, 'Article');
240
        $this->assertEquals($sub->name, 'articleSubscribe');
241
    }
242
243
    /**
244
     * @see it('defines an enum type with deprecated value')
245
     */
246
    public function testDefinesEnumTypeWithDeprecatedValue() : void
247
    {
248
        $enumTypeWithDeprecatedValue = new EnumType([
249
            'name'   => 'EnumWithDeprecatedValue',
250
            'values' => [
251
                'foo' => ['deprecationReason' => 'Just because'],
252
            ],
253
        ]);
254
255
        $value = $enumTypeWithDeprecatedValue->getValues()[0];
256
257
        $this->assertArraySubset(
258
            [
259
                'name'              => 'foo',
260
                'description'       => null,
261
                'deprecationReason' => 'Just because',
262
                'value'             => 'foo',
263
                'astNode'           => null,
264
            ],
265
            (array) $value
266
        );
267
268
        $this->assertEquals(true, $value->isDeprecated());
269
    }
270
271
    /**
272
     * @see it('defines an enum type with a value of `null` and `undefined`')
273
     */
274
    public function testDefinesAnEnumTypeWithAValueOfNullAndUndefined() : void
275
    {
276
        $EnumTypeWithNullishValue = new EnumType([
277
            'name'   => 'EnumWithNullishValue',
278
            'values' => [
279
                'NULL'      => ['value' => null],
280
                'UNDEFINED' => ['value' => null],
281
            ],
282
        ]);
283
284
        $expected = [
285
            [
286
                'name'              => 'NULL',
287
                'description'       => null,
288
                'deprecationReason' => null,
289
                'value'             => null,
290
                'astNode'           => null,
291
            ],
292
            [
293
                'name'              => 'UNDEFINED',
294
                'description'       => null,
295
                'deprecationReason' => null,
296
                'value'             => null,
297
                'astNode'           => null,
298
            ],
299
        ];
300
301
        $actual = $EnumTypeWithNullishValue->getValues();
302
303
        $this->assertEquals(count($expected), count($actual));
304
        $this->assertArraySubset($expected[0], (array) $actual[0]);
305
        $this->assertArraySubset($expected[1], (array) $actual[1]);
306
    }
307
308
    /**
309
     * @see it('defines an object type with deprecated field')
310
     */
311
    public function testDefinesAnObjectTypeWithDeprecatedField() : void
312
    {
313
        $TypeWithDeprecatedField = new ObjectType([
314
            'name'   => 'foo',
315
            'fields' => [
316
                'bar' => [
317
                    'type'              => Type::string(),
318
                    'deprecationReason' => 'A terrible reason',
319
                ],
320
            ],
321
        ]);
322
323
        $field = $TypeWithDeprecatedField->getField('bar');
324
325
        $this->assertEquals(Type::string(), $field->getType());
326
        $this->assertEquals(true, $field->isDeprecated());
327
        $this->assertEquals('A terrible reason', $field->deprecationReason);
328
        $this->assertEquals('bar', $field->name);
329
        $this->assertEquals([], $field->args);
330
    }
331
332
    /**
333
     * @see it('includes nested input objects in the map')
334
     */
335
    public function testIncludesNestedInputObjectInTheMap() : void
336
    {
337
        $nestedInputObject = new InputObjectType([
338
            'name'   => 'NestedInputObject',
339
            'fields' => ['value' => ['type' => Type::string()]],
340
        ]);
341
        $someInputObject   = new InputObjectType([
342
            'name'   => 'SomeInputObject',
343
            'fields' => ['nested' => ['type' => $nestedInputObject]],
344
        ]);
345
        $someMutation      = new ObjectType([
346
            'name'   => 'SomeMutation',
347
            'fields' => [
348
                'mutateSomething' => [
349
                    'type' => $this->blogArticle,
350
                    'args' => ['input' => ['type' => $someInputObject]],
351
                ],
352
            ],
353
        ]);
354
355
        $schema = new Schema([
356
            'query'    => $this->blogQuery,
357
            'mutation' => $someMutation,
358
        ]);
359
        $this->assertSame($nestedInputObject, $schema->getType('NestedInputObject'));
360
    }
361
362
    /**
363
     * @see it('includes interface possible types in the type map')
364
     */
365
    public function testIncludesInterfaceSubtypesInTheTypeMap() : void
366
    {
367
        $someInterface = new InterfaceType([
368
            'name'   => 'SomeInterface',
369
            'fields' => [
370
                'f' => ['type' => Type::int()],
371
            ],
372
        ]);
373
374
        $someSubtype = new ObjectType([
375
            'name'       => 'SomeSubtype',
376
            'fields'     => [
377
                'f' => ['type' => Type::int()],
378
            ],
379
            'interfaces' => [$someInterface],
380
        ]);
381
382
        $schema = new Schema([
383
            'query' => new ObjectType([
384
                'name'   => 'Query',
385
                'fields' => [
386
                    'iface' => ['type' => $someInterface],
387
                ],
388
            ]),
389
            'types' => [$someSubtype],
390
        ]);
391
        $this->assertSame($someSubtype, $schema->getType('SomeSubtype'));
392
    }
393
394
    /**
395
     * @see it('includes interfaces' thunk subtypes in the type map')
396
     */
397
    public function testIncludesInterfacesThunkSubtypesInTheTypeMap() : void
398
    {
399
        $someInterface = null;
400
401
        $someSubtype = new ObjectType([
402
            'name'       => 'SomeSubtype',
403
            'fields'     => [
404
                'f' => ['type' => Type::int()],
405
            ],
406
            'interfaces' => function () use (&$someInterface) {
407
                return [$someInterface];
408
            },
409
        ]);
410
411
        $someInterface = new InterfaceType([
412
            'name'   => 'SomeInterface',
413
            'fields' => [
414
                'f' => ['type' => Type::int()],
415
            ],
416
        ]);
417
418
        $schema = new Schema([
419
            'query' => new ObjectType([
420
                'name'   => 'Query',
421
                'fields' => [
422
                    'iface' => ['type' => $someInterface],
423
                ],
424
            ]),
425
            'types' => [$someSubtype],
426
        ]);
427
428
        $this->assertSame($someSubtype, $schema->getType('SomeSubtype'));
429
    }
430
431
    /**
432
     * @see it('stringifies simple types')
433
     */
434
    public function testStringifiesSimpleTypes() : void
435
    {
436
        $this->assertSame('Int', (string) Type::int());
437
        $this->assertSame('Article', (string) $this->blogArticle);
438
439
        $this->assertSame('Interface', (string) $this->interfaceType);
440
        $this->assertSame('Union', (string) $this->unionType);
441
        $this->assertSame('Enum', (string) $this->enumType);
442
        $this->assertSame('InputObject', (string) $this->inputObjectType);
443
        $this->assertSame('Object', (string) $this->objectType);
444
445
        $this->assertSame('Int!', (string) new NonNull(Type::int()));
446
        $this->assertSame('[Int]', (string) new ListOfType(Type::int()));
447
        $this->assertSame('[Int]!', (string) new NonNull(new ListOfType(Type::int())));
448
        $this->assertSame('[Int!]', (string) new ListOfType(new NonNull(Type::int())));
449
        $this->assertSame('[[Int]]', (string) new ListOfType(new ListOfType(Type::int())));
450
    }
451
452
    /**
453
     * @see it('JSON stringifies simple types')
454
     */
455
    public function testJSONStringifiesSimpleTypes() : void
456
    {
457
        $this->assertEquals('"Int"', json_encode(Type::int()));
458
        $this->assertEquals('"Article"', json_encode($this->blogArticle));
459
        $this->assertEquals('"Interface"', json_encode($this->interfaceType));
460
        $this->assertEquals('"Union"', json_encode($this->unionType));
461
        $this->assertEquals('"Enum"', json_encode($this->enumType));
462
        $this->assertEquals('"InputObject"', json_encode($this->inputObjectType));
463
        $this->assertEquals('"Int!"', json_encode(Type::nonNull(Type::int())));
464
        $this->assertEquals('"[Int]"', json_encode(Type::listOf(Type::int())));
465
        $this->assertEquals('"[Int]!"', json_encode(Type::nonNull(Type::listOf(Type::int()))));
466
        $this->assertEquals('"[Int!]"', json_encode(Type::listOf(Type::nonNull(Type::int()))));
467
        $this->assertEquals('"[[Int]]"', json_encode(Type::listOf(Type::listOf(Type::int()))));
468
    }
469
470
    /**
471
     * @see it('identifies input types')
472
     */
473
    public function testIdentifiesInputTypes() : void
474
    {
475
        $expected = [
476
            [Type::int(), true],
477
            [$this->objectType, false],
478
            [$this->interfaceType, false],
479
            [$this->unionType, false],
480
            [$this->enumType, true],
481
            [$this->inputObjectType, true],
482
        ];
483
484
        foreach ($expected as $index => $entry) {
485
            $this->assertSame(
486
                $entry[1],
487
                Type::isInputType($entry[0]),
488
                sprintf('Type %s was detected incorrectly', $entry[0])
489
            );
490
        }
491
    }
492
493
    /**
494
     * @see it('identifies output types')
495
     */
496
    public function testIdentifiesOutputTypes() : void
497
    {
498
        $expected = [
499
            [Type::int(), true],
500
            [$this->objectType, true],
501
            [$this->interfaceType, true],
502
            [$this->unionType, true],
503
            [$this->enumType, true],
504
            [$this->inputObjectType, false],
505
        ];
506
507
        foreach ($expected as $index => $entry) {
508
            $this->assertSame(
509
                $entry[1],
510
                Type::isOutputType($entry[0]),
511
                sprintf('Type %s was detected incorrectly', $entry[0])
512
            );
513
        }
514
    }
515
516
    /**
517
     * @see it('prohibits nesting NonNull inside NonNull')
518
     */
519
    public function testProhibitsNestingNonNullInsideNonNull() : void
520
    {
521
        $this->expectException(InvariantViolation::class);
522
        $this->expectExceptionMessage(
523
            'Expected Int! to be a GraphQL nullable type.'
524
        );
525
        Type::nonNull(Type::nonNull(Type::int()));
526
    }
527
528
    /**
529
     * @see it('allows a thunk for Union member types')
530
     */
531
    public function testAllowsThunkForUnionTypes() : void
532
    {
533
        $union = new UnionType([
534
            'name'  => 'ThunkUnion',
535
            'types' => function () {
536
                return [$this->objectType];
537
            },
538
        ]);
539
540
        $types = $union->getTypes();
541
        $this->assertEquals(1, count($types));
542
        $this->assertSame($this->objectType, $types[0]);
543
    }
544
545
    public function testAllowsRecursiveDefinitions() : void
546
    {
547
        // See https://github.com/webonyx/graphql-php/issues/16
548
        $node = new InterfaceType([
549
            'name'   => 'Node',
550
            'fields' => [
551
                'id' => ['type' => Type::nonNull(Type::id())],
552
            ],
553
        ]);
554
555
        $blog   = null;
556
        $called = false;
557
558
        $user = new ObjectType([
559
            'name'       => 'User',
560
            'fields'     => function () use (&$blog, &$called) {
561
                $this->assertNotNull($blog, 'Blog type is expected to be defined at this point, but it is null');
562
                $called = true;
563
564
                return [
565
                    'id'    => ['type' => Type::nonNull(Type::id())],
566
                    'blogs' => ['type' => Type::nonNull(Type::listOf(Type::nonNull($blog)))],
567
                ];
568
            },
569
            'interfaces' => function () use ($node) {
570
                return [$node];
571
            },
572
        ]);
573
574
        $blog = new ObjectType([
575
            'name'       => 'Blog',
576
            'fields'     => function () use ($user) {
577
                return [
578
                    'id'    => ['type' => Type::nonNull(Type::id())],
579
                    'owner' => ['type' => Type::nonNull($user)],
580
                ];
581
            },
582
            'interfaces' => function () use ($node) {
583
                return [$node];
584
            },
585
        ]);
586
587
        $schema = new Schema([
588
            'query' => new ObjectType([
589
                'name'   => 'Query',
590
                'fields' => [
591
                    'node' => ['type' => $node],
592
                ],
593
            ]),
594
            'types' => [$user, $blog],
595
        ]);
596
597
        $this->assertTrue($called);
598
        $schema->getType('Blog');
599
600
        $this->assertEquals([$node], $blog->getInterfaces());
601
        $this->assertEquals([$node], $user->getInterfaces());
602
603
        $this->assertNotNull($user->getField('blogs'));
604
        $this->assertSame($blog, $user->getField('blogs')->getType()->getWrappedType(true));
0 ignored issues
show
Bug introduced by
The method getWrappedType() does not exist on GraphQL\Type\Definition\Type. It seems like you code against a sub-type of GraphQL\Type\Definition\Type such as GraphQL\Type\Definition\ListOfType or GraphQL\Type\Definition\NonNull. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

604
        $this->assertSame($blog, $user->getField('blogs')->getType()->/** @scrutinizer ignore-call */ getWrappedType(true));
Loading history...
605
606
        $this->assertNotNull($blog->getField('owner'));
607
        $this->assertSame($user, $blog->getField('owner')->getType()->getWrappedType(true));
608
    }
609
610
    public function testInputObjectTypeAllowsRecursiveDefinitions() : void
611
    {
612
        $called      = false;
613
        $inputObject = new InputObjectType([
614
            'name'   => 'InputObject',
615
            'fields' => function () use (&$inputObject, &$called) {
616
                $called = true;
617
618
                return [
619
                    'value'  => ['type' => Type::string()],
620
                    'nested' => ['type' => $inputObject],
621
                ];
622
            },
623
        ]);
624
625
        $someMutation = new ObjectType([
626
            'name'   => 'SomeMutation',
627
            'fields' => [
628
                'mutateSomething' => [
629
                    'type' => $this->blogArticle,
630
                    'args' => ['input' => ['type' => $inputObject]],
631
                ],
632
            ],
633
        ]);
634
635
        $schema = new Schema([
636
            'query'    => $this->blogQuery,
637
            'mutation' => $someMutation,
638
        ]);
639
640
        $this->assertSame($inputObject, $schema->getType('InputObject'));
641
        $this->assertTrue($called);
642
        $this->assertEquals(count($inputObject->getFields()), 2);
643
        $this->assertSame($inputObject->getField('nested')->getType(), $inputObject);
644
        $this->assertSame($someMutation->getField('mutateSomething')->getArg('input')->getType(), $inputObject);
645
    }
646
647
    public function testInterfaceTypeAllowsRecursiveDefinitions() : void
648
    {
649
        $called    = false;
650
        $interface = new InterfaceType([
651
            'name'   => 'SomeInterface',
652
            'fields' => function () use (&$interface, &$called) {
653
                $called = true;
654
655
                return [
656
                    'value'  => ['type' => Type::string()],
657
                    'nested' => ['type' => $interface],
658
                ];
659
            },
660
        ]);
661
662
        $query = new ObjectType([
663
            'name'   => 'Query',
664
            'fields' => [
665
                'test' => ['type' => $interface],
666
            ],
667
        ]);
668
669
        $schema = new Schema(['query' => $query]);
670
671
        $this->assertSame($interface, $schema->getType('SomeInterface'));
672
        $this->assertTrue($called);
673
        $this->assertEquals(count($interface->getFields()), 2);
674
        $this->assertSame($interface->getField('nested')->getType(), $interface);
675
        $this->assertSame($interface->getField('value')->getType(), Type::string());
676
    }
677
678
    public function testAllowsShorthandFieldDefinition() : void
679
    {
680
        $interface = new InterfaceType([
681
            'name'   => 'SomeInterface',
682
            'fields' => function () use (&$interface) {
683
                return [
684
                    'value'   => Type::string(),
685
                    'nested'  => $interface,
686
                    'withArg' => [
687
                        'type' => Type::string(),
688
                        'args' => [
689
                            'arg1' => Type::int(),
690
                        ],
691
                    ],
692
                ];
693
            },
694
        ]);
695
696
        $query = new ObjectType([
697
            'name'   => 'Query',
698
            'fields' => ['test' => $interface],
699
        ]);
700
701
        $schema = new Schema(['query' => $query]);
702
703
        $valueField  = $schema->getType('SomeInterface')->getField('value');
0 ignored issues
show
Bug introduced by
The method getField() does not exist on GraphQL\Type\Definition\Type. It seems like you code against a sub-type of GraphQL\Type\Definition\Type such as GraphQL\Type\Definition\InterfaceType or GraphQL\Type\Definition\ObjectType or GraphQL\Type\Definition\InputObjectType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

703
        $valueField  = $schema->getType('SomeInterface')->/** @scrutinizer ignore-call */ getField('value');
Loading history...
704
        $nestedField = $schema->getType('SomeInterface')->getField('nested');
705
706
        $this->assertEquals(Type::string(), $valueField->getType());
707
        $this->assertEquals($interface, $nestedField->getType());
708
709
        $withArg = $schema->getType('SomeInterface')->getField('withArg');
710
        $this->assertEquals(Type::string(), $withArg->getType());
711
712
        $this->assertEquals('arg1', $withArg->args[0]->name);
713
        $this->assertEquals(Type::int(), $withArg->args[0]->getType());
714
715
        $testField = $schema->getType('Query')->getField('test');
716
        $this->assertEquals($interface, $testField->getType());
717
        $this->assertEquals('test', $testField->name);
718
    }
719
720
    public function testInfersNameFromClassname() : void
721
    {
722
        $myObj = new MyCustomType();
723
        $this->assertEquals('MyCustom', $myObj->name);
724
725
        $otherCustom = new OtherCustom();
726
        $this->assertEquals('OtherCustom', $otherCustom->name);
727
    }
728
729
    public function testAllowsOverridingInternalTypes() : void
730
    {
731
        $idType = new CustomScalarType([
732
            'name'         => 'ID',
733
            'serialize'    => function () {
734
            },
735
            'parseValue'   => function () {
736
            },
737
            'parseLiteral' => function () {
738
            },
739
        ]);
740
741
        $schema = new Schema([
742
            'query' => new ObjectType(['name' => 'Query', 'fields' => []]),
743
            'types' => [$idType],
744
        ]);
745
746
        $this->assertSame($idType, $schema->getType('ID'));
747
    }
748
749
    // Field config must be object
750
751
    /**
752
     * @see it('accepts an Object type with a field function')
753
     */
754
    public function testAcceptsAnObjectTypeWithAFieldFunction() : void
755
    {
756
        $objType = new ObjectType([
757
            'name'   => 'SomeObject',
758
            'fields' => function () {
759
                return [
760
                    'f' => ['type' => Type::string()],
761
                ];
762
            },
763
        ]);
764
        $objType->assertValid(true);
0 ignored issues
show
Unused Code introduced by
The call to GraphQL\Type\Definition\ObjectType::assertValid() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

764
        $objType->/** @scrutinizer ignore-call */ 
765
                  assertValid(true);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
765
        $this->assertSame(Type::string(), $objType->getField('f')->getType());
766
    }
767
768
    /**
769
     * @see it('rejects an Object type field with undefined config')
770
     */
771
    public function testRejectsAnObjectTypeFieldWithUndefinedConfig() : void
772
    {
773
        $objType = new ObjectType([
774
            'name'   => 'SomeObject',
775
            'fields' => ['f' => null],
776
        ]);
777
        $this->expectException(InvariantViolation::class);
778
        $this->expectExceptionMessage(
779
            'SomeObject.f field config must be an array, but got: null'
780
        );
781
        $objType->getFields();
782
    }
783
784
    /**
785
     * @see it('rejects an Object type with incorrectly typed fields')
786
     */
787
    public function testRejectsAnObjectTypeWithIncorrectlyTypedFields() : void
788
    {
789
        $objType = new ObjectType([
790
            'name'   => 'SomeObject',
791
            'fields' => [['field' => Type::string()]],
792
        ]);
793
        $this->expectException(InvariantViolation::class);
794
        $this->expectExceptionMessage(
795
            'SomeObject fields must be an associative array with field names as keys or a ' .
796
            'function which returns such an array.'
797
        );
798
        $objType->getFields();
799
    }
800
801
    /**
802
     * @see it('rejects an Object type with a field function that returns incorrect type')
803
     */
804
    public function testRejectsAnObjectTypeWithAFieldFunctionThatReturnsIncorrectType() : void
805
    {
806
        $objType = new ObjectType([
807
            'name'   => 'SomeObject',
808
            'fields' => function () {
809
                return [['field' => Type::string()]];
810
            },
811
        ]);
812
        $this->expectException(InvariantViolation::class);
813
        $this->expectExceptionMessage(
814
            'SomeObject fields must be an associative array with field names as keys or a ' .
815
            'function which returns such an array.'
816
        );
817
        $objType->getFields();
818
    }
819
820
    // Field arg config must be object
821
822
    /**
823
     * @see it('accepts an Object type with field args')
824
     */
825
    public function testAcceptsAnObjectTypeWithFieldArgs() : void
826
    {
827
        $this->expectNotToPerformAssertions();
828
        $objType = new ObjectType([
829
            'name'   => 'SomeObject',
830
            'fields' => [
831
                'goodField' => [
832
                    'type' => Type::string(),
833
                    'args' => [
834
                        'goodArg' => ['type' => Type::string()],
835
                    ],
836
                ],
837
            ],
838
        ]);
839
        // Should not throw:
840
        $objType->assertValid();
841
    }
842
843
    // rejects an Object type with incorrectly typed field args
844
845
    /**
846
     * @see it('does not allow isDeprecated without deprecationReason on field')
847
     */
848
    public function testDoesNotAllowIsDeprecatedWithoutDeprecationReasonOnField() : void
849
    {
850
        $OldObject = new ObjectType([
851
            'name'   => 'OldObject',
852
            'fields' => [
853
                'field' => [
854
                    'type'         => Type::string(),
855
                    'isDeprecated' => true,
856
                ],
857
            ],
858
        ]);
859
860
        $this->expectException(InvariantViolation::class);
861
        $this->expectExceptionMessage(
862
            'OldObject.field should provide "deprecationReason" instead of "isDeprecated".'
863
        );
864
        $OldObject->assertValid();
865
    }
866
867
    // Object interfaces must be array
868
869
    /**
870
     * @see it('accepts an Object type with array interfaces')
871
     */
872
    public function testAcceptsAnObjectTypeWithArrayInterfaces() : void
873
    {
874
        $objType = new ObjectType([
875
            'name'       => 'SomeObject',
876
            'interfaces' => [$this->interfaceType],
877
            'fields'     => ['f' => ['type' => Type::string()]],
878
        ]);
879
        $this->assertSame($this->interfaceType, $objType->getInterfaces()[0]);
880
    }
881
882
    /**
883
     * @see it('accepts an Object type with interfaces as a function returning an array')
884
     */
885
    public function testAcceptsAnObjectTypeWithInterfacesAsAFunctionReturningAnArray() : void
886
    {
887
        $objType = new ObjectType([
888
            'name'       => 'SomeObject',
889
            'interfaces' => function () {
890
                return [$this->interfaceType];
891
            },
892
            'fields'     => ['f' => ['type' => Type::string()]],
893
        ]);
894
        $this->assertSame($this->interfaceType, $objType->getInterfaces()[0]);
895
    }
896
897
    /**
898
     * @see it('rejects an Object type with incorrectly typed interfaces')
899
     */
900
    public function testRejectsAnObjectTypeWithIncorrectlyTypedInterfaces() : void
901
    {
902
        $objType = new ObjectType([
903
            'name'       => 'SomeObject',
904
            'interfaces' => new \stdClass(),
905
            'fields'     => ['f' => ['type' => Type::string()]],
906
        ]);
907
        $this->expectException(InvariantViolation::class);
908
        $this->expectExceptionMessage(
909
            'SomeObject interfaces must be an Array or a callable which returns an Array.'
910
        );
911
        $objType->getInterfaces();
912
    }
913
914
    /**
915
     * @see it('rejects an Object type with interfaces as a function returning an incorrect type')
916
     */
917
    public function testRejectsAnObjectTypeWithInterfacesAsAFunctionReturningAnIncorrectType() : void
918
    {
919
        $objType = new ObjectType([
920
            'name'       => 'SomeObject',
921
            'interfaces' => function () {
922
                return new \stdClass();
923
            },
924
            'fields'     => ['f' => ['type' => Type::string()]],
925
        ]);
926
        $this->expectException(InvariantViolation::class);
927
        $this->expectExceptionMessage(
928
            'SomeObject interfaces must be an Array or a callable which returns an Array.'
929
        );
930
        $objType->getInterfaces();
931
    }
932
933
    // Type System: Object fields must have valid resolve values
934
935
    /**
936
     * @see it('accepts a lambda as an Object field resolver')
937
     */
938
    public function testAcceptsALambdaAsAnObjectFieldResolver() : void
939
    {
940
        $this->expectNotToPerformAssertions();
941
        // should not throw:
942
        $this->schemaWithObjectWithFieldResolver(function () {
943
        });
944
    }
945
946
    private function schemaWithObjectWithFieldResolver($resolveValue)
947
    {
948
        $BadResolverType = new ObjectType([
949
            'name'   => 'BadResolver',
950
            'fields' => [
951
                'badField' => [
952
                    'type'    => Type::string(),
953
                    'resolve' => $resolveValue,
954
                ],
955
            ],
956
        ]);
957
958
        $schema = new Schema([
959
            'query' => new ObjectType([
960
                'name'   => 'Query',
961
                'fields' => [
962
                    'f' => ['type' => $BadResolverType],
963
                ],
964
            ]),
965
        ]);
966
        $schema->assertValid();
967
968
        return $schema;
969
    }
970
971
    /**
972
     * @see it('rejects an empty Object field resolver')
973
     */
974
    public function testRejectsAnEmptyObjectFieldResolver() : void
975
    {
976
        $this->expectException(InvariantViolation::class);
977
        $this->expectExceptionMessage(
978
            'BadResolver.badField field resolver must be a function if provided, but got: []'
979
        );
980
        $this->schemaWithObjectWithFieldResolver([]);
981
    }
982
983
    /**
984
     * @see it('rejects a constant scalar value resolver')
985
     */
986
    public function testRejectsAConstantScalarValueResolver() : void
987
    {
988
        $this->expectException(InvariantViolation::class);
989
        $this->expectExceptionMessage(
990
            'BadResolver.badField field resolver must be a function if provided, but got: 0'
991
        );
992
        $this->schemaWithObjectWithFieldResolver(0);
993
    }
994
995
    // Type System: Interface types must be resolvable
996
997
    /**
998
     * @see it('accepts an Interface type defining resolveType')
999
     */
1000
    public function testAcceptsAnInterfaceTypeDefiningResolveType() : void
1001
    {
1002
        $this->expectNotToPerformAssertions();
1003
        $AnotherInterfaceType = new InterfaceType([
1004
            'name'   => 'AnotherInterface',
1005
            'fields' => ['f' => ['type' => Type::string()]],
1006
        ]);
1007
1008
        // Should not throw:
1009
        $this->schemaWithFieldType(
1010
            new ObjectType([
1011
                'name'       => 'SomeObject',
1012
                'interfaces' => [$AnotherInterfaceType],
1013
                'fields'     => ['f' => ['type' => Type::string()]],
1014
            ])
1015
        );
1016
    }
1017
1018
    private function schemaWithFieldType($type)
1019
    {
1020
        $schema = new Schema([
1021
            'query' => new ObjectType([
1022
                'name'   => 'Query',
1023
                'fields' => ['field' => ['type' => $type]],
1024
            ]),
1025
            'types' => [$type],
1026
        ]);
1027
        $schema->assertValid();
1028
1029
        return $schema;
1030
    }
1031
1032
    /**
1033
     * @see it('accepts an Interface with implementing type defining isTypeOf')
1034
     */
1035
    public function testAcceptsAnInterfaceWithImplementingTypeDefiningIsTypeOf() : void
1036
    {
1037
        $this->expectNotToPerformAssertions();
1038
        $InterfaceTypeWithoutResolveType = new InterfaceType([
1039
            'name'   => 'InterfaceTypeWithoutResolveType',
1040
            'fields' => ['f' => ['type' => Type::string()]],
1041
        ]);
1042
1043
        // Should not throw:
1044
        $this->schemaWithFieldType(
1045
            new ObjectType([
1046
                'name'       => 'SomeObject',
1047
                'interfaces' => [$InterfaceTypeWithoutResolveType],
1048
                'fields'     => ['f' => ['type' => Type::string()]],
1049
            ])
1050
        );
1051
    }
1052
1053
    /**
1054
     * @see it('accepts an Interface type defining resolveType with implementing type defining isTypeOf')
1055
     */
1056
    public function testAcceptsAnInterfaceTypeDefiningResolveTypeWithImplementingTypeDefiningIsTypeOf() : void
1057
    {
1058
        $this->expectNotToPerformAssertions();
1059
        $AnotherInterfaceType = new InterfaceType([
1060
            'name'   => 'AnotherInterface',
1061
            'fields' => ['f' => ['type' => Type::string()]],
1062
        ]);
1063
1064
        // Should not throw:
1065
        $this->schemaWithFieldType(
1066
            new ObjectType([
1067
                'name'       => 'SomeObject',
1068
                'interfaces' => [$AnotherInterfaceType],
1069
                'fields'     => ['f' => ['type' => Type::string()]],
1070
            ])
1071
        );
1072
    }
1073
1074
    /**
1075
     * @see it('rejects an Interface type with an incorrect type for resolveType')
1076
     */
1077
    public function testRejectsAnInterfaceTypeWithAnIncorrectTypeForResolveType() : void
1078
    {
1079
        $this->expectException(InvariantViolation::class);
1080
        $this->expectExceptionMessage(
1081
            'AnotherInterface must provide "resolveType" as a function, but got: instance of stdClass'
1082
        );
1083
1084
        $type = new InterfaceType([
1085
            'name'        => 'AnotherInterface',
1086
            'resolveType' => new \stdClass(),
1087
            'fields'      => ['f' => ['type' => Type::string()]],
1088
        ]);
1089
        $type->assertValid();
1090
    }
1091
1092
    // Type System: Union types must be resolvable
1093
1094
    /**
1095
     * @see it('accepts a Union type defining resolveType')
1096
     */
1097
    public function testAcceptsAUnionTypeDefiningResolveType() : void
1098
    {
1099
        $this->expectNotToPerformAssertions();
1100
        // Should not throw:
1101
        $this->schemaWithFieldType(
1102
            new UnionType([
1103
                'name'  => 'SomeUnion',
1104
                'types' => [$this->objectType],
1105
            ])
1106
        );
1107
    }
1108
1109
    /**
1110
     * @see it('accepts a Union of Object types defining isTypeOf')
1111
     */
1112
    public function testAcceptsAUnionOfObjectTypesDefiningIsTypeOf() : void
1113
    {
1114
        $this->expectNotToPerformAssertions();
1115
        // Should not throw:
1116
        $this->schemaWithFieldType(
1117
            new UnionType([
1118
                'name'  => 'SomeUnion',
1119
                'types' => [$this->objectWithIsTypeOf],
1120
            ])
1121
        );
1122
    }
1123
1124
    /**
1125
     * @see it('accepts a Union type defining resolveType of Object types defining isTypeOf')
1126
     */
1127
    public function testAcceptsAUnionTypeDefiningResolveTypeOfObjectTypesDefiningIsTypeOf() : void
1128
    {
1129
        $this->expectNotToPerformAssertions();
1130
        // Should not throw:
1131
        $this->schemaWithFieldType(
1132
            new UnionType([
1133
                'name'  => 'SomeUnion',
1134
                'types' => [$this->objectWithIsTypeOf],
1135
            ])
1136
        );
1137
    }
1138
1139
    /**
1140
     * @see it('rejects an Union type with an incorrect type for resolveType')
1141
     */
1142
    public function testRejectsAnUnionTypeWithAnIncorrectTypeForResolveType() : void
1143
    {
1144
        $this->expectException(InvariantViolation::class);
1145
        $this->expectExceptionMessage(
1146
            'SomeUnion must provide "resolveType" as a function, but got: instance of stdClass'
1147
        );
1148
        $this->schemaWithFieldType(
1149
            new UnionType([
1150
                'name'        => 'SomeUnion',
1151
                'resolveType' => new \stdClass(),
1152
                'types'       => [$this->objectWithIsTypeOf],
1153
            ])
1154
        );
1155
    }
1156
1157
    /**
1158
     * @see it('accepts a Scalar type defining serialize')
1159
     */
1160
    public function testAcceptsAScalarTypeDefiningSerialize() : void
1161
    {
1162
        $this->expectNotToPerformAssertions();
1163
        // Should not throw
1164
        $this->schemaWithFieldType(
1165
            new CustomScalarType([
1166
                'name'      => 'SomeScalar',
1167
                'serialize' => function () {
1168
                    return null;
1169
                },
1170
            ])
1171
        );
1172
    }
1173
1174
    // Type System: Scalar types must be serializable
1175
1176
    /**
1177
     * @see it('rejects a Scalar type not defining serialize')
1178
     */
1179
    public function testRejectsAScalarTypeNotDefiningSerialize() : void
1180
    {
1181
        $this->expectException(InvariantViolation::class);
1182
        $this->expectExceptionMessage(
1183
            'SomeScalar must provide "serialize" function. If this custom Scalar ' .
1184
            'is also used as an input type, ensure "parseValue" and "parseLiteral" ' .
1185
            'functions are also provided.'
1186
        );
1187
        $this->schemaWithFieldType(
1188
            new CustomScalarType(['name' => 'SomeScalar'])
1189
        );
1190
    }
1191
1192
    /**
1193
     * @see it('rejects a Scalar type defining serialize with an incorrect type')
1194
     */
1195
    public function testRejectsAScalarTypeDefiningSerializeWithAnIncorrectType() : void
1196
    {
1197
        $this->expectException(InvariantViolation::class);
1198
        $this->expectExceptionMessage(
1199
            'SomeScalar must provide "serialize" function. If this custom Scalar ' .
1200
            'is also used as an input type, ensure "parseValue" and "parseLiteral" ' .
1201
            'functions are also provided.'
1202
        );
1203
        $this->schemaWithFieldType(
1204
            new CustomScalarType([
1205
                'name'      => 'SomeScalar',
1206
                'serialize' => new \stdClass(),
1207
            ])
1208
        );
1209
    }
1210
1211
    /**
1212
     * @see it('accepts a Scalar type defining parseValue and parseLiteral')
1213
     */
1214
    public function testAcceptsAScalarTypeDefiningParseValueAndParseLiteral() : void
1215
    {
1216
        $this->expectNotToPerformAssertions();
1217
        // Should not throw:
1218
        $this->schemaWithFieldType(
1219
            new CustomScalarType([
1220
                'name'         => 'SomeScalar',
1221
                'serialize'    => function () {
1222
                },
1223
                'parseValue'   => function () {
1224
                },
1225
                'parseLiteral' => function () {
1226
                },
1227
            ])
1228
        );
1229
    }
1230
1231
    /**
1232
     * @see it('rejects a Scalar type defining parseValue but not parseLiteral')
1233
     */
1234
    public function testRejectsAScalarTypeDefiningParseValueButNotParseLiteral() : void
1235
    {
1236
        $this->expectException(InvariantViolation::class);
1237
        $this->expectExceptionMessage(
1238
            'SomeScalar must provide both "parseValue" and "parseLiteral" functions.'
1239
        );
1240
        $this->schemaWithFieldType(
1241
            new CustomScalarType([
1242
                'name'       => 'SomeScalar',
1243
                'serialize'  => function () {
1244
                },
1245
                'parseValue' => function () {
1246
                },
1247
            ])
1248
        );
1249
    }
1250
1251
    /**
1252
     * @see it('rejects a Scalar type defining parseLiteral but not parseValue')
1253
     */
1254
    public function testRejectsAScalarTypeDefiningParseLiteralButNotParseValue() : void
1255
    {
1256
        $this->expectException(InvariantViolation::class);
1257
        $this->expectExceptionMessage(
1258
            'SomeScalar must provide both "parseValue" and "parseLiteral" functions.'
1259
        );
1260
        $this->schemaWithFieldType(
1261
            new CustomScalarType([
1262
                'name'         => 'SomeScalar',
1263
                'serialize'    => function () {
1264
                },
1265
                'parseLiteral' => function () {
1266
                },
1267
            ])
1268
        );
1269
    }
1270
1271
    /**
1272
     * @see it('rejects a Scalar type defining parseValue and parseLiteral with an incorrect type')
1273
     */
1274
    public function testRejectsAScalarTypeDefiningParseValueAndParseLiteralWithAnIncorrectType() : void
1275
    {
1276
        $this->expectException(InvariantViolation::class);
1277
        $this->expectExceptionMessage(
1278
            'SomeScalar must provide both "parseValue" and "parseLiteral" functions.'
1279
        );
1280
        $this->schemaWithFieldType(
1281
            new CustomScalarType([
1282
                'name'         => 'SomeScalar',
1283
                'serialize'    => function () {
1284
                },
1285
                'parseValue'   => new \stdClass(),
1286
                'parseLiteral' => new \stdClass(),
1287
            ])
1288
        );
1289
    }
1290
1291
    /**
1292
     * @see it('accepts an Object type with an isTypeOf function')
1293
     */
1294
    public function testAcceptsAnObjectTypeWithAnIsTypeOfFunction() : void
1295
    {
1296
        $this->expectNotToPerformAssertions();
1297
        // Should not throw
1298
        $this->schemaWithFieldType(
1299
            new ObjectType([
1300
                'name'   => 'AnotherObject',
1301
                'fields' => ['f' => ['type' => Type::string()]],
1302
            ])
1303
        );
1304
    }
1305
1306
    // Type System: Object types must be assertable
1307
1308
    /**
1309
     * @see it('rejects an Object type with an incorrect type for isTypeOf')
1310
     */
1311
    public function testRejectsAnObjectTypeWithAnIncorrectTypeForIsTypeOf() : void
1312
    {
1313
        $this->expectException(InvariantViolation::class);
1314
        $this->expectExceptionMessage(
1315
            'AnotherObject must provide "isTypeOf" as a function, but got: instance of stdClass'
1316
        );
1317
        $this->schemaWithFieldType(
1318
            new ObjectType([
1319
                'name'     => 'AnotherObject',
1320
                'isTypeOf' => new \stdClass(),
1321
                'fields'   => ['f' => ['type' => Type::string()]],
1322
            ])
1323
        );
1324
    }
1325
1326
    /**
1327
     * @see it('accepts a Union type with array types')
1328
     */
1329
    public function testAcceptsAUnionTypeWithArrayTypes() : void
1330
    {
1331
        $this->expectNotToPerformAssertions();
1332
        // Should not throw:
1333
        $this->schemaWithFieldType(
1334
            new UnionType([
1335
                'name'  => 'SomeUnion',
1336
                'types' => [$this->objectType],
1337
            ])
1338
        );
1339
    }
1340
1341
    // Type System: Union types must be array
1342
1343
    /**
1344
     * @see it('accepts a Union type with function returning an array of types')
1345
     */
1346
    public function testAcceptsAUnionTypeWithFunctionReturningAnArrayOfTypes() : void
1347
    {
1348
        $this->expectNotToPerformAssertions();
1349
        $this->schemaWithFieldType(
1350
            new UnionType([
1351
                'name'  => 'SomeUnion',
1352
                'types' => function () {
1353
                    return [$this->objectType];
1354
                },
1355
            ])
1356
        );
1357
    }
1358
1359
    /**
1360
     * @see it('rejects a Union type without types')
1361
     */
1362
    public function testRejectsAUnionTypeWithoutTypes() : void
1363
    {
1364
        $this->expectException(InvariantViolation::class);
1365
        $this->expectExceptionMessage(
1366
            'Must provide Array of types or a callable which returns such an array for Union SomeUnion'
1367
        );
1368
        $this->schemaWithFieldType(
1369
            new UnionType(['name' => 'SomeUnion'])
1370
        );
1371
    }
1372
1373
    /**
1374
     * @see it('rejects a Union type with incorrectly typed types')
1375
     */
1376
    public function testRejectsAUnionTypeWithIncorrectlyTypedTypes() : void
1377
    {
1378
        $this->expectException(InvariantViolation::class);
1379
        $this->expectExceptionMessage(
1380
            'Must provide Array of types or a callable which returns such an array for Union SomeUnion'
1381
        );
1382
        $this->schemaWithFieldType(
1383
            new UnionType([
1384
                'name'  => 'SomeUnion',
1385
                'types' => (object) ['test' => $this->objectType],
1386
            ])
1387
        );
1388
    }
1389
1390
    /**
1391
     * @see it('accepts an Input Object type with fields')
1392
     */
1393
    public function testAcceptsAnInputObjectTypeWithFields() : void
1394
    {
1395
        $inputObjType = new InputObjectType([
1396
            'name'   => 'SomeInputObject',
1397
            'fields' => [
1398
                'f' => ['type' => Type::string()],
1399
            ],
1400
        ]);
1401
        $inputObjType->assertValid();
1402
        $this->assertSame(Type::string(), $inputObjType->getField('f')->getType());
1403
    }
1404
1405
    // Type System: Input Objects must have fields
1406
1407
    /**
1408
     * @see it('accepts an Input Object type with a field function')
1409
     */
1410
    public function testAcceptsAnInputObjectTypeWithAFieldFunction() : void
1411
    {
1412
        $inputObjType = new InputObjectType([
1413
            'name'   => 'SomeInputObject',
1414
            'fields' => function () {
1415
                return [
1416
                    'f' => ['type' => Type::string()],
1417
                ];
1418
            },
1419
        ]);
1420
        $inputObjType->assertValid();
1421
        $this->assertSame(Type::string(), $inputObjType->getField('f')->getType());
1422
    }
1423
1424
    /**
1425
     * @see it('rejects an Input Object type with incorrect fields')
1426
     */
1427
    public function testRejectsAnInputObjectTypeWithIncorrectFields() : void
1428
    {
1429
        $inputObjType = new InputObjectType([
1430
            'name'   => 'SomeInputObject',
1431
            'fields' => [],
1432
        ]);
1433
        $this->expectException(InvariantViolation::class);
1434
        $this->expectExceptionMessage(
1435
            'SomeInputObject fields must be an associative array with field names as keys or a callable ' .
1436
            'which returns such an array.'
1437
        );
1438
        $inputObjType->assertValid();
1439
    }
1440
1441
    /**
1442
     * @see it('rejects an Input Object type with fields function that returns incorrect type')
1443
     */
1444
    public function testRejectsAnInputObjectTypeWithFieldsFunctionThatReturnsIncorrectType() : void
1445
    {
1446
        $inputObjType = new InputObjectType([
1447
            'name'   => 'SomeInputObject',
1448
            'fields' => function () {
1449
                return [];
1450
            },
1451
        ]);
1452
        $this->expectException(InvariantViolation::class);
1453
        $this->expectExceptionMessage(
1454
            'SomeInputObject fields must be an associative array with field names as keys or a ' .
1455
            'callable which returns such an array.'
1456
        );
1457
        $inputObjType->assertValid();
1458
    }
1459
1460
    /**
1461
     * @see it('rejects an Input Object type with resolvers')
1462
     */
1463
    public function testRejectsAnInputObjectTypeWithResolvers() : void
1464
    {
1465
        $inputObjType = new InputObjectType([
1466
            'name'   => 'SomeInputObject',
1467
            'fields' => [
1468
                'f' => [
1469
                    'type'    => Type::string(),
1470
                    'resolve' => function () {
1471
                        return 0;
1472
                    },
1473
                ],
1474
            ],
1475
        ]);
1476
        $this->expectException(InvariantViolation::class);
1477
        $this->expectExceptionMessage(
1478
            'SomeInputObject.f field type has a resolve property, ' .
1479
            'but Input Types cannot define resolvers.'
1480
        );
1481
        $inputObjType->assertValid();
1482
    }
1483
1484
    // Type System: Input Object fields must not have resolvers
1485
1486
    /**
1487
     * @see it('rejects an Input Object type with resolver constant')
1488
     */
1489
    public function testRejectsAnInputObjectTypeWithResolverConstant() : void
1490
    {
1491
        $inputObjType = new InputObjectType([
1492
            'name'   => 'SomeInputObject',
1493
            'fields' => [
1494
                'f' => [
1495
                    'type'    => Type::string(),
1496
                    'resolve' => new \stdClass(),
1497
                ],
1498
            ],
1499
        ]);
1500
        $this->expectException(InvariantViolation::class);
1501
        $this->expectExceptionMessage(
1502
            'SomeInputObject.f field type has a resolve property, ' .
1503
            'but Input Types cannot define resolvers.'
1504
        );
1505
        $inputObjType->assertValid();
1506
    }
1507
1508
    /**
1509
     * @see it('accepts a well defined Enum type with empty value definition')
1510
     */
1511
    public function testAcceptsAWellDefinedEnumTypeWithEmptyValueDefinition() : void
1512
    {
1513
        $enumType = new EnumType([
1514
            'name'   => 'SomeEnum',
1515
            'values' => [
1516
                'FOO' => [],
1517
                'BAR' => [],
1518
            ],
1519
        ]);
1520
        $this->assertEquals('FOO', $enumType->getValue('FOO')->value);
1521
        $this->assertEquals('BAR', $enumType->getValue('BAR')->value);
1522
    }
1523
1524
    // Type System: Enum types must be well defined
1525
1526
    /**
1527
     * @see it('accepts a well defined Enum type with internal value definition')
1528
     */
1529
    public function testAcceptsAWellDefinedEnumTypeWithInternalValueDefinition() : void
1530
    {
1531
        $enumType = new EnumType([
1532
            'name'   => 'SomeEnum',
1533
            'values' => [
1534
                'FOO' => ['value' => 10],
1535
                'BAR' => ['value' => 20],
1536
            ],
1537
        ]);
1538
        $this->assertEquals(10, $enumType->getValue('FOO')->value);
1539
        $this->assertEquals(20, $enumType->getValue('BAR')->value);
1540
    }
1541
1542
    /**
1543
     * @see it('rejects an Enum type with incorrectly typed values')
1544
     */
1545
    public function testRejectsAnEnumTypeWithIncorrectlyTypedValues() : void
1546
    {
1547
        $enumType = new EnumType([
1548
            'name'   => 'SomeEnum',
1549
            'values' => [['FOO' => 10]],
1550
        ]);
1551
        $this->expectException(InvariantViolation::class);
1552
        $this->expectExceptionMessage(
1553
            'SomeEnum values must be an array with value names as keys.'
1554
        );
1555
        $enumType->assertValid();
1556
    }
1557
1558
    /**
1559
     * @see it('does not allow isDeprecated without deprecationReason on enum')
1560
     */
1561
    public function testDoesNotAllowIsDeprecatedWithoutDeprecationReasonOnEnum() : void
1562
    {
1563
        $enumType = new EnumType([
1564
            'name'   => 'SomeEnum',
1565
            'values' => [
1566
                'FOO' => ['isDeprecated' => true],
1567
            ],
1568
        ]);
1569
        $this->expectException(InvariantViolation::class);
1570
        $this->expectExceptionMessage(
1571
            'SomeEnum.FOO should provide "deprecationReason" instead ' .
1572
            'of "isDeprecated".'
1573
        );
1574
        $enumType->assertValid();
1575
    }
1576
1577
    /**
1578
     * Type System: List must accept only types
1579
     */
1580
    public function testListMustAcceptOnlyTypes() : void
1581
    {
1582
        $types = [
1583
            Type::string(),
1584
            $this->scalarType,
1585
            $this->objectType,
1586
            $this->unionType,
1587
            $this->interfaceType,
1588
            $this->enumType,
1589
            $this->inputObjectType,
1590
            Type::listOf(Type::string()),
1591
            Type::nonNull(Type::string()),
1592
        ];
1593
1594
        $badTypes = [[], new \stdClass(), '', null];
1595
1596
        foreach ($types as $type) {
1597
            try {
1598
                Type::listOf($type);
1599
            } catch (\Throwable $e) {
1600
                $this->fail('List is expected to accept type: ' . get_class($type) . ', but got error: ' . $e->getMessage());
1601
            }
1602
        }
1603
        foreach ($badTypes as $badType) {
1604
            $typeStr = Utils::printSafe($badType);
1605
            try {
1606
                Type::listOf($badType);
1607
                $this->fail(sprintf('List should not accept %s', $typeStr));
1608
            } catch (InvariantViolation $e) {
1609
                $this->assertEquals(sprintf('Expected %s to be a GraphQL type.', $typeStr), $e->getMessage());
1610
            }
1611
        }
1612
    }
1613
1614
    /**
1615
     * Type System: NonNull must only accept non-nullable types
1616
     */
1617
    public function testNonNullMustOnlyAcceptNonNullableTypes() : void
1618
    {
1619
        $nullableTypes    = [
1620
            Type::string(),
1621
            $this->scalarType,
1622
            $this->objectType,
1623
            $this->unionType,
1624
            $this->interfaceType,
1625
            $this->enumType,
1626
            $this->inputObjectType,
1627
            Type::listOf(Type::string()),
1628
            Type::listOf(Type::nonNull(Type::string())),
1629
        ];
1630
        $notNullableTypes = [
1631
            Type::nonNull(Type::string()),
1632
            [],
1633
            new \stdClass(),
1634
            '',
1635
            null,
1636
        ];
1637
        foreach ($nullableTypes as $type) {
1638
            try {
1639
                Type::nonNull($type);
1640
            } catch (\Throwable $e) {
1641
                $this->fail('NonNull is expected to accept type: ' . get_class($type) . ', but got error: ' . $e->getMessage());
1642
            }
1643
        }
1644
        foreach ($notNullableTypes as $badType) {
1645
            $typeStr = Utils::printSafe($badType);
1646
            try {
1647
                Type::nonNull($badType);
1648
                $this->fail(sprintf('Nulls should not accept %s', $typeStr));
1649
            } catch (InvariantViolation $e) {
1650
                $this->assertEquals(sprintf('Expected %s to be a GraphQL nullable type.', $typeStr), $e->getMessage());
1651
            }
1652
        }
1653
    }
1654
1655
    /**
1656
     * @see it('rejects a Schema which redefines a built-in type')
1657
     */
1658
    public function testRejectsASchemaWhichRedefinesABuiltInType() : void
1659
    {
1660
        $FakeString = new CustomScalarType([
1661
            'name'      => 'String',
1662
            'serialize' => function () {
1663
            },
1664
        ]);
1665
1666
        $QueryType = new ObjectType([
1667
            'name'   => 'Query',
1668
            'fields' => [
1669
                'normal' => ['type' => Type::string()],
1670
                'fake'   => ['type' => $FakeString],
1671
            ],
1672
        ]);
1673
1674
        $this->expectException(InvariantViolation::class);
1675
        $this->expectExceptionMessage(
1676
            'Schema must contain unique named types but contains multiple types named "String" ' .
1677
            '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
1678
        );
1679
        $schema = new Schema(['query' => $QueryType]);
1680
        $schema->assertValid();
1681
    }
1682
1683
    // Type System: A Schema must contain uniquely named types
1684
1685
    /**
1686
     * @see it('rejects a Schema which defines an object type twice')
1687
     */
1688
    public function testRejectsASchemaWhichDefinesAnObjectTypeTwice() : void
1689
    {
1690
        $A = new ObjectType([
1691
            'name'   => 'SameName',
1692
            'fields' => ['f' => ['type' => Type::string()]],
1693
        ]);
1694
1695
        $B = new ObjectType([
1696
            'name'   => 'SameName',
1697
            'fields' => ['f' => ['type' => Type::string()]],
1698
        ]);
1699
1700
        $QueryType = new ObjectType([
1701
            'name'   => 'Query',
1702
            'fields' => [
1703
                'a' => ['type' => $A],
1704
                'b' => ['type' => $B],
1705
            ],
1706
        ]);
1707
        $this->expectException(InvariantViolation::class);
1708
        $this->expectExceptionMessage(
1709
            'Schema must contain unique named types but contains multiple types named "SameName" ' .
1710
            '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
1711
        );
1712
        $schema = new Schema(['query' => $QueryType]);
1713
        $schema->assertValid();
1714
    }
1715
1716
    /**
1717
     * @see it('rejects a Schema which have same named objects implementing an interface')
1718
     */
1719
    public function testRejectsASchemaWhichHaveSameNamedObjectsImplementingAnInterface() : void
1720
    {
1721
        $AnotherInterface = new InterfaceType([
1722
            'name'   => 'AnotherInterface',
1723
            'fields' => ['f' => ['type' => Type::string()]],
1724
        ]);
1725
1726
        $FirstBadObject = new ObjectType([
1727
            'name'       => 'BadObject',
1728
            'interfaces' => [$AnotherInterface],
1729
            'fields'     => ['f' => ['type' => Type::string()]],
1730
        ]);
1731
1732
        $SecondBadObject = new ObjectType([
1733
            'name'       => 'BadObject',
1734
            'interfaces' => [$AnotherInterface],
1735
            'fields'     => ['f' => ['type' => Type::string()]],
1736
        ]);
1737
1738
        $QueryType = new ObjectType([
1739
            'name'   => 'Query',
1740
            'fields' => [
1741
                'iface' => ['type' => $AnotherInterface],
1742
            ],
1743
        ]);
1744
1745
        $this->expectException(InvariantViolation::class);
1746
        $this->expectExceptionMessage(
1747
            'Schema must contain unique named types but contains multiple types named "BadObject" ' .
1748
            '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
1749
        );
1750
        $schema = new Schema([
1751
            'query' => $QueryType,
1752
            'types' => [$FirstBadObject, $SecondBadObject],
1753
        ]);
1754
        $schema->assertValid();
1755
    }
1756
1757
    public function objectWithIsTypeOf() : ObjectType
1758
    {
1759
        return new ObjectType([
1760
            'name'   => 'ObjectWithIsTypeOf',
1761
            'fields' => ['f' => ['type' => Type::string()]],
1762
        ]);
1763
    }
1764
}
1765