Passed
Pull Request — master (#52)
by David
01:37
created

php$2 ➔ testRightInSourceField()   A

Complexity

Conditions 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
cc 1

1 Method

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

120
            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...
121
            {
122
                return 'foo';
123
            }
124
        };
125
126
        $queryProvider = $this->buildControllerQueryProvider($controller);
127
128
        $this->expectException(MissingTypeHintException::class);
129
        $queryProvider->getQueries();
130
    }
131
132
    public function testQueryProviderWithFixedReturnType()
133
    {
134
        $controller = new TestController();
135
136
        $queryProvider = $this->buildControllerQueryProvider($controller);
137
138
        $queries = $queryProvider->getQueries();
139
140
        $this->assertCount(6, $queries);
141
        $fixedQuery = $queries[1];
142
143
        $this->assertInstanceOf(StringType::class, $fixedQuery->getType());
144
    }
145
146
    public function testNameFromAnnotation()
147
    {
148
        $controller = new TestController();
149
150
        $queryProvider = $this->buildControllerQueryProvider($controller);
151
152
        $queries = $queryProvider->getQueries();
153
154
        $query = $queries[2];
155
156
        $this->assertSame('nameFromAnnotation', $query->name);
157
    }
158
159
    public function testSourceField()
160
    {
161
        $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

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