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() |
|
|
|
|
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() |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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 } }'; |
|
|
|
|
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
|
|
|
|
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.