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

ExecutorTest::testNullsOutErrorSubtrees()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 201
Code Lines 122

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 122
c 1
b 0
f 0
dl 0
loc 201
rs 8
cc 1
nc 1
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Tests\Executor;
6
7
use GraphQL\Deferred;
8
use GraphQL\Error\Error;
9
use GraphQL\Error\UserError;
10
use GraphQL\Executor\Executor;
11
use GraphQL\Language\Parser;
12
use GraphQL\Tests\Executor\TestClasses\NotSpecial;
13
use GraphQL\Tests\Executor\TestClasses\Special;
14
use GraphQL\Type\Definition\EnumType;
15
use GraphQL\Type\Definition\FieldDefinition;
16
use GraphQL\Type\Definition\InputObjectType;
17
use GraphQL\Type\Definition\InterfaceType;
18
use GraphQL\Type\Definition\ObjectType;
19
use GraphQL\Type\Definition\ResolveInfo;
20
use GraphQL\Type\Definition\Type;
21
use GraphQL\Type\Schema;
22
use PHPUnit\Framework\TestCase;
23
use stdClass;
24
use function count;
25
use function json_encode;
26
27
class ExecutorTest extends TestCase
28
{
29
    public function tearDown()
30
    {
31
        Executor::setPromiseAdapter(null);
32
    }
33
34
    // Execute: Handles basic execution tasks
35
36
    /**
37
     * @see it('executes arbitrary code')
38
     */
39
    public function testExecutesArbitraryCode() : void
40
    {
41
        $deepData = null;
42
        $data     = null;
43
44
        $promiseData = static function () use (&$data) {
45
            return new Deferred(static function () use (&$data) {
46
                return $data;
47
            });
48
        };
49
50
        $data = [
51
            'a'       => static function () {
52
                return 'Apple';
53
            },
54
            'b'       => static function () {
55
                return 'Banana';
56
            },
57
            'c'       => static function () {
58
                return 'Cookie';
59
            },
60
            'd'       => static function () {
61
                return 'Donut';
62
            },
63
            'e'       => static function () {
64
                return 'Egg';
65
            },
66
            'f'       => 'Fish',
67
            'pic'     => static function ($size = 50) {
68
                return 'Pic of size: ' . $size;
69
            },
70
            'promise' => static function () use ($promiseData) {
71
                return $promiseData();
72
            },
73
            'deep'    => static function () use (&$deepData) {
74
                return $deepData;
75
            },
76
        ];
77
78
        // Required for that & reference above
79
        $deepData = [
80
            'a'      => static function () {
81
                return 'Already Been Done';
82
            },
83
            'b'      => static function () {
84
                return 'Boring';
85
            },
86
            'c'      => static function () {
87
                return ['Contrived', null, 'Confusing'];
88
            },
89
            'deeper' => static function () use (&$data) {
90
                return [$data, null, $data];
91
            },
92
        ];
93
94
        $doc = '
95
      query Example($size: Int) {
96
        a,
97
        b,
98
        x: c
99
        ...c
100
        f
101
        ...on DataType {
102
          pic(size: $size)
103
          promise {
104
            a
105
          }
106
        }
107
        deep {
108
          a
109
          b
110
          c
111
          deeper {
112
            a
113
            b
114
          }
115
        }
116
      }
117
118
      fragment c on DataType {
119
        d
120
        e
121
      }
122
    ';
123
124
        $ast      = Parser::parse($doc);
125
        $expected = [
126
            'data' => [
127
                'a'       => 'Apple',
128
                'b'       => 'Banana',
129
                'x'       => 'Cookie',
130
                'd'       => 'Donut',
131
                'e'       => 'Egg',
132
                'f'       => 'Fish',
133
                'pic'     => 'Pic of size: 100',
134
                'promise' => ['a' => 'Apple'],
135
                'deep'    => [
136
                    'a'      => 'Already Been Done',
137
                    'b'      => 'Boring',
138
                    'c'      => ['Contrived', null, 'Confusing'],
139
                    'deeper' => [
140
                        ['a' => 'Apple', 'b' => 'Banana'],
141
                        null,
142
                        ['a' => 'Apple', 'b' => 'Banana'],
143
                    ],
144
                ],
145
            ],
146
        ];
147
148
        $deepDataType = null;
149
        $dataType     = new ObjectType([
150
            'name'   => 'DataType',
151
            'fields' => static function () use (&$dataType, &$deepDataType) {
152
                return [
153
                    'a'       => ['type' => Type::string()],
154
                    'b'       => ['type' => Type::string()],
155
                    'c'       => ['type' => Type::string()],
156
                    'd'       => ['type' => Type::string()],
157
                    'e'       => ['type' => Type::string()],
158
                    'f'       => ['type' => Type::string()],
159
                    'pic'     => [
160
                        'args'    => ['size' => ['type' => Type::int()]],
161
                        'type'    => Type::string(),
162
                        'resolve' => static function ($obj, $args) {
163
                            return $obj['pic']($args['size']);
164
                        },
165
                    ],
166
                    'promise' => ['type' => $dataType],
167
                    'deep'    => ['type' => $deepDataType],
168
                ];
169
            },
170
        ]);
171
172
        // Required for that & reference above
173
        $deepDataType = new ObjectType([
174
            'name'   => 'DeepDataType',
175
            'fields' => [
176
                'a'      => ['type' => Type::string()],
177
                'b'      => ['type' => Type::string()],
178
                'c'      => ['type' => Type::listOf(Type::string())],
179
                'deeper' => ['type' => Type::listOf($dataType)],
180
            ],
181
        ]);
182
        $schema       = new Schema(['query' => $dataType]);
183
184
        self::assertEquals(
185
            $expected,
186
            Executor::execute($schema, $ast, $data, null, ['size' => 100], 'Example')->toArray()
187
        );
188
    }
189
190
    /**
191
     * @see it('merges parallel fragments')
192
     */
193
    public function testMergesParallelFragments() : void
194
    {
195
        $ast = Parser::parse('
196
      { a, ...FragOne, ...FragTwo }
197
198
      fragment FragOne on Type {
199
        b
200
        deep { b, deeper: deep { b } }
201
      }
202
203
      fragment FragTwo on Type {
204
        c
205
        deep { c, deeper: deep { c } }
206
      }
207
        ');
208
209
        $Type = new ObjectType([
210
            'name'   => 'Type',
211
            'fields' => static function () use (&$Type) {
212
                return [
213
                    'a'    => [
214
                        'type'    => Type::string(),
215
                        'resolve' => static function () {
216
                            return 'Apple';
217
                        },
218
                    ],
219
                    'b'    => [
220
                        'type'    => Type::string(),
221
                        'resolve' => static function () {
222
                            return 'Banana';
223
                        },
224
                    ],
225
                    'c'    => [
226
                        'type'    => Type::string(),
227
                        'resolve' => static function () {
228
                            return 'Cherry';
229
                        },
230
                    ],
231
                    'deep' => [
232
                        'type'    => $Type,
233
                        'resolve' => static function () {
234
                            return [];
235
                        },
236
                    ],
237
                ];
238
            },
239
        ]);
240
241
        $schema   = new Schema(['query' => $Type]);
242
        $expected = [
243
            'data' => [
244
                'a'    => 'Apple',
245
                'b'    => 'Banana',
246
                'c'    => 'Cherry',
247
                'deep' => [
248
                    'b'      => 'Banana',
249
                    'c'      => 'Cherry',
250
                    'deeper' => [
251
                        'b' => 'Banana',
252
                        'c' => 'Cherry',
253
                    ],
254
                ],
255
            ],
256
        ];
257
258
        self::assertEquals($expected, Executor::execute($schema, $ast)->toArray());
259
    }
260
261
    /**
262
     * @see it('provides info about current execution state')
263
     */
264
    public function testProvidesInfoAboutCurrentExecutionState() : void
265
    {
266
        $ast = Parser::parse('query ($var: String) { result: test }');
267
268
        /** @var ResolveInfo $info */
269
        $info   = null;
270
        $schema = new Schema([
271
            'query' => new ObjectType([
272
                'name'   => 'Test',
273
                'fields' => [
274
                    'test' => [
275
                        'type'    => Type::string(),
276
                        'resolve' => static function ($test, $args, $ctx, $_info) use (&$info) {
277
                            $info = $_info;
278
                        },
279
                    ],
280
                ],
281
            ]),
282
        ]);
283
284
        $rootValue = ['root' => 'val'];
285
286
        Executor::execute($schema, $ast, $rootValue, null, ['var' => '123']);
287
288
        self::assertEquals('test', $info->fieldName);
289
        self::assertEquals(1, count($info->fieldNodes));
290
        self::assertSame($ast->definitions[0]->selectionSet->selections[0], $info->fieldNodes[0]);
291
        self::assertSame(Type::string(), $info->returnType);
292
        self::assertSame($schema->getQueryType(), $info->parentType);
293
        self::assertEquals(['result'], $info->path);
294
        self::assertSame($schema, $info->schema);
295
        self::assertSame($rootValue, $info->rootValue);
296
        self::assertEquals($ast->definitions[0], $info->operation);
297
        self::assertEquals(['var' => '123'], $info->variableValues);
298
        self::assertInstanceOf(FieldDefinition::class, $info->fieldDefinition);
299
    }
300
301
    /**
302
     * @see it('threads root value context correctly')
303
     */
304
    public function testThreadsContextCorrectly() : void
305
    {
306
        // threads context correctly
307
        $doc = 'query Example { a }';
308
309
        $gotHere = false;
310
311
        $data = ['contextThing' => 'thing'];
312
313
        $ast    = Parser::parse($doc);
314
        $schema = new Schema([
315
            'query' => new ObjectType([
316
                'name'   => 'Type',
317
                'fields' => [
318
                    'a' => [
319
                        'type'    => Type::string(),
320
                        'resolve' => static function ($context) use (&$gotHere) {
321
                            self::assertEquals('thing', $context['contextThing']);
322
                            $gotHere = true;
323
                        },
324
                    ],
325
                ],
326
            ]),
327
        ]);
328
329
        Executor::execute($schema, $ast, $data, null, [], 'Example');
330
        self::assertEquals(true, $gotHere);
331
    }
332
333
    /**
334
     * @see it('correctly threads arguments')
335
     */
336
    public function testCorrectlyThreadsArguments() : void
337
    {
338
        $doc = '
339
      query Example {
340
        b(numArg: 123, stringArg: "foo")
341
      }
342
        ';
343
344
        $gotHere = false;
345
346
        $docAst = Parser::parse($doc);
347
        $schema = new Schema([
348
            'query' => new ObjectType([
349
                'name'   => 'Type',
350
                'fields' => [
351
                    'b' => [
352
                        'args'    => [
353
                            'numArg'    => ['type' => Type::int()],
354
                            'stringArg' => ['type' => Type::string()],
355
                        ],
356
                        'type'    => Type::string(),
357
                        'resolve' => static function ($_, $args) use (&$gotHere) {
358
                            self::assertEquals(123, $args['numArg']);
359
                            self::assertEquals('foo', $args['stringArg']);
360
                            $gotHere = true;
361
                        },
362
                    ],
363
                ],
364
            ]),
365
        ]);
366
        Executor::execute($schema, $docAst, null, null, [], 'Example');
367
        self::assertSame($gotHere, true);
368
    }
369
370
    /**
371
     * @see it('nulls out error subtrees')
372
     */
373
    public function testNullsOutErrorSubtrees() : void
374
    {
375
        $doc = '{
376
      sync
377
      syncError
378
      syncRawError
379
      syncReturnError
380
      syncReturnErrorList
381
      async
382
      asyncReject
383
      asyncRawReject
384
      asyncEmptyReject
385
      asyncError
386
      asyncRawError
387
      asyncReturnError
388
      asyncReturnErrorWithExtensions
389
        }';
390
391
        $data = [
392
            'sync'                => static function () {
393
                return 'sync';
394
            },
395
            'syncError'           => static function () {
396
                throw new UserError('Error getting syncError');
397
            },
398
            'syncRawError'        => static function () {
399
                throw new UserError('Error getting syncRawError');
400
            },
401
            // inherited from JS reference implementation, but make no sense in this PHP impl
402
            // leaving it just to simplify migrations from newer js versions
403
            'syncReturnError'     => static function () {
404
                return new UserError('Error getting syncReturnError');
405
            },
406
            'syncReturnErrorList' => static function () {
407
                return [
408
                    'sync0',
409
                    new UserError('Error getting syncReturnErrorList1'),
410
                    'sync2',
411
                    new UserError('Error getting syncReturnErrorList3'),
412
                ];
413
            },
414
            'async'               => static function () {
415
                return new Deferred(static function () {
416
                    return 'async';
417
                });
418
            },
419
            'asyncReject'         => static function () {
420
                return new Deferred(static function () {
421
                    throw new UserError('Error getting asyncReject');
422
                });
423
            },
424
            'asyncRawReject'      => static function () {
425
                return new Deferred(static function () {
426
                    throw new UserError('Error getting asyncRawReject');
427
                });
428
            },
429
            'asyncEmptyReject'    => static function () {
430
                return new Deferred(static function () {
431
                    throw new UserError();
432
                });
433
            },
434
            'asyncError'          => static function () {
435
                return new Deferred(static function () {
436
                    throw new UserError('Error getting asyncError');
437
                });
438
            },
439
            // inherited from JS reference implementation, but make no sense in this PHP impl
440
            // leaving it just to simplify migrations from newer js versions
441
            'asyncRawError'       => static function () {
442
                return new Deferred(static function () {
443
                    throw new UserError('Error getting asyncRawError');
444
                });
445
            },
446
            'asyncReturnError'    => static function () {
447
                return new Deferred(static function () {
448
                    throw new UserError('Error getting asyncReturnError');
449
                });
450
            },
451
            'asyncReturnErrorWithExtensions' => static function () {
452
                return new Deferred(static function () {
453
                    $error = new Error(
454
                        'Error getting asyncReturnErrorWithExtensions',
455
                        null,
456
                        null,
457
                        null,
458
                        null,
459
                        null,
460
                        ['foo' => 'bar']
461
                    );
462
                    throw $error;
463
                });
464
            },
465
        ];
466
467
        $docAst = Parser::parse($doc);
468
        $schema = new Schema([
469
            'query' => new ObjectType([
470
                'name'   => 'Type',
471
                'fields' => [
472
                    'sync'                => ['type' => Type::string()],
473
                    'syncError'           => ['type' => Type::string()],
474
                    'syncRawError'        => ['type' => Type::string()],
475
                    'syncReturnError'     => ['type' => Type::string()],
476
                    'syncReturnErrorList' => ['type' => Type::listOf(Type::string())],
477
                    'async'               => ['type' => Type::string()],
478
                    'asyncReject'         => ['type' => Type::string()],
479
                    'asyncRejectWithExtensions' => [ 'type' => Type::string() ],
480
                    'asyncRawReject'      => ['type' => Type::string()],
481
                    'asyncEmptyReject'    => ['type' => Type::string()],
482
                    'asyncError'          => ['type' => Type::string()],
483
                    'asyncRawError'       => ['type' => Type::string()],
484
                    'asyncReturnError'    => ['type' => Type::string()],
485
                    'asyncReturnErrorWithExtensions' => [ 'type' => Type::string() ],
486
                ],
487
            ]),
488
        ]);
489
490
        $expected = [
491
            'data'   => [
492
                'sync'                => 'sync',
493
                'syncError'           => null,
494
                'syncRawError'        => null,
495
                'syncReturnError'     => null,
496
                'syncReturnErrorList' => ['sync0', null, 'sync2', null],
497
                'async'               => 'async',
498
                'asyncReject'         => null,
499
                'asyncRawReject'      => null,
500
                'asyncEmptyReject'    => null,
501
                'asyncError'          => null,
502
                'asyncRawError'       => null,
503
                'asyncReturnError'    => null,
504
                'asyncReturnErrorWithExtensions' => null,
505
            ],
506
            'errors' => [
507
                [
508
                    'message'   => 'Error getting syncError',
509
                    'locations' => [['line' => 3, 'column' => 7]],
510
                    'path'      => ['syncError'],
511
                ],
512
                [
513
                    'message'   => 'Error getting syncRawError',
514
                    'locations' => [['line' => 4, 'column' => 7]],
515
                    'path'      => ['syncRawError'],
516
                ],
517
                [
518
                    'message'   => 'Error getting syncReturnError',
519
                    'locations' => [['line' => 5, 'column' => 7]],
520
                    'path'      => ['syncReturnError'],
521
                ],
522
                [
523
                    'message'   => 'Error getting syncReturnErrorList1',
524
                    'locations' => [['line' => 6, 'column' => 7]],
525
                    'path'      => ['syncReturnErrorList', 1],
526
                ],
527
                [
528
                    'message'   => 'Error getting syncReturnErrorList3',
529
                    'locations' => [['line' => 6, 'column' => 7]],
530
                    'path'      => ['syncReturnErrorList', 3],
531
                ],
532
                [
533
                    'message'   => 'Error getting asyncReject',
534
                    'locations' => [['line' => 8, 'column' => 7]],
535
                    'path'      => ['asyncReject'],
536
                ],
537
                [
538
                    'message'   => 'Error getting asyncRawReject',
539
                    'locations' => [['line' => 9, 'column' => 7]],
540
                    'path'      => ['asyncRawReject'],
541
                ],
542
                [
543
                    'message'   => 'An unknown error occurred.',
544
                    'locations' => [['line' => 10, 'column' => 7]],
545
                    'path'      => ['asyncEmptyReject'],
546
                ],
547
                [
548
                    'message'   => 'Error getting asyncError',
549
                    'locations' => [['line' => 11, 'column' => 7]],
550
                    'path'      => ['asyncError'],
551
                ],
552
                [
553
                    'message'   => 'Error getting asyncRawError',
554
                    'locations' => [['line' => 12, 'column' => 7]],
555
                    'path'      => ['asyncRawError'],
556
                ],
557
                [
558
                    'message'   => 'Error getting asyncReturnError',
559
                    'locations' => [['line' => 13, 'column' => 7]],
560
                    'path'      => ['asyncReturnError'],
561
                ],
562
                [
563
                    'message' => 'Error getting asyncReturnErrorWithExtensions',
564
                    'locations' => [[ 'line' => 14, 'column' => 7 ]],
565
                    'path' => ['asyncReturnErrorWithExtensions'],
566
                    'extensions' => [ 'foo' => 'bar' ],
567
                ],
568
            ],
569
        ];
570
571
        $result = Executor::execute($schema, $docAst, $data)->toArray();
572
573
        self::assertArraySubset($expected, $result);
574
    }
575
576
    /**
577
     * @see it('uses the inline operation if no operation name is provided')
578
     */
579
    public function testUsesTheInlineOperationIfNoOperationIsProvided() : void
580
    {
581
        $doc    = '{ a }';
582
        $data   = ['a' => 'b'];
583
        $ast    = Parser::parse($doc);
584
        $schema = new Schema([
585
            'query' => new ObjectType([
586
                'name'   => 'Type',
587
                'fields' => [
588
                    'a' => ['type' => Type::string()],
589
                ],
590
            ]),
591
        ]);
592
593
        $ex = Executor::execute($schema, $ast, $data);
594
595
        self::assertEquals(['data' => ['a' => 'b']], $ex->toArray());
596
    }
597
598
    /**
599
     * @see it('uses the only operation if no operation name is provided')
600
     */
601
    public function testUsesTheOnlyOperationIfNoOperationIsProvided() : void
602
    {
603
        $doc    = 'query Example { a }';
604
        $data   = ['a' => 'b'];
605
        $ast    = Parser::parse($doc);
606
        $schema = new Schema([
607
            'query' => new ObjectType([
608
                'name'   => 'Type',
609
                'fields' => [
610
                    'a' => ['type' => Type::string()],
611
                ],
612
            ]),
613
        ]);
614
615
        $ex = Executor::execute($schema, $ast, $data);
616
        self::assertEquals(['data' => ['a' => 'b']], $ex->toArray());
617
    }
618
619
    /**
620
     * @see it('uses the named operation if operation name is provided')
621
     */
622
    public function testUsesTheNamedOperationIfOperationNameIsProvided() : void
623
    {
624
        $doc    = 'query Example { first: a } query OtherExample { second: a }';
625
        $data   = ['a' => 'b'];
626
        $ast    = Parser::parse($doc);
627
        $schema = new Schema([
628
            'query' => new ObjectType([
629
                'name'   => 'Type',
630
                'fields' => [
631
                    'a' => ['type' => Type::string()],
632
                ],
633
            ]),
634
        ]);
635
636
        $result = Executor::execute($schema, $ast, $data, null, null, 'OtherExample');
637
        self::assertEquals(['data' => ['second' => 'b']], $result->toArray());
638
    }
639
640
    /**
641
     * @see it('provides error if no operation is provided')
642
     */
643
    public function testProvidesErrorIfNoOperationIsProvided() : void
644
    {
645
        $doc    = 'fragment Example on Type { a }';
646
        $data   = ['a' => 'b'];
647
        $ast    = Parser::parse($doc);
648
        $schema = new Schema([
649
            'query' => new ObjectType([
650
                'name'   => 'Type',
651
                'fields' => [
652
                    'a' => ['type' => Type::string()],
653
                ],
654
            ]),
655
        ]);
656
657
        $result   = Executor::execute($schema, $ast, $data);
658
        $expected = [
659
            'errors' => [
660
                ['message' => 'Must provide an operation.'],
661
            ],
662
        ];
663
664
        self::assertArraySubset($expected, $result->toArray());
665
    }
666
667
    /**
668
     * @see it('errors if no op name is provided with multiple operations')
669
     */
670
    public function testErrorsIfNoOperationIsProvidedWithMultipleOperations() : void
671
    {
672
        $doc    = 'query Example { a } query OtherExample { a }';
673
        $data   = ['a' => 'b'];
674
        $ast    = Parser::parse($doc);
675
        $schema = new Schema([
676
            'query' => new ObjectType([
677
                'name'   => 'Type',
678
                'fields' => [
679
                    'a' => ['type' => Type::string()],
680
                ],
681
            ]),
682
        ]);
683
684
        $result = Executor::execute($schema, $ast, $data);
685
686
        $expected = [
687
            'errors' => [
688
                ['message' => 'Must provide operation name if query contains multiple operations.'],
689
            ],
690
        ];
691
692
        self::assertArraySubset($expected, $result->toArray());
693
    }
694
695
    /**
696
     * @see it('errors if unknown operation name is provided')
697
     */
698
    public function testErrorsIfUnknownOperationNameIsProvided() : void
699
    {
700
        $doc    = 'query Example { a } query OtherExample { a }';
701
        $ast    = Parser::parse($doc);
702
        $schema = new Schema([
703
            'query' => new ObjectType([
704
                'name'   => 'Type',
705
                'fields' => [
706
                    'a' => ['type' => Type::string()],
707
                ],
708
            ]),
709
        ]);
710
711
        $result = Executor::execute(
712
            $schema,
713
            $ast,
714
            null,
715
            null,
716
            null,
717
            'UnknownExample'
718
        );
719
720
        $expected = [
721
            'errors' => [
722
                ['message' => 'Unknown operation named "UnknownExample".'],
723
            ],
724
725
        ];
726
727
        self::assertArraySubset($expected, $result->toArray());
728
    }
729
730
    /**
731
     * @see it('uses the query schema for queries')
732
     */
733
    public function testUsesTheQuerySchemaForQueries() : void
734
    {
735
        $doc    = 'query Q { a } mutation M { c }';
736
        $data   = ['a' => 'b', 'c' => 'd'];
737
        $ast    = Parser::parse($doc);
738
        $schema = new Schema([
739
            'query'    => new ObjectType([
740
                'name'   => 'Q',
741
                'fields' => [
742
                    'a' => ['type' => Type::string()],
743
                ],
744
            ]),
745
            'mutation' => new ObjectType([
746
                'name'   => 'M',
747
                'fields' => [
748
                    'c' => ['type' => Type::string()],
749
                ],
750
            ]),
751
        ]);
752
753
        $queryResult = Executor::execute($schema, $ast, $data, null, [], 'Q');
754
        self::assertEquals(['data' => ['a' => 'b']], $queryResult->toArray());
755
    }
756
757
    /**
758
     * @see it('uses the mutation schema for mutations')
759
     */
760
    public function testUsesTheMutationSchemaForMutations() : void
761
    {
762
        $doc            = 'query Q { a } mutation M { c }';
763
        $data           = ['a' => 'b', 'c' => 'd'];
764
        $ast            = Parser::parse($doc);
765
        $schema         = new Schema([
766
            'query'    => new ObjectType([
767
                'name'   => 'Q',
768
                'fields' => [
769
                    'a' => ['type' => Type::string()],
770
                ],
771
            ]),
772
            'mutation' => new ObjectType([
773
                'name'   => 'M',
774
                'fields' => [
775
                    'c' => ['type' => Type::string()],
776
                ],
777
            ]),
778
        ]);
779
        $mutationResult = Executor::execute($schema, $ast, $data, null, [], 'M');
780
        self::assertEquals(['data' => ['c' => 'd']], $mutationResult->toArray());
781
    }
782
783
    /**
784
     * @see it('uses the subscription schema for subscriptions')
785
     */
786
    public function testUsesTheSubscriptionSchemaForSubscriptions() : void
787
    {
788
        $doc    = 'query Q { a } subscription S { a }';
789
        $data   = ['a' => 'b', 'c' => 'd'];
790
        $ast    = Parser::parse($doc);
791
        $schema = new Schema([
792
            'query'        => new ObjectType([
793
                'name'   => 'Q',
794
                'fields' => [
795
                    'a' => ['type' => Type::string()],
796
                ],
797
            ]),
798
            'subscription' => new ObjectType([
799
                'name'   => 'S',
800
                'fields' => [
801
                    'a' => ['type' => Type::string()],
802
                ],
803
            ]),
804
        ]);
805
806
        $subscriptionResult = Executor::execute($schema, $ast, $data, null, [], 'S');
807
        self::assertEquals(['data' => ['a' => 'b']], $subscriptionResult->toArray());
808
    }
809
810
    public function testCorrectFieldOrderingDespiteExecutionOrder() : void
811
    {
812
        $doc  = '{
813
      a,
814
      b,
815
      c,
816
      d,
817
      e
818
    }';
819
        $data = [
820
            'a' => static function () {
821
                return 'a';
822
            },
823
            'b' => static function () {
824
                return new Deferred(static function () {
825
                    return 'b';
826
                });
827
            },
828
            'c' => static function () {
829
                return 'c';
830
            },
831
            'd' => static function () {
832
                return new Deferred(static function () {
833
                    return 'd';
834
                });
835
            },
836
            'e' => static function () {
837
                return 'e';
838
            },
839
        ];
840
841
        $ast = Parser::parse($doc);
842
843
        $queryType = new ObjectType([
844
            'name'   => 'DeepDataType',
845
            'fields' => [
846
                'a' => ['type' => Type::string()],
847
                'b' => ['type' => Type::string()],
848
                'c' => ['type' => Type::string()],
849
                'd' => ['type' => Type::string()],
850
                'e' => ['type' => Type::string()],
851
            ],
852
        ]);
853
        $schema    = new Schema(['query' => $queryType]);
854
855
        $expected = [
856
            'data' => [
857
                'a' => 'a',
858
                'b' => 'b',
859
                'c' => 'c',
860
                'd' => 'd',
861
                'e' => 'e',
862
            ],
863
        ];
864
865
        self::assertEquals($expected, Executor::execute($schema, $ast, $data)->toArray());
866
    }
867
868
    /**
869
     * @see it('Avoids recursion')
870
     */
871
    public function testAvoidsRecursion() : void
872
    {
873
        $doc    = '
874
      query Q {
875
        a
876
        ...Frag
877
        ...Frag
878
      }
879
880
      fragment Frag on Type {
881
        a,
882
        ...Frag
883
      }
884
        ';
885
        $data   = ['a' => 'b'];
886
        $ast    = Parser::parse($doc);
887
        $schema = new Schema([
888
            'query' => new ObjectType([
889
                'name'   => 'Type',
890
                'fields' => [
891
                    'a' => ['type' => Type::string()],
892
                ],
893
            ]),
894
        ]);
895
896
        $queryResult = Executor::execute($schema, $ast, $data, null, [], 'Q');
897
        self::assertEquals(['data' => ['a' => 'b']], $queryResult->toArray());
898
    }
899
900
    /**
901
     * @see it('does not include illegal fields in output')
902
     */
903
    public function testDoesNotIncludeIllegalFieldsInOutput() : void
904
    {
905
        $doc            = 'mutation M {
906
      thisIsIllegalDontIncludeMe
907
    }';
908
        $ast            = Parser::parse($doc);
909
        $schema         = new Schema([
910
            'query'    => new ObjectType([
911
                'name'   => 'Q',
912
                'fields' => [
913
                    'a' => ['type' => Type::string()],
914
                ],
915
            ]),
916
            'mutation' => new ObjectType([
917
                'name'   => 'M',
918
                'fields' => [
919
                    'c' => ['type' => Type::string()],
920
                ],
921
            ]),
922
        ]);
923
        $mutationResult = Executor::execute($schema, $ast);
924
        self::assertEquals(['data' => []], $mutationResult->toArray());
925
    }
926
927
    /**
928
     * @see it('does not include arguments that were not set')
929
     */
930
    public function testDoesNotIncludeArgumentsThatWereNotSet() : void
931
    {
932
        $schema = new Schema([
933
            'query' => new ObjectType([
934
                'name'   => 'Type',
935
                'fields' => [
936
                    'field' => [
937
                        'type'    => Type::string(),
938
                        'resolve' => static function ($data, $args) {
939
                            return $args ? json_encode($args) : '';
940
                        },
941
                        'args'    => [
942
                            'a' => ['type' => Type::boolean()],
943
                            'b' => ['type' => Type::boolean()],
944
                            'c' => ['type' => Type::boolean()],
945
                            'd' => ['type' => Type::int()],
946
                            'e' => ['type' => Type::int()],
947
                        ],
948
                    ],
949
                ],
950
            ]),
951
        ]);
952
953
        $query    = Parser::parse('{ field(a: true, c: false, e: 0) }');
954
        $result   = Executor::execute($schema, $query);
955
        $expected = [
956
            'data' => ['field' => '{"a":true,"c":false,"e":0}'],
957
        ];
958
959
        self::assertEquals($expected, $result->toArray());
960
    }
961
962
    /**
963
     * @see it('fails when an isTypeOf check is not met')
964
     */
965
    public function testFailsWhenAnIsTypeOfCheckIsNotMet() : void
966
    {
967
        $SpecialType = new ObjectType([
968
            'name'     => 'SpecialType',
969
            'isTypeOf' => static function ($obj) {
970
                return $obj instanceof Special;
971
            },
972
            'fields'   => [
973
                'value' => ['type' => Type::string()],
974
            ],
975
        ]);
976
977
        $schema = new Schema([
978
            'query' => new ObjectType([
979
                'name'   => 'Query',
980
                'fields' => [
981
                    'specials' => [
982
                        'type'    => Type::listOf($SpecialType),
983
                        'resolve' => static function ($rootValue) {
984
                            return $rootValue['specials'];
985
                        },
986
                    ],
987
                ],
988
            ]),
989
        ]);
990
991
        $query  = Parser::parse('{ specials { value } }');
992
        $value  = [
993
            'specials' => [new Special('foo'), new NotSpecial('bar')],
994
        ];
995
        $result = Executor::execute($schema, $query, $value);
996
997
        self::assertEquals(
998
            [
999
                'specials' => [
1000
                    ['value' => 'foo'],
1001
                    null,
1002
                ],
1003
            ],
1004
            $result->data
1005
        );
1006
1007
        self::assertEquals(1, count($result->errors));
1008
        self::assertEquals(
1009
            [
1010
                'message'   => 'Expected value of type "SpecialType" but got: instance of GraphQL\Tests\Executor\TestClasses\NotSpecial.',
1011
                'locations' => [['line' => 1, 'column' => 3]],
1012
                'path'      => ['specials', 1],
1013
            ],
1014
            $result->errors[0]->toSerializableArray()
1015
        );
1016
    }
1017
1018
    /**
1019
     * @see it('executes ignoring invalid non-executable definitions')
1020
     */
1021
    public function testExecutesIgnoringInvalidNonExecutableDefinitions() : void
1022
    {
1023
        $query = Parser::parse('
1024
      { foo }
1025
1026
      type Query { bar: String }
1027
    ');
1028
1029
        $schema = new Schema([
1030
            'query' => new ObjectType([
1031
                'name'   => 'Query',
1032
                'fields' => [
1033
                    'foo' => ['type' => Type::string()],
1034
                ],
1035
            ]),
1036
        ]);
1037
1038
        $result = Executor::execute($schema, $query);
1039
1040
        $expected = [
1041
            'data' => ['foo' => null],
1042
        ];
1043
1044
        self::assertArraySubset($expected, $result->toArray());
1045
    }
1046
1047
    /**
1048
     * @see it('uses a custom field resolver')
1049
     */
1050
    public function testUsesACustomFieldResolver() : void
1051
    {
1052
        $query = Parser::parse('{ foo }');
1053
1054
        $schema = new Schema([
1055
            'query' => new ObjectType([
1056
                'name'   => 'Query',
1057
                'fields' => [
1058
                    'foo' => ['type' => Type::string()],
1059
                ],
1060
            ]),
1061
        ]);
1062
1063
        // For the purposes of test, just return the name of the field!
1064
        $customResolver = static function ($source, $args, $context, ResolveInfo $info) {
1065
            return $info->fieldName;
1066
        };
1067
1068
        $result = Executor::execute(
1069
            $schema,
1070
            $query,
1071
            null,
1072
            null,
1073
            null,
1074
            null,
1075
            $customResolver
1076
        );
1077
1078
        $expected = [
1079
            'data' => ['foo' => 'foo'],
1080
        ];
1081
1082
        self::assertEquals($expected, $result->toArray());
1083
    }
1084
1085
    public function testSubstitutesArgumentWithDefaultValue() : void
1086
    {
1087
        $schema = new Schema([
1088
            'query' => new ObjectType([
1089
                'name'   => 'Type',
1090
                'fields' => [
1091
                    'field' => [
1092
                        'type'    => Type::string(),
1093
                        'resolve' => static function ($data, $args) {
1094
                            return $args ? json_encode($args) : '';
1095
                        },
1096
                        'args'    => [
1097
                            'a' => ['type' => Type::boolean(), 'defaultValue' => 1],
1098
                            'b' => ['type' => Type::boolean(), 'defaultValue' => null],
1099
                            'c' => ['type' => Type::boolean(), 'defaultValue' => 0],
1100
                            'd' => ['type' => Type::int(), 'defaultValue' => false],
1101
                            'e' => ['type' => Type::int(), 'defaultValue' => '0'],
1102
                            'f' => ['type' => Type::int(), 'defaultValue' => 'some-string'],
1103
                            'g' => ['type' => Type::boolean()],
1104
                            'h' => [
1105
                                'type' => new InputObjectType([
1106
                                    'name'   => 'ComplexType',
1107
                                    'fields' => [
1108
                                        'a' => ['type' => Type::int()],
1109
                                        'b' => ['type' => Type::string()],
1110
                                    ],
1111
                                ]),
1112
                                'defaultValue' => ['a' => 1, 'b' => 'test'],
1113
                            ],
1114
                            'i' => [
1115
                                'type' => new EnumType([
1116
                                    'name' => 'EnumType',
1117
                                    'values' => [
1118
                                        'VALUE1' => 1,
1119
                                        'VALUE2' => 2,
1120
                                    ],
1121
                                ]),
1122
                                'defaultValue' => 1,
1123
                            ],
1124
                        ],
1125
                    ],
1126
                ],
1127
            ]),
1128
        ]);
1129
1130
        $query    = Parser::parse('{ field }');
1131
        $result   = Executor::execute($schema, $query);
1132
        $expected = [
1133
            'data' => ['field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"},"i":1}'],
1134
        ];
1135
1136
        self::assertEquals($expected, $result->toArray());
1137
    }
1138
1139
    /**
1140
     * @see https://github.com/webonyx/graphql-php/issues/59
1141
     */
1142
    public function testSerializesToEmptyObjectVsEmptyArray() : void
1143
    {
1144
        $iface = null;
1145
1146
        $a = new ObjectType([
1147
            'name'       => 'A',
1148
            'fields'     => [
1149
                'id' => Type::id(),
1150
            ],
1151
            'interfaces' => static function () use (&$iface) {
1152
                return [$iface];
1153
            },
1154
        ]);
1155
1156
        $b = new ObjectType([
1157
            'name'       => 'B',
1158
            'fields'     => [
1159
                'id' => Type::id(),
1160
            ],
1161
            'interfaces' => static function () use (&$iface) {
1162
                return [$iface];
1163
            },
1164
        ]);
1165
1166
        $iface = new InterfaceType([
1167
            'name'        => 'Iface',
1168
            'fields'      => [
1169
                'id' => Type::id(),
1170
            ],
1171
            'resolveType' => static function ($v) use ($a, $b) {
1172
                return $v['type'] === 'A' ? $a : $b;
1173
            },
1174
        ]);
1175
1176
        $schema = new Schema([
1177
            'query' => new ObjectType([
1178
                'name'   => 'Query',
1179
                'fields' => [
1180
                    'ab' => Type::listOf($iface),
1181
                ],
1182
            ]),
1183
            'types' => [$a, $b],
1184
        ]);
1185
1186
        $data = [
1187
            'ab' => [
1188
                ['id' => 1, 'type' => 'A'],
1189
                ['id' => 2, 'type' => 'A'],
1190
                ['id' => 3, 'type' => 'B'],
1191
                ['id' => 4, 'type' => 'B'],
1192
            ],
1193
        ];
1194
1195
        $query = Parser::parse('
1196
            {
1197
                ab {
1198
                    ... on A{
1199
                        id
1200
                    }
1201
                }
1202
            }
1203
        ');
1204
1205
        $result = Executor::execute($schema, $query, $data, null);
1206
1207
        self::assertEquals(
1208
            [
1209
                'data' => [
1210
                    'ab' => [
1211
                        ['id' => '1'],
1212
                        ['id' => '2'],
1213
                        new stdClass(),
1214
                        new stdClass(),
1215
                    ],
1216
                ],
1217
            ],
1218
            $result->toArray()
1219
        );
1220
    }
1221
}
1222