Completed
Pull Request — master (#204)
by Ryan
11:34
created

ProcessorTest::testSchemaOperations()   B

Complexity

Conditions 4
Paths 1

Size

Total Lines 220
Code Lines 149

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 220
rs 8.1935
cc 4
eloc 149
nc 1
nop 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Copyright (c) 2015–2018 Alexandr Viniychuk <http://youshido.com>.
4
 * Copyright (c) 2015–2018 Portey Vasil <https://github.com/portey>.
5
 * Copyright (c) 2018 Ryan Parman <https://github.com/skyzyx>.
6
 * Copyright (c) 2018 Ashley Hutson <https://github.com/asheliahut>.
7
 * Copyright (c) 2015–2018 Contributors.
8
 *
9
 * http://opensource.org/licenses/MIT
10
 */
11
12
declare(strict_types=1);
13
/*
14
 * This file is a part of GraphQL project.
15
 *
16
 * @author Alexandr Viniychuk <[email protected]>
17
 * created: 11:02 PM 5/13/16
18
 */
19
20
namespace Youshido\Tests\Schema;
21
22
use Youshido\GraphQL\Execution\Container\Container;
23
use Youshido\GraphQL\Execution\Context\ExecutionContext;
24
use Youshido\GraphQL\Execution\Processor;
25
use Youshido\GraphQL\Execution\ResolveInfo;
26
use Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor;
27
use Youshido\GraphQL\Field\Field;
28
use Youshido\GraphQL\Schema\Schema;
29
use Youshido\GraphQL\Type\Enum\EnumType;
30
use Youshido\GraphQL\Type\ListType\ListType;
31
use Youshido\GraphQL\Type\NonNullType;
32
use Youshido\GraphQL\Type\Object\ObjectType;
33
use Youshido\GraphQL\Type\Scalar\BooleanType;
34
use Youshido\GraphQL\Type\Scalar\IdType;
35
use Youshido\GraphQL\Type\Scalar\IntType;
36
use Youshido\GraphQL\Type\Scalar\StringType;
37
use Youshido\GraphQL\Type\Union\UnionType;
38
use Youshido\Tests\DataProvider\TestEmptySchema;
39
use Youshido\Tests\DataProvider\TestEnumType;
40
use Youshido\Tests\DataProvider\TestInterfaceType;
41
use Youshido\Tests\DataProvider\TestObjectType;
42
use Youshido\Tests\DataProvider\TestSchema;
43
44
class ProcessorTest extends \PHPUnit_Framework_TestCase
45
{
46
    private $_counter = 0;
47
48
    public function testInit(): void
49
    {
50
        $processor = new Processor(new TestEmptySchema());
51
        $this->assertEquals([['message' => 'Schema has to have fields']], $processor->getExecutionContext()->getErrorsArray());
52
    }
53
54
    public function testEmptyQueries(): void
55
    {
56
        $processor = new Processor(new TestSchema());
57
        $processor->processPayload('');
58
        $this->assertEquals(['errors' => [
59
            ['message' => 'Must provide an operation.'],
60
        ]], $processor->getResponseData());
61
62
        $processor->getExecutionContext()->clearErrors();
63
        $processor->processPayload('{ me { name } }');
64
        $this->assertEquals(['data' => [
65
            'me' => ['name' => 'John'],
66
        ]], $processor->getResponseData());
67
    }
68
69
    public function testNestedVariables(): void
70
    {
71
        $processor    = new Processor(new TestSchema());
72
        $noArgsQuery  = '{ me { echo(value:"foo") } }';
73
        $expectedData = ['data' => ['me' => ['echo' => 'foo']]];
74
        $processor->processPayload($noArgsQuery, ['value' => 'foo']);
75
        $this->assertEquals($expectedData, $processor->getResponseData());
76
77
        $parameterizedFieldQuery = 'query nestedFieldQuery($value:String!){
78
          me {
79
            echo(value:$value)
80
          }
81
        }';
82
        $processor->processPayload($parameterizedFieldQuery, ['value' => 'foo']);
83
        $response = $processor->getResponseData();
84
        $this->assertEquals($expectedData, $response);
85
86
        $parameterizedQueryQuery = 'query nestedQueryQuery($value:Int){
87
          me {
88
            location(noop:$value) {
89
              address
90
            }
91
          }
92
        }';
93
        $processor->processPayload($parameterizedQueryQuery, ['value' => 1]);
94
        $this->assertArrayNotHasKey('errors', $processor->getResponseData());
95
    }
96
97
    public function testNullListVariable(): void
98
    {
99
        $processor = new Processor(new TestSchema());
100
        $processor->processPayload(
101
            'mutation ($list: [Int], $status: TestEnum) { updateStatus(list: $list, newStatus: $status) }',
102
            [
103
                'list'   => null,
104
                'status' => null,
105
            ]
106
        );
107
108
        $this->assertArrayNotHasKey('errors', $processor->getResponseData());
109
    }
110
111
    public function testListNullResponse(): void
112
    {
113
        $processor = new Processor(new Schema([
114
            'query' => new ObjectType([
115
                'name'   => 'RootQuery',
116
                'fields' => [
117
                    'list' => [
118
                        'type'    => new ListType(new StringType()),
119
                        'resolve' => static function () {
120
                        },
121
                    ],
122
                ],
123
            ]),
124
        ]));
125
        $data = $processor->processPayload(' { list }')->getResponseData();
126
        $this->assertEquals(['data' => ['list' => null]], $data);
127
    }
128
129
    public function testSubscriptionNullResponse(): void
130
    {
131
        $processor = new Processor(new Schema([
132
            'query' => new ObjectType([
133
                'name'   => 'RootQuery',
134
                'fields' => [
135
                    'list' => [
136
                        'type'    => new ListType(new StringType()),
137
                        'resolve' => static function () {
138
                        },
139
                    ],
140
                ],
141
            ]),
142
        ]));
143
        $data = $processor->processPayload(' { __schema { subscriptionType { name } } }')->getResponseData();
144
        $this->assertEquals(['data' => ['__schema' => ['subscriptionType' => null]]], $data);
145
    }
146
147
    public function testSchemaOperations(): void
148
    {
149
        $schema = new Schema([
150
            'query' => new ObjectType([
151
                'name'   => 'RootQuery',
152
                'fields' => [
153
                    'me' => [
154
                        'type' => new ObjectType([
155
                            'name'   => 'User',
156
                            'fields' => [
157
                                'firstName' => [
158
                                    'type' => new StringType(),
159
                                    'args' => [
160
                                        'shorten' => new BooleanType(),
161
                                    ],
162
                                    'resolve' => static function ($value, $args) {
163
                                        return empty($args['shorten']) ? $value['firstName'] : $value['firstName'];
164
                                    },
165
                                ],
166
                                'id_alias' => [
167
                                    'type'    => new IdType(),
168
                                    'resolve' => static function ($value) {
169
                                        return $value['id'];
170
                                    },
171
                                ],
172
                                'lastName' => new StringType(),
173
                                'code'     => new StringType(),
174
                            ],
175
                        ]),
176
                        'resolve' => static function ($value, $args) {
177
                            $data = ['id' => '123', 'firstName' => 'John', 'code' => '007'];
178
179
                            if (!empty($args['upper'])) {
180
                                foreach ($data as $key => $value) {
181
                                    $data[$key] = \mb_strtoupper($value);
182
                                }
183
                            }
184
185
                            return $data;
186
                        },
187
                        'args' => [
188
                            'upper' => [
189
                                'type'         => new BooleanType(),
190
                                'defaultValue' => false,
191
                            ],
192
                        ],
193
                    ],
194
                    'randomUser' => [
195
                        'type'    => new TestObjectType(),
196
                        'resolve' => static function () {
197
                            return ['invalidField' => 'John'];
198
                        },
199
                    ],
200
                    'invalidValueQuery' => [
201
                        'type'    => new TestObjectType(),
202
                        'resolve' => static function () {
203
                            return 'stringValue';
204
                        },
205
                    ],
206
                    'labels' => [
207
                        'type'    => new ListType(new StringType()),
208
                        'resolve' => static function () {
209
                            return ['one', 'two'];
210
                        },
211
                    ],
212
                ],
213
            ]),
214
        ]);
215
        $processor = new Processor($schema);
216
217
        $processor->processPayload('mutation { __typename }');
218
        $this->assertEquals(['data' => ['__typename' => 'RootSchemaMutation']], $processor->getResponseData());
219
220
        $processor->processPayload('{ __typename }');
221
        $this->assertEquals(['data' => ['__typename' => 'RootQuery']], $processor->getResponseData());
222
223
        $processor->processPayload('{ me { firstName } }');
224
        $this->assertEquals(['data' => ['me' => ['firstName' => 'John']]], $processor->getResponseData());
225
226
        $processor->processPayload('{ me { id_alias } }');
227
        $this->assertEquals(['data' => ['me' => ['id_alias' => '123']]], $processor->getResponseData());
228
229
        $processor->processPayload('{ me { firstName, lastName } }');
230
        $this->assertEquals(['data' => ['me' => ['firstName' => 'John', 'lastName' => null]]], $processor->getResponseData());
231
232
        $processor->processPayload('{ me { code } }');
233
        $this->assertEquals(['data' => ['me' => ['code' => 7]]], $processor->getResponseData());
234
235
        $processor->processPayload('{ me(upper:true) { firstName } }');
236
        $this->assertEquals(['data' => ['me' => ['firstName' => 'JOHN']]], $processor->getResponseData());
237
238
        $processor->processPayload('{ labels }');
239
        $this->assertEquals(['data' => ['labels' => ['one', 'two']]], $processor->getResponseData());
240
241
        $schema->getMutationType()
242
            ->addField(new Field([
243
                'name'    => 'increaseCounter',
244
                'type'    => new IntType(),
245
                'resolve' => function ($value, $args, ResolveInfo $info) {
246
                    return $this->_counter += $args['amount'];
247
                },
248
                'args' => [
249
                    'amount' => [
250
                        'type'         => new IntType(),
251
                        'defaultValue' => 1,
252
                    ],
253
                ],
254
            ]))->addField(new Field([
255
                'name'    => 'invalidResolveTypeMutation',
256
                'type'    => new NonNullType(new IntType()),
257
                'resolve' => static function () {
258
                },
259
            ]))->addField(new Field([
260
                'name'    => 'interfacedMutation',
261
                'type'    => new TestInterfaceType(),
262
                'resolve' => static function () {
263
                    return ['name' => 'John'];
264
                },
265
            ]));
266
        $processor->processPayload('mutation { increaseCounter }');
267
        $this->assertEquals(['data' => ['increaseCounter' => 1]], $processor->getResponseData());
268
269
        $processor->processPayload('mutation { invalidMutation }');
270
        $this->assertEquals(['errors' => [[
271
            'message'   => 'Field "invalidMutation" not found in type "RootSchemaMutation". Available fields are: "increaseCounter", "invalidResolveTypeMutation", "interfacedMutation"',
272
            'locations' => [
273
                [
274
                    'line'   => 1,
275
                    'column' => 12,
276
                ],
277
            ],
278
        ]]], $processor->getResponseData());
279
        $processor->getExecutionContext()->clearErrors();
280
281
        $processor->processPayload('mutation { increaseCounter(noArg: 2) }');
282
        $this->assertEquals([
283
            'data'   => ['increaseCounter' => null],
284
            'errors' => [[
285
                'message'   => 'Unknown argument "noArg" on field "increaseCounter"',
286
                'locations' => [
287
                    [
288
                        'line'   => 1,
289
                        'column' => 28,
290
                    ],
291
                ],
292
            ]],
293
        ], $processor->getResponseData());
294
        $processor->getExecutionContext()->clearErrors();
295
296
        $processor->processPayload('mutation { increaseCounter(amount: 2) { invalidProp } }');
297
        $this->assertEquals(['errors' => [[
298
            'message'   => 'You can\'t specify fields for scalar type "Int"',
299
            'locations' => [
300
                [
301
                    'line'   => 1,
302
                    'column' => 12,
303
                ],
304
            ],
305
        ]], 'data' => ['increaseCounter' => null]], $processor->getResponseData());
306
        $processor->getExecutionContext()->clearErrors();
307
308
        $processor->processPayload('mutation { increaseCounter(amount: 2) }');
309
        $this->assertEquals(['data' => ['increaseCounter' => 3]], $processor->getResponseData());
310
311
        $processor->processPayload('{ invalidQuery }');
312
        $this->assertEquals(['errors' => [[
313
            'message'   => 'Field "invalidQuery" not found in type "RootQuery". Available fields are: "me", "randomUser", "invalidValueQuery", "labels", "__schema", "__type"',
314
            'locations' => [
315
                [
316
                    'line'   => 1,
317
                    'column' => 3,
318
                ],
319
            ],
320
        ]]], $processor->getResponseData());
321
        $processor->getExecutionContext()->clearErrors();
322
323
        $processor->processPayload('{ invalidValueQuery { id } }');
324
        $this->assertEquals(['errors' => [['message' => 'Not valid resolved type for field "invalidValueQuery": (no details available)']], 'data' => ['invalidValueQuery' => null]], $processor->getResponseData());
325
        $processor->getExecutionContext()->clearErrors();
326
327
        $processor->processPayload('{ me { firstName(shorten: true), middle }}');
328
        $this->assertEquals([
329
            'data'   => ['me' => null],
330
            'errors' => [[
331
                'message'   => 'Field "middle" not found in type "User". Available fields are: "firstName", "id_alias", "lastName", "code"',
332
                'locations' => [
333
                    [
334
                        'line'   => 1,
335
                        'column' => 34,
336
                    ],
337
                ],
338
            ]],
339
        ], $processor->getResponseData());
340
        $processor->getExecutionContext()->clearErrors();
341
342
        $processor->processPayload('{ randomUser { region }}');
343
        $this->assertEquals([
344
            'data'   => ['randomUser' => null],
345
            'errors' => [[
346
                'message'   => 'You have to specify fields for "region"',
347
                'locations' => [
348
                    [
349
                        'line'   => 1,
350
                        'column' => 16,
351
                    ],
352
                ],
353
            ]],
354
        ], $processor->getResponseData());
355
        $processor->getExecutionContext()->clearErrors();
356
357
        $processor->processPayload('mutation { invalidResolveTypeMutation }');
358
        $this->assertEquals([
359
            'data'   => ['invalidResolveTypeMutation' => null],
360
            'errors' => [['message' => 'Cannot return null for non-nullable field "invalidResolveTypeMutation"']],
361
        ], $processor->getResponseData());
362
        $processor->getExecutionContext()->clearErrors();
363
364
        $processor->processPayload('mutation { user:interfacedMutation { name }  }');
365
        $this->assertEquals(['data' => ['user' => ['name' => 'John']]], $processor->getResponseData());
366
    }
367
368
    public function testEnumType(): void
369
    {
370
        $processor = new Processor(new Schema([
371
            'query' => new ObjectType([
372
                'name'   => 'RootQuery',
373
                'fields' => [
374
                    'test' => [
375
                        'args' => [
376
                            'argument1' => new NonNullType(new EnumType([
377
                                'name'   => 'TestEnumType',
378
                                'values' => [
379
                                    [
380
                                        'name'  => 'VALUE1',
381
                                        'value' => 'val1',
382
                                    ],
383
                                    [
384
                                        'name'  => 'VALUE2',
385
                                        'value' => 'val2',
386
                                    ],
387
                                ],
388
                            ])),
389
                        ],
390
                        'type'    => new StringType(),
391
                        'resolve' => static function ($value, $args) {
392
                            return $args['argument1'];
393
                        },
394
                    ],
395
                ],
396
            ]),
397
        ]));
398
399
        $processor->processPayload('{ test }');
400
        $response = $processor->getResponseData();
401
        $this->assertEquals([
402
            'data'   => ['test' => null],
403
            'errors' => [['message' => 'Require "argument1" arguments to query "test"']],
404
        ], $response);
405
        $processor->getExecutionContext()->clearErrors();
406
407
        $processor->processPayload('{ alias: test() }');
408
        $response = $processor->getResponseData();
409
        $this->assertEquals([
410
            'data'   => ['alias' => null],
411
            'errors' => [['message' => 'Require "argument1" arguments to query "test"']],
412
        ], $response);
413
        $processor->getExecutionContext()->clearErrors();
414
415
        $processor->processPayload('{ alias: test(argument1: VALUE4) }');
416
        $response = $processor->getResponseData();
417
        $this->assertEquals([
418
            'data'   => ['alias' => null],
419
            'errors' => [[
420
                'message'   => 'Not valid type for argument "argument1" in query "test": Value must be one of the allowed ones: VALUE1 (val1), VALUE2 (val2)',
421
                'locations' => [
422
                    [
423
                        'line'   => 1,
424
                        'column' => 15,
425
                    ],
426
                ],
427
            ]],
428
        ], $response);
429
        $processor->getExecutionContext()->clearErrors();
430
431
        $processor->processPayload('{ alias: test(argument1: VALUE1) }');
432
        $response = $processor->getResponseData();
433
        $this->assertEquals(['data' => ['alias' => 'val1']], $response);
434
    }
435
436
    public function testListEnumsSchemaOperations(): void
437
    {
438
        $processor = new Processor(new Schema([
439
            'query' => new ObjectType([
440
                'name'   => 'RootQuery',
441
                'fields' => [
442
                    'listQuery' => [
443
                        'type'    => new ListType(new TestEnumType()),
444
                        'resolve' => static function () {
445
                            return 'invalid list';
446
                        },
447
                    ],
448
                    'listEnumQuery' => [
449
                        'type'    => new ListType(new TestEnumType()),
450
                        'resolve' => static function () {
451
                            return ['invalid enum'];
452
                        },
453
                    ],
454
                    'invalidEnumQuery' => [
455
                        'type'    => new TestEnumType(),
456
                        'resolve' => static function () {
457
                            return 'invalid enum';
458
                        },
459
                    ],
460
                    'enumQuery' => [
461
                        'type'    => new TestEnumType(),
462
                        'resolve' => static function () {
463
                            return 1;
464
                        },
465
                    ],
466
                    'invalidNonNullQuery' => [
467
                        'type'    => new NonNullType(new IntType()),
468
                        'resolve' => static function () {
469
                        },
470
                    ],
471
                    'invalidNonNullInsideQuery' => [
472
                        'type'    => new NonNullType(new IntType()),
473
                        'resolve' => static function () {
474
                            return 'hello';
475
                        },
476
                    ],
477
                    'objectQuery' => [
478
                        'type'    => new TestObjectType(),
479
                        'resolve' => static function () {
480
                            return ['name' => 'John'];
481
                        },
482
                    ],
483
                    'deepObjectQuery' => [
484
                        'type' => new ObjectType([
485
                            'name'   => 'deepObject',
486
                            'fields' => [
487
                                'object' => new TestObjectType(),
488
                                'enum'   => new TestEnumType(),
489
                            ],
490
                        ]),
491
                        'resolve' => static function () {
492
                            return [
493
                                'object' => [
494
                                    'name' => 'John',
495
                                ],
496
                                'enum' => 1,
497
                            ];
498
                        },
499
                    ],
500
                ],
501
            ]),
502
        ]));
503
504
        $processor->processPayload('{ listQuery }');
505
        $this->assertEquals([
506
            'data'   => ['listQuery' => null],
507
            'errors' => [['message' => 'Not valid resolved type for field "listQuery": The value is not an iterable.']],
508
        ], $processor->getResponseData());
509
        $processor->getExecutionContext()->clearErrors();
510
511
        $processor->processPayload('{ listEnumQuery }');
512
        $this->assertEquals([
513
            'data'   => ['listEnumQuery' => [null]],
514
            'errors' => [['message' => 'Not valid resolved type for field "listEnumQuery": Value must be one of the allowed ones: FINISHED (1), NEW (0)']],
515
        ], $processor->getResponseData());
516
        $processor->getExecutionContext()->clearErrors();
517
518
        $processor->processPayload('{ invalidEnumQuery }');
519
        $this->assertEquals([
520
            'data'   => ['invalidEnumQuery' => null],
521
            'errors' => [['message' => 'Not valid resolved type for field "invalidEnumQuery": Value must be one of the allowed ones: FINISHED (1), NEW (0)']],
522
        ], $processor->getResponseData());
523
        $processor->getExecutionContext()->clearErrors();
524
525
        $processor->processPayload('{ enumQuery }');
526
        $this->assertEquals(['data' => ['enumQuery' => 'FINISHED']], $processor->getResponseData());
527
528
        $processor->processPayload('{ invalidNonNullQuery }');
529
        $this->assertEquals([
530
            'data'   => ['invalidNonNullQuery' => null],
531
            'errors' => [['message' => 'Cannot return null for non-nullable field "invalidNonNullQuery"']],
532
        ], $processor->getResponseData());
533
        $processor->getExecutionContext()->clearErrors();
534
535
        $processor->processPayload('{ invalidNonNullInsideQuery }');
536
        $this->assertEquals([
537
            'data'   => ['invalidNonNullInsideQuery' => null],
538
            'errors' => [['message' => 'Not valid resolved type for field "invalidNonNullInsideQuery": (no details available)']],
539
        ], $processor->getResponseData());
540
        $processor->getExecutionContext()->clearErrors();
541
542
        $processor->processPayload('{ test:deepObjectQuery { object { name } } }');
543
        $this->assertEquals(['data' => ['test' => ['object' => ['name' => 'John']]]], $processor->getResponseData());
544
    }
545
546
    public function testTypedFragment(): void
547
    {
548
        $object1 = new ObjectType([
549
            'name'   => 'Object1',
550
            'fields' => [
551
                'id' => ['type' => 'int', 'cost' => 13],
552
            ],
553
        ]);
554
555
        $object2 = new ObjectType([
556
            'name'   => 'Object2',
557
            'fields' => [
558
                'name' => ['type' => 'string'],
559
            ],
560
        ]);
561
562
        $object3 = new ObjectType([
563
            'name'   => 'Object3',
564
            'fields' => [
565
                'name' => ['type' => 'string'],
566
            ],
567
        ]);
568
569
        $union = new UnionType([
570
            'name'        => 'TestUnion',
571
            'types'       => [$object1, $object2],
572
            'resolveType' => static function ($object) use ($object1, $object2) {
573
                if (isset($object['id'])) {
574
                    return $object1;
575
                }
576
577
                return $object2;
578
            },
579
        ]);
580
        $invalidUnion = new UnionType([
581
            'name'        => 'TestUnion',
582
            'types'       => [$object1, $object2],
583
            'resolveType' => static function ($object) use ($object3) {
584
                return $object3;
585
            },
586
        ]);
587
        $processor = new Processor(new Schema([
588
            'query' => new ObjectType([
589
                'name'   => 'RootQuery',
590
                'fields' => [
591
                    'union' => [
592
                        'type' => $union,
593
                        'args' => [
594
                            'type' => ['type' => 'string'],
595
                        ],
596
                        'cost'    => 10,
597
                        'resolve' => static function ($value, $args) {
598
                            if ('object1' === $args['type']) {
599
                                return [
600
                                    'id' => 43,
601
                                ];
602
                            }
603
604
                            return [
605
                                    'name' => 'name resolved',
606
                                ];
607
                        },
608
                    ],
609
                    'invalidUnion' => [
610
                        'type'    => $invalidUnion,
611
                        'resolve' => static function () {
612
                            return ['name' => 'name resolved'];
613
                        },
614
                    ],
615
                ],
616
            ]),
617
        ]));
618
        $processor->processPayload('{ union(type: "object1") { ... on Object2 { id } } }');
619
        $this->assertEquals(['data' => ['union' => []]], $processor->getResponseData());
620
621
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { name } } }');
622
        $this->assertEquals([
623
            'data' => [
624
                'union' => null,
625
            ],
626
            'errors' => [
627
                [
628
                    'message'   => 'Field "name" not found in type "Object1". Available fields are: "id"',
629
                    'locations' => [
630
                        [
631
                            'line'   => 1,
632
                            'column' => 45,
633
                        ],
634
                    ],
635
                ],
636
            ],
637
        ], $processor->getResponseData());
638
        $processor->getExecutionContext()->clearErrors();
639
640
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { id } } }');
641
        $this->assertEquals(['data' => ['union' => ['id' => 43]]], $processor->getResponseData());
642
643
        $processor->processPayload('{ union(type: "asd") { ... on Object2 { name } } }');
644
        $this->assertEquals(['data' => ['union' => ['name' => 'name resolved']]], $processor->getResponseData());
645
646
        $processor->processPayload('{ invalidUnion { ... on Object2 { name } } }');
647
        $this->assertEquals([
648
            'data' => [
649
                'invalidUnion' => null,
650
            ],
651
            'errors' => [
652
                ['message' => 'Type "Object3" not exist in types of "TestUnion"'],
653
            ],
654
        ], $processor->getResponseData());
655
656
        $visitor = new MaxComplexityQueryVisitor(1000); // arbitrarily high cost
657
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { id } } }', [], [$visitor]);
658
        $this->assertEquals(10 + 13, $visitor->getMemo());
659
660
        $visitor = new MaxComplexityQueryVisitor(1000); // arbitrarily high cost
661
        $processor->processPayload('{ union(type: "object1") { ... on Object1 { id }, ... on Object2 { name } } }', [], [$visitor]);
662
        $this->assertEquals(10 + 13 + 1, $visitor->getMemo());
663
664
        // planning phase currently has no knowledge of what types the union will resolve to, this will have the same score as above
665
        $visitor = new MaxComplexityQueryVisitor(1000); // arbitrarily high cost
666
        $processor->processPayload('{ union(type: "object2") { ... on Object1 { id }, ... on Object2 { name } } }', [], [$visitor]);
667
        $this->assertEquals(10 + 13 + 1, $visitor->getMemo());
668
    }
669
670
    public function testContainer(): void
671
    {
672
        $container = new Container();
673
        $container->set('user', ['name' => 'Alex']);
674
675
        $executionContext = new ExecutionContext(new Schema([
676
            'query' => new ObjectType([
677
                'name'   => 'RootQuery',
678
                'fields' => [
679
                    'currentUser' => [
680
                        'type'    => new StringType(),
681
                        'resolve' => static function ($source, $args, ResolveInfo $info) {
682
                            return $info->getContainer()->get('user')['name'];
683
                        },
684
                    ],
685
                ],
686
            ]),
687
        ]));
688
        $executionContext->setContainer($container);
689
        $this->assertNotNull($executionContext->getContainer());
690
691
        $processor = new Processor($executionContext->getSchema());
692
        $processor->getExecutionContext()->setContainer($container);
693
694
        $this->assertEquals(['data' => ['currentUser' => 'Alex']], $processor->processPayload('{ currentUser }')->getResponseData());
695
    }
696
697
    public function testComplexityReducer(): void
698
    {
699
        $schema = new Schema(
700
            [
701
                'query' => new ObjectType(
702
                    [
703
                        'name'   => 'RootQuery',
704
                        'fields' => [
705
                            'me' => [
706
                                'type' => new ObjectType(
707
                                    [
708
                                        'name'   => 'User',
709
                                        'fields' => [
710
                                            'firstName' => [
711
                                                'type' => new StringType(),
712
                                                'args' => [
713
                                                    'shorten' => new BooleanType(),
714
                                                ],
715
                                                'resolve' => static function ($value, $args) {
716
                                                    return empty($args['shorten']) ? $value['firstName'] : $value['firstName'];
717
                                                },
718
                                            ],
719
                                            'lastName' => new StringType(),
720
                                            'code'     => new StringType(),
721
                                            'likes'    => [
722
                                                'type'    => new IntType(),
723
                                                'cost'    => 10,
724
                                                'resolve' => static function () {
725
                                                    return 42;
726
                                                },
727
                                            ],
728
                                        ],
729
                                    ]
730
                                ),
731
                                'cost' => static function ($args, $context, $childCost) {
732
                                    $argsCost = $args['cost'] ?? 1;
733
734
                                    return 1 + $argsCost * $childCost;
735
                                },
736
                                'resolve' => static function ($value, $args) {
737
                                    $data = ['firstName' => 'John', 'code' => '007'];
738
739
                                    return $data;
740
                                },
741
                                'args' => [
742
                                    'cost' => [
743
                                        'type'    => new IntType(),
744
                                        'default' => 1,
745
                                    ],
746
                                ],
747
                            ],
748
                        ],
749
                    ]
750
                ),
751
            ]
752
        );
753
        $processor = new Processor($schema);
754
755
        $processor->setMaxComplexity(10);
756
757
        $processor->processPayload('{ me { firstName, lastName } }');
758
        $this->assertArrayNotHasKey('error', $processor->getResponseData());
759
760
        $processor->processPayload('{ me { } }');
761
        $this->assertEquals(['errors' => [[
762
            'message'   => 'Unexpected token "RBRACE"',
763
            'locations' => [
764
                [
765
                    'line'   => 1,
766
                    'column' => 10,
767
                ],
768
            ],
769
        ]]], $processor->getResponseData());
770
        $processor->getExecutionContext()->clearErrors();
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 } }';
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