Completed
Push — master ( ab2a5d...d6b376 )
by David
17s
created

FieldsBuilderTest.php$2 ➔ testSourceFieldIsId()   A

Complexity

Conditions 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

123
            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...
124
            {
125
                return 'foo';
126
            }
127
        };
128
129
        $queryProvider = $this->buildFieldsBuilder();
130
131
        $this->expectException(MissingTypeHintException::class);
132
        $queryProvider->getQueries($controller);
133
    }
134
135
    public function testQueryProviderWithFixedReturnType()
136
    {
137
        $controller = new TestController();
138
139
        $queryProvider = $this->buildFieldsBuilder();
140
141
        $queries = $queryProvider->getQueries($controller);
142
143
        $this->assertCount(6, $queries);
144
        $fixedQuery = $queries[1];
145
146
        $this->assertInstanceOf(StringType::class, $fixedQuery->getType());
147
    }
148
149
    public function testNameFromAnnotation()
150
    {
151
        $controller = new TestController();
152
153
        $queryProvider = $this->buildFieldsBuilder();
154
155
        $queries = $queryProvider->getQueries($controller);
156
157
        $query = $queries[2];
158
159
        $this->assertSame('nameFromAnnotation', $query->name);
160
    }
161
162
    public function testSourceField()
163
    {
164
        $controller = new TestType($this->getRegistry());
0 ignored issues
show
Unused Code introduced by
The call to TheCodingMachine\GraphQL...TestType::__construct() has too many arguments starting with $this->getRegistry(). ( Ignorable by Annotation )

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

164
        $controller = /** @scrutinizer ignore-call */ new TestType($this->getRegistry());

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
165
166
        $queryProvider = $this->buildFieldsBuilder();
167
168
        $fields = $queryProvider->getFields($controller);
169
170
        $this->assertCount(3, $fields);
171
172
        $this->assertSame('customField', $fields['customField']->name);
173
        $this->assertSame('test', $fields['test']->name);
174
        // Test the "self" name resolution
175
        $this->assertSame('sibling', $fields['sibling']->name);
176
        $this->assertInstanceOf(NonNull::class, $fields['sibling']->getType());
177
        $this->assertInstanceOf(ObjectType::class, $fields['sibling']->getType()->getWrappedType());
178
        $this->assertSame('TestObject', $fields['sibling']->getType()->getWrappedType()->name);
179
    }
180
181
    public function testLoggedInSourceField()
182
    {
183
        $queryProvider = new FieldsBuilder(
184
            $this->getAnnotationReader(),
185
            $this->getTypeMapper(),
186
            $this->getHydrator(),
187
            new class implements AuthenticationServiceInterface {
188
                public function isLogged(): bool
189
                {
190
                    return true;
191
                }
192
            },
193
            new VoidAuthorizationService(),
194
            new EmptyContainer(),
195
            new CachedDocBlockFactory(new ArrayCache())
196
        );
197
198
        $fields = $queryProvider->getFields(new TestType());
199
        $this->assertCount(4, $fields);
200
201
        $this->assertSame('testBool', $fields['testBool']->name);
202
203
    }
204
205
    public function testRightInSourceField()
206
    {
207
        $queryProvider = new FieldsBuilder(
208
            $this->getAnnotationReader(),
209
            $this->getTypeMapper(),
210
            $this->getHydrator(),
211
            new VoidAuthenticationService(),
212
            new class implements AuthorizationServiceInterface {
213
                public function isAllowed(string $right): bool
214
                {
215
                    return true;
216
                }
217
            },new EmptyContainer(),
218
            new CachedDocBlockFactory(new ArrayCache())
219
        );
220
221
        $fields = $queryProvider->getFields(new TestType());
222
        $this->assertCount(4, $fields);
223
224
        $this->assertSame('testRight', $fields['testRight']->name);
225
226
    }
227
228
    public function testMissingTypeAnnotation()
229
    {
230
        $queryProvider = $this->buildFieldsBuilder();
231
232
        $this->expectException(MissingAnnotationException::class);
233
        $queryProvider->getFields(new TestTypeMissingAnnotation());
234
    }
235
236
    public function testSourceFieldDoesNotExists()
237
    {
238
        $queryProvider = $this->buildFieldsBuilder();
239
240
        $this->expectException(FieldNotFoundException::class);
241
        $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()");
242
        $queryProvider->getFields(new TestTypeMissingField());
243
    }
244
245
    public function testSourceFieldHasMissingReturnType()
246
    {
247
        $queryProvider = $this->buildFieldsBuilder();
248
249
        $this->expectException(TypeMappingException::class);
250
        $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.");
251
        $queryProvider->getFields(new TestTypeMissingReturnType());
252
    }
253
254
    public function testSourceFieldIsId()
255
    {
256
        $queryProvider = $this->buildFieldsBuilder();
257
        $fields = $queryProvider->getFields(new TestTypeId());
258
        $this->assertCount(1, $fields);
259
260
        $this->assertSame('test', $fields['test']->name);
261
        $this->assertInstanceOf(NonNull::class, $fields['test']->getType());
262
        $this->assertInstanceOf(IDType::class, $fields['test']->getType()->getWrappedType());
263
    }
264
265
    public function testFromSourceFieldsInterface()
266
    {
267
        $queryProvider = new FieldsBuilder(
268
            $this->getAnnotationReader(),
269
            $this->getTypeMapper(),
270
            $this->getHydrator(),
271
            new VoidAuthenticationService(),
272
            new VoidAuthorizationService(),
273
            new EmptyContainer(),
274
            new CachedDocBlockFactory(new ArrayCache())
275
        );
276
        $fields = $queryProvider->getFields(new TestTypeWithSourceFieldInterface());
277
        $this->assertCount(1, $fields);
278
279
        $this->assertSame('test', $fields['test']->name);
280
281
    }
282
283
    public function testQueryProviderWithIterableClass()
284
    {
285
        $controller = new TestController();
286
287
        $queryProvider = $this->buildFieldsBuilder();
288
289
        $queries = $queryProvider->getQueries($controller);
290
291
        $this->assertCount(6, $queries);
292
        $iterableQuery = $queries[3];
293
294
        $this->assertInstanceOf(NonNull::class, $iterableQuery->getType());
295
        $this->assertInstanceOf(ListOfType::class, $iterableQuery->getType()->getWrappedType());
296
        $this->assertInstanceOf(NonNull::class, $iterableQuery->getType()->getWrappedType()->getWrappedType());
297
        $this->assertInstanceOf(ObjectType::class, $iterableQuery->getType()->getWrappedType()->getWrappedType()->getWrappedType());
298
        $this->assertSame('TestObject', $iterableQuery->getType()->getWrappedType()->getWrappedType()->getWrappedType()->name);
299
    }
300
301
    public function testQueryProviderWithIterable()
302
    {
303
        $queryProvider = $this->buildFieldsBuilder();
304
305
        $queries = $queryProvider->getQueries(new TestController());
306
307
        $this->assertCount(6, $queries);
308
        $iterableQuery = $queries[4];
309
310
        $this->assertInstanceOf(NonNull::class, $iterableQuery->getType());
311
        $this->assertInstanceOf(ListOfType::class, $iterableQuery->getType()->getWrappedType());
312
        $this->assertInstanceOf(NonNull::class, $iterableQuery->getType()->getWrappedType()->getWrappedType());
313
        $this->assertInstanceOf(ObjectType::class, $iterableQuery->getType()->getWrappedType()->getWrappedType()->getWrappedType());
314
        $this->assertSame('TestObject', $iterableQuery->getType()->getWrappedType()->getWrappedType()->getWrappedType()->name);
315
    }
316
317
    public function testNoReturnTypeError()
318
    {
319
        $queryProvider = $this->buildFieldsBuilder();
320
        $this->expectException(TypeMappingException::class);
321
        $queryProvider->getQueries(new TestControllerNoReturnType());
322
    }
323
324
    public function testQueryProviderWithUnion()
325
    {
326
        $controller = new TestController();
327
328
        $queryProvider = $this->buildFieldsBuilder();
329
330
        $queries = $queryProvider->getQueries($controller);
331
332
        $this->assertCount(6, $queries);
333
        $unionQuery = $queries[5];
334
335
        $this->assertInstanceOf(NonNull::class, $unionQuery->getType());
336
        $this->assertInstanceOf(UnionType::class, $unionQuery->getType()->getWrappedType());
337
        $this->assertInstanceOf(ObjectType::class, $unionQuery->getType()->getWrappedType()->getTypes()[0]);
338
        $this->assertSame('TestObject', $unionQuery->getType()->getWrappedType()->getTypes()[0]->name);
339
        $this->assertInstanceOf(ObjectType::class, $unionQuery->getType()->getWrappedType()->getTypes()[1]);
340
        $this->assertSame('TestObject2', $unionQuery->getType()->getWrappedType()->getTypes()[1]->name);
341
    }
342
343
    public function testQueryProviderWithInvalidInputType()
344
    {
345
        $controller = new TestControllerWithInvalidInputType();
346
347
        $queryProvider = $this->buildFieldsBuilder();
348
349
        $this->expectException(CannotMapTypeException::class);
350
        $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.');
351
        $queryProvider->getQueries($controller);
352
    }
353
354
    public function testQueryProviderWithInvalidReturnType()
355
    {
356
        $controller = new TestControllerWithInvalidReturnType();
357
358
        $queryProvider = $this->buildFieldsBuilder();
359
360
        $this->expectException(CannotMapTypeException::class);
361
        $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.');
362
        $queryProvider->getQueries($controller);
363
    }
364
365
    public function testQueryProviderWithIterableReturnType()
366
    {
367
        $controller = new TestControllerWithIterableReturnType();
368
369
        $queryProvider = $this->buildFieldsBuilder();
370
371
        $this->expectException(TypeMappingException::class);
372
        $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[]');
373
        $queryProvider->getQueries($controller);
374
    }
375
376
    public function testQueryProviderWithArrayReturnType()
377
    {
378
        $controller = new TestControllerWithArrayReturnType();
379
380
        $queryProvider = $this->buildFieldsBuilder();
381
382
        $this->expectException(TypeMappingException::class);
383
        $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[]');
384
        $queryProvider->getQueries($controller);
385
    }
386
387
    public function testQueryProviderWithArrayParams()
388
    {
389
        $controller = new TestControllerWithArrayParam();
390
391
        $queryProvider = $this->buildFieldsBuilder();
392
393
        $this->expectException(TypeMappingException::class);
394
        $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.');
395
        $queryProvider->getQueries($controller);
396
    }
397
398
    public function testQueryProviderWithIterableParams()
399
    {
400
        $controller = new TestControllerWithIterableParam();
401
402
        $queryProvider = $this->buildFieldsBuilder();
403
404
        $this->expectException(TypeMappingException::class);
405
        $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.');
406
        $queryProvider->getQueries($controller);
407
    }
408
}
409