Passed
Pull Request — master (#586)
by Šimon
12:52
created

BuildSchemaTest   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 1325
Duplicated Lines 0 %

Importance

Changes 8
Bugs 1 Features 0
Metric Value
wmc 54
eloc 336
c 8
b 1
f 0
dl 0
loc 1325
rs 6.4799

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Tests\Utils;
6
7
use Closure;
8
use GraphQL\Error\Error;
9
use GraphQL\GraphQL;
10
use GraphQL\Language\AST\EnumTypeDefinitionNode;
11
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
12
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
13
use GraphQL\Language\Parser;
14
use GraphQL\Language\Printer;
15
use GraphQL\Type\Definition\Directive;
16
use GraphQL\Type\Definition\EnumType;
17
use GraphQL\Type\Definition\InputObjectType;
18
use GraphQL\Type\Definition\InterfaceType;
19
use GraphQL\Type\Definition\ObjectType;
20
use GraphQL\Type\Definition\ScalarType;
21
use GraphQL\Type\Definition\UnionType;
22
use GraphQL\Utils\BuildSchema;
23
use GraphQL\Utils\SchemaPrinter;
24
use PHPUnit\Framework\TestCase;
25
use function array_keys;
26
use function count;
27
28
class BuildSchemaTest extends TestCase
29
{
30
    // Describe: Schema Builder
31
    /**
32
     * @see it('can use built schema for limited execution')
33
     */
34
    public function testUseBuiltSchemaForLimitedExecution() : void
35
    {
36
        $schema = BuildSchema::buildAST(Parser::parse('
37
            type Query {
38
                str: String
39
            }
40
        '));
41
42
        $result = GraphQL::executeQuery($schema, '{ str }', ['str' => 123]);
43
        self::assertEquals(['str' => 123], $result->toArray(true)['data']);
44
    }
45
46
    /**
47
     * @see it('can build a schema directly from the source')
48
     */
49
    public function testBuildSchemaDirectlyFromSource() : void
50
    {
51
        $schema = BuildSchema::build('
52
            type Query {
53
                add(x: Int, y: Int): Int
54
            }
55
        ');
56
57
        $root = [
58
            'add' => static function ($rootValue, $args) {
59
                return $args['x'] + $args['y'];
60
            },
61
        ];
62
63
        $result = GraphQL::executeQuery(
64
            $schema,
65
            '{ add(x: 34, y: 55) }',
66
            $root
67
        );
68
        self::assertEquals(['data' => ['add' => 89]], $result->toArray(true));
69
    }
70
71
    /**
72
     * @see it('Simple Type')
73
     */
74
    public function testSimpleType() : void
75
    {
76
        $body   = '
77
type HelloScalars {
78
  str: String
79
  int: Int
80
  float: Float
81
  id: ID
82
  bool: Boolean
83
}
84
';
85
        $output = $this->cycleOutput($body);
86
        self::assertEquals($output, $body);
87
    }
88
89
    private function cycleOutput($body, $options = [])
90
    {
91
        $ast    = Parser::parse($body);
92
        $schema = BuildSchema::buildAST($ast, null, $options);
93
94
        return "\n" . SchemaPrinter::doPrint($schema, $options);
95
    }
96
97
    /**
98
     * @see it('With directives')
99
     */
100
    public function testWithDirectives() : void
101
    {
102
        $body   = '
103
directive @foo(arg: Int) on FIELD
104
105
type Query {
106
  str: String
107
}
108
';
109
        $output = $this->cycleOutput($body);
110
        self::assertEquals($output, $body);
111
    }
112
113
    /**
114
     * @see it('Supports descriptions')
115
     */
116
    public function testSupportsDescriptions() : void
117
    {
118
        $body = '
119
"""This is a directive"""
120
directive @foo(
121
  """It has an argument"""
122
  arg: Int
123
) on FIELD
124
125
"""With an enum"""
126
enum Color {
127
  RED
128
129
  """Not a creative color"""
130
  GREEN
131
  BLUE
132
}
133
134
"""What a great type"""
135
type Query {
136
  """And a field to boot"""
137
  str: String
138
}
139
';
140
141
        $output = $this->cycleOutput($body);
142
        self::assertEquals($body, $output);
143
    }
144
145
    /**
146
     * @see it('Supports option for comment descriptions')
147
     */
148
    public function testSupportsOptionForCommentDescriptions() : void
149
    {
150
        $body   = '
151
# This is a directive
152
directive @foo(
153
  # It has an argument
154
  arg: Int
155
) on FIELD
156
157
# With an enum
158
enum Color {
159
  RED
160
161
  # Not a creative color
162
  GREEN
163
  BLUE
164
}
165
166
# What a great type
167
type Query {
168
  # And a field to boot
169
  str: String
170
}
171
';
172
        $output = $this->cycleOutput($body, ['commentDescriptions' => true]);
173
        self::assertEquals($body, $output);
174
    }
175
176
    /**
177
     * @see it('Maintains @skip & @include')
178
     */
179
    public function testMaintainsSkipAndInclude() : void
180
    {
181
        $body   = '
182
type Query {
183
  str: String
184
}
185
';
186
        $schema = BuildSchema::buildAST(Parser::parse($body));
187
        self::assertEquals(count($schema->getDirectives()), 3);
188
        self::assertEquals($schema->getDirective('skip'), Directive::skipDirective());
189
        self::assertEquals($schema->getDirective('include'), Directive::includeDirective());
190
        self::assertEquals($schema->getDirective('deprecated'), Directive::deprecatedDirective());
191
    }
192
193
    /**
194
     * @see it('Overriding directives excludes specified')
195
     */
196
    public function testOverridingDirectivesExcludesSpecified() : void
197
    {
198
        $body   = '
199
directive @skip on FIELD
200
directive @include on FIELD
201
directive @deprecated on FIELD_DEFINITION
202
203
type Query {
204
  str: String
205
}
206
    ';
207
        $schema = BuildSchema::buildAST(Parser::parse($body));
208
        self::assertEquals(count($schema->getDirectives()), 3);
209
        self::assertNotEquals($schema->getDirective('skip'), Directive::skipDirective());
210
        self::assertNotEquals($schema->getDirective('include'), Directive::includeDirective());
211
        self::assertNotEquals($schema->getDirective('deprecated'), Directive::deprecatedDirective());
212
    }
213
214
    /**
215
     * @see it('Adding directives maintains @skip & @include')
216
     */
217
    public function testAddingDirectivesMaintainsSkipAndInclude() : void
218
    {
219
        $body   = '
220
      directive @foo(arg: Int) on FIELD
221
222
      type Query {
223
        str: String
224
      }
225
    ';
226
        $schema = BuildSchema::buildAST(Parser::parse($body));
227
        self::assertCount(4, $schema->getDirectives());
228
        self::assertNotEquals(null, $schema->getDirective('skip'));
229
        self::assertNotEquals(null, $schema->getDirective('include'));
230
        self::assertNotEquals(null, $schema->getDirective('deprecated'));
231
    }
232
233
    /**
234
     * @see it('Type modifiers')
235
     */
236
    public function testTypeModifiers() : void
237
    {
238
        $body   = '
239
type HelloScalars {
240
  nonNullStr: String!
241
  listOfStrs: [String]
242
  listOfNonNullStrs: [String!]
243
  nonNullListOfStrs: [String]!
244
  nonNullListOfNonNullStrs: [String!]!
245
}
246
';
247
        $output = $this->cycleOutput($body);
248
        self::assertEquals($output, $body);
249
    }
250
251
    /**
252
     * @see it('Recursive type')
253
     */
254
    public function testRecursiveType() : void
255
    {
256
        $body   = '
257
type Query {
258
  str: String
259
  recurse: Query
260
}
261
';
262
        $output = $this->cycleOutput($body);
263
        self::assertEquals($output, $body);
264
    }
265
266
    /**
267
     * @see it('Two types circular')
268
     */
269
    public function testTwoTypesCircular() : void
270
    {
271
        $body   = '
272
schema {
273
  query: TypeOne
274
}
275
276
type TypeOne {
277
  str: String
278
  typeTwo: TypeTwo
279
}
280
281
type TypeTwo {
282
  str: String
283
  typeOne: TypeOne
284
}
285
';
286
        $output = $this->cycleOutput($body);
287
        self::assertEquals($output, $body);
288
    }
289
290
    /**
291
     * @see it('Single argument field')
292
     */
293
    public function testSingleArgumentField() : void
294
    {
295
        $body   = '
296
type Query {
297
  str(int: Int): String
298
  floatToStr(float: Float): String
299
  idToStr(id: ID): String
300
  booleanToStr(bool: Boolean): String
301
  strToStr(bool: String): String
302
}
303
';
304
        $output = $this->cycleOutput($body);
305
        self::assertEquals($output, $body);
306
    }
307
308
    /**
309
     * @see it('Simple type with multiple arguments')
310
     */
311
    public function testSimpleTypeWithMultipleArguments() : void
312
    {
313
        $body   = '
314
type Query {
315
  str(int: Int, bool: Boolean): String
316
}
317
';
318
        $output = $this->cycleOutput($body);
319
        self::assertEquals($output, $body);
320
    }
321
322
    /**
323
     * @see it('Simple type with interface')
324
     */
325
    public function testSimpleTypeWithInterface() : void
326
    {
327
        $body   = '
328
type Query implements WorldInterface {
329
  str: String
330
}
331
332
interface WorldInterface {
333
  str: String
334
}
335
';
336
        $output = $this->cycleOutput($body);
337
        self::assertEquals($output, $body);
338
    }
339
340
    /**
341
     * @see it('Simple output enum')
342
     */
343
    public function testSimpleOutputEnum() : void
344
    {
345
        $body   = '
346
enum Hello {
347
  WORLD
348
}
349
350
type Query {
351
  hello: Hello
352
}
353
';
354
        $output = $this->cycleOutput($body);
355
        self::assertEquals($output, $body);
356
    }
357
358
    /**
359
     * @see it('Simple input enum')
360
     */
361
    public function testSimpleInputEnum() : void
362
    {
363
        $body   = '
364
enum Hello {
365
  WORLD
366
}
367
368
type Query {
369
  str(hello: Hello): String
370
}
371
';
372
        $output = $this->cycleOutput($body);
373
        self::assertEquals($body, $output);
374
    }
375
376
    /**
377
     * @see it('Multiple value enum')
378
     */
379
    public function testMultipleValueEnum() : void
380
    {
381
        $body   = '
382
enum Hello {
383
  WO
384
  RLD
385
}
386
387
type Query {
388
  hello: Hello
389
}
390
';
391
        $output = $this->cycleOutput($body);
392
        self::assertEquals($output, $body);
393
    }
394
395
    /**
396
     * @see it('Simple Union')
397
     */
398
    public function testSimpleUnion() : void
399
    {
400
        $body   = '
401
union Hello = World
402
403
type Query {
404
  hello: Hello
405
}
406
407
type World {
408
  str: String
409
}
410
';
411
        $output = $this->cycleOutput($body);
412
        self::assertEquals($output, $body);
413
    }
414
415
    /**
416
     * @see it('Multiple Union')
417
     */
418
    public function testMultipleUnion() : void
419
    {
420
        $body   = '
421
union Hello = WorldOne | WorldTwo
422
423
type Query {
424
  hello: Hello
425
}
426
427
type WorldOne {
428
  str: String
429
}
430
431
type WorldTwo {
432
  str: String
433
}
434
';
435
        $output = $this->cycleOutput($body);
436
        self::assertEquals($output, $body);
437
    }
438
439
    /**
440
     * @see it('Can build recursive Union')
441
     */
442
    public function testCanBuildRecursiveUnion()
443
    {
444
        $schema = BuildSchema::build('
445
          union Hello = Hello
446
    
447
          type Query {
448
            hello: Hello
449
          }
450
        ');
451
        $errors = $schema->validate();
452
        self::assertNotEmpty($errors);
453
    }
454
455
    /**
456
     * @see it('Specifying Union type using __typename')
457
     */
458
    public function testSpecifyingUnionTypeUsingTypename() : void
459
    {
460
        $schema    = BuildSchema::buildAST(Parser::parse('
461
            type Query {
462
              fruits: [Fruit]
463
            }
464
            
465
            union Fruit = Apple | Banana
466
            
467
            type Apple {
468
              color: String
469
            }
470
            
471
            type Banana {
472
              length: Int
473
            }
474
        '));
475
        $query     = '
476
            {
477
              fruits {
478
                ... on Apple {
479
                  color
480
                }
481
                ... on Banana {
482
                  length
483
                }
484
              }
485
            }
486
        ';
487
        $rootValue = [
488
            'fruits' => [
489
                [
490
                    'color'      => 'green',
491
                    '__typename' => 'Apple',
492
                ],
493
                [
494
                    'length'     => 5,
495
                    '__typename' => 'Banana',
496
                ],
497
            ],
498
        ];
499
        $expected  = [
500
            'data' => [
501
                'fruits' => [
502
                    ['color' => 'green'],
503
                    ['length' => 5],
504
                ],
505
            ],
506
        ];
507
508
        $result = GraphQL::executeQuery($schema, $query, $rootValue);
509
        self::assertEquals($expected, $result->toArray(true));
510
    }
511
512
    /**
513
     * @see it('Specifying Interface type using __typename')
514
     */
515
    public function testSpecifyingInterfaceUsingTypename() : void
516
    {
517
        $schema    = BuildSchema::buildAST(Parser::parse('
518
            type Query {
519
              characters: [Character]
520
            }
521
            
522
            interface Character {
523
              name: String!
524
            }
525
            
526
            type Human implements Character {
527
              name: String!
528
              totalCredits: Int
529
            }
530
            
531
            type Droid implements Character {
532
              name: String!
533
              primaryFunction: String
534
            }
535
        '));
536
        $query     = '
537
            {
538
              characters {
539
                name
540
                ... on Human {
541
                  totalCredits
542
                }
543
                ... on Droid {
544
                  primaryFunction
545
                }
546
              }
547
            }
548
        ';
549
        $rootValue = [
550
            'characters' => [
551
                [
552
                    'name'         => 'Han Solo',
553
                    'totalCredits' => 10,
554
                    '__typename'   => 'Human',
555
                ],
556
                [
557
                    'name'            => 'R2-D2',
558
                    'primaryFunction' => 'Astromech',
559
                    '__typename'      => 'Droid',
560
                ],
561
            ],
562
        ];
563
        $expected  = [
564
            'data' => [
565
                'characters' => [
566
                    ['name' => 'Han Solo', 'totalCredits' => 10],
567
                    ['name' => 'R2-D2', 'primaryFunction' => 'Astromech'],
568
                ],
569
            ],
570
        ];
571
572
        $result = GraphQL::executeQuery($schema, $query, $rootValue);
573
        self::assertEquals($expected, $result->toArray(true));
574
    }
575
576
    /**
577
     * @see it('Custom Scalar')
578
     */
579
    public function testCustomScalar() : void
580
    {
581
        $body   = '
582
scalar CustomScalar
583
584
type Query {
585
  customScalar: CustomScalar
586
}
587
';
588
        $output = $this->cycleOutput($body);
589
        self::assertEquals($output, $body);
590
    }
591
592
    /**
593
     * @see it('Input Object')
594
     */
595
    public function testInputObject() : void
596
    {
597
        $body   = '
598
input Input {
599
  int: Int
600
}
601
602
type Query {
603
  field(in: Input): String
604
}
605
';
606
        $output = $this->cycleOutput($body);
607
        self::assertEquals($output, $body);
608
    }
609
610
    /**
611
     * @see it('Simple argument field with default')
612
     */
613
    public function testSimpleArgumentFieldWithDefault() : void
614
    {
615
        $body   = '
616
type Query {
617
  str(int: Int = 2): String
618
}
619
';
620
        $output = $this->cycleOutput($body);
621
        self::assertEquals($output, $body);
622
    }
623
624
    /**
625
     * @see it('Custom scalar argument field with default')
626
     */
627
    public function testCustomScalarArgumentFieldWithDefault() : void
628
    {
629
        $body   = '
630
scalar CustomScalar
631
632
type Query {
633
  str(int: CustomScalar = 2): String
634
}
635
';
636
        $output = $this->cycleOutput($body);
637
        self::assertEquals($output, $body);
638
    }
639
640
    /**
641
     * @see it('Simple type with mutation')
642
     */
643
    public function testSimpleTypeWithMutation() : void
644
    {
645
        $body   = '
646
schema {
647
  query: HelloScalars
648
  mutation: Mutation
649
}
650
651
type HelloScalars {
652
  str: String
653
  int: Int
654
  bool: Boolean
655
}
656
657
type Mutation {
658
  addHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars
659
}
660
';
661
        $output = $this->cycleOutput($body);
662
        self::assertEquals($output, $body);
663
    }
664
665
    /**
666
     * @see it('Simple type with subscription')
667
     */
668
    public function testSimpleTypeWithSubscription() : void
669
    {
670
        $body   = '
671
schema {
672
  query: HelloScalars
673
  subscription: Subscription
674
}
675
676
type HelloScalars {
677
  str: String
678
  int: Int
679
  bool: Boolean
680
}
681
682
type Subscription {
683
  subscribeHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars
684
}
685
';
686
        $output = $this->cycleOutput($body);
687
        self::assertEquals($output, $body);
688
    }
689
690
    /**
691
     * @see it('Unreferenced type implementing referenced interface')
692
     */
693
    public function testUnreferencedTypeImplementingReferencedInterface() : void
694
    {
695
        $body   = '
696
type Concrete implements Iface {
697
  key: String
698
}
699
700
interface Iface {
701
  key: String
702
}
703
704
type Query {
705
  iface: Iface
706
}
707
';
708
        $output = $this->cycleOutput($body);
709
        self::assertEquals($output, $body);
710
    }
711
712
    /**
713
     * @see it('Unreferenced type implementing referenced union')
714
     */
715
    public function testUnreferencedTypeImplementingReferencedUnion() : void
716
    {
717
        $body   = '
718
type Concrete {
719
  key: String
720
}
721
722
type Query {
723
  union: Union
724
}
725
726
union Union = Concrete
727
';
728
        $output = $this->cycleOutput($body);
729
        self::assertEquals($output, $body);
730
    }
731
732
    /**
733
     * @see it('Supports @deprecated')
734
     */
735
    public function testSupportsDeprecated() : void
736
    {
737
        $body   = '
738
enum MyEnum {
739
  VALUE
740
  OLD_VALUE @deprecated
741
  OTHER_VALUE @deprecated(reason: "Terrible reasons")
742
}
743
744
type Query {
745
  field1: String @deprecated
746
  field2: Int @deprecated(reason: "Because I said so")
747
  enum: MyEnum
748
}
749
';
750
        $output = $this->cycleOutput($body);
751
        self::assertEquals($output, $body);
752
753
        $ast    = Parser::parse($body);
754
        $schema = BuildSchema::buildAST($ast);
755
756
        /** @var EnumType $myEnum */
757
        $myEnum = $schema->getType('MyEnum');
758
759
        $value = $myEnum->getValue('VALUE');
760
        self::assertFalse($value->isDeprecated());
761
762
        $oldValue = $myEnum->getValue('OLD_VALUE');
763
        self::assertTrue($oldValue->isDeprecated());
764
        self::assertEquals('No longer supported', $oldValue->deprecationReason);
765
766
        $otherValue = $myEnum->getValue('OTHER_VALUE');
767
        self::assertTrue($otherValue->isDeprecated());
768
        self::assertEquals('Terrible reasons', $otherValue->deprecationReason);
769
770
        /** @var ObjectType $queryType */
771
        $queryType  = $schema->getType('Query');
772
        $rootFields = $queryType->getFields();
773
        self::assertEquals($rootFields['field1']->isDeprecated(), true);
774
        self::assertEquals($rootFields['field1']->deprecationReason, 'No longer supported');
775
776
        self::assertEquals($rootFields['field2']->isDeprecated(), true);
777
        self::assertEquals($rootFields['field2']->deprecationReason, 'Because I said so');
778
    }
779
780
    /**
781
     * @see it('Correctly assign AST nodes')
782
     */
783
    public function testCorrectlyAssignASTNodes() : void
784
    {
785
        $schemaAST = Parser::parse('
786
      schema {
787
        query: Query
788
      }
789
790
      type Query {
791
        testField(testArg: TestInput): TestUnion
792
      }
793
794
      input TestInput {
795
        testInputField: TestEnum
796
      }
797
798
      enum TestEnum {
799
        TEST_VALUE
800
      }
801
802
      union TestUnion = TestType
803
804
      interface TestInterface {
805
        interfaceField: String
806
      }
807
808
      type TestType implements TestInterface {
809
        interfaceField: String
810
      }
811
      
812
      scalar TestScalar
813
814
      directive @test(arg: TestScalar) on FIELD
815
    ');
816
        $schema    = BuildSchema::buildAST($schemaAST);
817
818
        /** @var ObjectType $query */
819
        $query = $schema->getType('Query');
820
        /** @var InputObjectType $testInput */
821
        $testInput = $schema->getType('TestInput');
822
        /** @var EnumType $testEnum */
823
        $testEnum = $schema->getType('TestEnum');
824
        /** @var UnionType $testUnion */
825
        $testUnion = $schema->getType('TestUnion');
826
        /** @var InterfaceType $testInterface */
827
        $testInterface = $schema->getType('TestInterface');
828
        /** @var ObjectType $testType */
829
        $testType = $schema->getType('TestType');
830
        /** @var ScalarType $testScalar */
831
        $testScalar    = $schema->getType('TestScalar');
832
        $testDirective = $schema->getDirective('test');
833
834
        $restoredIDL = SchemaPrinter::doPrint(BuildSchema::build(
835
            Printer::doPrint($schema->getAstNode()) . "\n" .
836
            Printer::doPrint($query->astNode) . "\n" .
837
            Printer::doPrint($testInput->astNode) . "\n" .
838
            Printer::doPrint($testEnum->astNode) . "\n" .
839
            Printer::doPrint($testUnion->astNode) . "\n" .
840
            Printer::doPrint($testInterface->astNode) . "\n" .
841
            Printer::doPrint($testType->astNode) . "\n" .
842
            Printer::doPrint($testScalar->astNode) . "\n" .
843
            Printer::doPrint($testDirective->astNode)
844
        ));
845
846
        self::assertEquals($restoredIDL, SchemaPrinter::doPrint($schema));
847
848
        $testField = $query->getField('testField');
849
        self::assertEquals('testField(testArg: TestInput): TestUnion', Printer::doPrint($testField->astNode));
850
        self::assertEquals('testArg: TestInput', Printer::doPrint($testField->args[0]->astNode));
851
        self::assertEquals(
852
            'testInputField: TestEnum',
853
            Printer::doPrint($testInput->getField('testInputField')->astNode)
854
        );
855
        self::assertEquals('TEST_VALUE', Printer::doPrint($testEnum->getValue('TEST_VALUE')->astNode));
856
        self::assertEquals(
857
            'interfaceField: String',
858
            Printer::doPrint($testInterface->getField('interfaceField')->astNode)
859
        );
860
        self::assertEquals('interfaceField: String', Printer::doPrint($testType->getField('interfaceField')->astNode));
861
        self::assertEquals('arg: TestScalar', Printer::doPrint($testDirective->args[0]->astNode));
862
    }
863
864
    /**
865
     * @see it('Root operation types with custom names')
866
     */
867
    public function testRootOperationTypesWithCustomNames() : void
868
    {
869
        $schema = BuildSchema::build('
870
          schema {
871
            query: SomeQuery
872
            mutation: SomeMutation
873
            subscription: SomeSubscription
874
          }
875
          type SomeQuery { str: String }
876
          type SomeMutation { str: String }
877
          type SomeSubscription { str: String }
878
        ');
879
880
        self::assertEquals('SomeQuery', $schema->getQueryType()->name);
881
        self::assertEquals('SomeMutation', $schema->getMutationType()->name);
882
        self::assertEquals('SomeSubscription', $schema->getSubscriptionType()->name);
883
    }
884
885
    /**
886
     * @see it('Default root operation type names')
887
     */
888
    public function testDefaultRootOperationTypeNames() : void
889
    {
890
        $schema = BuildSchema::build('
891
          type Query { str: String }
892
          type Mutation { str: String }
893
          type Subscription { str: String }
894
        ');
895
        self::assertEquals('Query', $schema->getQueryType()->name);
896
        self::assertEquals('Mutation', $schema->getMutationType()->name);
897
        self::assertEquals('Subscription', $schema->getSubscriptionType()->name);
898
    }
899
900
    /**
901
     * @see it('can build invalid schema')
902
     */
903
    public function testCanBuildInvalidSchema() : void
904
    {
905
        $schema = BuildSchema::build('
906
          # Invalid schema, because it is missing query root type
907
          type Mutation {
908
            str: String
909
          }
910
        ');
911
        $errors = $schema->validate();
912
        self::assertGreaterThan(0, $errors);
913
    }
914
915
    /**
916
     * @see it('Rejects invalid SDL')
917
     */
918
    public function testRejectsInvalidSDL()
919
    {
920
        $doc = Parser::parse('
921
          type Query {
922
            foo: String @unknown
923
          }
924
        ');
925
        $this->expectException(Error::class);
926
        $this->expectExceptionMessage('Unknown directive "unknown".');
927
        BuildSchema::build($doc);
928
    }
929
930
    /**
931
     * @see it('Allows to disable SDL validation')
932
     */
933
    public function testAllowsToDisableSDLValidation()
934
    {
935
        $body = '
936
          type Query {
937
            foo: String @unknown
938
          }
939
        ';
940
        // Should not throw:
941
        BuildSchema::build($body, null, ['assumeValid' => true]);
942
        BuildSchema::build($body, null, ['assumeValidSDL' => true]);
943
        self::assertTrue(true);
944
    }
945
946
    // Describe: Failures
947
948
    /**
949
     * @see it('Allows only a single query type')
950
     */
951
    public function testAllowsOnlySingleQueryType() : void
952
    {
953
        $this->expectException(Error::class);
954
        $this->expectExceptionMessage('Must provide only one query type in schema.');
955
        $body = '
956
schema {
957
  query: Hello
958
  query: Yellow
959
}
960
961
type Hello {
962
  bar: String
963
}
964
965
type Yellow {
966
  isColor: Boolean
967
}
968
';
969
        $doc  = Parser::parse($body);
970
        BuildSchema::buildAST($doc);
971
    }
972
973
    /**
974
     * @see it('Allows only a single mutation type')
975
     */
976
    public function testAllowsOnlySingleMutationType() : void
977
    {
978
        $this->expectException(Error::class);
979
        $this->expectExceptionMessage('Must provide only one mutation type in schema.');
980
        $body = '
981
schema {
982
  query: Hello
983
  mutation: Hello
984
  mutation: Yellow
985
}
986
987
type Hello {
988
  bar: String
989
}
990
991
type Yellow {
992
  isColor: Boolean
993
}
994
';
995
        $doc  = Parser::parse($body);
996
        BuildSchema::buildAST($doc);
997
    }
998
999
    /**
1000
     * @see it('Allows only a single subscription type')
1001
     */
1002
    public function testAllowsOnlySingleSubscriptionType() : void
1003
    {
1004
        $this->expectException(Error::class);
1005
        $this->expectExceptionMessage('Must provide only one subscription type in schema.');
1006
        $body = '
1007
schema {
1008
  query: Hello
1009
  subscription: Hello
1010
  subscription: Yellow
1011
}
1012
1013
type Hello {
1014
  bar: String
1015
}
1016
1017
type Yellow {
1018
  isColor: Boolean
1019
}
1020
';
1021
        $doc  = Parser::parse($body);
1022
        BuildSchema::buildAST($doc);
1023
    }
1024
1025
    /**
1026
     * @see it('Unknown type referenced')
1027
     */
1028
    public function testUnknownTypeReferenced() : void
1029
    {
1030
        $this->expectException(Error::class);
1031
        $this->expectExceptionMessage('Type "Bar" not found in document.');
1032
        $body   = '
1033
schema {
1034
  query: Hello
1035
}
1036
1037
type Hello {
1038
  bar: Bar
1039
}
1040
';
1041
        $doc    = Parser::parse($body);
1042
        $schema = BuildSchema::buildAST($doc);
1043
        $schema->getTypeMap();
1044
    }
1045
1046
    /**
1047
     * @see it('Unknown type in interface list')
1048
     */
1049
    public function testUnknownTypeInInterfaceList() : void
1050
    {
1051
        $this->expectException(Error::class);
1052
        $this->expectExceptionMessage('Type "Bar" not found in document.');
1053
        $body   = '
1054
type Query implements Bar {
1055
  field: String
1056
}
1057
';
1058
        $doc    = Parser::parse($body);
1059
        $schema = BuildSchema::buildAST($doc);
1060
        $schema->getTypeMap();
1061
    }
1062
1063
    /**
1064
     * @see it('Unknown type in union list')
1065
     */
1066
    public function testUnknownTypeInUnionList() : void
1067
    {
1068
        $this->expectException(Error::class);
1069
        $this->expectExceptionMessage('Type "Bar" not found in document.');
1070
        $body   = '
1071
union TestUnion = Bar
1072
type Query { testUnion: TestUnion }
1073
';
1074
        $doc    = Parser::parse($body);
1075
        $schema = BuildSchema::buildAST($doc);
1076
        $schema->getTypeMap();
1077
    }
1078
1079
    /**
1080
     * @see it('Unknown query type')
1081
     */
1082
    public function testUnknownQueryType() : void
1083
    {
1084
        $this->expectException(Error::class);
1085
        $this->expectExceptionMessage('Specified query type "Wat" not found in document.');
1086
        $body = '
1087
schema {
1088
  query: Wat
1089
}
1090
1091
type Hello {
1092
  str: String
1093
}
1094
';
1095
        $doc  = Parser::parse($body);
1096
        BuildSchema::buildAST($doc);
1097
    }
1098
1099
    /**
1100
     * @see it('Unknown mutation type')
1101
     */
1102
    public function testUnknownMutationType() : void
1103
    {
1104
        $this->expectException(Error::class);
1105
        $this->expectExceptionMessage('Specified mutation type "Wat" not found in document.');
1106
        $body = '
1107
schema {
1108
  query: Hello
1109
  mutation: Wat
1110
}
1111
1112
type Hello {
1113
  str: String
1114
}
1115
';
1116
        $doc  = Parser::parse($body);
1117
        BuildSchema::buildAST($doc);
1118
    }
1119
1120
    /**
1121
     * @see it('Unknown subscription type')
1122
     */
1123
    public function testUnknownSubscriptionType() : void
1124
    {
1125
        $this->expectException(Error::class);
1126
        $this->expectExceptionMessage('Specified subscription type "Awesome" not found in document.');
1127
        $body = '
1128
schema {
1129
  query: Hello
1130
  mutation: Wat
1131
  subscription: Awesome
1132
}
1133
1134
type Hello {
1135
  str: String
1136
}
1137
1138
type Wat {
1139
  str: String
1140
}
1141
';
1142
        $doc  = Parser::parse($body);
1143
        BuildSchema::buildAST($doc);
1144
    }
1145
1146
    /**
1147
     * @see it('Does not consider directive names')
1148
     */
1149
    public function testDoesNotConsiderDirectiveNames()
1150
    {
1151
        $body = '
1152
          schema {
1153
            query: Foo
1154
          }
1155
    
1156
          directive @Foo on QUERY
1157
        ';
1158
        $doc  = Parser::parse($body);
1159
        $this->expectExceptionMessage('Specified query type "Foo" not found in document.');
1160
        BuildSchema::build($doc);
1161
    }
1162
1163
    /**
1164
     * @see it('Does not consider operation names')
1165
     */
1166
    public function testDoesNotConsiderOperationNames() : void
1167
    {
1168
        $this->expectException(Error::class);
1169
        $this->expectExceptionMessage('Specified query type "Foo" not found in document.');
1170
        $body = '
1171
schema {
1172
  query: Foo
1173
}
1174
1175
query Foo { field }
1176
';
1177
        $doc  = Parser::parse($body);
1178
        BuildSchema::buildAST($doc);
1179
    }
1180
1181
    /**
1182
     * @see it('Does not consider fragment names')
1183
     */
1184
    public function testDoesNotConsiderFragmentNames() : void
1185
    {
1186
        $this->expectException(Error::class);
1187
        $this->expectExceptionMessage('Specified query type "Foo" not found in document.');
1188
        $body = '
1189
schema {
1190
  query: Foo
1191
}
1192
1193
fragment Foo on Type { field }
1194
';
1195
        $doc  = Parser::parse($body);
1196
        BuildSchema::buildAST($doc);
1197
    }
1198
1199
    /**
1200
     * @see it('Forbids duplicate type definitions')
1201
     */
1202
    public function testForbidsDuplicateTypeDefinitions() : void
1203
    {
1204
        $body = '
1205
schema {
1206
  query: Repeated
1207
}
1208
1209
type Repeated {
1210
  id: Int
1211
}
1212
1213
type Repeated {
1214
  id: String
1215
}
1216
';
1217
        $doc  = Parser::parse($body);
1218
1219
        $this->expectException(Error::class);
1220
        $this->expectExceptionMessage('Type "Repeated" was defined more than once.');
1221
        BuildSchema::buildAST($doc);
1222
    }
1223
1224
    public function testSupportsTypeConfigDecorator() : void
1225
    {
1226
        $body = '
1227
schema {
1228
  query: Query
1229
}
1230
1231
type Query {
1232
  str: String
1233
  color: Color
1234
  hello: Hello
1235
}
1236
1237
enum Color {
1238
  RED
1239
  GREEN
1240
  BLUE
1241
}
1242
1243
interface Hello {
1244
  world: String
1245
}
1246
';
1247
        $doc  = Parser::parse($body);
1248
1249
        $decorated = [];
1250
        $calls     = [];
1251
1252
        $typeConfigDecorator = static function ($defaultConfig, $node, $allNodesMap) use (&$decorated, &$calls) {
1253
            $decorated[] = $defaultConfig['name'];
1254
            $calls[]     = [$defaultConfig, $node, $allNodesMap];
1255
1256
            return ['description' => 'My description of ' . $node->name->value] + $defaultConfig;
1257
        };
1258
1259
        $schema = BuildSchema::buildAST($doc, $typeConfigDecorator);
1260
        $schema->getTypeMap();
1261
        self::assertEquals(['Query', 'Color', 'Hello'], $decorated);
1262
1263
        [$defaultConfig, $node, $allNodesMap] = $calls[0];
1264
        self::assertInstanceOf(ObjectTypeDefinitionNode::class, $node);
1265
        self::assertEquals('Query', $defaultConfig['name']);
1266
        self::assertInstanceOf(Closure::class, $defaultConfig['fields']);
1267
        self::assertInstanceOf(Closure::class, $defaultConfig['interfaces']);
1268
        self::assertArrayHasKey('description', $defaultConfig);
1269
        self::assertCount(5, $defaultConfig);
1270
        self::assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
1271
        self::assertEquals('My description of Query', $schema->getType('Query')->description);
1272
1273
        [$defaultConfig, $node, $allNodesMap] = $calls[1];
1274
        self::assertInstanceOf(EnumTypeDefinitionNode::class, $node);
1275
        self::assertEquals('Color', $defaultConfig['name']);
1276
        $enumValue = [
1277
            'description'       => '',
1278
            'deprecationReason' => '',
1279
        ];
1280
        self::assertArraySubset(
1281
            [
1282
                'RED'   => $enumValue,
1283
                'GREEN' => $enumValue,
1284
                'BLUE'  => $enumValue,
1285
            ],
1286
            $defaultConfig['values']
1287
        );
1288
        self::assertCount(4, $defaultConfig); // 3 + astNode
1289
        self::assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
1290
        self::assertEquals('My description of Color', $schema->getType('Color')->description);
1291
1292
        [$defaultConfig, $node, $allNodesMap] = $calls[2];
1293
        self::assertInstanceOf(InterfaceTypeDefinitionNode::class, $node);
1294
        self::assertEquals('Hello', $defaultConfig['name']);
1295
        self::assertInstanceOf(Closure::class, $defaultConfig['fields']);
1296
        self::assertArrayHasKey('description', $defaultConfig);
1297
        self::assertCount(4, $defaultConfig);
1298
        self::assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
1299
        self::assertEquals('My description of Hello', $schema->getType('Hello')->description);
1300
    }
1301
1302
    public function testCreatesTypesLazily() : void
1303
    {
1304
        $body    = '
1305
schema {
1306
  query: Query
1307
}
1308
1309
type Query {
1310
  str: String
1311
  color: Color
1312
  hello: Hello
1313
}
1314
1315
enum Color {
1316
  RED
1317
  GREEN
1318
  BLUE
1319
}
1320
1321
interface Hello {
1322
  world: String
1323
}
1324
1325
type World implements Hello {
1326
  world: String
1327
}
1328
';
1329
        $doc     = Parser::parse($body);
1330
        $created = [];
1331
1332
        $typeConfigDecorator = static function ($config, $node) use (&$created) {
1333
            $created[] = $node->name->value;
1334
1335
            return $config;
1336
        };
1337
1338
        $schema = BuildSchema::buildAST($doc, $typeConfigDecorator);
1339
        self::assertEquals(['Query'], $created);
1340
1341
        $schema->getType('Color');
1342
        self::assertEquals(['Query', 'Color'], $created);
1343
1344
        $schema->getType('Hello');
1345
        self::assertEquals(['Query', 'Color', 'Hello'], $created);
1346
1347
        $types = $schema->getTypeMap();
1348
        self::assertEquals(['Query', 'Color', 'Hello', 'World'], $created);
1349
        self::assertArrayHasKey('Query', $types);
1350
        self::assertArrayHasKey('Color', $types);
1351
        self::assertArrayHasKey('Hello', $types);
1352
        self::assertArrayHasKey('World', $types);
1353
    }
1354
}
1355