ProcessorTest::testSubscriptionNullResponse()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18

Duplication

Lines 18
Ratio 100 %

Importance

Changes 0
Metric Value
dl 18
loc 18
rs 9.6666
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/*
3
 * This file is a part of GraphQL project.
4
 *
5
 * @author Alexandr Viniychuk <[email protected]>
6
 * created: 11:02 PM 5/13/16
7
 */
8
9
namespace Youshido\Tests\Schema;
10
11
12
use Youshido\GraphQL\Execution\Container\Container;
13
use Youshido\GraphQL\Execution\Context\ExecutionContext;
14
use Youshido\GraphQL\Execution\Processor;
15
use Youshido\GraphQL\Execution\ResolveInfo;
16
use Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor;
17
use Youshido\GraphQL\Field\Field;
18
use Youshido\GraphQL\Schema\Schema;
19
use Youshido\GraphQL\Type\Enum\EnumType;
20
use Youshido\GraphQL\Type\ListType\ListType;
21
use Youshido\GraphQL\Type\NonNullType;
22
use Youshido\GraphQL\Type\Object\ObjectType;
23
use Youshido\GraphQL\Type\Scalar\BooleanType;
24
use Youshido\GraphQL\Type\Scalar\IdType;
25
use Youshido\GraphQL\Type\Scalar\IntType;
26
use Youshido\GraphQL\Type\Scalar\StringType;
27
use Youshido\GraphQL\Type\Union\UnionType;
28
use Youshido\Tests\DataProvider\TestEmptySchema;
29
use Youshido\Tests\DataProvider\TestEnumType;
30
use Youshido\Tests\DataProvider\TestInterfaceType;
31
use Youshido\Tests\DataProvider\TestObjectType;
32
use Youshido\Tests\DataProvider\TestSchema;
33
34
class ProcessorTest extends \PHPUnit_Framework_TestCase
35
{
36
37
    private $_counter = 0;
38
39
    public function testInit()
40
    {
41
        $processor = new Processor(new TestEmptySchema());
42
        $this->assertEquals([['message' => 'Schema has to have fields']], $processor->getExecutionContext()->getErrorsArray());
43
    }
44
45
    public function testEmptyQueries()
46
    {
47
        $processor = new Processor(new TestSchema());
48
        $processor->processPayload('');
49
        $this->assertEquals(['errors' => [
50
            ['message' => 'Must provide an operation.']
51
        ]], $processor->getResponseData());
52
53
        $processor->getExecutionContext()->clearErrors();
54
        $processor->processPayload('{ me { name } }');
55
        $this->assertEquals(['data' => [
56
            'me' => ['name' => 'John']
57
        ]], $processor->getResponseData());
58
59
    }
60
61
    public function testNestedVariables()
62
    {
63
        $processor    = new Processor(new TestSchema());
64
        $noArgsQuery  = '{ me { echo(value:"foo") } }';
65
        $expectedData = ['data' => ['me' => ['echo' => 'foo']]];
66
        $processor->processPayload($noArgsQuery, ['value' => 'foo']);
67
        $this->assertEquals($expectedData, $processor->getResponseData());
68
69
        $parameterizedFieldQuery =
70
            'query nestedFieldQuery($value:String!){
71
          me {
72
            echo(value:$value)
73
          }
74
        }';
75
        $processor->processPayload($parameterizedFieldQuery, ['value' => 'foo']);
76
        $response = $processor->getResponseData();
77
        $this->assertEquals($expectedData, $response);
78
79
        $parameterizedQueryQuery =
80
            'query nestedQueryQuery($value:Int){
81
          me {
82
            location(noop:$value) {
83
              address
84
            }
85
          }
86
        }';
87
        $processor->processPayload($parameterizedQueryQuery, ['value' => 1]);
88
        $this->assertArrayNotHasKey('errors', $processor->getResponseData());
89
    }
90
91
    public function testNullListVariable()
92
    {
93
        $processor = new Processor(new TestSchema());
94
        $processor->processPayload(
95
            'mutation ($list: [Int], $status: TestEnum) { updateStatus(list: $list, newStatus: $status) }',
96
            [
97
                'list'   => null,
98
                'status' => null,
99
            ]
100
        );
101
102
        $this->assertArrayNotHasKey('errors', $processor->getResponseData());
103
    }
104
105 View Code Duplication
    public function testListNullResponse()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
106
    {
107
        $processor = new Processor(new Schema([
108
            'query' => new ObjectType([
109
                'name'   => 'RootQuery',
110
                'fields' => [
111
                    'list' => [
112
                        'type'    => new ListType(new StringType()),
113
                        'resolve' => function () {
114
                            return null;
115
                        }
116
                    ]
117
                ]
118
            ])
119
        ]));
120
        $data      = $processor->processPayload(' { list }')->getResponseData();
121
        $this->assertEquals(['data' => ['list' => null]], $data);
122
    }
123
124
125 View Code Duplication
    public function testSubscriptionNullResponse()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
    {
127
        $processor = new Processor(new Schema([
128
            'query' => new ObjectType([
129
                'name'   => 'RootQuery',
130
                'fields' => [
131
                    'list' => [
132
                        'type'    => new ListType(new StringType()),
133
                        'resolve' => function () {
134
                            return null;
135
                        }
136
                    ]
137
                ]
138
            ])
139
        ]));
140
        $data      = $processor->processPayload(' { __schema { subscriptionType { name } } }')->getResponseData();
141
        $this->assertEquals(['data' => ['__schema' => ['subscriptionType' => null]]], $data);
142
    }
143
144
    public function testSchemaOperations()
145
    {
146
        $schema    = new Schema([
147
            'query' => new ObjectType([
148
                'name'   => 'RootQuery',
149
                'fields' => [
150
                    'me'                => [
151
                        'type'    => new ObjectType([
152
                            'name'   => 'User',
153
                            'fields' => [
154
                                'firstName' => [
155
                                    'type'    => new StringType(),
156
                                    'args'    => [
157
                                        'shorten' => new BooleanType()
158
                                    ],
159
                                    'resolve' => function ($value, $args) {
160
                                        return empty($args['shorten']) ? $value['firstName'] : $value['firstName'];
161
                                    }
162
                                ],
163
                                'id_alias'  => [
164
                                    'type'    => new IdType(),
165
                                    'resolve' => function ($value) {
166
                                        return $value['id'];
167
                                    }
168
                                ],
169
                                'lastName'  => new StringType(),
170
                                'code'      => new StringType(),
171
                            ]
172
                        ]),
173
                        'resolve' => function ($value, $args) {
174
                            $data = ['id' => '123', 'firstName' => 'John', 'code' => '007'];
175
                            if (!empty($args['upper'])) {
176
                                foreach ($data as $key => $value) {
177
                                    $data[$key] = strtoupper($value);
178
                                }
179
                            }
180
181
                            return $data;
182
                        },
183
                        'args'    => [
184
                            'upper' => [
185
                                'type'         => new BooleanType(),
186
                                'defaultValue' => false
187
                            ]
188
                        ]
189
                    ],
190
                    'randomUser'        => [
191
                        'type'    => new TestObjectType(),
192
                        'resolve' => function () {
193
                            return ['invalidField' => 'John'];
194
                        }
195
                    ],
196
                    'invalidValueQuery' => [
197
                        'type'    => new TestObjectType(),
198
                        'resolve' => function () {
199
                            return 'stringValue';
200
                        }
201
                    ],
202
                    'labels'            => [
203
                        'type'    => new ListType(new StringType()),
204
                        'resolve' => function () {
205
                            return ['one', 'two'];
206
                        }
207
                    ]
208
                ],
209
            ])
210
        ]);
211
        $processor = new Processor($schema);
212
213
        $processor->processPayload('mutation { __typename }');
214
        $this->assertEquals(['data' => ['__typename' => 'RootSchemaMutation']], $processor->getResponseData());
215
216
        $processor->processPayload('{ __typename }');
217
        $this->assertEquals(['data' => ['__typename' => 'RootQuery']], $processor->getResponseData());
218
219
        $processor->processPayload('{ me { firstName } }');
220
        $this->assertEquals(['data' => ['me' => ['firstName' => 'John']]], $processor->getResponseData());
221
222
        $processor->processPayload('{ me { id_alias } }');
223
        $this->assertEquals(['data' => ['me' => ['id_alias' => '123']]], $processor->getResponseData());
224
225
        $processor->processPayload('{ me { firstName, lastName } }');
226
        $this->assertEquals(['data' => ['me' => ['firstName' => 'John', 'lastName' => null]]], $processor->getResponseData());
227
228
        $processor->processPayload('{ me { code } }');
229
        $this->assertEquals(['data' => ['me' => ['code' => 7]]], $processor->getResponseData());
230
231
        $processor->processPayload('{ me(upper:true) { firstName } }');
232
        $this->assertEquals(['data' => ['me' => ['firstName' => 'JOHN']]], $processor->getResponseData());
233
234
        $processor->processPayload('{ labels }');
235
        $this->assertEquals(['data' => ['labels' => ['one', 'two']]], $processor->getResponseData());
236
237
        $schema->getMutationType()
238
            ->addField(new Field([
239
                'name'    => 'increaseCounter',
240
                'type'    => new IntType(),
241
                'resolve' => function ($value, $args, ResolveInfo $info) {
0 ignored issues
show
Unused Code introduced by
The parameter $info is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
242
                    return $this->_counter += $args['amount'];
243
                },
244
                'args'    => [
245
                    'amount' => [
246
                        'type'    => new IntType(),
247
                        'defaultValue' => 1
248
                    ]
249
                ]
250
            ]))->addField(new Field([
251
                'name'    => 'invalidResolveTypeMutation',
252
                'type'    => new NonNullType(new IntType()),
253
                'resolve' => function () {
254
                    return null;
255
                }
256
            ]))->addField(new Field([
257
                'name'    => 'interfacedMutation',
258
                'type'    => new TestInterfaceType(),
259
                'resolve' => function () {
260
                    return ['name' => 'John'];
261
                }
262
            ]));
263
        $processor->processPayload('mutation { increaseCounter }');
264
        $this->assertEquals(['data' => ['increaseCounter' => 1]], $processor->getResponseData());
265
266
        $processor->processPayload('mutation { invalidMutation }');
267
        $this->assertEquals(['errors' => [[
268
            'message'   => 'Field "invalidMutation" not found in type "RootSchemaMutation". Available fields are: "increaseCounter", "invalidResolveTypeMutation", "interfacedMutation"',
269
            'locations' => [
270
                [
271
                    'line'   => 1,
272
                    'column' => 12
273
                ]
274
            ]
275
        ]]], $processor->getResponseData());
276
        $processor->getExecutionContext()->clearErrors();
277
278
        $processor->processPayload('mutation { increaseCounter(noArg: 2) }');
279
        $this->assertEquals([
280
            'data'   => ['increaseCounter' => null],
281
            'errors' => [[
282
                'message'   => 'Unknown argument "noArg" on field "increaseCounter"',
283
                'locations' => [
284
                    [
285
                        'line'   => 1,
286
                        'column' => 28
287
                    ]
288
                ]
289
            ]]
290
        ], $processor->getResponseData());
291
        $processor->getExecutionContext()->clearErrors();
292
293
        $processor->processPayload('mutation { increaseCounter(amount: 2) { invalidProp } }');
294
        $this->assertEquals(['errors' => [[
295
            'message'   => 'You can\'t specify fields for scalar type "Int"',
296
            'locations' => [
297
                [
298
                    'line'   => 1,
299
                    'column' => 12
300
                ]
301
            ]
302
        ]], 'data'                    => ['increaseCounter' => null]], $processor->getResponseData());
303
        $processor->getExecutionContext()->clearErrors();
304
305
        $processor->processPayload('mutation { increaseCounter(amount: 2) }');
306
        $this->assertEquals(['data' => ['increaseCounter' => 3]], $processor->getResponseData());
307
308
        $processor->processPayload('{ invalidQuery }');
309
        $this->assertEquals(['errors' => [[
310
            'message'   => 'Field "invalidQuery" not found in type "RootQuery". Available fields are: "me", "randomUser", "invalidValueQuery", "labels", "__schema", "__type"',
311
            'locations' => [
312
                [
313
                    'line'   => 1,
314
                    'column' => 3
315
                ]
316
            ]
317
        ]]], $processor->getResponseData());
318
        $processor->getExecutionContext()->clearErrors();
319
320
        $processor->processPayload('{ invalidValueQuery { id } }');
321
        $this->assertEquals(['errors' => [['message' => 'Not valid resolved type for field "invalidValueQuery": (no details available)']], 'data' => ['invalidValueQuery' => null]], $processor->getResponseData());
322
        $processor->getExecutionContext()->clearErrors();
323
324
        $processor->processPayload('{ me { firstName(shorten: true), middle }}');
325
        $this->assertEquals([
326
            'data'   => ['me' => null],
327
            'errors' => [[
328
                'message'   => 'Field "middle" not found in type "User". Available fields are: "firstName", "id_alias", "lastName", "code"',
329
                'locations' => [
330
                    [
331
                        'line'   => 1,
332
                        'column' => 34
333
                    ]
334
                ]
335
            ]],
336
        ], $processor->getResponseData());
337
        $processor->getExecutionContext()->clearErrors();
338
339
        $processor->processPayload('{ randomUser { region }}');
340
        $this->assertEquals([
341
            'data'   => ['randomUser' => null],
342
            'errors' => [[
343
                'message'   => 'You have to specify fields for "region"',
344
                'locations' => [
345
                    [
346
                        'line'   => 1,
347
                        'column' => 16
348
                    ]
349
                ]
350
            ]]
351
        ], $processor->getResponseData());
352
        $processor->getExecutionContext()->clearErrors();
353
354
        $processor->processPayload('mutation { invalidResolveTypeMutation }');
355
        $this->assertEquals([
356
            'data'   => ['invalidResolveTypeMutation' => null],
357
            'errors' => [['message' => 'Cannot return null for non-nullable field "invalidResolveTypeMutation"']],
358
        ], $processor->getResponseData());
359
        $processor->getExecutionContext()->clearErrors();
360
361
        $processor->processPayload('mutation { user:interfacedMutation { name }  }');
362
        $this->assertEquals(['data' => ['user' => ['name' => 'John']]], $processor->getResponseData());
363
    }
364
365
    public function testEnumType()
366
    {
367
        $processor = new Processor(new Schema([
368
            'query' => new ObjectType([
369
                'name'   => 'RootQuery',
370
                'fields' => [
371
                    'test' => [
372
                        'args'    => [
373
                            'argument1' => new NonNullType(new EnumType([
374
                                'name'   => 'TestEnumType',
375
                                'values' => [
376
                                    [
377
                                        'name'  => 'VALUE1',
378
                                        'value' => 'val1'
379
                                    ],
380
                                    [
381
                                        'name'  => 'VALUE2',
382
                                        'value' => 'val2'
383
                                    ]
384
                                ]
385
                            ]))
386
                        ],
387
                        'type'    => new StringType(),
388
                        'resolve' => function ($value, $args) {
389
                            return $args['argument1'];
390
                        }
391
                    ]
392
                ]
393
            ])
394
        ]));
395
396
        $processor->processPayload('{ test }');
397
        $response = $processor->getResponseData();
398
        $this->assertEquals([
399
            'data'   => ['test' => null],
400
            'errors' => [['message' => 'Require "argument1" arguments to query "test"']]
401
        ], $response);
402
        $processor->getExecutionContext()->clearErrors();
403
404
        $processor->processPayload('{ alias: test() }');
405
        $response = $processor->getResponseData();
406
        $this->assertEquals([
407
            'data'   => ['alias' => null],
408
            'errors' => [['message' => 'Require "argument1" arguments to query "test"']]
409
        ], $response);
410
        $processor->getExecutionContext()->clearErrors();
411
412
        $processor->processPayload('{ alias: test(argument1: VALUE4) }');
413
        $response = $processor->getResponseData();
414
        $this->assertEquals([
415
            'data'   => ['alias' => null],
416
            'errors' => [[
417
                'message'   => 'Not valid type for argument "argument1" in query "test": Value must be one of the allowed ones: VALUE1 (val1), VALUE2 (val2)',
418
                'locations' => [
419
                    [
420
                        'line'   => 1,
421
                        'column' => 15
422
                    ]
423
                ]
424
            ]]
425
        ], $response);
426
        $processor->getExecutionContext()->clearErrors();
427
428
        $processor->processPayload('{ alias: test(argument1: VALUE1) }');
429
        $response = $processor->getResponseData();
430
        $this->assertEquals(['data' => ['alias' => 'val1']], $response);
431
    }
432
433
    public function testListEnumsSchemaOperations()
434
    {
435
        $processor = new Processor(new Schema([
436
            'query' => new ObjectType([
437
                'name'   => 'RootQuery',
438
                'fields' => [
439
                    'listQuery'                 => [
440
                        'type'    => new ListType(new TestEnumType()),
441
                        'resolve' => function () {
442
                            return 'invalid list';
443
                        }
444
                    ],
445
                    'listEnumQuery'             => [
446
                        'type'    => new ListType(new TestEnumType()),
447
                        'resolve' => function () {
448
                            return ['invalid enum'];
449
                        }
450
                    ],
451
                    'invalidEnumQuery'          => [
452
                        'type'    => new TestEnumType(),
453
                        'resolve' => function () {
454
                            return 'invalid enum';
455
                        }
456
                    ],
457
                    'enumQuery'                 => [
458
                        'type'    => new TestEnumType(),
459
                        'resolve' => function () {
460
                            return 1;
461
                        }
462
                    ],
463
                    'invalidNonNullQuery'       => [
464
                        'type'    => new NonNullType(new IntType()),
465
                        'resolve' => function () {
466
                            return null;
467
                        }
468
                    ],
469
                    'invalidNonNullInsideQuery' => [
470
                        'type'    => new NonNullType(new IntType()),
471
                        'resolve' => function () {
472
                            return 'hello';
473
                        }
474
                    ],
475
                    'objectQuery'               => [
476
                        'type'    => new TestObjectType(),
477
                        'resolve' => function () {
478
                            return ['name' => 'John'];
479
                        }
480
                    ],
481
                    'deepObjectQuery'           => [
482
                        'type'    => new ObjectType([
483
                            'name'   => 'deepObject',
484
                            'fields' => [
485
                                'object' => new TestObjectType(),
486
                                'enum'   => new TestEnumType(),
487
                            ],
488
                        ]),
489
                        'resolve' => function () {
490
                            return [
491
                                'object' => [
492
                                    'name' => 'John'
493
                                ],
494
                                'enum'   => 1
495
                            ];
496
                        },
497
                    ],
498
                ]
499
            ])
500
        ]));
501
502
        $processor->processPayload('{ listQuery }');
503
        $this->assertEquals([
504
            'data'   => ['listQuery' => null],
505
            'errors' => [['message' => 'Not valid resolved type for field "listQuery": The value is not an iterable.']]
506
        ], $processor->getResponseData());
507
        $processor->getExecutionContext()->clearErrors();
508
509
        $processor->processPayload('{ listEnumQuery }');
510
        $this->assertEquals([
511
            'data'   => ['listEnumQuery' => [null]],
512
            'errors' => [['message' => 'Not valid resolved type for field "listEnumQuery": Value must be one of the allowed ones: FINISHED (1), NEW (0)']]
513
        ], $processor->getResponseData());
514
        $processor->getExecutionContext()->clearErrors();
515
516
        $processor->processPayload('{ invalidEnumQuery }');
517
        $this->assertEquals([
518
            'data'   => ['invalidEnumQuery' => null],
519
            'errors' => [['message' => 'Not valid resolved type for field "invalidEnumQuery": Value must be one of the allowed ones: FINISHED (1), NEW (0)']],
520
        ], $processor->getResponseData());
521
        $processor->getExecutionContext()->clearErrors();
522
523
        $processor->processPayload('{ enumQuery }');
524
        $this->assertEquals(['data' => ['enumQuery' => 'FINISHED']], $processor->getResponseData());
525
526
        $processor->processPayload('{ invalidNonNullQuery }');
527
        $this->assertEquals([
528
            'data'   => ['invalidNonNullQuery' => null],
529
            'errors' => [['message' => 'Cannot return null for non-nullable field "invalidNonNullQuery"']],
530
        ], $processor->getResponseData());
531
        $processor->getExecutionContext()->clearErrors();
532
533
        $processor->processPayload('{ invalidNonNullInsideQuery }');
534
        $this->assertEquals([
535
            'data'   => ['invalidNonNullInsideQuery' => null],
536
            'errors' => [['message' => 'Not valid resolved type for field "invalidNonNullInsideQuery": (no details available)']],
537
        ], $processor->getResponseData());
538
        $processor->getExecutionContext()->clearErrors();
539
540
        $processor->processPayload('{ test:deepObjectQuery { object { name } } }');
541
        $this->assertEquals(['data' => ['test' => ['object' => ['name' => 'John']]]], $processor->getResponseData());
542
    }
543
544
    public function testTypedFragment()
545
    {
546
547
        $object1 = new ObjectType([
548
            'name'   => 'Object1',
549
            'fields' => [
550
                'id' => ['type' => 'int', 'cost' => 13]
551
            ]
552
        ]);
553
554
        $object2 = new ObjectType([
555
            'name'   => 'Object2',
556
            'fields' => [
557
                'name' => ['type' => 'string']
558
            ]
559
        ]);
560
561
        $object3 = new ObjectType([
562
            'name'   => 'Object3',
563
            'fields' => [
564
                'name' => ['type' => 'string']
565
            ]
566
        ]);
567
568
        $union        = new UnionType([
569
            'name'        => 'TestUnion',
570
            'types'       => [$object1, $object2],
571
            'resolveType' => function ($object) use ($object1, $object2) {
572
                if (isset($object['id'])) {
573
                    return $object1;
574
                }
575
576
                return $object2;
577
            }
578
        ]);
579
        $invalidUnion = new UnionType([
580
            'name'        => 'TestUnion',
581
            'types'       => [$object1, $object2],
582
            'resolveType' => function ($object) use ($object3) {
0 ignored issues
show
Unused Code introduced by
The parameter $object is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
583
                return $object3;
584
            }
585
        ]);
586
        $processor    = new Processor(new Schema([
587
            'query' => new ObjectType([
588
                'name'   => 'RootQuery',
589
                'fields' => [
590
                    'union'        => [
591
                        'type'    => $union,
592
                        'args'    => [
593
                            'type' => ['type' => 'string']
594
                        ],
595
                        'cost'    => 10,
596
                        'resolve' => function ($value, $args) {
597
                            if ($args['type'] == 'object1') {
598
                                return [
599
                                    'id' => 43
600
                                ];
601
                            } else {
602
                                return [
603
                                    'name' => 'name resolved'
604
                                ];
605
                            }
606
                        }
607
                    ],
608
                    'invalidUnion' => [
609
                        'type'    => $invalidUnion,
610
                        'resolve' => function () {
611
                            return ['name' => 'name resolved'];
612
                        }
613
                    ],
614
                ]
615
            ])
616
        ]));
617
        $processor->processPayload('{ union(type: "object1") { ... on Object2 { id } } }');
618
        $this->assertEquals(['data' => ['union' => []]], $processor->getResponseData());
619
620
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { name } } }');
621
        $this->assertEquals([
622
            'data'   => [
623
                'union' => null
624
            ],
625
            'errors' => [
626
                [
627
                    'message'   => 'Field "name" not found in type "Object1". Available fields are: "id"',
628
                    'locations' => [
629
                        [
630
                            'line'   => 1,
631
                            'column' => 45
632
                        ]
633
                    ]
634
                ]
635
            ]
636
        ], $processor->getResponseData());
637
        $processor->getExecutionContext()->clearErrors();
638
639
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { id } } }');
640
        $this->assertEquals(['data' => ['union' => ['id' => 43]]], $processor->getResponseData());
641
642
        $processor->processPayload('{ union(type: "asd") { ... on Object2 { name } } }');
643
        $this->assertEquals(['data' => ['union' => ['name' => 'name resolved']]], $processor->getResponseData());
644
645
        $processor->processPayload('{ invalidUnion { ... on Object2 { name } } }');
646
        $this->assertEquals([
647
            'data'   => [
648
                'invalidUnion' => null
649
            ],
650
            'errors' => [
651
                ['message' => 'Type "Object3" not exist in types of "TestUnion"']
652
            ]
653
        ], $processor->getResponseData());
654
655
        $visitor = new MaxComplexityQueryVisitor(1000); // arbitrarily high cost
656
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { id } } }', [], [$visitor]);
657
        $this->assertEquals(10 + 13, $visitor->getMemo());
658
659
        $visitor = new MaxComplexityQueryVisitor(1000); // arbitrarily high cost
660
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { id }, ... on Object2 { name } } }', [], [$visitor]);
661
        $this->assertEquals(10 + 13 + 1, $visitor->getMemo());
662
663
        // planning phase currently has no knowledge of what types the union will resolve to, this will have the same score as above
664
        $visitor = new MaxComplexityQueryVisitor(1000); // arbitrarily high cost
665
        $processor->processPayload('{ union(type: "object2") { ... on Object1 { id }, ... on Object2 { name } } }', [], [$visitor]);
666
        $this->assertEquals(10 + 13 + 1, $visitor->getMemo());
667
    }
668
669
    public function testContainer()
670
    {
671
        $container = new Container();
672
        $container->set('user', ['name' => 'Alex']);
673
674
        $executionContext = new ExecutionContext(new Schema([
675
            'query' => new ObjectType([
676
                'name'   => 'RootQuery',
677
                'fields' => [
678
                    'currentUser' => [
679
                        'type'    => new StringType(),
680
                        'resolve' => function ($source, $args, ResolveInfo $info) {
681
                            return $info->getContainer()->get('user')['name'];
682
                        }
683
                    ]
684
                ]
685
            ])
686
        ]));
687
        $executionContext->setContainer($container);
688
        $this->assertNotNull($executionContext->getContainer());
689
690
        $processor = new Processor($executionContext->getSchema());
691
        $processor->getExecutionContext()->setContainer($container);
692
693
        $this->assertEquals(['data' => ['currentUser' => 'Alex']], $processor->processPayload('{ currentUser }')->getResponseData());
694
    }
695
696
    public function testComplexityReducer()
697
    {
698
        $schema    = new Schema(
699
            [
700
                'query' => new ObjectType(
701
                    [
702
                        'name'   => 'RootQuery',
703
                        'fields' => [
704
                            'me' => [
705
                                'type'    => new ObjectType(
706
                                    [
707
                                        'name'   => 'User',
708
                                        'fields' => [
709
                                            'firstName' => [
710
                                                'type'    => new StringType(),
711
                                                'args'    => [
712
                                                    'shorten' => new BooleanType()
713
                                                ],
714
                                                'resolve' => function ($value, $args) {
715
                                                    return empty($args['shorten']) ? $value['firstName'] : $value['firstName'];
716
                                                }
717
                                            ],
718
                                            'lastName'  => new StringType(),
719
                                            'code'      => new StringType(),
720
                                            'likes'     => [
721
                                                'type'    => new IntType(),
722
                                                'cost'    => 10,
723
                                                'resolve' => function () {
724
                                                    return 42;
725
                                                }
726
                                            ]
727
                                        ]
728
                                    ]
729
                                ),
730
                                'cost'    => function ($args, $context, $childCost) {
731
                                    $argsCost = isset($args['cost']) ? $args['cost'] : 1;
732
733
                                    return 1 + $argsCost * $childCost;
734
                                },
735
                                'resolve' => function ($value, $args) {
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
736
                                    $data = ['firstName' => 'John', 'code' => '007'];
737
738
                                    return $data;
739
                                },
740
                                'args'    => [
741
                                    'cost' => [
742
                                        'type'    => new IntType(),
743
                                        'default' => 1
744
                                    ]
745
                                ]
746
                            ]
747
                        ]
748
                    ]
749
                )
750
            ]
751
        );
752
        $processor = new Processor($schema);
753
754
        $processor->setMaxComplexity(10);
755
756
        $processor->processPayload('{ me { firstName, lastName } }');
757
        $this->assertArrayNotHasKey('error', $processor->getResponseData());
758
759
        $processor->processPayload('{ me { } }');
760
        $this->assertEquals(['errors' => [[
761
            'message'   => 'Unexpected token "RBRACE"',
762
            'locations' => [
763
                [
764
                    'line'   => 1,
765
                    'column' => 10
766
                ]
767
            ]
768
        ]]], $processor->getResponseData());
769
        $processor->getExecutionContext()->clearErrors();
770
771
772
        $processor->processPayload('{ me { firstName, likes } }');
773
        $this->assertEquals(['errors' => [['message' => 'query exceeded max allowed complexity of 10']]], $processor->getResponseData());
774
        $processor->getExecutionContext()->clearErrors();
775
776
        // don't let complexity reducer affect query errors
777
        $processor->processPayload('{ me { badfield } }');
778
        $this->assertArraySubset(['errors' => [['message' => 'Field "badfield" not found in type "User". Available fields are: "firstName", "lastName", "code", "likes"']]], $processor->getResponseData());
779
        $processor->getExecutionContext()->clearErrors();
780
781
        foreach (range(1, 5) as $cost_multiplier) {
782
            $visitor = new MaxComplexityQueryVisitor(1000); // arbitrarily high cost
783
            $processor->processPayload("{ me (cost: $cost_multiplier) { firstName, lastName, code, likes } }", ['cost' => $cost_multiplier], [$visitor]);
784
            $expected = 1 + 13 * (1 + $cost_multiplier);
785
            $this->assertEquals($expected, $visitor->getMemo());
786
        }
787
788
        // TODO, variables not yet supported
789
        /*$query = 'query costQuery ($cost: Int) { me (cost: $cost) { firstName, lastName, code, likes } }';
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
790
        foreach (range(1,5) as $cost_multiplier) {
791
          $visitor = new \Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor(1000); // arbitrarily high cost
792
          $processor->processPayload($query, ['cost' => $cost_multiplier], [$visitor]);
793
          $expected = 1 + 13 * (1 + $cost_multiplier);
794
          $this->assertEquals($expected, $visitor->getMemo());
795
        }*/
796
    }
797
}
798