Failed Conditions
Push — master ( 84f136...849c15 )
by Vladimir
15:41 queued 12:38
created

tests/Executor/ExecutorTest.php (1 issue)

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