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

testDoesNotIncludeIllegalFieldsInOutput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 22
c 0
b 0
f 0
rs 9.8333
cc 1
nc 1
nop 0
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 = [
0 ignored issues
show
Unused Code introduced by
The assignment to $deepData is dead and can be removed.
Loading history...
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([
0 ignored issues
show
Unused Code introduced by
The assignment to $deepDataType is dead and can be removed.
Loading history...
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()
0 ignored issues
show
Bug introduced by
The method toArray() does not exist on GraphQL\Executor\Promise\Promise. ( Ignorable by Annotation )

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

183
            Executor::execute($schema, $ast, $data, null, ['size' => 100], 'Example')->/** @scrutinizer ignore-call */ toArray()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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]);
0 ignored issues
show
Bug introduced by
Accessing selectionSet on the interface GraphQL\Language\AST\DefinitionNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
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
0 ignored issues
show
Bug introduced by
The property data does not seem to exist on GraphQL\Executor\Promise\Promise.
Loading history...
993
        );
994
995
        self::assertEquals(1, count($result->errors));
0 ignored issues
show
Bug introduced by
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()
0 ignored issues
show
Deprecated Code introduced by
The function GraphQL\Error\Error::toSerializableArray() has been deprecated: Use FormattedError::createFromException() instead ( Ignorable by Annotation )

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

1002
            /** @scrutinizer ignore-deprecated */ $result->errors[0]->toSerializableArray()

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