Failed Conditions
Push — master ( a4f39b...12ee90 )
by Vladimir
11:17
created

testAllowsOnlySingleSubscriptionType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 21
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Tests\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);
0 ignored issues
show
Bug introduced by
It seems like $defaultConfig can also be of type ArrayAccess; however, parameter $haystack of PHPUnit\Framework\Assert::assertCount() does only seem to accept Countable|iterable, maybe add an additional type check? ( Ignorable by Annotation )

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

1269
        self::assertCount(5, /** @scrutinizer ignore-type */ $defaultConfig);
Loading history...
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(
0 ignored issues
show
Deprecated Code introduced by
The function PHPUnit\Framework\Assert::assertArraySubset() has been deprecated: https://github.com/sebastianbergmann/phpunit/issues/3494 ( Ignorable by Annotation )

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

1280
        /** @scrutinizer ignore-deprecated */ self::assertArraySubset(

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

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

Loading history...
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