1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace GraphQL\Tests\Executor; |
6
|
|
|
|
7
|
|
|
use Exception; |
8
|
|
|
use GraphQL\Deferred; |
9
|
|
|
use GraphQL\Error\FormattedError; |
10
|
|
|
use GraphQL\Error\UserError; |
11
|
|
|
use GraphQL\Executor\Executor; |
12
|
|
|
use GraphQL\GraphQL; |
13
|
|
|
use GraphQL\Language\Parser; |
14
|
|
|
use GraphQL\Language\SourceLocation; |
15
|
|
|
use GraphQL\Type\Definition\ObjectType; |
16
|
|
|
use GraphQL\Type\Definition\Type; |
17
|
|
|
use GraphQL\Type\Schema; |
18
|
|
|
use PHPUnit\Framework\ExpectationFailedException; |
19
|
|
|
use PHPUnit\Framework\TestCase; |
20
|
|
|
use function count; |
21
|
|
|
use function is_string; |
22
|
|
|
use function json_encode; |
23
|
|
|
|
24
|
|
|
class NonNullTest extends TestCase |
25
|
|
|
{ |
26
|
|
|
/** @var Exception */ |
27
|
|
|
public $syncError; |
28
|
|
|
|
29
|
|
|
/** @var Exception */ |
30
|
|
|
public $syncNonNullError; |
31
|
|
|
|
32
|
|
|
/** @var Exception */ |
33
|
|
|
public $promiseError; |
34
|
|
|
|
35
|
|
|
/** @var Exception */ |
36
|
|
|
public $promiseNonNullError; |
37
|
|
|
|
38
|
|
|
/** @var callable[] */ |
39
|
|
|
public $throwingData; |
40
|
|
|
|
41
|
|
|
/** @var callable[] */ |
42
|
|
|
public $nullingData; |
43
|
|
|
|
44
|
|
|
/** @var Schema */ |
45
|
|
|
public $schema; |
46
|
|
|
|
47
|
|
|
/** @var Schema */ |
48
|
|
|
public $schemaWithNonNullArg; |
49
|
|
|
|
50
|
|
|
public function setUp() |
51
|
|
|
{ |
52
|
|
|
$this->syncError = new UserError('sync'); |
53
|
|
|
$this->syncNonNullError = new UserError('syncNonNull'); |
54
|
|
|
$this->promiseError = new UserError('promise'); |
55
|
|
|
$this->promiseNonNullError = new UserError('promiseNonNull'); |
56
|
|
|
|
57
|
|
|
$this->throwingData = [ |
58
|
|
|
'sync' => function () { |
59
|
|
|
throw $this->syncError; |
60
|
|
|
}, |
61
|
|
|
'syncNonNull' => function () { |
62
|
|
|
throw $this->syncNonNullError; |
63
|
|
|
}, |
64
|
|
|
'promise' => function () { |
65
|
|
|
return new Deferred(function () { |
66
|
|
|
throw $this->promiseError; |
67
|
|
|
}); |
68
|
|
|
}, |
69
|
|
|
'promiseNonNull' => function () { |
70
|
|
|
return new Deferred(function () { |
71
|
|
|
throw $this->promiseNonNullError; |
72
|
|
|
}); |
73
|
|
|
}, |
74
|
|
|
'syncNest' => function () { |
75
|
|
|
return $this->throwingData; |
76
|
|
|
}, |
77
|
|
|
'syncNonNullNest' => function () { |
78
|
|
|
return $this->throwingData; |
79
|
|
|
}, |
80
|
|
|
'promiseNest' => function () { |
81
|
|
|
return new Deferred(function () { |
82
|
|
|
return $this->throwingData; |
83
|
|
|
}); |
84
|
|
|
}, |
85
|
|
|
'promiseNonNullNest' => function () { |
86
|
|
|
return new Deferred(function () { |
87
|
|
|
return $this->throwingData; |
88
|
|
|
}); |
89
|
|
|
}, |
90
|
|
|
]; |
91
|
|
|
|
92
|
|
|
$this->nullingData = [ |
93
|
|
|
'sync' => static function () { |
94
|
|
|
return null; |
95
|
|
|
}, |
96
|
|
|
'syncNonNull' => static function () { |
97
|
|
|
return null; |
98
|
|
|
}, |
99
|
|
|
'promise' => static function () { |
100
|
|
|
return new Deferred(static function () { |
101
|
|
|
return null; |
102
|
|
|
}); |
103
|
|
|
}, |
104
|
|
|
'promiseNonNull' => static function () { |
105
|
|
|
return new Deferred(static function () { |
106
|
|
|
return null; |
107
|
|
|
}); |
108
|
|
|
}, |
109
|
|
|
'syncNest' => function () { |
110
|
|
|
return $this->nullingData; |
111
|
|
|
}, |
112
|
|
|
'syncNonNullNest' => function () { |
113
|
|
|
return $this->nullingData; |
114
|
|
|
}, |
115
|
|
|
'promiseNest' => function () { |
116
|
|
|
return new Deferred(function () { |
117
|
|
|
return $this->nullingData; |
118
|
|
|
}); |
119
|
|
|
}, |
120
|
|
|
'promiseNonNullNest' => function () { |
121
|
|
|
return new Deferred(function () { |
122
|
|
|
return $this->nullingData; |
123
|
|
|
}); |
124
|
|
|
}, |
125
|
|
|
]; |
126
|
|
|
|
127
|
|
|
$dataType = new ObjectType([ |
128
|
|
|
'name' => 'DataType', |
129
|
|
|
'fields' => static function () use (&$dataType) { |
130
|
|
|
return [ |
131
|
|
|
'sync' => ['type' => Type::string()], |
132
|
|
|
'syncNonNull' => ['type' => Type::nonNull(Type::string())], |
133
|
|
|
'promise' => Type::string(), |
134
|
|
|
'promiseNonNull' => Type::nonNull(Type::string()), |
135
|
|
|
'syncNest' => $dataType, |
136
|
|
|
'syncNonNullNest' => Type::nonNull($dataType), |
137
|
|
|
'promiseNest' => $dataType, |
138
|
|
|
'promiseNonNullNest' => Type::nonNull($dataType), |
139
|
|
|
]; |
140
|
|
|
}, |
141
|
|
|
]); |
142
|
|
|
|
143
|
|
|
$this->schema = new Schema(['query' => $dataType]); |
144
|
|
|
|
145
|
|
|
$this->schemaWithNonNullArg = new Schema([ |
146
|
|
|
'query' => new ObjectType([ |
147
|
|
|
'name' => 'Query', |
148
|
|
|
'fields' => [ |
149
|
|
|
'withNonNullArg' => [ |
150
|
|
|
'type' => Type::string(), |
151
|
|
|
'args' => [ |
152
|
|
|
'cannotBeNull' => [ |
153
|
|
|
'type' => Type::nonNull(Type::string()), |
154
|
|
|
], |
155
|
|
|
], |
156
|
|
|
'resolve' => static function ($value, $args) { |
157
|
|
|
if (is_string($args['cannotBeNull'])) { |
158
|
|
|
return 'Passed: ' . $args['cannotBeNull']; |
159
|
|
|
} |
160
|
|
|
}, |
161
|
|
|
], |
162
|
|
|
], |
163
|
|
|
]), |
164
|
|
|
]); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
// Execute: handles non-nullable types |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* @see it('nulls a nullable field that throws synchronously') |
171
|
|
|
*/ |
172
|
|
|
public function testNullsANullableFieldThatThrowsSynchronously() : void |
173
|
|
|
{ |
174
|
|
|
$doc = ' |
175
|
|
|
query Q { |
176
|
|
|
sync |
177
|
|
|
} |
178
|
|
|
'; |
179
|
|
|
|
180
|
|
|
$ast = Parser::parse($doc); |
181
|
|
|
|
182
|
|
|
$expected = [ |
183
|
|
|
'data' => ['sync' => null], |
184
|
|
|
'errors' => [ |
185
|
|
|
FormattedError::create( |
|
|
|
|
186
|
|
|
$this->syncError->getMessage(), |
187
|
|
|
[new SourceLocation(3, 9)] |
188
|
|
|
), |
189
|
|
|
], |
190
|
|
|
]; |
191
|
|
|
self::assertArraySubset( |
|
|
|
|
192
|
|
|
$expected, |
193
|
|
|
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() |
|
|
|
|
194
|
|
|
); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
public function testNullsANullableFieldThatThrowsInAPromise() : void |
198
|
|
|
{ |
199
|
|
|
$doc = ' |
200
|
|
|
query Q { |
201
|
|
|
promise |
202
|
|
|
} |
203
|
|
|
'; |
204
|
|
|
|
205
|
|
|
$ast = Parser::parse($doc); |
206
|
|
|
|
207
|
|
|
$expected = [ |
208
|
|
|
'data' => ['promise' => null], |
209
|
|
|
'errors' => [ |
210
|
|
|
FormattedError::create( |
|
|
|
|
211
|
|
|
$this->promiseError->getMessage(), |
212
|
|
|
[new SourceLocation(3, 9)] |
213
|
|
|
), |
214
|
|
|
], |
215
|
|
|
]; |
216
|
|
|
|
217
|
|
|
self::assertArraySubset( |
|
|
|
|
218
|
|
|
$expected, |
219
|
|
|
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() |
220
|
|
|
); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously() : void |
224
|
|
|
{ |
225
|
|
|
// nulls a synchronously returned object that contains a non-nullable field that throws synchronously |
226
|
|
|
$doc = ' |
227
|
|
|
query Q { |
228
|
|
|
syncNest { |
229
|
|
|
syncNonNull, |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
'; |
233
|
|
|
|
234
|
|
|
$ast = Parser::parse($doc); |
235
|
|
|
|
236
|
|
|
$expected = [ |
237
|
|
|
'data' => ['syncNest' => null], |
238
|
|
|
'errors' => [ |
239
|
|
|
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]), |
|
|
|
|
240
|
|
|
], |
241
|
|
|
]; |
242
|
|
|
self::assertArraySubset( |
|
|
|
|
243
|
|
|
$expected, |
244
|
|
|
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() |
245
|
|
|
); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
public function testNullsAsynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsInAPromise() : void |
249
|
|
|
{ |
250
|
|
|
$doc = ' |
251
|
|
|
query Q { |
252
|
|
|
syncNest { |
253
|
|
|
promiseNonNull, |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
'; |
257
|
|
|
|
258
|
|
|
$ast = Parser::parse($doc); |
259
|
|
|
|
260
|
|
|
$expected = [ |
261
|
|
|
'data' => ['syncNest' => null], |
262
|
|
|
'errors' => [ |
263
|
|
|
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]), |
|
|
|
|
264
|
|
|
], |
265
|
|
|
]; |
266
|
|
|
|
267
|
|
|
self::assertArraySubset( |
|
|
|
|
268
|
|
|
$expected, |
269
|
|
|
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() |
270
|
|
|
); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsSynchronously() : void |
274
|
|
|
{ |
275
|
|
|
$doc = ' |
276
|
|
|
query Q { |
277
|
|
|
promiseNest { |
278
|
|
|
syncNonNull, |
279
|
|
|
} |
280
|
|
|
} |
281
|
|
|
'; |
282
|
|
|
|
283
|
|
|
$ast = Parser::parse($doc); |
284
|
|
|
|
285
|
|
|
$expected = [ |
286
|
|
|
'data' => ['promiseNest' => null], |
287
|
|
|
'errors' => [ |
288
|
|
|
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]), |
|
|
|
|
289
|
|
|
], |
290
|
|
|
]; |
291
|
|
|
|
292
|
|
|
self::assertArraySubset( |
|
|
|
|
293
|
|
|
$expected, |
294
|
|
|
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() |
295
|
|
|
); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsInAPromise() : void |
299
|
|
|
{ |
300
|
|
|
$doc = ' |
301
|
|
|
query Q { |
302
|
|
|
promiseNest { |
303
|
|
|
promiseNonNull, |
304
|
|
|
} |
305
|
|
|
} |
306
|
|
|
'; |
307
|
|
|
|
308
|
|
|
$ast = Parser::parse($doc); |
309
|
|
|
|
310
|
|
|
$expected = [ |
311
|
|
|
'data' => ['promiseNest' => null], |
312
|
|
|
'errors' => [ |
313
|
|
|
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]), |
|
|
|
|
314
|
|
|
], |
315
|
|
|
]; |
316
|
|
|
|
317
|
|
|
self::assertArraySubset( |
|
|
|
|
318
|
|
|
$expected, |
319
|
|
|
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() |
320
|
|
|
); |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* @see it('nulls a complex tree of nullable fields that throw') |
325
|
|
|
*/ |
326
|
|
|
public function testNullsAComplexTreeOfNullableFieldsThatThrow() : void |
327
|
|
|
{ |
328
|
|
|
$doc = ' |
329
|
|
|
query Q { |
330
|
|
|
syncNest { |
331
|
|
|
sync |
332
|
|
|
promise |
333
|
|
|
syncNest { |
334
|
|
|
sync |
335
|
|
|
promise |
336
|
|
|
} |
337
|
|
|
promiseNest { |
338
|
|
|
sync |
339
|
|
|
promise |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
promiseNest { |
343
|
|
|
sync |
344
|
|
|
promise |
345
|
|
|
syncNest { |
346
|
|
|
sync |
347
|
|
|
promise |
348
|
|
|
} |
349
|
|
|
promiseNest { |
350
|
|
|
sync |
351
|
|
|
promise |
352
|
|
|
} |
353
|
|
|
} |
354
|
|
|
} |
355
|
|
|
'; |
356
|
|
|
|
357
|
|
|
$ast = Parser::parse($doc); |
358
|
|
|
|
359
|
|
|
$expected = [ |
360
|
|
|
'data' => [ |
361
|
|
|
'syncNest' => [ |
362
|
|
|
'sync' => null, |
363
|
|
|
'promise' => null, |
364
|
|
|
'syncNest' => [ |
365
|
|
|
'sync' => null, |
366
|
|
|
'promise' => null, |
367
|
|
|
], |
368
|
|
|
'promiseNest' => [ |
369
|
|
|
'sync' => null, |
370
|
|
|
'promise' => null, |
371
|
|
|
], |
372
|
|
|
], |
373
|
|
|
'promiseNest' => [ |
374
|
|
|
'sync' => null, |
375
|
|
|
'promise' => null, |
376
|
|
|
'syncNest' => [ |
377
|
|
|
'sync' => null, |
378
|
|
|
'promise' => null, |
379
|
|
|
], |
380
|
|
|
'promiseNest' => [ |
381
|
|
|
'sync' => null, |
382
|
|
|
'promise' => null, |
383
|
|
|
], |
384
|
|
|
], |
385
|
|
|
], |
386
|
|
|
'errors' => [ |
387
|
|
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(4, 11)]), |
|
|
|
|
388
|
|
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(7, 13)]), |
|
|
|
|
389
|
|
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(11, 13)]), |
|
|
|
|
390
|
|
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(16, 11)]), |
|
|
|
|
391
|
|
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(19, 13)]), |
|
|
|
|
392
|
|
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(5, 11)]), |
|
|
|
|
393
|
|
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(8, 13)]), |
|
|
|
|
394
|
|
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(23, 13)]), |
|
|
|
|
395
|
|
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(12, 13)]), |
|
|
|
|
396
|
|
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(17, 11)]), |
|
|
|
|
397
|
|
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(20, 13)]), |
|
|
|
|
398
|
|
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(24, 13)]), |
|
|
|
|
399
|
|
|
], |
400
|
|
|
]; |
401
|
|
|
|
402
|
|
|
$result = Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray(); |
403
|
|
|
|
404
|
|
|
self::assertEquals($expected['data'], $result['data']); |
405
|
|
|
|
406
|
|
|
self::assertCount(count($expected['errors']), $result['errors']); |
407
|
|
|
foreach ($expected['errors'] as $expectedError) { |
408
|
|
|
$found = false; |
409
|
|
|
foreach ($result['errors'] as $error) { |
410
|
|
|
try { |
411
|
|
|
self::assertArraySubset($expectedError, $error); |
|
|
|
|
412
|
|
|
$found = true; |
413
|
|
|
break; |
414
|
|
|
} catch (ExpectationFailedException $e) { |
415
|
|
|
continue; |
416
|
|
|
} |
417
|
|
|
} |
418
|
|
|
self::assertTrue($found, 'Did not find error: ' . json_encode($expectedError)); |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
public function testNullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfFieldsThatAreNonNull() : void |
423
|
|
|
{ |
424
|
|
|
$doc = ' |
425
|
|
|
query Q { |
426
|
|
|
syncNest { |
427
|
|
|
syncNonNullNest { |
428
|
|
|
promiseNonNullNest { |
429
|
|
|
syncNonNullNest { |
430
|
|
|
promiseNonNullNest { |
431
|
|
|
syncNonNull |
432
|
|
|
} |
433
|
|
|
} |
434
|
|
|
} |
435
|
|
|
} |
436
|
|
|
} |
437
|
|
|
promiseNest { |
438
|
|
|
syncNonNullNest { |
439
|
|
|
promiseNonNullNest { |
440
|
|
|
syncNonNullNest { |
441
|
|
|
promiseNonNullNest { |
442
|
|
|
syncNonNull |
443
|
|
|
} |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
anotherNest: syncNest { |
449
|
|
|
syncNonNullNest { |
450
|
|
|
promiseNonNullNest { |
451
|
|
|
syncNonNullNest { |
452
|
|
|
promiseNonNullNest { |
453
|
|
|
promiseNonNull |
454
|
|
|
} |
455
|
|
|
} |
456
|
|
|
} |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
anotherPromiseNest: promiseNest { |
460
|
|
|
syncNonNullNest { |
461
|
|
|
promiseNonNullNest { |
462
|
|
|
syncNonNullNest { |
463
|
|
|
promiseNonNullNest { |
464
|
|
|
promiseNonNull |
465
|
|
|
} |
466
|
|
|
} |
467
|
|
|
} |
468
|
|
|
} |
469
|
|
|
} |
470
|
|
|
} |
471
|
|
|
'; |
472
|
|
|
|
473
|
|
|
$ast = Parser::parse($doc); |
474
|
|
|
|
475
|
|
|
$expected = [ |
476
|
|
|
'data' => [ |
477
|
|
|
'syncNest' => null, |
478
|
|
|
'promiseNest' => null, |
479
|
|
|
'anotherNest' => null, |
480
|
|
|
'anotherPromiseNest' => null, |
481
|
|
|
], |
482
|
|
|
'errors' => [ |
483
|
|
|
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(8, 19)]), |
|
|
|
|
484
|
|
|
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(19, 19)]), |
|
|
|
|
485
|
|
|
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(30, 19)]), |
|
|
|
|
486
|
|
|
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(41, 19)]), |
|
|
|
|
487
|
|
|
], |
488
|
|
|
]; |
489
|
|
|
|
490
|
|
|
self::assertArraySubset( |
|
|
|
|
491
|
|
|
$expected, |
492
|
|
|
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() |
493
|
|
|
); |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
public function testNullsANullableFieldThatSynchronouslyReturnsNull() : void |
497
|
|
|
{ |
498
|
|
|
$doc = ' |
499
|
|
|
query Q { |
500
|
|
|
sync |
501
|
|
|
} |
502
|
|
|
'; |
503
|
|
|
|
504
|
|
|
$ast = Parser::parse($doc); |
505
|
|
|
|
506
|
|
|
$expected = [ |
507
|
|
|
'data' => ['sync' => null], |
508
|
|
|
]; |
509
|
|
|
self::assertEquals( |
510
|
|
|
$expected, |
511
|
|
|
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray() |
512
|
|
|
); |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
public function testNullsANullableFieldThatReturnsNullInAPromise() : void |
516
|
|
|
{ |
517
|
|
|
$doc = ' |
518
|
|
|
query Q { |
519
|
|
|
promise |
520
|
|
|
} |
521
|
|
|
'; |
522
|
|
|
|
523
|
|
|
$ast = Parser::parse($doc); |
524
|
|
|
|
525
|
|
|
$expected = [ |
526
|
|
|
'data' => ['promise' => null], |
527
|
|
|
]; |
528
|
|
|
|
529
|
|
|
self::assertArraySubset( |
|
|
|
|
530
|
|
|
$expected, |
531
|
|
|
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray() |
532
|
|
|
); |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullSynchronously() : void |
536
|
|
|
{ |
537
|
|
|
$doc = ' |
538
|
|
|
query Q { |
539
|
|
|
syncNest { |
540
|
|
|
syncNonNull, |
541
|
|
|
} |
542
|
|
|
} |
543
|
|
|
'; |
544
|
|
|
|
545
|
|
|
$ast = Parser::parse($doc); |
546
|
|
|
|
547
|
|
|
$expected = [ |
548
|
|
|
'data' => ['syncNest' => null], |
549
|
|
|
'errors' => [ |
550
|
|
|
[ |
551
|
|
|
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', |
552
|
|
|
'locations' => [['line' => 4, 'column' => 11]], |
553
|
|
|
], |
554
|
|
|
], |
555
|
|
|
]; |
556
|
|
|
self::assertArraySubset( |
|
|
|
|
557
|
|
|
$expected, |
558
|
|
|
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) |
559
|
|
|
); |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise() : void |
563
|
|
|
{ |
564
|
|
|
$doc = ' |
565
|
|
|
query Q { |
566
|
|
|
syncNest { |
567
|
|
|
promiseNonNull, |
568
|
|
|
} |
569
|
|
|
} |
570
|
|
|
'; |
571
|
|
|
|
572
|
|
|
$ast = Parser::parse($doc); |
573
|
|
|
|
574
|
|
|
$expected = [ |
575
|
|
|
'data' => ['syncNest' => null], |
576
|
|
|
'errors' => [ |
577
|
|
|
[ |
578
|
|
|
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', |
579
|
|
|
'locations' => [['line' => 4, 'column' => 11]], |
580
|
|
|
], |
581
|
|
|
], |
582
|
|
|
]; |
583
|
|
|
|
584
|
|
|
self::assertArraySubset( |
|
|
|
|
585
|
|
|
$expected, |
586
|
|
|
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) |
587
|
|
|
); |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatReturnsNullSynchronously() : void |
591
|
|
|
{ |
592
|
|
|
$doc = ' |
593
|
|
|
query Q { |
594
|
|
|
promiseNest { |
595
|
|
|
syncNonNull, |
596
|
|
|
} |
597
|
|
|
} |
598
|
|
|
'; |
599
|
|
|
|
600
|
|
|
$ast = Parser::parse($doc); |
601
|
|
|
|
602
|
|
|
$expected = [ |
603
|
|
|
'data' => ['promiseNest' => null], |
604
|
|
|
'errors' => [ |
605
|
|
|
[ |
606
|
|
|
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', |
607
|
|
|
'locations' => [['line' => 4, 'column' => 11]], |
608
|
|
|
], |
609
|
|
|
], |
610
|
|
|
]; |
611
|
|
|
|
612
|
|
|
self::assertArraySubset( |
|
|
|
|
613
|
|
|
$expected, |
614
|
|
|
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) |
615
|
|
|
); |
616
|
|
|
} |
617
|
|
|
|
618
|
|
|
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatReturnsNullInaAPromise() : void |
619
|
|
|
{ |
620
|
|
|
$doc = ' |
621
|
|
|
query Q { |
622
|
|
|
promiseNest { |
623
|
|
|
promiseNonNull, |
624
|
|
|
} |
625
|
|
|
} |
626
|
|
|
'; |
627
|
|
|
|
628
|
|
|
$ast = Parser::parse($doc); |
629
|
|
|
|
630
|
|
|
$expected = [ |
631
|
|
|
'data' => ['promiseNest' => null], |
632
|
|
|
'errors' => [ |
633
|
|
|
[ |
634
|
|
|
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', |
635
|
|
|
'locations' => [['line' => 4, 'column' => 11]], |
636
|
|
|
], |
637
|
|
|
], |
638
|
|
|
]; |
639
|
|
|
|
640
|
|
|
self::assertArraySubset( |
|
|
|
|
641
|
|
|
$expected, |
642
|
|
|
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) |
643
|
|
|
); |
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
public function testNullsAComplexTreeOfNullableFieldsThatReturnNull() : void |
647
|
|
|
{ |
648
|
|
|
$doc = ' |
649
|
|
|
query Q { |
650
|
|
|
syncNest { |
651
|
|
|
sync |
652
|
|
|
promise |
653
|
|
|
syncNest { |
654
|
|
|
sync |
655
|
|
|
promise |
656
|
|
|
} |
657
|
|
|
promiseNest { |
658
|
|
|
sync |
659
|
|
|
promise |
660
|
|
|
} |
661
|
|
|
} |
662
|
|
|
promiseNest { |
663
|
|
|
sync |
664
|
|
|
promise |
665
|
|
|
syncNest { |
666
|
|
|
sync |
667
|
|
|
promise |
668
|
|
|
} |
669
|
|
|
promiseNest { |
670
|
|
|
sync |
671
|
|
|
promise |
672
|
|
|
} |
673
|
|
|
} |
674
|
|
|
} |
675
|
|
|
'; |
676
|
|
|
|
677
|
|
|
$ast = Parser::parse($doc); |
678
|
|
|
|
679
|
|
|
$expected = [ |
680
|
|
|
'data' => [ |
681
|
|
|
'syncNest' => [ |
682
|
|
|
'sync' => null, |
683
|
|
|
'promise' => null, |
684
|
|
|
'syncNest' => [ |
685
|
|
|
'sync' => null, |
686
|
|
|
'promise' => null, |
687
|
|
|
], |
688
|
|
|
'promiseNest' => [ |
689
|
|
|
'sync' => null, |
690
|
|
|
'promise' => null, |
691
|
|
|
], |
692
|
|
|
], |
693
|
|
|
'promiseNest' => [ |
694
|
|
|
'sync' => null, |
695
|
|
|
'promise' => null, |
696
|
|
|
'syncNest' => [ |
697
|
|
|
'sync' => null, |
698
|
|
|
'promise' => null, |
699
|
|
|
], |
700
|
|
|
'promiseNest' => [ |
701
|
|
|
'sync' => null, |
702
|
|
|
'promise' => null, |
703
|
|
|
], |
704
|
|
|
], |
705
|
|
|
], |
706
|
|
|
]; |
707
|
|
|
|
708
|
|
|
$actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(); |
709
|
|
|
self::assertEquals($expected, $actual); |
710
|
|
|
} |
711
|
|
|
|
712
|
|
|
/** |
713
|
|
|
* @see it('nulls the first nullable object after a field in a long chain of non-null fields') |
714
|
|
|
*/ |
715
|
|
|
public function testNullsTheFirstNullableObjectAfterAFieldReturnsNullInALongChainOfFieldsThatAreNonNull() : void |
716
|
|
|
{ |
717
|
|
|
$doc = ' |
718
|
|
|
query Q { |
719
|
|
|
syncNest { |
720
|
|
|
syncNonNullNest { |
721
|
|
|
promiseNonNullNest { |
722
|
|
|
syncNonNullNest { |
723
|
|
|
promiseNonNullNest { |
724
|
|
|
syncNonNull |
725
|
|
|
} |
726
|
|
|
} |
727
|
|
|
} |
728
|
|
|
} |
729
|
|
|
} |
730
|
|
|
promiseNest { |
731
|
|
|
syncNonNullNest { |
732
|
|
|
promiseNonNullNest { |
733
|
|
|
syncNonNullNest { |
734
|
|
|
promiseNonNullNest { |
735
|
|
|
syncNonNull |
736
|
|
|
} |
737
|
|
|
} |
738
|
|
|
} |
739
|
|
|
} |
740
|
|
|
} |
741
|
|
|
anotherNest: syncNest { |
742
|
|
|
syncNonNullNest { |
743
|
|
|
promiseNonNullNest { |
744
|
|
|
syncNonNullNest { |
745
|
|
|
promiseNonNullNest { |
746
|
|
|
promiseNonNull |
747
|
|
|
} |
748
|
|
|
} |
749
|
|
|
} |
750
|
|
|
} |
751
|
|
|
} |
752
|
|
|
anotherPromiseNest: promiseNest { |
753
|
|
|
syncNonNullNest { |
754
|
|
|
promiseNonNullNest { |
755
|
|
|
syncNonNullNest { |
756
|
|
|
promiseNonNullNest { |
757
|
|
|
promiseNonNull |
758
|
|
|
} |
759
|
|
|
} |
760
|
|
|
} |
761
|
|
|
} |
762
|
|
|
} |
763
|
|
|
} |
764
|
|
|
'; |
765
|
|
|
|
766
|
|
|
$ast = Parser::parse($doc); |
767
|
|
|
|
768
|
|
|
$expected = [ |
769
|
|
|
'data' => [ |
770
|
|
|
'syncNest' => null, |
771
|
|
|
'promiseNest' => null, |
772
|
|
|
'anotherNest' => null, |
773
|
|
|
'anotherPromiseNest' => null, |
774
|
|
|
], |
775
|
|
|
'errors' => [ |
776
|
|
|
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 8, 'column' => 19]]], |
777
|
|
|
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 19, 'column' => 19]]], |
778
|
|
|
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 30, 'column' => 19]]], |
779
|
|
|
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 41, 'column' => 19]]], |
780
|
|
|
], |
781
|
|
|
]; |
782
|
|
|
|
783
|
|
|
self::assertArraySubset( |
|
|
|
|
784
|
|
|
$expected, |
785
|
|
|
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) |
786
|
|
|
); |
787
|
|
|
} |
788
|
|
|
|
789
|
|
|
/** |
790
|
|
|
* @see it('nulls the top level if non-nullable field') |
791
|
|
|
*/ |
792
|
|
|
public function testNullsTheTopLevelIfSyncNonNullableFieldThrows() : void |
793
|
|
|
{ |
794
|
|
|
$doc = ' |
795
|
|
|
query Q { syncNonNull } |
796
|
|
|
'; |
797
|
|
|
|
798
|
|
|
$expected = [ |
799
|
|
|
'errors' => [ |
800
|
|
|
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(2, 17)]), |
|
|
|
|
801
|
|
|
], |
802
|
|
|
]; |
803
|
|
|
$actual = Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray(); |
804
|
|
|
self::assertArraySubset($expected, $actual); |
|
|
|
|
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
/** |
808
|
|
|
* @see describe('Handles non-null argument') |
809
|
|
|
* @see it('succeeds when passed non-null literal value') |
810
|
|
|
*/ |
811
|
|
|
public function succeedsWhenPassedNonNullLiteralValue() : void |
812
|
|
|
{ |
813
|
|
|
$result = Executor::execute( |
814
|
|
|
$this->schemaWithNonNullArg, |
815
|
|
|
Parser::parse(' |
816
|
|
|
query { |
817
|
|
|
withNonNullArg (cannotBeNull: "literal value") |
818
|
|
|
} |
819
|
|
|
') |
820
|
|
|
); |
821
|
|
|
|
822
|
|
|
$expected = ['data' => ['withNonNullArg' => 'Passed: literal value']]; |
823
|
|
|
self::assertEquals($expected, $result->toArray()); |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
/** |
827
|
|
|
* @see it('succeeds when passed non-null variable value') |
828
|
|
|
*/ |
829
|
|
|
public function succeedsWhenPassedNonNullVariableValue() |
830
|
|
|
{ |
831
|
|
|
$result = Executor::execute( |
832
|
|
|
$this->schemaWithNonNullArg, |
833
|
|
|
Parser::parse(' |
834
|
|
|
query ($testVar: String!) { |
835
|
|
|
withNonNullArg (cannotBeNull: $testVar) |
836
|
|
|
} |
837
|
|
|
'), |
838
|
|
|
null, |
839
|
|
|
null, |
840
|
|
|
['testVar' => 'variable value'] |
841
|
|
|
); |
842
|
|
|
|
843
|
|
|
$expected = ['data' => ['withNonNullArg' => 'Passed: variable value']]; |
844
|
|
|
self::assertEquals($expected, $result->toArray()); |
845
|
|
|
} |
846
|
|
|
|
847
|
|
|
/** |
848
|
|
|
* @see it('succeeds when missing variable has default value') |
849
|
|
|
*/ |
850
|
|
|
public function testSucceedsWhenMissingVariableHasDefaultValue() |
851
|
|
|
{ |
852
|
|
|
$result = Executor::execute( |
853
|
|
|
$this->schemaWithNonNullArg, |
854
|
|
|
Parser::parse(' |
855
|
|
|
query ($testVar: String = "default value") { |
856
|
|
|
withNonNullArg (cannotBeNull: $testVar) |
857
|
|
|
} |
858
|
|
|
'), |
859
|
|
|
null, |
860
|
|
|
null, |
861
|
|
|
[] // Intentionally missing variable |
862
|
|
|
); |
863
|
|
|
|
864
|
|
|
$expected = ['data' => ['withNonNullArg' => 'Passed: default value']]; |
865
|
|
|
self::assertEquals($expected, $result->toArray()); |
866
|
|
|
} |
867
|
|
|
|
868
|
|
|
/** |
869
|
|
|
* @see it('field error when missing non-null arg') |
870
|
|
|
*/ |
871
|
|
|
public function testFieldErrorWhenMissingNonNullArg() |
872
|
|
|
{ |
873
|
|
|
// Note: validation should identify this issue first (missing args rule) |
874
|
|
|
// however execution should still protect against this. |
875
|
|
|
$result = Executor::execute( |
876
|
|
|
$this->schemaWithNonNullArg, |
877
|
|
|
Parser::parse(' |
878
|
|
|
query { |
879
|
|
|
withNonNullArg |
880
|
|
|
} |
881
|
|
|
') |
882
|
|
|
); |
883
|
|
|
|
884
|
|
|
$expected = [ |
885
|
|
|
'data' => ['withNonNullArg' => null], |
886
|
|
|
'errors' => [ |
887
|
|
|
[ |
888
|
|
|
'message' => 'Argument "cannotBeNull" of required type "String!" was not provided.', |
889
|
|
|
'locations' => [['line' => 3, 'column' => 13]], |
890
|
|
|
'path' => ['withNonNullArg'], |
891
|
|
|
'extensions' => ['category' => 'graphql'], |
892
|
|
|
], |
893
|
|
|
], |
894
|
|
|
]; |
895
|
|
|
self::assertEquals($expected, $result->toArray()); |
896
|
|
|
} |
897
|
|
|
|
898
|
|
|
/** |
899
|
|
|
* @see it('field error when non-null arg provided null') |
900
|
|
|
*/ |
901
|
|
|
public function testFieldErrorWhenNonNullArgProvidedNull() |
902
|
|
|
{ |
903
|
|
|
// Note: validation should identify this issue first (values of correct |
904
|
|
|
// type rule) however execution should still protect against this. |
905
|
|
|
$result = Executor::execute( |
906
|
|
|
$this->schemaWithNonNullArg, |
907
|
|
|
Parser::parse(' |
908
|
|
|
query { |
909
|
|
|
withNonNullArg(cannotBeNull: null) |
910
|
|
|
} |
911
|
|
|
') |
912
|
|
|
); |
913
|
|
|
|
914
|
|
|
$expected = [ |
915
|
|
|
'data' => ['withNonNullArg' => null], |
916
|
|
|
'errors' => [ |
917
|
|
|
[ |
918
|
|
|
'message' => 'Argument "cannotBeNull" of non-null type "String!" must not be null.', |
919
|
|
|
'locations' => [['line' => 3, 'column' => 13]], |
920
|
|
|
'path' => ['withNonNullArg'], |
921
|
|
|
'extensions' => ['category' => 'graphql'], |
922
|
|
|
], |
923
|
|
|
], |
924
|
|
|
]; |
925
|
|
|
self::assertEquals($expected, $result->toArray()); |
926
|
|
|
} |
927
|
|
|
|
928
|
|
|
/** |
929
|
|
|
* @see it('field error when non-null arg not provided variable value') |
930
|
|
|
*/ |
931
|
|
|
public function testFieldErrorWhenNonNullArgNotProvidedVariableValue() : void |
932
|
|
|
{ |
933
|
|
|
// Note: validation should identify this issue first (variables in allowed |
934
|
|
|
// position rule) however execution should still protect against this. |
935
|
|
|
$result = Executor::execute( |
936
|
|
|
$this->schemaWithNonNullArg, |
937
|
|
|
Parser::parse(' |
938
|
|
|
query ($testVar: String) { |
939
|
|
|
withNonNullArg(cannotBeNull: $testVar) |
940
|
|
|
} |
941
|
|
|
'), |
942
|
|
|
null, |
943
|
|
|
null, |
944
|
|
|
[] // Intentionally missing variable |
945
|
|
|
); |
946
|
|
|
|
947
|
|
|
$expected = [ |
948
|
|
|
'data' => ['withNonNullArg' => null], |
949
|
|
|
'errors' => [ |
950
|
|
|
[ |
951
|
|
|
'message' => 'Argument "cannotBeNull" of required type "String!" was ' . |
952
|
|
|
'provided the variable "$testVar" which was not provided a ' . |
953
|
|
|
'runtime value.', |
954
|
|
|
'locations' => [['line' => 3, 'column' => 42]], |
955
|
|
|
'path' => ['withNonNullArg'], |
956
|
|
|
'extensions' => ['category' => 'graphql'], |
957
|
|
|
], |
958
|
|
|
], |
959
|
|
|
]; |
960
|
|
|
self::assertEquals($expected, $result->toArray()); |
961
|
|
|
} |
962
|
|
|
|
963
|
|
|
/** |
964
|
|
|
* @see it('field error when non-null arg provided variable with explicit null value') |
965
|
|
|
*/ |
966
|
|
|
public function testFieldErrorWhenNonNullArgProvidedVariableWithExplicitNullValue() |
967
|
|
|
{ |
968
|
|
|
$result = Executor::execute( |
969
|
|
|
$this->schemaWithNonNullArg, |
970
|
|
|
Parser::parse(' |
971
|
|
|
query ($testVar: String = "default value") { |
972
|
|
|
withNonNullArg (cannotBeNull: $testVar) |
973
|
|
|
} |
974
|
|
|
'), |
975
|
|
|
null, |
976
|
|
|
null, |
977
|
|
|
['testVar' => null] |
978
|
|
|
); |
979
|
|
|
|
980
|
|
|
$expected = [ |
981
|
|
|
'data' => ['withNonNullArg' => null], |
982
|
|
|
'errors' => [ |
983
|
|
|
[ |
984
|
|
|
'message' => 'Argument "cannotBeNull" of non-null type "String!" must not be null.', |
985
|
|
|
'locations' => [['line' => 3, 'column' => 13]], |
986
|
|
|
'path' => ['withNonNullArg'], |
987
|
|
|
'extensions' => ['category' => 'graphql'], |
988
|
|
|
], |
989
|
|
|
], |
990
|
|
|
]; |
991
|
|
|
|
992
|
|
|
self::assertEquals($expected, $result->toArray()); |
993
|
|
|
} |
994
|
|
|
|
995
|
|
|
public function testNullsTheTopLevelIfAsyncNonNullableFieldErrors() : void |
996
|
|
|
{ |
997
|
|
|
$doc = ' |
998
|
|
|
query Q { promiseNonNull } |
999
|
|
|
'; |
1000
|
|
|
|
1001
|
|
|
$ast = Parser::parse($doc); |
1002
|
|
|
|
1003
|
|
|
$expected = [ |
1004
|
|
|
'errors' => [ |
1005
|
|
|
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(2, 17)]), |
|
|
|
|
1006
|
|
|
], |
1007
|
|
|
]; |
1008
|
|
|
|
1009
|
|
|
self::assertArraySubset( |
|
|
|
|
1010
|
|
|
$expected, |
1011
|
|
|
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() |
1012
|
|
|
); |
1013
|
|
|
} |
1014
|
|
|
|
1015
|
|
|
public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() : void |
1016
|
|
|
{ |
1017
|
|
|
// nulls the top level if sync non-nullable field returns null |
1018
|
|
|
$doc = ' |
1019
|
|
|
query Q { syncNonNull } |
1020
|
|
|
'; |
1021
|
|
|
|
1022
|
|
|
$expected = [ |
1023
|
|
|
'errors' => [ |
1024
|
|
|
[ |
1025
|
|
|
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', |
1026
|
|
|
'locations' => [['line' => 2, 'column' => 17]], |
1027
|
|
|
], |
1028
|
|
|
], |
1029
|
|
|
]; |
1030
|
|
|
self::assertArraySubset( |
|
|
|
|
1031
|
|
|
$expected, |
1032
|
|
|
Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray(true) |
1033
|
|
|
); |
1034
|
|
|
} |
1035
|
|
|
|
1036
|
|
|
public function testNullsTheTopLevelIfAsyncNonNullableFieldResolvesNull() : void |
1037
|
|
|
{ |
1038
|
|
|
$doc = ' |
1039
|
|
|
query Q { promiseNonNull } |
1040
|
|
|
'; |
1041
|
|
|
|
1042
|
|
|
$ast = Parser::parse($doc); |
1043
|
|
|
|
1044
|
|
|
$expected = [ |
1045
|
|
|
'errors' => [ |
1046
|
|
|
[ |
1047
|
|
|
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', |
1048
|
|
|
'locations' => [['line' => 2, 'column' => 17]], |
1049
|
|
|
], |
1050
|
|
|
], |
1051
|
|
|
]; |
1052
|
|
|
|
1053
|
|
|
self::assertArraySubset( |
|
|
|
|
1054
|
|
|
$expected, |
1055
|
|
|
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) |
1056
|
|
|
); |
1057
|
|
|
} |
1058
|
|
|
} |
1059
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.