Passed
Push — master ( 24c473...e804cc )
by Vladimir
18:07 queued 12s
created

testExtendsScalarsByAddingNewDirectives()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 11
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Tests\Utils;
6
7
use GraphQL\Error\Error;
8
use GraphQL\GraphQL;
9
use GraphQL\Language\AST\DocumentNode;
10
use GraphQL\Language\AST\Node;
11
use GraphQL\Language\AST\NodeList;
12
use GraphQL\Language\DirectiveLocation;
13
use GraphQL\Language\Parser;
14
use GraphQL\Language\Printer;
15
use GraphQL\Type\Definition\CustomScalarType;
16
use GraphQL\Type\Definition\Directive;
17
use GraphQL\Type\Definition\EnumType;
18
use GraphQL\Type\Definition\FieldArgument;
19
use GraphQL\Type\Definition\InputObjectType;
20
use GraphQL\Type\Definition\InterfaceType;
21
use GraphQL\Type\Definition\NonNull;
22
use GraphQL\Type\Definition\ObjectType;
23
use GraphQL\Type\Definition\ScalarType;
24
use GraphQL\Type\Definition\Type;
25
use GraphQL\Type\Definition\UnionType;
26
use GraphQL\Type\Schema;
27
use GraphQL\Utils\BuildSchema;
28
use GraphQL\Utils\SchemaExtender;
29
use GraphQL\Utils\SchemaPrinter;
30
use PHPUnit\Framework\TestCase;
31
use function array_filter;
32
use function array_map;
33
use function array_merge;
34
use function array_values;
35
use function count;
36
use function implode;
37
use function in_array;
38
use function iterator_to_array;
39
use function preg_match;
40
use function preg_replace;
41
use function trim;
42
43
class SchemaExtenderTest extends TestCase
44
{
45
    /** @var Schema */
46
    protected $testSchema;
47
48
    /** @var string[] */
49
    protected $testSchemaDefinitions;
50
51
    /** @var ObjectType */
52
    protected $FooType;
53
54
    /** @var Directive */
55
    protected $FooDirective;
56
57
    public function setUp()
58
    {
59
        parent::setUp();
60
61
        $SomeScalarType = new CustomScalarType([
62
            'name' => 'SomeScalar',
63
            'serialize' => static function ($x) {
64
                return $x;
65
            },
66
        ]);
67
68
        $SomeInterfaceType = new InterfaceType([
69
            'name' => 'SomeInterface',
70
            'fields' => static function () use (&$SomeInterfaceType) {
71
                return [
72
                    'name' => [ 'type' => Type::string()],
73
                    'some' => [ 'type' => $SomeInterfaceType],
74
                ];
75
            },
76
        ]);
77
78
        $FooType = new ObjectType([
79
            'name' => 'Foo',
80
            'interfaces' => [$SomeInterfaceType],
81
            'fields' => static function () use ($SomeInterfaceType, &$FooType) {
82
                return [
83
                    'name' => [ 'type' => Type::string() ],
84
                    'some' => [ 'type' => $SomeInterfaceType ],
85
                    'tree' => [ 'type' => Type::nonNull(Type::listOf($FooType))],
86
                ];
87
            },
88
        ]);
89
90
        $BarType = new ObjectType([
91
            'name' => 'Bar',
92
            'interfaces' => [$SomeInterfaceType],
93
            'fields' => static function () use ($SomeInterfaceType, $FooType) : array {
94
                return [
95
                    'name' => [ 'type' => Type::string() ],
96
                    'some' => [ 'type' => $SomeInterfaceType ],
97
                    'foo' => [ 'type' => $FooType ],
98
                ];
99
            },
100
        ]);
101
102
        $BizType = new ObjectType([
103
            'name' => 'Biz',
104
            'fields' => static function () : array {
105
                return [
106
                    'fizz' => [ 'type' => Type::string() ],
107
                ];
108
            },
109
        ]);
110
111
        $SomeUnionType = new UnionType([
112
            'name' => 'SomeUnion',
113
            'types' => [$FooType, $BizType],
114
        ]);
115
116
        $SomeEnumType = new EnumType([
117
            'name' => 'SomeEnum',
118
            'values' => [
119
                'ONE' => [ 'value' => 1 ],
120
                'TWO' => [ 'value' => 2 ],
121
            ],
122
        ]);
123
124
        $SomeInputType = new InputObjectType([
125
            'name' => 'SomeInput',
126
            'fields' => static function () : array {
127
                return [
128
                    'fooArg' => [ 'type' => Type::string() ],
129
                ];
130
            },
131
        ]);
132
133
        $FooDirective = new Directive([
134
            'name' => 'foo',
135
            'args' => [
136
                new FieldArgument([
137
                    'name' => 'input',
138
                    'type' => $SomeInputType,
139
                ]),
140
            ],
141
            'locations' => [
142
                DirectiveLocation::SCHEMA,
143
                DirectiveLocation::SCALAR,
144
                DirectiveLocation::OBJECT,
145
                DirectiveLocation::FIELD_DEFINITION,
146
                DirectiveLocation::ARGUMENT_DEFINITION,
147
                DirectiveLocation::IFACE,
148
                DirectiveLocation::UNION,
149
                DirectiveLocation::ENUM,
150
                DirectiveLocation::ENUM_VALUE,
151
                DirectiveLocation::INPUT_OBJECT,
152
                DirectiveLocation::INPUT_FIELD_DEFINITION,
153
            ],
154
        ]);
155
156
        $this->testSchema = new Schema([
157
            'query' => new ObjectType([
158
                'name' => 'Query',
159
                'fields' => static function () use ($FooType, $SomeScalarType, $SomeUnionType, $SomeEnumType, $SomeInterfaceType, $SomeInputType) : array {
160
                    return [
161
                        'foo' => [ 'type' => $FooType ],
162
                        'someScalar' => [ 'type' => $SomeScalarType ],
163
                        'someUnion' => [ 'type' => $SomeUnionType ],
164
                        'someEnum' => [ 'type' => $SomeEnumType ],
165
                        'someInterface' => [
166
                            'args' => [
167
                                'id' => [
168
                                    'type' => Type::nonNull(Type::id()),
169
                                ],
170
                            ],
171
                            'type' => $SomeInterfaceType,
172
                        ],
173
                        'someInput' => [
174
                            'args' => [ 'input' => [ 'type' => $SomeInputType ] ],
175
                            'type' => Type::string(),
176
                        ],
177
                    ];
178
                },
179
            ]),
180
            'types' => [$FooType, $BarType],
181
            'directives' => array_merge(GraphQL::getStandardDirectives(), [$FooDirective]),
182
        ]);
183
184
        $testSchemaAst = Parser::parse(SchemaPrinter::doPrint($this->testSchema));
185
186
        $this->testSchemaDefinitions = array_map(static function ($node) {
187
            return Printer::doPrint($node);
188
        }, iterator_to_array($testSchemaAst->definitions->getIterator()));
189
190
        $this->FooDirective = $FooDirective;
191
        $this->FooType      = $FooType;
192
    }
193
194
    protected function dedent(string $str) : string
195
    {
196
        $trimmedStr = trim($str, "\n");
197
        $trimmedStr = preg_replace('/[ \t]*$/', '', $trimmedStr);
198
199
        preg_match('/^[ \t]*/', $trimmedStr, $indentMatch);
200
        $indent = $indentMatch[0];
201
202
        return preg_replace('/^' . $indent . '/m', '', $trimmedStr);
203
    }
204
205
    /**
206
     * @param mixed[]|null $options
207
     */
208
    protected function extendTestSchema(string $sdl, ?array $options = null) : Schema
209
    {
210
        $originalPrint  = SchemaPrinter::doPrint($this->testSchema);
211
        $ast            = Parser::parse($sdl);
212
        $extendedSchema = SchemaExtender::extend($this->testSchema, $ast, $options);
213
        self::assertEquals(SchemaPrinter::doPrint($this->testSchema), $originalPrint);
214
215
        return $extendedSchema;
216
    }
217
218
    protected function printTestSchemaChanges(Schema $extendedSchema) : string
219
    {
220
        $ast              = Parser::parse(SchemaPrinter::doPrint($extendedSchema));
221
        $ast->definitions = array_values(array_filter(
222
            $ast->definitions instanceof NodeList
223
                ? iterator_to_array($ast->definitions->getIterator())
224
                : $ast->definitions,
225
            function (Node $node) : bool {
226
                return ! in_array(Printer::doPrint($node), $this->testSchemaDefinitions, true);
227
            }
228
        ));
229
230
        return Printer::doPrint($ast);
231
    }
232
233
    /**
234
     * @see it('returns the original schema when there are no type definitions')
235
     */
236
    public function testReturnsTheOriginalSchemaWhenThereAreNoTypeDefinitions()
237
    {
238
        $extendedSchema = $this->extendTestSchema('{ field }');
239
        self::assertEquals($extendedSchema, $this->testSchema);
240
    }
241
242
    /**
243
     * @see it('extends without altering original schema')
244
     */
245
    public function testExtendsWithoutAlteringOriginalSchema()
246
    {
247
        $extendedSchema = $this->extendTestSchema('
248
            extend type Query {
249
                newField: String
250
            }');
251
        self::assertNotEquals($extendedSchema, $this->testSchema);
252
        self::assertContains('newField', SchemaPrinter::doPrint($extendedSchema));
253
        self::assertNotContains('newField', SchemaPrinter::doPrint($this->testSchema));
254
    }
255
256
    /**
257
     * @see it('can be used for limited execution')
258
     */
259
    public function testCanBeUsedForLimitedExecution()
260
    {
261
        $extendedSchema = $this->extendTestSchema('
262
          extend type Query {
263
            newField: String
264
          }
265
        ');
266
267
        $result = GraphQL::executeQuery($extendedSchema, '{ newField }', ['newField' => 123]);
268
269
        self::assertEquals($result->toArray(), [
270
            'data' => ['newField' => '123'],
271
        ]);
272
    }
273
274
    /**
275
     * @see it('can describe the extended fields')
276
     */
277
    public function testCanDescribeTheExtendedFields()
278
    {
279
        $extendedSchema = $this->extendTestSchema('
280
            extend type Query {
281
                "New field description."
282
                newField: String
283
            }
284
        ');
285
286
        self::assertEquals(
287
            $extendedSchema->getQueryType()->getField('newField')->description,
288
            'New field description.'
289
        );
290
    }
291
292
    /**
293
     * @see it('can describe the extended fields with legacy comments')
294
     */
295
    public function testCanDescribeTheExtendedFieldsWithLegacyComments()
296
    {
297
        $extendedSchema = $this->extendTestSchema('
298
            extend type Query {
299
                # New field description.
300
                newField: String
301
            }
302
        ', ['commentDescriptions' => true]);
303
304
        self::assertEquals(
305
            $extendedSchema->getQueryType()->getField('newField')->description,
306
            'New field description.'
307
        );
308
    }
309
310
    /**
311
     * @see it('describes extended fields with strings when present')
312
     */
313
    public function testDescribesExtendedFieldsWithStringsWhenPresent()
314
    {
315
        $extendedSchema = $this->extendTestSchema('
316
            extend type Query {
317
                # New field description.
318
                "Actually use this description."
319
                newField: String
320
            }
321
        ', ['commentDescriptions' => true ]);
322
323
        self::assertEquals(
324
            $extendedSchema->getQueryType()->getField('newField')->description,
325
            'Actually use this description.'
326
        );
327
    }
328
329
    /**
330
     * @see it('extends objects by adding new fields')
331
     */
332
    public function testExtendsObjectsByAddingNewFields()
333
    {
334
        $extendedSchema = $this->extendTestSchema(
335
            '
336
            extend type Foo {
337
                newField: String
338
            }
339
        '
340
        );
341
342
        self::assertEquals(
343
            $this->printTestSchemaChanges($extendedSchema),
344
            $this->dedent('
345
              type Foo implements SomeInterface {
346
                name: String
347
                some: SomeInterface
348
                tree: [Foo]!
349
                newField: String
350
              }
351
            ')
352
        );
353
354
        $fooType  = $extendedSchema->getType('Foo');
355
        $fooField = $extendedSchema->getQueryType()->getField('foo');
356
        self::assertEquals($fooField->getType(), $fooType);
357
    }
358
359
    /**
360
     * @see it('extends enums by adding new values')
361
     */
362
    public function testExtendsEnumsByAddingNewValues()
363
    {
364
        $extendedSchema = $this->extendTestSchema('
365
          extend enum SomeEnum {
366
            NEW_ENUM
367
          }
368
        ');
369
370
        self::assertEquals(
371
            $this->printTestSchemaChanges($extendedSchema),
372
            $this->dedent('
373
              enum SomeEnum {
374
                ONE
375
                TWO
376
                NEW_ENUM
377
              }
378
          ')
379
        );
380
381
        $someEnumType = $extendedSchema->getType('SomeEnum');
382
        $enumField    = $extendedSchema->getQueryType()->getField('someEnum');
383
        self::assertEquals($enumField->getType(), $someEnumType);
384
    }
385
386
    /**
387
     * @see it('extends unions by adding new types')
388
     */
389
    public function testExtendsUnionsByAddingNewTypes()
390
    {
391
        $extendedSchema = $this->extendTestSchema('
392
          extend union SomeUnion = Bar
393
        ');
394
        self::assertEquals(
395
            $this->printTestSchemaChanges($extendedSchema),
396
            $this->dedent('
397
              union SomeUnion = Foo | Biz | Bar
398
            ')
399
        );
400
401
        $someUnionType = $extendedSchema->getType('SomeUnion');
402
        $unionField    = $extendedSchema->getQueryType()->getField('someUnion');
403
        self::assertEquals($unionField->getType(), $someUnionType);
404
    }
405
406
    /**
407
     * @see it('allows extension of union by adding itself')
408
     */
409
    public function testAllowsExtensionOfUnionByAddingItself()
410
    {
411
        $extendedSchema = $this->extendTestSchema('
412
          extend union SomeUnion = SomeUnion
413
        ');
414
415
        $errors = $extendedSchema->validate();
416
        self::assertGreaterThan(0, count($errors));
417
418
        self::assertEquals(
419
            $this->printTestSchemaChanges($extendedSchema),
420
            $this->dedent('
421
                union SomeUnion = Foo | Biz | SomeUnion
422
            ')
423
        );
424
    }
425
426
    /**
427
     * @see it('extends inputs by adding new fields')
428
     */
429
    public function testExtendsInputsByAddingNewFields()
430
    {
431
        $extendedSchema = $this->extendTestSchema('
432
          extend input SomeInput {
433
            newField: String
434
          }
435
        ');
436
437
        self::assertEquals(
438
            $this->printTestSchemaChanges($extendedSchema),
439
            $this->dedent('
440
              input SomeInput {
441
                fooArg: String
442
                newField: String
443
              }
444
            ')
445
        );
446
447
        $someInputType = $extendedSchema->getType('SomeInput');
448
        $inputField    = $extendedSchema->getQueryType()->getField('someInput');
449
        self::assertEquals($inputField->args[0]->getType(), $someInputType);
450
451
        $fooDirective = $extendedSchema->getDirective('foo');
452
        self::assertEquals($fooDirective->args[0]->getType(), $someInputType);
453
    }
454
455
    /**
456
     * @see it('extends scalars by adding new directives')
457
     */
458
    public function testExtendsScalarsByAddingNewDirectives()
459
    {
460
        $extendedSchema = $this->extendTestSchema('
461
          extend scalar SomeScalar @foo
462
        ');
463
464
        $someScalar = $extendedSchema->getType('SomeScalar');
465
        self::assertCount(1, $someScalar->extensionASTNodes);
466
        self::assertEquals(
467
            Printer::doPrint($someScalar->extensionASTNodes[0]),
468
            'extend scalar SomeScalar @foo'
469
        );
470
    }
471
472
    /**
473
     * @see it('correctly assign AST nodes to new and extended types')
474
     */
475
    public function testCorrectlyAssignASTNodesToNewAndExtendedTypes()
476
    {
477
        $extendedSchema = $this->extendTestSchema('
478
              extend type Query {
479
                newField(testArg: TestInput): TestEnum
480
              }
481
              extend scalar SomeScalar @foo
482
              extend enum SomeEnum {
483
                NEW_VALUE
484
              }
485
              extend union SomeUnion = Bar
486
              extend input SomeInput {
487
                newField: String
488
              }
489
              extend interface SomeInterface {
490
                newField: String
491
              }
492
              enum TestEnum {
493
                TEST_VALUE
494
              }
495
              input TestInput {
496
                testInputField: TestEnum
497
              }
498
            ');
499
500
        $ast = Parser::parse('
501
            extend type Query {
502
                oneMoreNewField: TestUnion
503
            }
504
            extend scalar SomeScalar @test
505
            extend enum SomeEnum {
506
                ONE_MORE_NEW_VALUE
507
            }
508
            extend union SomeUnion = TestType
509
            extend input SomeInput {
510
                oneMoreNewField: String
511
            }
512
            extend interface SomeInterface {
513
                oneMoreNewField: String
514
            }
515
            union TestUnion = TestType
516
            interface TestInterface {
517
                interfaceField: String
518
            }
519
            type TestType implements TestInterface {
520
                interfaceField: String
521
            }
522
            directive @test(arg: Int) on FIELD | SCALAR
523
        ');
524
525
        $extendedTwiceSchema = SchemaExtender::extend($extendedSchema, $ast);
526
        $query               = $extendedTwiceSchema->getQueryType();
527
        /** @var ScalarType $someScalar */
528
        $someScalar = $extendedTwiceSchema->getType('SomeScalar');
529
        /** @var EnumType $someEnum */
530
        $someEnum = $extendedTwiceSchema->getType('SomeEnum');
531
        /** @var UnionType $someUnion */
532
        $someUnion = $extendedTwiceSchema->getType('SomeUnion');
533
        /** @var InputObjectType $someInput */
534
        $someInput = $extendedTwiceSchema->getType('SomeInput');
535
        /** @var InterfaceType $someInterface */
536
        $someInterface = $extendedTwiceSchema->getType('SomeInterface');
537
538
        /** @var InputObjectType $testInput */
539
        $testInput = $extendedTwiceSchema->getType('TestInput');
540
        /** @var EnumType $testEnum */
541
        $testEnum = $extendedTwiceSchema->getType('TestEnum');
542
        /** @var UnionType $testUnion */
543
        $testUnion = $extendedTwiceSchema->getType('TestUnion');
544
        /** @var InterfaceType $testInterface */
545
        $testInterface = $extendedTwiceSchema->getType('TestInterface');
546
        /** @var ObjectType $testType */
547
        $testType = $extendedTwiceSchema->getType('TestType');
548
        /** @var Directive $testDirective */
549
        $testDirective = $extendedTwiceSchema->getDirective('test');
550
551
        self::assertCount(2, $query->extensionASTNodes);
552
        self::assertCount(2, $someScalar->extensionASTNodes);
553
        self::assertCount(2, $someEnum->extensionASTNodes);
554
        self::assertCount(2, $someUnion->extensionASTNodes);
555
        self::assertCount(2, $someInput->extensionASTNodes);
556
        self::assertCount(2, $someInterface->extensionASTNodes);
557
558
        self::assertCount(0, $testType->extensionASTNodes ?? []);
559
        self::assertCount(0, $testEnum->extensionASTNodes ?? []);
560
        self::assertCount(0, $testUnion->extensionASTNodes ?? []);
561
        self::assertCount(0, $testInput->extensionASTNodes ?? []);
562
        self::assertCount(0, $testInterface->extensionASTNodes ?? []);
563
564
        $restoredExtensionAST = new DocumentNode([
565
            'definitions' => array_merge(
566
                $query->extensionASTNodes,
567
                $someScalar->extensionASTNodes,
568
                $someEnum->extensionASTNodes,
569
                $someUnion->extensionASTNodes,
570
                $someInput->extensionASTNodes,
571
                $someInterface->extensionASTNodes,
572
                [
573
                    $testInput->astNode,
574
                    $testEnum->astNode,
575
                    $testUnion->astNode,
576
                    $testInterface->astNode,
577
                    $testType->astNode,
578
                    $testDirective->astNode,
579
                ]
580
            ),
581
        ]);
582
583
        self::assertEquals(
584
            SchemaPrinter::doPrint(SchemaExtender::extend($this->testSchema, $restoredExtensionAST)),
585
            SchemaPrinter::doPrint($extendedTwiceSchema)
586
        );
587
588
        $newField = $query->getField('newField');
589
590
        self::assertEquals(Printer::doPrint($newField->astNode), 'newField(testArg: TestInput): TestEnum');
591
        self::assertEquals(Printer::doPrint($newField->args[0]->astNode), 'testArg: TestInput');
592
        self::assertEquals(Printer::doPrint($query->getField('oneMoreNewField')->astNode), 'oneMoreNewField: TestUnion');
593
        self::assertEquals(Printer::doPrint($someEnum->getValue('NEW_VALUE')->astNode), 'NEW_VALUE');
594
        self::assertEquals(Printer::doPrint($someEnum->getValue('ONE_MORE_NEW_VALUE')->astNode), 'ONE_MORE_NEW_VALUE');
595
        self::assertEquals(Printer::doPrint($someInput->getField('newField')->astNode), 'newField: String');
596
        self::assertEquals(Printer::doPrint($someInput->getField('oneMoreNewField')->astNode), 'oneMoreNewField: String');
597
        self::assertEquals(Printer::doPrint($someInterface->getField('newField')->astNode), 'newField: String');
598
        self::assertEquals(Printer::doPrint($someInterface->getField('oneMoreNewField')->astNode), 'oneMoreNewField: String');
599
        self::assertEquals(Printer::doPrint($testInput->getField('testInputField')->astNode), 'testInputField: TestEnum');
600
        self::assertEquals(Printer::doPrint($testEnum->getValue('TEST_VALUE')->astNode), 'TEST_VALUE');
601
        self::assertEquals(Printer::doPrint($testInterface->getField('interfaceField')->astNode), 'interfaceField: String');
602
        self::assertEquals(Printer::doPrint($testType->getField('interfaceField')->astNode), 'interfaceField: String');
603
        self::assertEquals(Printer::doPrint($testDirective->args[0]->astNode), 'arg: Int');
604
    }
605
606
    /**
607
     * @see it('builds types with deprecated fields/values')
608
     */
609
    public function testBuildsTypesWithDeprecatedFieldsOrValues()
610
    {
611
        $extendedSchema = $this->extendTestSchema('
612
            type TypeWithDeprecatedField {
613
                newDeprecatedField: String @deprecated(reason: "not used anymore")
614
            }
615
            enum EnumWithDeprecatedValue {
616
                DEPRECATED @deprecated(reason: "do not use")
617
            }
618
        ');
619
620
        /** @var ObjectType $typeWithDeprecatedField */
621
        $typeWithDeprecatedField = $extendedSchema->getType('TypeWithDeprecatedField');
622
        $deprecatedFieldDef      = $typeWithDeprecatedField->getField('newDeprecatedField');
623
624
        self::assertEquals(true, $deprecatedFieldDef->isDeprecated());
625
        self::assertEquals('not used anymore', $deprecatedFieldDef->deprecationReason);
626
627
        /** @var EnumType $enumWithDeprecatedValue */
628
        $enumWithDeprecatedValue = $extendedSchema->getType('EnumWithDeprecatedValue');
629
        $deprecatedEnumDef       = $enumWithDeprecatedValue->getValue('DEPRECATED');
630
631
        self::assertEquals(true, $deprecatedEnumDef->isDeprecated());
632
        self::assertEquals('do not use', $deprecatedEnumDef->deprecationReason);
633
    }
634
635
    /**
636
     * @see it('extends objects with deprecated fields')
637
     */
638
    public function testExtendsObjectsWithDeprecatedFields()
639
    {
640
        $extendedSchema = $this->extendTestSchema('
641
          extend type Foo {
642
            deprecatedField: String @deprecated(reason: "not used anymore")
643
          }
644
        ');
645
        /** @var ObjectType $fooType */
646
        $fooType            = $extendedSchema->getType('Foo');
647
        $deprecatedFieldDef = $fooType->getField('deprecatedField');
648
649
        self::assertTrue($deprecatedFieldDef->isDeprecated());
650
        self::assertEquals('not used anymore', $deprecatedFieldDef->deprecationReason);
651
    }
652
653
    /**
654
     * @see it('extends enums with deprecated values')
655
     */
656
    public function testExtendsEnumsWithDeprecatedValues()
657
    {
658
        $extendedSchema = $this->extendTestSchema('
659
          extend enum SomeEnum {
660
            DEPRECATED @deprecated(reason: "do not use")
661
          }
662
        ');
663
664
        /** @var EnumType $someEnumType */
665
        $someEnumType      = $extendedSchema->getType('SomeEnum');
666
        $deprecatedEnumDef = $someEnumType->getValue('DEPRECATED');
667
668
        self::assertTrue($deprecatedEnumDef->isDeprecated());
669
        self::assertEquals('do not use', $deprecatedEnumDef->deprecationReason);
670
    }
671
672
    /**
673
     * @see it('adds new unused object type')
674
     */
675
    public function testAddsNewUnusedObjectType()
676
    {
677
        $extendedSchema = $this->extendTestSchema('
678
          type Unused {
679
            someField: String
680
          }
681
        ');
682
        self::assertNotEquals($this->testSchema, $extendedSchema);
683
        self::assertEquals(
684
            $this->dedent('
685
                type Unused {
686
                  someField: String
687
                }
688
            '),
689
            $this->printTestSchemaChanges($extendedSchema)
690
        );
691
    }
692
693
    /**
694
     * @see it('adds new unused enum type')
695
     */
696
    public function testAddsNewUnusedEnumType()
697
    {
698
        $extendedSchema = $this->extendTestSchema('
699
          enum UnusedEnum {
700
            SOME
701
          }
702
        ');
703
        self::assertNotEquals($extendedSchema, $this->testSchema);
704
        self::assertEquals(
705
            $this->dedent('
706
                enum UnusedEnum {
707
                  SOME
708
                }
709
            '),
710
            $this->printTestSchemaChanges($extendedSchema)
711
        );
712
    }
713
714
    /**
715
     * @see it('adds new unused input object type')
716
     */
717
    public function testAddsNewUnusedInputObjectType()
718
    {
719
        $extendedSchema = $this->extendTestSchema('
720
          input UnusedInput {
721
            someInput: String
722
          }
723
        ');
724
725
        self::assertNotEquals($extendedSchema, $this->testSchema);
726
        self::assertEquals(
727
            $this->dedent('
728
              input UnusedInput {
729
                someInput: String
730
              }
731
            '),
732
            $this->printTestSchemaChanges($extendedSchema)
733
        );
734
    }
735
736
    /**
737
     * @see it('adds new union using new object type')
738
     */
739
    public function testAddsNewUnionUsingNewObjectType()
740
    {
741
        $extendedSchema = $this->extendTestSchema('
742
          type DummyUnionMember {
743
            someField: String
744
          }
745
746
          union UnusedUnion = DummyUnionMember
747
        ');
748
749
        self::assertNotEquals($extendedSchema, $this->testSchema);
750
        self::assertEquals(
751
            $this->dedent('
752
              type DummyUnionMember {
753
                someField: String
754
              }
755
756
              union UnusedUnion = DummyUnionMember
757
            '),
758
            $this->printTestSchemaChanges($extendedSchema)
759
        );
760
    }
761
762
    /**
763
     * @see it('extends objects by adding new fields with arguments')
764
     */
765
    public function testExtendsObjectsByAddingNewFieldsWithArguments()
766
    {
767
        $extendedSchema = $this->extendTestSchema('
768
          extend type Foo {
769
            newField(arg1: String, arg2: NewInputObj!): String
770
          }
771
772
          input NewInputObj {
773
            field1: Int
774
            field2: [Float]
775
            field3: String!
776
          }
777
        ');
778
779
        self::assertEquals(
780
            $this->dedent('
781
                type Foo implements SomeInterface {
782
                  name: String
783
                  some: SomeInterface
784
                  tree: [Foo]!
785
                  newField(arg1: String, arg2: NewInputObj!): String
786
                }
787
788
                input NewInputObj {
789
                  field1: Int
790
                  field2: [Float]
791
                  field3: String!
792
                }
793
            '),
794
            $this->printTestSchemaChanges($extendedSchema)
795
        );
796
    }
797
798
    /**
799
     * @see it('extends objects by adding new fields with existing types')
800
     */
801
    public function testExtendsObjectsByAddingNewFieldsWithExistingTypes()
802
    {
803
        $extendedSchema = $this->extendTestSchema('
804
          extend type Foo {
805
            newField(arg1: SomeEnum!): SomeEnum
806
          }
807
        ');
808
809
        self::assertEquals(
810
            $this->dedent('
811
                type Foo implements SomeInterface {
812
                  name: String
813
                  some: SomeInterface
814
                  tree: [Foo]!
815
                  newField(arg1: SomeEnum!): SomeEnum
816
                }
817
            '),
818
            $this->printTestSchemaChanges($extendedSchema)
819
        );
820
    }
821
822
    /**
823
     * @see it('extends objects by adding implemented interfaces')
824
     */
825
    public function testExtendsObjectsByAddingImplementedInterfaces()
826
    {
827
        $extendedSchema = $this->extendTestSchema('
828
          extend type Biz implements SomeInterface {
829
            name: String
830
            some: SomeInterface
831
          }
832
        ');
833
834
        self::assertEquals(
835
            $this->dedent('
836
                type Biz implements SomeInterface {
837
                  fizz: String
838
                  name: String
839
                  some: SomeInterface
840
                }
841
            '),
842
            $this->printTestSchemaChanges($extendedSchema)
843
        );
844
    }
845
846
    /**
847
     * @see it('extends objects by including new types')
848
     */
849
    public function testExtendsObjectsByIncludingNewTypes()
850
    {
851
        $extendedSchema = $this->extendTestSchema('
852
          extend type Foo {
853
            newObject: NewObject
854
            newInterface: NewInterface
855
            newUnion: NewUnion
856
            newScalar: NewScalar
857
            newEnum: NewEnum
858
            newTree: [Foo]!
859
          }
860
861
          type NewObject implements NewInterface {
862
            baz: String
863
          }
864
865
          type NewOtherObject {
866
            fizz: Int
867
          }
868
869
          interface NewInterface {
870
            baz: String
871
          }
872
873
          union NewUnion = NewObject | NewOtherObject
874
875
          scalar NewScalar
876
877
          enum NewEnum {
878
            OPTION_A
879
            OPTION_B
880
          }
881
        ');
882
883
        self::assertEquals(
884
            $this->dedent('
885
                type Foo implements SomeInterface {
886
                  name: String
887
                  some: SomeInterface
888
                  tree: [Foo]!
889
                  newObject: NewObject
890
                  newInterface: NewInterface
891
                  newUnion: NewUnion
892
                  newScalar: NewScalar
893
                  newEnum: NewEnum
894
                  newTree: [Foo]!
895
                }
896
897
                enum NewEnum {
898
                  OPTION_A
899
                  OPTION_B
900
                }
901
902
                interface NewInterface {
903
                  baz: String
904
                }
905
906
                type NewObject implements NewInterface {
907
                  baz: String
908
                }
909
910
                type NewOtherObject {
911
                  fizz: Int
912
                }
913
914
                scalar NewScalar
915
916
                union NewUnion = NewObject | NewOtherObject
917
            '),
918
            $this->printTestSchemaChanges($extendedSchema)
919
        );
920
    }
921
922
    /**
923
     * @see it('extends objects by adding implemented new interfaces')
924
     */
925
    public function testExtendsObjectsByAddingImplementedNewInterfaces()
926
    {
927
        $extendedSchema = $this->extendTestSchema('
928
          extend type Foo implements NewInterface {
929
            baz: String
930
          }
931
932
          interface NewInterface {
933
            baz: String
934
          }
935
        ');
936
937
        self::assertEquals(
938
            $this->dedent('
939
                type Foo implements SomeInterface & NewInterface {
940
                  name: String
941
                  some: SomeInterface
942
                  tree: [Foo]!
943
                  baz: String
944
                }
945
946
                interface NewInterface {
947
                  baz: String
948
                }
949
            '),
950
            $this->printTestSchemaChanges($extendedSchema)
951
        );
952
    }
953
954
    /**
955
     * @see it('extends different types multiple times')
956
     */
957
    public function testExtendsDifferentTypesMultipleTimes()
958
    {
959
        $extendedSchema = $this->extendTestSchema('
960
          extend type Biz implements NewInterface {
961
            buzz: String
962
          }
963
964
          extend type Biz implements SomeInterface {
965
            name: String
966
            some: SomeInterface
967
            newFieldA: Int
968
          }
969
970
          extend type Biz {
971
            newFieldA: Int
972
            newFieldB: Float
973
          }
974
975
          interface NewInterface {
976
            buzz: String
977
          }
978
979
          extend enum SomeEnum {
980
            THREE
981
          }
982
983
          extend enum SomeEnum {
984
            FOUR
985
          }
986
987
          extend union SomeUnion = Boo
988
989
          extend union SomeUnion = Joo
990
991
          type Boo {
992
            fieldA: String
993
          }
994
995
          type Joo {
996
            fieldB: String
997
          }
998
999
          extend input SomeInput {
1000
            fieldA: String
1001
          }
1002
1003
          extend input SomeInput {
1004
            fieldB: String
1005
          }
1006
        ');
1007
1008
        self::assertEquals(
1009
            $this->dedent('
1010
                type Biz implements NewInterface & SomeInterface {
1011
                  fizz: String
1012
                  buzz: String
1013
                  name: String
1014
                  some: SomeInterface
1015
                  newFieldA: Int
1016
                  newFieldB: Float
1017
                }
1018
1019
                type Boo {
1020
                  fieldA: String
1021
                }
1022
1023
                type Joo {
1024
                  fieldB: String
1025
                }
1026
1027
                interface NewInterface {
1028
                  buzz: String
1029
                }
1030
1031
                enum SomeEnum {
1032
                  ONE
1033
                  TWO
1034
                  THREE
1035
                  FOUR
1036
                }
1037
1038
                input SomeInput {
1039
                  fooArg: String
1040
                  fieldA: String
1041
                  fieldB: String
1042
                }
1043
1044
                union SomeUnion = Foo | Biz | Boo | Joo
1045
            '),
1046
            $this->printTestSchemaChanges($extendedSchema)
1047
        );
1048
    }
1049
1050
    /**
1051
     * @see it('extends interfaces by adding new fields')
1052
     */
1053
    public function testExtendsInterfacesByAddingNewFields()
1054
    {
1055
        $extendedSchema = $this->extendTestSchema('
1056
          extend interface SomeInterface {
1057
            newField: String
1058
          }
1059
1060
          extend type Bar {
1061
            newField: String
1062
          }
1063
1064
          extend type Foo {
1065
            newField: String
1066
          }
1067
        ');
1068
1069
        self::assertEquals(
1070
            $this->dedent('
1071
                type Bar implements SomeInterface {
1072
                  name: String
1073
                  some: SomeInterface
1074
                  foo: Foo
1075
                  newField: String
1076
                }
1077
1078
                type Foo implements SomeInterface {
1079
                  name: String
1080
                  some: SomeInterface
1081
                  tree: [Foo]!
1082
                  newField: String
1083
                }
1084
1085
                interface SomeInterface {
1086
                  name: String
1087
                  some: SomeInterface
1088
                  newField: String
1089
                }
1090
            '),
1091
            $this->printTestSchemaChanges($extendedSchema)
1092
        );
1093
    }
1094
1095
    /**
1096
     * @see it('allows extension of interface with missing Object fields')
1097
     */
1098
    public function testAllowsExtensionOfInterfaceWithMissingObjectFields()
1099
    {
1100
        $extendedSchema = $this->extendTestSchema('
1101
          extend interface SomeInterface {
1102
            newField: String
1103
          }
1104
        ');
1105
1106
        $errors = $extendedSchema->validate();
1107
        self::assertGreaterThan(0, $errors);
1108
1109
        self::assertEquals(
1110
            $this->dedent('
1111
                interface SomeInterface {
1112
                  name: String
1113
                  some: SomeInterface
1114
                  newField: String
1115
                }
1116
            '),
1117
            $this->printTestSchemaChanges($extendedSchema)
1118
        );
1119
    }
1120
1121
    /**
1122
     * @see it('extends interfaces multiple times')
1123
     */
1124
    public function testExtendsInterfacesMultipleTimes()
1125
    {
1126
        $extendedSchema = $this->extendTestSchema('
1127
          extend interface SomeInterface {
1128
            newFieldA: Int
1129
          }
1130
          extend interface SomeInterface {
1131
            newFieldB(test: Boolean): String
1132
          }
1133
        ');
1134
1135
        self::assertEquals(
1136
            $this->dedent('
1137
                interface SomeInterface {
1138
                  name: String
1139
                  some: SomeInterface
1140
                  newFieldA: Int
1141
                  newFieldB(test: Boolean): String
1142
                }
1143
            '),
1144
            $this->printTestSchemaChanges($extendedSchema)
1145
        );
1146
    }
1147
1148
    /**
1149
     * @see it('may extend mutations and subscriptions')
1150
     */
1151
    public function testMayExtendMutationsAndSubscriptions()
1152
    {
1153
        $mutationSchema = new Schema([
1154
            'query' => new ObjectType([
1155
                'name' => 'Query',
1156
                'fields' => static function () {
1157
                    return [ 'queryField' => [ 'type' => Type::string() ] ];
1158
                },
1159
            ]),
1160
            'mutation' => new ObjectType([
1161
                'name' => 'Mutation',
1162
                'fields' => static function () {
1163
                    return [ 'mutationField' => ['type' => Type::string() ] ];
1164
                },
1165
            ]),
1166
            'subscription' => new ObjectType([
1167
                'name' => 'Subscription',
1168
                'fields' => static function () {
1169
                    return ['subscriptionField' => ['type' => Type::string()]];
1170
                },
1171
            ]),
1172
        ]);
1173
1174
        $ast = Parser::parse('
1175
            extend type Query {
1176
              newQueryField: Int
1177
            }
1178
1179
            extend type Mutation {
1180
              newMutationField: Int
1181
            }
1182
1183
            extend type Subscription {
1184
              newSubscriptionField: Int
1185
            }
1186
        ');
1187
1188
        $originalPrint  = SchemaPrinter::doPrint($mutationSchema);
1189
        $extendedSchema = SchemaExtender::extend($mutationSchema, $ast);
1190
        self::assertNotEquals($mutationSchema, $extendedSchema);
1191
        self::assertEquals(SchemaPrinter::doPrint($mutationSchema), $originalPrint);
1192
        self::assertEquals(SchemaPrinter::doPrint($extendedSchema), $this->dedent('
1193
            type Mutation {
1194
              mutationField: String
1195
              newMutationField: Int
1196
            }
1197
1198
            type Query {
1199
              queryField: String
1200
              newQueryField: Int
1201
            }
1202
1203
            type Subscription {
1204
              subscriptionField: String
1205
              newSubscriptionField: Int
1206
            }
1207
        '));
1208
    }
1209
1210
    /**
1211
     * @see it('may extend directives with new simple directive')
1212
     */
1213
    public function testMayExtendDirectivesWithNewSimpleDirective()
1214
    {
1215
        $extendedSchema = $this->extendTestSchema('
1216
          directive @neat on QUERY
1217
        ');
1218
1219
        $newDirective = $extendedSchema->getDirective('neat');
1220
        self::assertEquals($newDirective->name, 'neat');
1221
        self::assertContains('QUERY', $newDirective->locations);
1222
    }
1223
1224
    /**
1225
     * @see it('sets correct description when extending with a new directive')
1226
     */
1227
    public function testSetsCorrectDescriptionWhenExtendingWithANewDirective()
1228
    {
1229
        $extendedSchema = $this->extendTestSchema('
1230
          """
1231
          new directive
1232
          """
1233
          directive @new on QUERY
1234
        ');
1235
1236
        $newDirective = $extendedSchema->getDirective('new');
1237
        self::assertEquals('new directive', $newDirective->description);
1238
    }
1239
1240
    /**
1241
     * @see it('sets correct description using legacy comments')
1242
     */
1243
    public function testSetsCorrectDescriptionUsingLegacyComments()
1244
    {
1245
        $extendedSchema = $this->extendTestSchema(
1246
            '
1247
          # new directive
1248
          directive @new on QUERY
1249
        ',
1250
            [ 'commentDescriptions' => true ]
1251
        );
1252
1253
        $newDirective = $extendedSchema->getDirective('new');
1254
        self::assertEquals('new directive', $newDirective->description);
1255
    }
1256
1257
    /**
1258
     * @see it('may extend directives with new complex directive')
1259
     */
1260
    public function testMayExtendDirectivesWithNewComplexDirective()
1261
    {
1262
        $extendedSchema = $this->extendTestSchema('
1263
          directive @profile(enable: Boolean! tag: String) on QUERY | FIELD
1264
        ');
1265
1266
        $extendedDirective = $extendedSchema->getDirective('profile');
1267
        self::assertContains('QUERY', $extendedDirective->locations);
1268
        self::assertContains('FIELD', $extendedDirective->locations);
1269
1270
        $args = $extendedDirective->args;
1271
        self::assertCount(2, $args);
1272
1273
        $arg0 = $args[0];
1274
        $arg1 = $args[1];
1275
        /** @var NonNull $arg0Type */
1276
        $arg0Type = $arg0->getType();
1277
1278
        self::assertEquals('enable', $arg0->name);
1279
        self::assertTrue($arg0Type instanceof NonNull);
1280
        self::assertTrue($arg0Type->getWrappedType() instanceof ScalarType);
1281
1282
        self::assertEquals('tag', $arg1->name);
1283
        self::assertTrue($arg1->getType() instanceof ScalarType);
1284
    }
1285
1286
    /**
1287
     * @see it('Rejects invalid SDL')
1288
     */
1289
    public function testRejectsInvalidSDL()
1290
    {
1291
        $sdl = '
1292
            extend schema @unknown
1293
        ';
1294
1295
        try {
1296
            $this->extendTestSchema($sdl);
1297
            self::fail();
1298
        } catch (Error $error) {
1299
            self::assertEquals('Unknown directive "unknown".', $error->getMessage());
1300
        }
1301
    }
1302
1303
    /**
1304
     * @see it('Allows to disable SDL validation')
1305
     */
1306
    public function testAllowsToDisableSDLValidation()
1307
    {
1308
        $sdl = '
1309
          extend schema @unknown
1310
        ';
1311
1312
        $this->extendTestSchema($sdl, [ 'assumeValid' => true ]);
1313
        $this->extendTestSchema($sdl, [ 'assumeValidSDL' => true ]);
1314
    }
1315
1316
    /**
1317
     * @see it('does not allow replacing a default directive')
1318
     */
1319
    public function testDoesNotAllowReplacingADefaultDirective()
1320
    {
1321
        $sdl = '
1322
          directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD
1323
        ';
1324
1325
        try {
1326
            $this->extendTestSchema($sdl);
1327
            self::fail();
1328
        } catch (Error $error) {
1329
            self::assertEquals('Directive "include" already exists in the schema. It cannot be redefined.', $error->getMessage());
1330
        }
1331
    }
1332
1333
    /**
1334
     * @see it('does not allow replacing a custom directive')
1335
     */
1336
    public function testDoesNotAllowReplacingACustomDirective()
1337
    {
1338
        $extendedSchema = $this->extendTestSchema('
1339
          directive @meow(if: Boolean!) on FIELD | FRAGMENT_SPREAD
1340
        ');
1341
1342
        $replacementAST = Parser::parse('
1343
            directive @meow(if: Boolean!) on FIELD | QUERY
1344
        ');
1345
1346
        try {
1347
            SchemaExtender::extend($extendedSchema, $replacementAST);
1348
            self::fail();
1349
        } catch (Error $error) {
1350
            self::assertEquals('Directive "meow" already exists in the schema. It cannot be redefined.', $error->getMessage());
1351
        }
1352
    }
1353
1354
    /**
1355
     * @see it('does not allow replacing an existing type')
1356
     */
1357
    public function testDoesNotAllowReplacingAnExistingType()
1358
    {
1359
        $existingTypeError = static function ($type) {
1360
            return 'Type "' . $type . '" already exists in the schema. It cannot also be defined in this type definition.';
1361
        };
1362
1363
        $typeSDL = '
1364
            type Bar
1365
        ';
1366
1367
        try {
1368
            $this->extendTestSchema($typeSDL);
1369
            self::fail();
1370
        } catch (Error $error) {
1371
            self::assertEquals($existingTypeError('Bar'), $error->getMessage());
1372
        }
1373
1374
        $scalarSDL = '
1375
          scalar SomeScalar
1376
        ';
1377
1378
        try {
1379
            $this->extendTestSchema($scalarSDL);
1380
            self::fail();
1381
        } catch (Error $error) {
1382
            self::assertEquals($existingTypeError('SomeScalar'), $error->getMessage());
1383
        }
1384
1385
        $interfaceSDL = '
1386
          interface SomeInterface
1387
        ';
1388
1389
        try {
1390
            $this->extendTestSchema($interfaceSDL);
1391
            self::fail();
1392
        } catch (Error $error) {
1393
            self::assertEquals($existingTypeError('SomeInterface'), $error->getMessage());
1394
        }
1395
1396
        $enumSDL = '
1397
          enum SomeEnum
1398
        ';
1399
1400
        try {
1401
            $this->extendTestSchema($enumSDL);
1402
            self::fail();
1403
        } catch (Error $error) {
1404
            self::assertEquals($existingTypeError('SomeEnum'), $error->getMessage());
1405
        }
1406
1407
        $unionSDL = '
1408
          union SomeUnion
1409
        ';
1410
1411
        try {
1412
            $this->extendTestSchema($unionSDL);
1413
            self::fail();
1414
        } catch (Error $error) {
1415
            self::assertEquals($existingTypeError('SomeUnion'), $error->getMessage());
1416
        }
1417
1418
        $inputSDL = '
1419
          input SomeInput
1420
        ';
1421
1422
        try {
1423
            $this->extendTestSchema($inputSDL);
1424
            self::fail();
1425
        } catch (Error $error) {
1426
            self::assertEquals($existingTypeError('SomeInput'), $error->getMessage());
1427
        }
1428
    }
1429
1430
    /**
1431
     * @see it('does not allow replacing an existing field')
1432
     */
1433
    public function testDoesNotAllowReplacingAnExistingField()
1434
    {
1435
        $existingFieldError = static function (string $type, string $field) {
1436
            return 'Field "' . $type . '.' . $field . '" already exists in the schema. It cannot also be defined in this type extension.';
1437
        };
1438
1439
        $typeSDL = '
1440
          extend type Bar {
1441
            foo: Foo
1442
          }
1443
        ';
1444
1445
        try {
1446
            $this->extendTestSchema($typeSDL);
1447
            self::fail();
1448
        } catch (Error $error) {
1449
            self::assertEquals($existingFieldError('Bar', 'foo'), $error->getMessage());
1450
        }
1451
1452
        $interfaceSDL = '
1453
          extend interface SomeInterface {
1454
            some: Foo
1455
          }
1456
        ';
1457
1458
        try {
1459
            $this->extendTestSchema($interfaceSDL);
1460
            self::fail();
1461
        } catch (Error  $error) {
1462
            self::assertEquals($existingFieldError('SomeInterface', 'some'), $error->getMessage());
1463
        }
1464
1465
        $inputSDL = '
1466
          extend input SomeInput {
1467
            fooArg: String
1468
          }
1469
        ';
1470
1471
        try {
1472
            $this->extendTestSchema($inputSDL);
1473
            self::fail();
1474
        } catch (Error $error) {
1475
            self::assertEquals($existingFieldError('SomeInput', 'fooArg'), $error->getMessage());
1476
        }
1477
    }
1478
1479
    /**
1480
     * @see it('does not allow replacing an existing enum value')
1481
     */
1482
    public function testDoesNotAllowReplacingAnExistingEnumValue()
1483
    {
1484
        $sdl = '
1485
          extend enum SomeEnum {
1486
            ONE
1487
          }
1488
        ';
1489
1490
        try {
1491
            $this->extendTestSchema($sdl);
1492
            self::fail();
1493
        } catch (Error $error) {
1494
            self::assertEquals('Enum value "SomeEnum.ONE" already exists in the schema. It cannot also be defined in this type extension.', $error->getMessage());
1495
        }
1496
    }
1497
1498
    /**
1499
     * @see it('does not allow referencing an unknown type')
1500
     */
1501
    public function testDoesNotAllowReferencingAnUnknownType()
1502
    {
1503
        $unknownTypeError = 'Unknown type: "Quix". Ensure that this type exists either in the original schema, or is added in a type definition.';
1504
1505
        $typeSDL = '
1506
          extend type Bar {
1507
            quix: Quix
1508
          }
1509
        ';
1510
1511
        try {
1512
            $this->extendTestSchema($typeSDL);
1513
            self::fail();
1514
        } catch (Error $error) {
1515
            self::assertEquals($unknownTypeError, $error->getMessage());
1516
        }
1517
1518
        $interfaceSDL = '
1519
          extend interface SomeInterface {
1520
            quix: Quix
1521
          }
1522
        ';
1523
1524
        try {
1525
            $this->extendTestSchema($interfaceSDL);
1526
            self::fail();
1527
        } catch (Error $error) {
1528
            self::assertEquals($unknownTypeError, $error->getMessage());
1529
        }
1530
1531
        $unionSDL = '
1532
          extend union SomeUnion = Quix
1533
        ';
1534
1535
        try {
1536
            $this->extendTestSchema($unionSDL);
1537
            self::fail();
1538
        } catch (Error $error) {
1539
            self::assertEquals($unknownTypeError, $error->getMessage());
1540
        }
1541
1542
        $inputSDL = '
1543
          extend input SomeInput {
1544
            quix: Quix
1545
          }
1546
        ';
1547
1548
        try {
1549
            $this->extendTestSchema($inputSDL);
1550
            self::fail();
1551
        } catch (Error $error) {
1552
            self::assertEquals($unknownTypeError, $error->getMessage());
1553
        }
1554
    }
1555
1556
    /**
1557
     * @see it('does not allow extending an unknown type')
1558
     */
1559
    public function testDoesNotAllowExtendingAnUnknownType()
1560
    {
1561
        $sdls = [
1562
            'extend scalar UnknownType @foo',
1563
            'extend type UnknownType @foo',
1564
            'extend interface UnknownType @foo',
1565
            'extend enum UnknownType @foo',
1566
            'extend union UnknownType @foo',
1567
            'extend input UnknownType @foo',
1568
        ];
1569
1570
        foreach ($sdls as $sdl) {
1571
            try {
1572
                $this->extendTestSchema($sdl);
1573
                self::fail();
1574
            } catch (Error $error) {
1575
                self::assertEquals('Cannot extend type "UnknownType" because it does not exist in the existing schema.', $error->getMessage());
1576
            }
1577
        }
1578
    }
1579
1580
    /**
1581
     * @see it('does not allow extending a mismatch type')
1582
     */
1583
    public function testDoesNotAllowExtendingAMismatchType()
1584
    {
1585
        $typeSDL = '
1586
          extend type SomeInterface @foo
1587
        ';
1588
1589
        try {
1590
            $this->extendTestSchema($typeSDL);
1591
            self::fail();
1592
        } catch (Error $error) {
1593
            self::assertEquals('Cannot extend non-object type "SomeInterface".', $error->getMessage());
1594
        }
1595
1596
        $interfaceSDL = '
1597
          extend interface Foo @foo
1598
        ';
1599
1600
        try {
1601
            $this->extendTestSchema($interfaceSDL);
1602
            self::fail();
1603
        } catch (Error $error) {
1604
            self::assertEquals('Cannot extend non-interface type "Foo".', $error->getMessage());
1605
        }
1606
1607
        $enumSDL = '
1608
          extend enum Foo @foo
1609
        ';
1610
1611
        try {
1612
            $this->extendTestSchema($enumSDL);
1613
            self::fail();
1614
        } catch (Error $error) {
1615
            self::assertEquals('Cannot extend non-enum type "Foo".', $error->getMessage());
1616
        }
1617
1618
        $unionSDL = '
1619
          extend union Foo @foo
1620
        ';
1621
1622
        try {
1623
            $this->extendTestSchema($unionSDL);
1624
            self::fail();
1625
        } catch (Error $error) {
1626
            self::assertEquals('Cannot extend non-union type "Foo".', $error->getMessage());
1627
        }
1628
1629
        $inputSDL = '
1630
          extend input Foo @foo
1631
        ';
1632
1633
        try {
1634
            $this->extendTestSchema($inputSDL);
1635
            self::fail();
1636
        } catch (Error $error) {
1637
            self::assertEquals('Cannot extend non-input object type "Foo".', $error->getMessage());
1638
        }
1639
    }
1640
1641
    /**
1642
     * @see it('does not automatically include common root type names')
1643
     */
1644
    public function testDoesNotAutomaticallyIncludeCommonRootTypeNames()
1645
    {
1646
        $schema = $this->extendTestSchema('
1647
            type Mutation {
1648
              doSomething: String
1649
            }
1650
        ');
1651
1652
        self::assertNull($schema->getMutationType());
1653
    }
1654
1655
    /**
1656
     * @see it('adds schema definition missing in the original schema')
1657
     */
1658
    public function testAddsSchemaDefinitionMissingInTheOriginalSchema()
1659
    {
1660
        $schema = new Schema([
1661
            'directives' => [$this->FooDirective],
1662
            'types' => [$this->FooType],
1663
        ]);
1664
1665
        self::assertNull($schema->getQueryType());
1666
1667
        $ast = Parser::parse('
1668
            schema @foo {
1669
              query: Foo
1670
            }
1671
        ');
1672
1673
        $schema    = SchemaExtender::extend($schema, $ast);
1674
        $queryType = $schema->getQueryType();
1675
1676
        self::assertEquals($queryType->name, 'Foo');
1677
    }
1678
1679
    /**
1680
     * @see it('adds new root types via schema extension')
1681
     */
1682
    public function testAddsNewRootTypesViaSchemaExtension()
1683
    {
1684
        $schema = $this->extendTestSchema('
1685
            extend schema {
1686
              mutation: Mutation
1687
            }
1688
            type Mutation {
1689
              doSomething: String
1690
            }
1691
        ');
1692
1693
        $mutationType = $schema->getMutationType();
1694
        self::assertEquals($mutationType->name, 'Mutation');
1695
    }
1696
1697
    /**
1698
     * @see it('adds multiple new root types via schema extension')
1699
     */
1700
    public function testAddsMultipleNewRootTypesViaSchemaExtension()
1701
    {
1702
        $schema           = $this->extendTestSchema('
1703
            extend schema {
1704
              mutation: Mutation
1705
              subscription: Subscription
1706
            }
1707
            type Mutation {
1708
              doSomething: String
1709
            }
1710
            type Subscription {
1711
              hearSomething: String
1712
            }
1713
        ');
1714
        $mutationType     = $schema->getMutationType();
1715
        $subscriptionType = $schema->getSubscriptionType();
1716
1717
        self::assertEquals('Mutation', $mutationType->name);
1718
        self::assertEquals('Subscription', $subscriptionType->name);
1719
    }
1720
1721
    /**
1722
     * @see it('applies multiple schema extensions')
1723
     */
1724
    public function testAppliesMultipleSchemaExtensions()
1725
    {
1726
        $schema = $this->extendTestSchema('
1727
            extend schema {
1728
              mutation: Mutation
1729
            }
1730
            extend schema {
1731
              subscription: Subscription
1732
            }
1733
            type Mutation {
1734
              doSomething: String
1735
            }
1736
            type Subscription {
1737
              hearSomething: String
1738
            }
1739
        ');
1740
1741
        $mutationType     = $schema->getMutationType();
1742
        $subscriptionType = $schema->getSubscriptionType();
1743
1744
        self::assertEquals('Mutation', $mutationType->name);
1745
        self::assertEquals('Subscription', $subscriptionType->name);
1746
    }
1747
1748
    /**
1749
     * @see it('schema extension AST are available from schema object')
1750
     */
1751
    public function testSchemaExtensionASTAreAvailableFromSchemaObject()
1752
    {
1753
        $schema = $this->extendTestSchema('
1754
            extend schema {
1755
              mutation: Mutation
1756
            }
1757
            extend schema {
1758
              subscription: Subscription
1759
            }
1760
            type Mutation {
1761
              doSomething: String
1762
            }
1763
            type Subscription {
1764
              hearSomething: String
1765
            }
1766
        ');
1767
1768
        $ast    = Parser::parse('
1769
            extend schema @foo
1770
        ');
1771
        $schema = SchemaExtender::extend($schema, $ast);
1772
1773
        $nodes = $schema->extensionASTNodes;
1774
        self::assertEquals(
1775
            $this->dedent('
1776
                extend schema {
1777
                  mutation: Mutation
1778
                }
1779
1780
                extend schema {
1781
                  subscription: Subscription
1782
                }
1783
1784
                extend schema @foo
1785
            '),
1786
            implode(
1787
                "\n",
1788
                array_map(static function ($node) {
1789
                    return Printer::doPrint($node) . "\n";
1790
                }, $nodes)
1791
            )
1792
        );
1793
    }
1794
1795
    /**
1796
     * @see it('does not allow redefining an existing root type')
1797
     */
1798
    public function testDoesNotAllowRedefiningAnExistingRootType()
1799
    {
1800
        $sdl = '
1801
            extend schema {
1802
                query: SomeType
1803
            }
1804
1805
            type SomeType {
1806
                seeSomething: String
1807
            }
1808
        ';
1809
1810
        try {
1811
            $this->extendTestSchema($sdl);
1812
            self::fail();
1813
        } catch (Error $error) {
1814
            self::assertEquals('Must provide only one query type in schema.', $error->getMessage());
1815
        }
1816
    }
1817
1818
    /**
1819
     * @see it('does not allow defining a root operation type twice')
1820
     */
1821
    public function testDoesNotAllowDefiningARootOperationTypeTwice()
1822
    {
1823
        $sdl = '
1824
            extend schema {
1825
              mutation: Mutation
1826
            }
1827
            extend schema {
1828
              mutation: Mutation
1829
            }
1830
            type Mutation {
1831
              doSomething: String
1832
            }
1833
        ';
1834
1835
        try {
1836
            $this->extendTestSchema($sdl);
1837
            self::fail();
1838
        } catch (Error $error) {
1839
            self::assertEquals('Must provide only one mutation type in schema.', $error->getMessage());
1840
        }
1841
    }
1842
1843
    /**
1844
     * @see it('does not allow defining a root operation type with different types')
1845
     */
1846
    public function testDoesNotAllowDefiningARootOperationTypeWithDifferentTypes()
1847
    {
1848
        $sdl = '
1849
            extend schema {
1850
              mutation: Mutation
1851
            }
1852
            extend schema {
1853
              mutation: SomethingElse
1854
            }
1855
            type Mutation {
1856
              doSomething: String
1857
            }
1858
            type SomethingElse {
1859
              doSomethingElse: String
1860
            }
1861
        ';
1862
1863
        try {
1864
            $this->extendTestSchema($sdl);
1865
            self::fail();
1866
        } catch (Error $error) {
1867
            self::assertEquals('Must provide only one mutation type in schema.', $error->getMessage());
1868
        }
1869
    }
1870
1871
    /**
1872
     * @see https://github.com/webonyx/graphql-php/pull/381
1873
     */
1874
    public function testOriginalResolversArePreserved()
1875
    {
1876
        $queryType = new ObjectType([
1877
            'name' => 'Query',
1878
            'fields' => [
1879
                'hello' => [
1880
                    'type' => Type::string(),
1881
                    'resolve' => static function () {
1882
                        return 'Hello World!';
1883
                    },
1884
                ],
1885
            ],
1886
        ]);
1887
1888
        $schema = new Schema(['query' => $queryType]);
1889
1890
        $documentNode = Parser::parse('
1891
extend type Query {
1892
	misc: String
1893
}
1894
');
1895
1896
        $extendedSchema = SchemaExtender::extend($schema, $documentNode);
1897
        $helloResolveFn = $extendedSchema->getQueryType()->getField('hello')->resolveFn;
1898
1899
        self::assertInternalType('callable', $helloResolveFn);
0 ignored issues
show
Deprecated Code introduced by
The function PHPUnit\Framework\Assert::assertInternalType() has been deprecated: https://github.com/sebastianbergmann/phpunit/issues/3369 ( Ignorable by Annotation )

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

1899
        /** @scrutinizer ignore-deprecated */ self::assertInternalType('callable', $helloResolveFn);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1900
1901
        $query  = '{ hello }';
1902
        $result = GraphQL::executeQuery($extendedSchema, $query);
1903
        self::assertSame(['data' => ['hello' => 'Hello World!']], $result->toArray());
1904
    }
1905
1906
    /**
1907
     * @see https://github.com/webonyx/graphql-php/issues/180
1908
     */
1909
    public function testShouldBeAbleToIntroduceNewTypesThroughExtension()
1910
    {
1911
        $sdl = '
1912
          type Query {
1913
            defaultValue: String
1914
          }
1915
          type Foo {
1916
            value: Int
1917
          }
1918
        ';
1919
1920
        $documentNode = Parser::parse($sdl);
1921
        $schema       = BuildSchema::build($documentNode);
1922
1923
        $extensionSdl = '
1924
          type Bar {
1925
            foo: Foo
1926
          }
1927
        ';
1928
1929
        $extendedDocumentNode = Parser::parse($extensionSdl);
1930
        $extendedSchema       = SchemaExtender::extend($schema, $extendedDocumentNode);
1931
1932
        $expected = '
1933
            type Bar {
1934
              foo: Foo
1935
            }
1936
            
1937
            type Foo {
1938
              value: Int
1939
            }
1940
            
1941
            type Query {
1942
              defaultValue: String
1943
            }
1944
        ';
1945
1946
        static::assertEquals($this->dedent($expected), SchemaPrinter::doPrint($extendedSchema));
1947
    }
1948
}
1949