Completed
Pull Request — master (#36)
by Sebastian
04:13
created

ProcessorTest::testSubscriptionNullResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 18
Ratio 100 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 18
loc 18
rs 9.4285
cc 1
eloc 11
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\Processor;
13
use Youshido\GraphQL\Execution\ResolveInfo;
14
use Youshido\GraphQL\Field\Field;
15
use Youshido\GraphQL\Schema\Schema;
16
use Youshido\GraphQL\Type\ListType\ListType;
17
use Youshido\GraphQL\Type\NonNullType;
18
use Youshido\GraphQL\Type\Object\ObjectType;
19
use Youshido\GraphQL\Type\Scalar\BooleanType;
20
use Youshido\GraphQL\Type\Scalar\IdType;
21
use Youshido\GraphQL\Type\Scalar\IntType;
22
use Youshido\GraphQL\Type\Scalar\StringType;
23
use Youshido\GraphQL\Type\Union\UnionType;
24
use Youshido\Tests\DataProvider\TestEmptySchema;
25
use Youshido\Tests\DataProvider\TestEnumType;
26
use Youshido\Tests\DataProvider\TestInterfaceType;
27
use Youshido\Tests\DataProvider\TestObjectType;
28
use Youshido\Tests\DataProvider\TestSchema;
29
30
class ProcessorTest extends \PHPUnit_Framework_TestCase
31
{
32
33
    private $_counter = 0;
34
35
    public function testEmptyQueries()
36
    {
37
        $processor = new Processor(new TestSchema());
38
        $processor->processPayload('');
39
        $this->assertEquals(['errors' => [
40
            ['message' => 'Must provide an operation.']
41
        ]], $processor->getResponseData());
42
43
        $processor->processPayload('{ me { name } }');
44
        $this->assertEquals(['data' => [
45
            'me' => ['name' => 'John']
46
        ]], $processor->getResponseData());
47
48
    }
49
50
    public function testNestedVariables()
51
    {
52
        $processor    = new Processor(new TestSchema());
53
        $noArgsQuery  = '{ me { echo(value:"foo") } }';
54
        $expectedData = ['data' => ['me' => ['echo' => 'foo']]];
55
        $processor->processPayload($noArgsQuery, ['value' => 'foo']);
56
        $this->assertEquals($expectedData, $processor->getResponseData());
57
58
        $parameterizedFieldQuery =
59
            'query nestedFieldQuery($value:String!){
60
          me {
61
            echo(value:$value)
62
          }
63
        }';
64
        $processor->processPayload($parameterizedFieldQuery, ['value' => 'foo']);
65
        $this->assertEquals($expectedData, $processor->getResponseData());
66
67
        $parameterizedQueryQuery =
68
            'query nestedQueryQuery($value:Int){
69
          me {
70
            location(noop:$value) {
71
              address
72
            }
73
          }
74
        }';
75
        $processor->processPayload($parameterizedQueryQuery, ['value' => 1]);
76
        $this->assertArrayNotHasKey('errors', $processor->getResponseData());
77
    }
78
79 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...
80
    {
81
        $processor = new Processor(new Schema([
82
            'query' => new ObjectType([
83
                'name'   => 'RootQuery',
84
                'fields' => [
85
                    'list' => [
86
                        'type'    => new ListType(new StringType()),
87
                        'resolve' => function () {
88
                            return null;
89
                        }
90
                    ]
91
                ]
92
            ])
93
        ]));
94
        $data      = $processor->processPayload(' { list }')->getResponseData();
95
        $this->assertEquals(['data' => ['list' => null]], $data);
96
    }
97
98
99 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...
100
    {
101
        $processor = new Processor(new Schema([
102
            'query' => new ObjectType([
103
                'name'   => 'RootQuery',
104
                'fields' => [
105
                    'list' => [
106
                        'type'    => new ListType(new StringType()),
107
                        'resolve' => function () {
108
                            return null;
109
                        }
110
                    ]
111
                ]
112
            ])
113
        ]));
114
        $data      = $processor->processPayload(' { __schema { subscriptionType { name } } }')->getResponseData();
115
        $this->assertEquals(['data' => ['__schema' => ['subscriptionType' => null]]], $data);
116
    }
117
118
    public function testSchemaOperations()
119
    {
120
        $schema    = new Schema([
121
            'query' => new ObjectType([
122
                'name'   => 'RootQuery',
123
                'fields' => [
124
                    'me'                => [
125
                        'type'    => new ObjectType([
126
                            'name'   => 'User',
127
                            'fields' => [
128
                                'firstName' => [
129
                                    'type'    => new StringType(),
130
                                    'args'    => [
131
                                        'shorten' => new BooleanType()
132
                                    ],
133
                                    'resolve' => function ($value, $args) {
134
                                        return empty($args['shorten']) ? $value : $value;
135
                                    }
136
                                ],
137
                                'id_alias'  => [
138
                                    'type'    => new IdType(),
139
                                    'resolve' => function ($value) {
140
                                        return $value['id'];
141
                                    }
142
                                ],
143
                                'lastName'  => new StringType(),
144
                                'code'      => new StringType(),
145
                            ]
146
                        ]),
147
                        'resolve' => function ($value, $args) {
148
                            $data = ['id' => '123', 'firstName' => 'John', 'code' => '007'];
149
                            if (!empty($args['upper'])) {
150
                                foreach ($data as $key => $value) {
151
                                    $data[$key] = strtoupper($value);
152
                                }
153
                            }
154
155
                            return $data;
156
                        },
157
                        'args'    => [
158
                            'upper' => [
159
                                'type'    => new BooleanType(),
160
                                'default' => false
161
                            ]
162
                        ]
163
                    ],
164
                    'randomUser'        => [
165
                        'type'    => new TestObjectType(),
166
                        'resolve' => function () {
167
                            return ['invalidField' => 'John'];
168
                        }
169
                    ],
170
                    'invalidValueQuery' => [
171
                        'type'    => new TestObjectType(),
172
                        'resolve' => function () {
173
                            return 'stringValue';
174
                        }
175
                    ],
176
                    'labels'            => [
177
                        'type'    => new ListType(new StringType()),
178
                        'resolve' => function () {
179
                            return ['one', 'two'];
180
                        }
181
                    ]
182
                ],
183
            ])
184
        ]);
185
        $processor = new Processor($schema);
186
187
        $processor->processPayload('{ me { firstName } }');
188
        $this->assertEquals(['data' => ['me' => ['firstName' => 'John']]], $processor->getResponseData());
189
190
        $processor->processPayload('{ me { id_alias } }');
191
        $this->assertEquals(['data' => ['me' => ['id_alias' => '123']]], $processor->getResponseData());
192
193
        $processor->processPayload('{ me { firstName, lastName } }');
194
        $this->assertEquals(['data' => ['me' => ['firstName' => 'John', 'lastName' => null]]], $processor->getResponseData());
195
196
        $processor->processPayload('{ me { code } }');
197
        $this->assertEquals(['data' => ['me' => ['code' => 7]]], $processor->getResponseData());
198
199
        $processor->processPayload('{ me(upper:true) { firstName } }');
200
        $this->assertEquals(['data' => ['me' => ['firstName' => 'JOHN']]], $processor->getResponseData());
201
202
        $processor->processPayload('{ labels }');
203
        $this->assertEquals(['data' => ['labels' => ['one', 'two']]], $processor->getResponseData());
204
205
        $schema->getMutationType()
206
               ->addField(new Field([
207
                   'name'    => 'increaseCounter',
208
                   'type'    => new IntType(),
209
                   '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...
210
                       return $this->_counter += $args['amount'];
211
                   },
212
                   'args'    => [
213
                       'amount' => [
214
                           'type'    => new IntType(),
215
                           'default' => 1
216
                       ]
217
                   ]
218
               ]))->addField(new Field([
219
                'name'    => 'invalidResolveTypeMutation',
220
                'type'    => new NonNullType(new IntType()),
221
                'resolve' => function () {
222
                    return null;
223
                }
224
            ]))->addField(new Field([
225
                'name'    => 'interfacedMutation',
226
                'type'    => new TestInterfaceType(),
227
                'resolve' => function () {
228
                    return ['name' => 'John'];
229
                }
230
            ]));
231
        $processor->processPayload('mutation { increaseCounter }');
232
        $this->assertEquals(['data' => ['increaseCounter' => 1]], $processor->getResponseData());
233
234
        $processor->processPayload('mutation { invalidMutation }');
235
        $this->assertEquals(['errors' => [['message' => 'Field "invalidMutation" not found in type "RootSchemaMutation"']]], $processor->getResponseData());
236
237
        $processor->processPayload('mutation { increaseCounter(noArg: 2) }');
238
        $this->assertEquals(['errors' => [['message' => 'Unknown argument "noArg" on field "increaseCounter"']]], $processor->getResponseData());
239
240
        $processor->processPayload('mutation { increaseCounter(amount: 2) { invalidProp } }');
241
        $this->assertEquals(['errors' => [['message' => 'Field "invalidProp" not found in type "Int"']], 'data' => ['increaseCounter' => null]], $processor->getResponseData());
242
243
        $processor->processPayload('mutation { increaseCounter(amount: 2) }');
244
        $this->assertEquals(['data' => ['increaseCounter' => 5]], $processor->getResponseData());
245
246
        $processor->processPayload('{ invalidQuery }');
247
        $this->assertEquals(['errors' => [['message' => 'Field "invalidQuery" not found in type "RootQuery"']]], $processor->getResponseData());
248
249
        $processor->processPayload('{ invalidValueQuery { id } }');
250
        $this->assertEquals(['errors' => [['message' => 'Not valid value for OBJECT field invalidValueQuery']], 'data' => ['invalidValueQuery' => null]], $processor->getResponseData());
251
252
        $processor->processPayload('{ me { firstName(shorten: true), middle }}');
253
        $this->assertEquals(['errors' => [['message' => 'Field "middle" not found in type "User"']], 'data' => ['me' => null]], $processor->getResponseData());
254
255
        $processor->processPayload('{ randomUser { region }}');
256
        $this->assertEquals(['errors' => [['message' => 'Property "region" not found in resolve result']]], $processor->getResponseData());
257
258
        $processor->processPayload('mutation { invalidResolveTypeMutation }');
259
        $this->assertEquals(['errors' => [['message' => 'Cannot return null for non-nullable field invalidResolveTypeMutation']], 'data' => ['invalidResolveTypeMutation' => null]], $processor->getResponseData());
260
261
        $processor->processPayload('mutation { user:interfacedMutation { name }  }');
262
        $this->assertEquals(['data' => ['user' => ['name' => 'John']]], $processor->getResponseData());
263
    }
264
265
    public function testListEnumsSchemaOperations()
266
    {
267
        $processor = new Processor(new Schema([
268
            'query' => new ObjectType([
269
                'name'   => 'RootQuery',
270
                'fields' => [
271
                    'listQuery'                 => [
272
                        'type'    => new ListType(new TestEnumType()),
273
                        'resolve' => function () {
274
                            return 'invalid list';
275
                        }
276
                    ],
277
                    'listEnumQuery'             => [
278
                        'type'    => new ListType(new TestEnumType()),
279
                        'resolve' => function () {
280
                            return ['invalid enum'];
281
                        }
282
                    ],
283
                    'invalidEnumQuery'          => [
284
                        'type'    => new TestEnumType(),
285
                        'resolve' => function () {
286
                            return 'invalid enum';
287
                        }
288
                    ],
289
                    'enumQuery'                 => [
290
                        'type'    => new TestEnumType(),
291
                        'resolve' => function () {
292
                            return 1;
293
                        }
294
                    ],
295
                    'invalidNonNullQuery'       => [
296
                        'type'    => new NonNullType(new IntType()),
297
                        'resolve' => function () {
298
                            return null;
299
                        }
300
                    ],
301
                    'invalidNonNullInsideQuery' => [
302
                        'type'    => new NonNullType(new IntType()),
303
                        'resolve' => function () {
304
                            return 'hello';
305
                        }
306
                    ],
307
                    'objectQuery'               => [
308
                        'type'    => new TestObjectType(),
309
                        'resolve' => function () {
310
                            return ['name' => 'John'];
311
                        }
312
                    ],
313
                    'deepObjectQuery'           => [
314
                        'type'    => new ObjectType([
315
                            'name'   => 'deepObject',
316
                            'fields' => [
317
                                'object' => new TestObjectType(),
318
                                'enum'   => new TestEnumType(),
319
                            ],
320
                        ]),
321
                        'resolve' => function () {
322
                            return [
323
                                'object' => [
324
                                    'name' => 'John'
325
                                ],
326
                                'enum'   => 1
327
                            ];
328
                        },
329
                    ],
330
                ]
331
            ])
332
        ]));
333
334
        $processor->processPayload('{ listQuery }');
335
        $this->assertEquals(['errors' => [
336
            ['message' => 'Not valid value for LIST field listQuery']
337
        ], 'data'                     => ['listQuery' => null]], $processor->getResponseData());
338
339
        $processor->processPayload('{ listEnumQuery }');
340
        $this->assertEquals(['errors' => [
341
            ['message' => 'Not valid resolve value in listEnumQuery field']
342
        ], 'data'                     => ['listEnumQuery' => [null]]], $processor->getResponseData());
343
344
        $processor->processPayload('{ invalidEnumQuery }');
345
        $this->assertEquals(['errors' => [
346
            ['message' => 'Not valid value for ENUM field invalidEnumQuery']
347
        ], 'data'                     => ['invalidEnumQuery' => null]], $processor->getResponseData());
348
349
        $processor->processPayload('{ enumQuery }');
350
        $this->assertEquals(['data' => ['enumQuery' => 'FINISHED']], $processor->getResponseData());
351
352
        $processor->processPayload('{ invalidNonNullQuery }');
353
        $this->assertEquals(['errors' => [
354
            ['message' => 'Cannot return null for non-nullable field invalidNonNullQuery']
355
        ], 'data'                     => ['invalidNonNullQuery' => null]], $processor->getResponseData());
356
357
        $processor->processPayload('{ invalidNonNullInsideQuery }');
358
        $this->assertEquals(['errors' => [
359
            ['message' => 'Not valid value for SCALAR field invalidNonNullInsideQuery']
360
        ], 'data'                     => ['invalidNonNullInsideQuery' => null]], $processor->getResponseData());
361
362
        $processor->processPayload('{ test:deepObjectQuery { object { name } } }');
363
        $this->assertEquals(['data' => ['test' => ['object' => ['name' => 'John']]]], $processor->getResponseData());
364
    }
365
366
    public function testTypedFragment()
367
    {
368
369
        $object1 = new ObjectType([
370
            'name'   => 'Object1',
371
            'fields' => [
372
                'id' => ['type' => 'int', 'cost' => 13]
373
            ]
374
        ]);
375
376
        $object2 = new ObjectType([
377
            'name'   => 'Object2',
378
            'fields' => [
379
                'name' => ['type' => 'string']
380
            ]
381
        ]);
382
383
        $object3 = new ObjectType([
384
            'name'   => 'Object3',
385
            'fields' => [
386
                'name' => ['type' => 'string']
387
            ]
388
        ]);
389
390
        $union        = new UnionType([
391
            'name'        => 'TestUnion',
392
            'types'       => [$object1, $object2],
393
            'resolveType' => function ($object) use ($object1, $object2) {
394
                if (isset($object['id'])) {
395
                    return $object1;
396
                }
397
398
                return $object2;
399
            }
400
        ]);
401
        $invalidUnion = new UnionType([
402
            'name'        => 'TestUnion',
403
            'types'       => [$object1, $object2],
404
            '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...
405
                return $object3;
406
            }
407
        ]);
408
        $processor    = new Processor(new Schema([
409
            'query' => new ObjectType([
410
                'name'   => 'RootQuery',
411
                'fields' => [
412
                    'union'        => [
413
                        'type'    => $union,
414
                        'args'    => [
415
                            'type' => ['type' => 'string']
416
                        ],
417
                        'cost' => 10,
418
                        'resolve' => function ($value, $args) {
419
                            if ($args['type'] == 'object1') {
420
                                return [
421
                                    'id' => 43
422
                                ];
423
                            } else {
424
                                return [
425
                                    'name' => 'name resolved'
426
                                ];
427
                            }
428
                        }
429
                    ],
430
                    'invalidUnion' => [
431
                        'type'    => $invalidUnion,
432
                        'resolve' => function () {
433
                            return ['name' => 'name resolved'];
434
                        }
435
                    ],
436
                ]
437
            ])
438
        ]));
439
        $processor->processPayload('{ union(type: "object1") { ... on Object2 { id } } }');
440
        $this->assertEquals(['data' => ['union' => []]], $processor->getResponseData());
441
442
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { name } } }');
443
        $this->assertEquals([
444
            'data'   => [
445
                'union' => []
446
            ],
447
            'errors' => [
448
                ['message' => 'Field "name" not found in type "Object1"']
449
            ]
450
        ], $processor->getResponseData());
451
452
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { id } } }');
453
        $this->assertEquals(['data' => ['union' => ['id' => 43]]], $processor->getResponseData());
454
455
        $processor->processPayload('{ union(type: "asd") { ... on Object2 { name } } }');
456
        $this->assertEquals(['data' => ['union' => ['name' => 'name resolved']]], $processor->getResponseData());
457
458
        $processor->processPayload('{ invalidUnion { ... on Object2 { name } } }');
459
        $this->assertEquals(['errors' => [['message' => 'Type Object3 not exist in types of Object2']]], $processor->getResponseData());
460
461
        $visitor = new \Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor(1000); // arbitrarily high cost
462
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { id } } }', [], [$visitor]);
463
        $this->assertEquals(10 + 13, $visitor->getMemo());
464
465
        $visitor = new \Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor(1000); // arbitrarily high cost
466
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { id }, ... on Object2 { name } } }', [], [$visitor]);
467
        $this->assertEquals(10 + 13 + 1, $visitor->getMemo());
468
469
        // planning phase currently has no knowledge of what types the union will resolve to, this will have the same score as above
470
        $visitor = new \Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor(1000); // arbitrarily high cost
471
        $processor->processPayload('{ union(type: "object2") { ... on Object1 { id }, ... on Object2 { name } } }', [], [$visitor]);
472
        $this->assertEquals(10 + 13 + 1, $visitor->getMemo());
473
    }
474
475
    public function testComplexityReducer() {
476
      $schema = new Schema(
477
          [
478
              'query' => new ObjectType(
479
                  [
480
                      'name'   => 'RootQuery',
481
                      'fields' => [
482
                          'me' => [
483
                              'type'    => new ObjectType(
484
                                  [
485
                                      'name'   => 'User',
486
                                      'fields' => [
487
                                          'firstName' => [
488
                                              'type'    => new StringType(),
489
                                              'args'    => [
490
                                                  'shorten' => new BooleanType()
491
                                              ],
492
                                              'resolve' => function ($value, $args) {
493
                                                return empty($args['shorten']) ? $value : $value;
494
                                              }
495
                                          ],
496
                                          'lastName'  => new StringType(),
497
                                          'code'      => new StringType(),
498
                                          'likes'     => [
499
                                              'type'    => new IntType(),
500
                                              'cost'    => 10,
501
                                              'resolve' => function () {
502
                                                return 42;
503
                                              }
504
                                          ]
505
                                      ]
506
                                  ]
507
                              ),
508
                              'cost' => function ($args, $context, $childCost) {
509
                                $argsCost = isset($args['cost']) ? $args['cost'] : 1;
510
                                return 1 + $argsCost * $childCost;
511
                              },
512
                              '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...
513
                                $data = ['firstName' => 'John', 'code' => '007'];
514
515
                                return $data;
516
                              },
517
                              'args'    => [
518
                                  'cost' => [
519
                                      'type'    => new IntType(),
520
                                      'default' => 1
521
                                  ]
522
                              ]
523
                          ]
524
                      ]
525
                  ]
526
              )
527
          ]
528
      );
529
      $processor = new Processor($schema);
530
531
      $processor->setMaxComplexity(10);
532
533
      $processor->processPayload('{ me { firstName, lastName } }');
534
      $this->assertArrayNotHasKey('error', $processor->getResponseData());
535
536
      $processor->processPayload('{ me { firstName, likes } }');
537
      $this->assertEquals(['errors' => [['message' => 'query exceeded max allowed complexity of 10']]], $processor->getResponseData());
538
539
      // don't let complexity reducer affect query errors
540
      $processor->processPayload('{ me { badfield } }');
541
      $this->assertArraySubset(['errors' => [['message' => 'Field "badfield" not found in type "User"']]], $processor->getResponseData());
542
543
      foreach (range(1,5) as $cost_multiplier) {
544
        $visitor = new \Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor(1000); // arbitrarily high cost
545
        $processor->processPayload("{ me (cost: $cost_multiplier) { firstName, lastName, code, likes } }", ['cost' => $cost_multiplier], [$visitor]);
546
        $expected = 1 + 13 * (1 + $cost_multiplier);
547
        $this->assertEquals($expected, $visitor->getMemo());
548
      }
549
550
      // TODO, variables not yet supported
551
      /*$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...
552
      foreach (range(1,5) as $cost_multiplier) {
553
        $visitor = new \Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor(1000); // arbitrarily high cost
554
        $processor->processPayload($query, ['cost' => $cost_multiplier], [$visitor]);
555
        $expected = 1 + 13 * (1 + $cost_multiplier);
556
        $this->assertEquals($expected, $visitor->getMemo());
557
      }*/
558
    }
559
}
560