Failed Conditions
Push — master ( 7ff3e9...e7de06 )
by Vladimir
04:33 queued 01:54
created

testDoesNotAllowDefiningARootOperationTypeWithDifferentTypes()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 22
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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