Completed
Push — master ( 0eb5d9...ce204c )
by David
14s
created

FieldsBuilderTest.php$2 ➔ testFailWith()   A

Complexity

Conditions 1

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 1
1
<?php
2
3
namespace TheCodingMachine\GraphQL\Controllers;
4
5
use Doctrine\Common\Annotations\AnnotationReader;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, TheCodingMachine\GraphQL...ollers\AnnotationReader. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
6
use GraphQL\Type\Definition\BooleanType;
7
use GraphQL\Type\Definition\FloatType;
8
use GraphQL\Type\Definition\IDType;
9
use GraphQL\Type\Definition\InputObjectType;
10
use GraphQL\Type\Definition\IntType;
11
use GraphQL\Type\Definition\ListOfType;
12
use GraphQL\Type\Definition\NonNull;
13
use GraphQL\Type\Definition\StringType;
14
use GraphQL\Type\Definition\ObjectType;
15
use GraphQL\Type\Definition\UnionType;
16
use Symfony\Component\Cache\Simple\ArrayCache;
17
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestController;
18
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerNoReturnType;
19
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithArrayParam;
20
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithArrayReturnType;
21
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithFailWith;
22
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithInputType;
0 ignored issues
show
Bug introduced by
The type TheCodingMachine\GraphQL...ControllerWithInputType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithInvalidInputType;
24
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithInvalidReturnType;
25
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithIterableParam;
26
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithIterableReturnType;
27
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestObject;
28
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestType;
29
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestTypeId;
30
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestTypeMissingAnnotation;
31
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestTypeMissingField;
32
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestTypeMissingReturnType;
33
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestTypeWithFailWith;
34
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestTypeWithSourceFieldInterface;
35
use TheCodingMachine\GraphQL\Controllers\Containers\EmptyContainer;
36
use TheCodingMachine\GraphQL\Controllers\Containers\BasicAutoWiringContainer;
37
use TheCodingMachine\GraphQL\Controllers\Mappers\CannotMapTypeException;
38
use TheCodingMachine\GraphQL\Controllers\Reflection\CachedDocBlockFactory;
39
use TheCodingMachine\GraphQL\Controllers\Security\AuthenticationServiceInterface;
40
use TheCodingMachine\GraphQL\Controllers\Security\AuthorizationServiceInterface;
41
use TheCodingMachine\GraphQL\Controllers\Security\VoidAuthenticationService;
42
use TheCodingMachine\GraphQL\Controllers\Security\VoidAuthorizationService;
43
use TheCodingMachine\GraphQL\Controllers\Annotations\Query;
44
use TheCodingMachine\GraphQL\Controllers\Types\DateTimeType;
45
use function var_dump;
46
47
class FieldsBuilderTest extends AbstractQueryProviderTest
48
{
49
    public function testQueryProvider()
50
    {
51
        $controller = new TestController();
52
53
        $queryProvider = $this->buildFieldsBuilder();
54
55
        $queries = $queryProvider->getQueries($controller);
56
57
        $this->assertCount(6, $queries);
58
        $usersQuery = $queries[0];
59
        $this->assertSame('test', $usersQuery->name);
60
61
        $this->assertCount(8, $usersQuery->args);
62
        $this->assertSame('int', $usersQuery->args[0]->name);
63
        $this->assertInstanceOf(NonNull::class, $usersQuery->args[0]->getType());
64
        $this->assertInstanceOf(IntType::class, $usersQuery->args[0]->getType()->getWrappedType());
65
        $this->assertInstanceOf(StringType::class, $usersQuery->args[7]->getType());
66
        $this->assertInstanceOf(NonNull::class, $usersQuery->args[1]->getType());
67
        $this->assertInstanceOf(ListOfType::class, $usersQuery->args[1]->getType()->getWrappedType());
68
        $this->assertInstanceOf(NonNull::class, $usersQuery->args[1]->getType()->getWrappedType()->getWrappedType());
69
        $this->assertInstanceOf(InputObjectType::class, $usersQuery->args[1]->getType()->getWrappedType()->getWrappedType()->getWrappedType());
70
        $this->assertInstanceOf(BooleanType::class, $usersQuery->args[2]->getType());
71
        $this->assertInstanceOf(FloatType::class, $usersQuery->args[3]->getType());
72
        $this->assertInstanceOf(DateTimeType::class, $usersQuery->args[4]->getType());
73
        $this->assertInstanceOf(DateTimeType::class, $usersQuery->args[5]->getType());
74
        $this->assertInstanceOf(StringType::class, $usersQuery->args[6]->getType());
75
        $this->assertSame('TestObjectInput', $usersQuery->args[1]->getType()->getWrappedType()->getWrappedType()->getWrappedType()->name);
76
77
        $context = ['int' => 42, 'string' => 'foo', 'list' => [
78
            ['test' => 42],
79
            ['test' => 12],
80
        ],
81
            'boolean' => true,
82
            'float' => 4.2,
83
            'dateTimeImmutable' => '2017-01-01 01:01:01',
84
            'dateTime' => '2017-01-01 01:01:01'
85
        ];
86
87
        $resolve = $usersQuery->resolveFn;
88
        $result = $resolve('foo', $context);
89
90
        $this->assertInstanceOf(TestObject::class, $result);
91
        $this->assertSame('foo424212true4.22017010101010120170101010101default', $result->getTest());
92
93
        unset($context['string']); // Testing null default value
94
        $result = $resolve('foo', $context);
95
96
        $this->assertSame('424212true4.22017010101010120170101010101default', $result->getTest());
97
    }
98
99
    public function testMutations()
100
    {
101
        $controller = new TestController();
102
103
        $queryProvider = $this->buildFieldsBuilder();
104
105
        $mutations = $queryProvider->getMutations($controller);
106
107
        $this->assertCount(1, $mutations);
108
        $mutation = $mutations[0];
109
        $this->assertSame('mutation', $mutation->name);
110
111
        $resolve = $mutation->resolveFn;
112
        $result = $resolve('foo', ['testObject' => ['test' => 42]]);
113
114
        $this->assertInstanceOf(TestObject::class, $result);
115
        $this->assertEquals('42', $result->getTest());
116
    }
117
118
    public function testErrors()
119
    {
120
        $controller = new class
121
        {
122
            /**
123
             * @Query
124
             * @return string
125
             */
126
            public function test($noTypeHint): string
0 ignored issues
show
Unused Code introduced by
The parameter $noTypeHint is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

126
            public function test(/** @scrutinizer ignore-unused */ $noTypeHint): string

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

Loading history...
127
            {
128
                return 'foo';
129
            }
130
        };
131
132
        $queryProvider = $this->buildFieldsBuilder();
133
134
        $this->expectException(MissingTypeHintException::class);
135
        $queryProvider->getQueries($controller);
136
    }
137
138
    public function testQueryProviderWithFixedReturnType()
139
    {
140
        $controller = new TestController();
141
142
        $queryProvider = $this->buildFieldsBuilder();
143
144
        $queries = $queryProvider->getQueries($controller);
145
146
        $this->assertCount(6, $queries);
147
        $fixedQuery = $queries[1];
148
149
        $this->assertInstanceOf(IDType::class, $fixedQuery->getType());
150
    }
151
152
    public function testNameFromAnnotation()
153
    {
154
        $controller = new TestController();
155
156
        $queryProvider = $this->buildFieldsBuilder();
157
158
        $queries = $queryProvider->getQueries($controller);
159
160
        $query = $queries[2];
161
162
        $this->assertSame('nameFromAnnotation', $query->name);
163
    }
164
165
    public function testSourceField()
166
    {
167
        $controller = new TestType();
168
169
        $queryProvider = $this->buildFieldsBuilder();
170
171
        $fields = $queryProvider->getFields($controller);
172
173
        $this->assertCount(3, $fields);
174
175
        $this->assertSame('customField', $fields['customField']->name);
176
        $this->assertSame('test', $fields['test']->name);
177
        // Test the "self" name resolution
178
        $this->assertSame('sibling', $fields['sibling']->name);
179
        $this->assertInstanceOf(NonNull::class, $fields['sibling']->getType());
180
        $this->assertInstanceOf(ObjectType::class, $fields['sibling']->getType()->getWrappedType());
181
        $this->assertSame('TestObject', $fields['sibling']->getType()->getWrappedType()->name);
182
    }
183
184
    public function testLoggedInSourceField()
185
    {
186
        $queryProvider = new FieldsBuilder(
187
            $this->getAnnotationReader(),
188
            $this->getTypeMapper(),
189
            $this->getHydrator(),
190
            new class implements AuthenticationServiceInterface {
191
                public function isLogged(): bool
192
                {
193
                    return true;
194
                }
195
            },
196
            new VoidAuthorizationService(),
197
            $this->getTypeResolver(),
198
            new CachedDocBlockFactory(new ArrayCache())
199
        );
200
201
        $fields = $queryProvider->getFields(new TestType());
202
        $this->assertCount(4, $fields);
203
204
        $this->assertSame('testBool', $fields['testBool']->name);
205
206
    }
207
208
    public function testRightInSourceField()
209
    {
210
        $queryProvider = new FieldsBuilder(
211
            $this->getAnnotationReader(),
212
            $this->getTypeMapper(),
213
            $this->getHydrator(),
214
            new VoidAuthenticationService(),
215
            new class implements AuthorizationServiceInterface {
216
                public function isAllowed(string $right): bool
217
                {
218
                    return true;
219
                }
220
            },
221
            $this->getTypeResolver(),
222
            new CachedDocBlockFactory(new ArrayCache())
223
        );
224
225
        $fields = $queryProvider->getFields(new TestType());
226
        $this->assertCount(4, $fields);
227
228
        $this->assertSame('testRight', $fields['testRight']->name);
229
230
    }
231
232
    public function testMissingTypeAnnotation()
233
    {
234
        $queryProvider = $this->buildFieldsBuilder();
235
236
        $this->expectException(MissingAnnotationException::class);
237
        $queryProvider->getFields(new TestTypeMissingAnnotation());
238
    }
239
240
    public function testSourceFieldDoesNotExists()
241
    {
242
        $queryProvider = $this->buildFieldsBuilder();
243
244
        $this->expectException(FieldNotFoundException::class);
245
        $this->expectExceptionMessage("There is an issue with a @SourceField annotation in class \"TheCodingMachine\GraphQL\Controllers\Fixtures\TestTypeMissingField\": Could not find a getter or a isser for field \"notExists\". Looked for: \"TheCodingMachine\GraphQL\Controllers\Fixtures\TestObject::notExists()\", \"TheCodingMachine\GraphQL\Controllers\Fixtures\TestObject::getNotExists()\", \"TheCodingMachine\GraphQL\Controllers\Fixtures\TestObject::isNotExists()");
246
        $queryProvider->getFields(new TestTypeMissingField());
247
    }
248
249
    public function testSourceFieldHasMissingReturnType()
250
    {
251
        $queryProvider = $this->buildFieldsBuilder();
252
253
        $this->expectException(TypeMappingException::class);
254
        $this->expectExceptionMessage("Return type in TheCodingMachine\\GraphQL\\Controllers\\Fixtures\\TestObjectMissingReturnType::getTest is missing a type-hint (or type-hinted to \"mixed\"). Please provide a better type-hint.");
255
        $queryProvider->getFields(new TestTypeMissingReturnType());
256
    }
257
258
    public function testSourceFieldIsId()
259
    {
260
        $queryProvider = $this->buildFieldsBuilder();
261
        $fields = $queryProvider->getFields(new TestTypeId());
262
        $this->assertCount(1, $fields);
263
264
        $this->assertSame('test', $fields['test']->name);
265
        $this->assertInstanceOf(NonNull::class, $fields['test']->getType());
266
        $this->assertInstanceOf(IDType::class, $fields['test']->getType()->getWrappedType());
267
    }
268
269
    public function testFromSourceFieldsInterface()
270
    {
271
        $queryProvider = new FieldsBuilder(
272
            $this->getAnnotationReader(),
273
            $this->getTypeMapper(),
274
            $this->getHydrator(),
275
            new VoidAuthenticationService(),
276
            new VoidAuthorizationService(),
277
            $this->getTypeResolver(),
278
            new CachedDocBlockFactory(new ArrayCache())
279
        );
280
        $fields = $queryProvider->getFields(new TestTypeWithSourceFieldInterface());
281
        $this->assertCount(1, $fields);
282
283
        $this->assertSame('test', $fields['test']->name);
284
285
    }
286
287
    public function testQueryProviderWithIterableClass()
288
    {
289
        $controller = new TestController();
290
291
        $queryProvider = $this->buildFieldsBuilder();
292
293
        $queries = $queryProvider->getQueries($controller);
294
295
        $this->assertCount(6, $queries);
296
        $iterableQuery = $queries[3];
297
298
        $this->assertInstanceOf(NonNull::class, $iterableQuery->getType());
299
        $this->assertInstanceOf(ListOfType::class, $iterableQuery->getType()->getWrappedType());
300
        $this->assertInstanceOf(NonNull::class, $iterableQuery->getType()->getWrappedType()->getWrappedType());
301
        $this->assertInstanceOf(ObjectType::class, $iterableQuery->getType()->getWrappedType()->getWrappedType()->getWrappedType());
302
        $this->assertSame('TestObject', $iterableQuery->getType()->getWrappedType()->getWrappedType()->getWrappedType()->name);
303
    }
304
305
    public function testQueryProviderWithIterable()
306
    {
307
        $queryProvider = $this->buildFieldsBuilder();
308
309
        $queries = $queryProvider->getQueries(new TestController());
310
311
        $this->assertCount(6, $queries);
312
        $iterableQuery = $queries[4];
313
314
        $this->assertInstanceOf(NonNull::class, $iterableQuery->getType());
315
        $this->assertInstanceOf(ListOfType::class, $iterableQuery->getType()->getWrappedType());
316
        $this->assertInstanceOf(NonNull::class, $iterableQuery->getType()->getWrappedType()->getWrappedType());
317
        $this->assertInstanceOf(ObjectType::class, $iterableQuery->getType()->getWrappedType()->getWrappedType()->getWrappedType());
318
        $this->assertSame('TestObject', $iterableQuery->getType()->getWrappedType()->getWrappedType()->getWrappedType()->name);
319
    }
320
321
    public function testNoReturnTypeError()
322
    {
323
        $queryProvider = $this->buildFieldsBuilder();
324
        $this->expectException(TypeMappingException::class);
325
        $queryProvider->getQueries(new TestControllerNoReturnType());
326
    }
327
328
    public function testQueryProviderWithUnion()
329
    {
330
        $controller = new TestController();
331
332
        $queryProvider = $this->buildFieldsBuilder();
333
334
        $queries = $queryProvider->getQueries($controller);
335
336
        $this->assertCount(6, $queries);
337
        $unionQuery = $queries[5];
338
339
        $this->assertInstanceOf(NonNull::class, $unionQuery->getType());
340
        $this->assertInstanceOf(UnionType::class, $unionQuery->getType()->getWrappedType());
341
        $this->assertInstanceOf(ObjectType::class, $unionQuery->getType()->getWrappedType()->getTypes()[0]);
342
        $this->assertSame('TestObject', $unionQuery->getType()->getWrappedType()->getTypes()[0]->name);
343
        $this->assertInstanceOf(ObjectType::class, $unionQuery->getType()->getWrappedType()->getTypes()[1]);
344
        $this->assertSame('TestObject2', $unionQuery->getType()->getWrappedType()->getTypes()[1]->name);
345
    }
346
347
    public function testQueryProviderWithInvalidInputType()
348
    {
349
        $controller = new TestControllerWithInvalidInputType();
350
351
        $queryProvider = $this->buildFieldsBuilder();
352
353
        $this->expectException(CannotMapTypeException::class);
354
        $this->expectExceptionMessage('For parameter $foo, in TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithInvalidInputType::test, cannot map class "Exception" to a known GraphQL input type. Check your TypeMapper configuration.');
355
        $queryProvider->getQueries($controller);
356
    }
357
358
    public function testQueryProviderWithInvalidReturnType()
359
    {
360
        $controller = new TestControllerWithInvalidReturnType();
361
362
        $queryProvider = $this->buildFieldsBuilder();
363
364
        $this->expectException(CannotMapTypeException::class);
365
        $this->expectExceptionMessage('For return type of TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithInvalidReturnType::test, cannot map class "Exception" to a known GraphQL type. Check your TypeMapper configuration.');
366
        $queryProvider->getQueries($controller);
367
    }
368
369
    public function testQueryProviderWithIterableReturnType()
370
    {
371
        $controller = new TestControllerWithIterableReturnType();
372
373
        $queryProvider = $this->buildFieldsBuilder();
374
375
        $this->expectException(TypeMappingException::class);
376
        $this->expectExceptionMessage('Return type in TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithIterableReturnType::test is type-hinted to "\ArrayObject", which is iterable. Please provide an additional @param in the PHPDoc block to further specify the type. For instance: @return \ArrayObject|User[]');
377
        $queryProvider->getQueries($controller);
378
    }
379
380
    public function testQueryProviderWithArrayReturnType()
381
    {
382
        $controller = new TestControllerWithArrayReturnType();
383
384
        $queryProvider = $this->buildFieldsBuilder();
385
386
        $this->expectException(TypeMappingException::class);
387
        $this->expectExceptionMessage('Return type in TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithArrayReturnType::test is type-hinted to array. Please provide an additional @return in the PHPDoc block to further specify the type of the array. For instance: @return string[]');
388
        $queryProvider->getQueries($controller);
389
    }
390
391
    public function testQueryProviderWithArrayParams()
392
    {
393
        $controller = new TestControllerWithArrayParam();
394
395
        $queryProvider = $this->buildFieldsBuilder();
396
397
        $this->expectException(TypeMappingException::class);
398
        $this->expectExceptionMessage('Parameter $params in TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithArrayParam::test is type-hinted to array. Please provide an additional @param in the PHPDoc block to further specify the type of the array. For instance: @param string[] $params.');
399
        $queryProvider->getQueries($controller);
400
    }
401
402
    public function testQueryProviderWithIterableParams()
403
    {
404
        $controller = new TestControllerWithIterableParam();
405
406
        $queryProvider = $this->buildFieldsBuilder();
407
408
        $this->expectException(TypeMappingException::class);
409
        $this->expectExceptionMessage('Parameter $params in TheCodingMachine\GraphQL\Controllers\Fixtures\TestControllerWithIterableParam::test is type-hinted to "\ArrayObject", which is iterable. Please provide an additional @param in the PHPDoc block to further specify the type. For instance: @param \ArrayObject|User[] $params.');
410
        $queryProvider->getQueries($controller);
411
    }
412
413
    public function testFailWith()
414
    {
415
        $controller = new TestControllerWithFailWith();
416
417
        $queryProvider = $this->buildFieldsBuilder();
418
419
        $queries = $queryProvider->getQueries($controller);
420
421
        $this->assertCount(1, $queries);
422
        $query = $queries[0];
423
        $this->assertSame('testFailWith', $query->name);
424
425
        $resolve = $query->resolveFn;
426
        $result = $resolve('foo', []);
427
428
        $this->assertNull($result);
429
430
        $this->assertInstanceOf(ObjectType::class, $query->getType());
431
    }
432
433
    public function testSourceFieldWithFailWith()
434
    {
435
        $controller = new TestTypeWithFailWith();
436
437
        $queryProvider = $this->buildFieldsBuilder();
438
439
        $fields = $queryProvider->getFields($controller);
440
441
        $this->assertCount(1, $fields);
442
443
        $this->assertSame('test', $fields['test']->name);
444
        $this->assertInstanceOf(StringType::class, $fields['test']->getType());
445
446
447
        $resolve = $fields['test']->resolveFn;
448
        $result = $resolve('foo', []);
449
450
        $this->assertNull($result);
451
452
        $this->assertInstanceOf(StringType::class, $fields['test']->getType());
453
    }
454
}
455