1 | <?php |
||||
2 | |||||
3 | namespace TheCodingMachine\GraphQL\Controllers\Integration; |
||||
4 | |||||
5 | use function class_exists; |
||||
6 | use Doctrine\Common\Annotations\AnnotationReader as DoctrineAnnotationReader; |
||||
7 | use Exception; |
||||
8 | use GraphQL\Error\Debug; |
||||
9 | use GraphQL\GraphQL; |
||||
10 | use GraphQL\Type\Definition\InputType; |
||||
11 | use Mouf\Picotainer\Picotainer; |
||||
12 | use PhpParser\Comment\Doc; |
||||
13 | use PHPUnit\Framework\TestCase; |
||||
14 | use PHPUnit\Util\Type; |
||||
15 | use function print_r; |
||||
16 | use Psr\Container\ContainerInterface; |
||||
17 | use Psr\Container\NotFoundExceptionInterface; |
||||
18 | use Symfony\Component\Cache\Simple\ArrayCache; |
||||
19 | use TheCodingMachine\GraphQL\Controllers\AnnotationReader; |
||||
20 | use TheCodingMachine\GraphQL\Controllers\FieldsBuilderFactory; |
||||
21 | use TheCodingMachine\GraphQL\Controllers\Fixtures\Integration\Models\Contact; |
||||
22 | use TheCodingMachine\GraphQL\Controllers\GlobControllerQueryProvider; |
||||
23 | use TheCodingMachine\GraphQL\Controllers\Hydrators\HydratorInterface; |
||||
24 | use TheCodingMachine\GraphQL\Controllers\Hydrators\FactoryHydrator; |
||||
25 | use TheCodingMachine\GraphQL\Controllers\InputTypeGenerator; |
||||
26 | use TheCodingMachine\GraphQL\Controllers\InputTypeUtils; |
||||
27 | use TheCodingMachine\GraphQL\Controllers\Mappers\CompositeTypeMapper; |
||||
28 | use TheCodingMachine\GraphQL\Controllers\Mappers\GlobTypeMapper; |
||||
29 | use TheCodingMachine\GraphQL\Controllers\Mappers\PorpaginasTypeMapper; |
||||
30 | use TheCodingMachine\GraphQL\Controllers\Mappers\RecursiveTypeMapper; |
||||
31 | use TheCodingMachine\GraphQL\Controllers\Mappers\RecursiveTypeMapperInterface; |
||||
32 | use TheCodingMachine\GraphQL\Controllers\Mappers\TypeMapperInterface; |
||||
33 | use TheCodingMachine\GraphQL\Controllers\NamingStrategy; |
||||
34 | use TheCodingMachine\GraphQL\Controllers\NamingStrategyInterface; |
||||
35 | use TheCodingMachine\GraphQL\Controllers\QueryProviderInterface; |
||||
36 | use TheCodingMachine\GraphQL\Controllers\Containers\BasicAutoWiringContainer; |
||||
37 | use TheCodingMachine\GraphQL\Controllers\Containers\EmptyContainer; |
||||
38 | use TheCodingMachine\GraphQL\Controllers\Reflection\CachedDocBlockFactory; |
||||
39 | use TheCodingMachine\GraphQL\Controllers\Schema; |
||||
40 | use TheCodingMachine\GraphQL\Controllers\Security\AuthenticationServiceInterface; |
||||
41 | use TheCodingMachine\GraphQL\Controllers\Security\AuthorizationServiceInterface; |
||||
42 | use TheCodingMachine\GraphQL\Controllers\Security\VoidAuthenticationService; |
||||
43 | use TheCodingMachine\GraphQL\Controllers\Security\VoidAuthorizationService; |
||||
44 | use TheCodingMachine\GraphQL\Controllers\TypeGenerator; |
||||
45 | use TheCodingMachine\GraphQL\Controllers\TypeRegistry; |
||||
46 | use TheCodingMachine\GraphQL\Controllers\Types\TypeResolver; |
||||
47 | use function var_dump; |
||||
48 | use function var_export; |
||||
49 | |||||
50 | class EndToEndTest extends TestCase |
||||
51 | { |
||||
52 | /** |
||||
53 | * @var ContainerInterface |
||||
54 | */ |
||||
55 | private $mainContainer; |
||||
56 | |||||
57 | public function setUp() |
||||
58 | { |
||||
59 | $this->mainContainer = new Picotainer([ |
||||
60 | Schema::class => function(ContainerInterface $container) { |
||||
61 | return new Schema($container->get(QueryProviderInterface::class), $container->get(RecursiveTypeMapperInterface::class), $container->get(TypeResolver::class)); |
||||
62 | }, |
||||
63 | QueryProviderInterface::class => function(ContainerInterface $container) { |
||||
64 | return new GlobControllerQueryProvider('TheCodingMachine\\GraphQL\\Controllers\\Fixtures\\Integration\\Controllers', $container->get(FieldsBuilderFactory::class), |
||||
65 | $container->get(RecursiveTypeMapperInterface::class), $container->get(BasicAutoWiringContainer::class), new ArrayCache()); |
||||
66 | }, |
||||
67 | FieldsBuilderFactory::class => function(ContainerInterface $container) { |
||||
68 | return new FieldsBuilderFactory( |
||||
69 | $container->get(AnnotationReader::class), |
||||
70 | $container->get(HydratorInterface::class), |
||||
71 | $container->get(AuthenticationServiceInterface::class), |
||||
72 | $container->get(AuthorizationServiceInterface::class), |
||||
73 | $container->get(TypeResolver::class), |
||||
74 | $container->get(CachedDocBlockFactory::class), |
||||
75 | $container->get(NamingStrategyInterface::class) |
||||
76 | ); |
||||
77 | }, |
||||
78 | TypeResolver::class => function(ContainerInterface $container) { |
||||
0 ignored issues
–
show
|
|||||
79 | return new TypeResolver(); |
||||
80 | }, |
||||
81 | BasicAutoWiringContainer::class => function(ContainerInterface $container) { |
||||
0 ignored issues
–
show
The parameter
$container 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
82 | return new BasicAutoWiringContainer(new EmptyContainer()); |
||||
83 | }, |
||||
84 | AuthorizationServiceInterface::class => function(ContainerInterface $container) { |
||||
0 ignored issues
–
show
The parameter
$container 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
85 | return new VoidAuthorizationService(); |
||||
86 | }, |
||||
87 | AuthenticationServiceInterface::class => function(ContainerInterface $container) { |
||||
0 ignored issues
–
show
The parameter
$container 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
88 | return new VoidAuthenticationService(); |
||||
89 | }, |
||||
90 | RecursiveTypeMapperInterface::class => function(ContainerInterface $container) { |
||||
91 | return new RecursiveTypeMapper( |
||||
92 | $container->get(TypeMapperInterface::class), |
||||
93 | $container->get(NamingStrategyInterface::class), |
||||
94 | new ArrayCache(), |
||||
95 | $container->get(TypeRegistry::class) |
||||
96 | ); |
||||
97 | }, |
||||
98 | TypeMapperInterface::class => function(ContainerInterface $container) { |
||||
99 | return new CompositeTypeMapper([ |
||||
100 | $container->get(GlobTypeMapper::class), |
||||
101 | $container->get(GlobTypeMapper::class.'2'), |
||||
102 | $container->get(PorpaginasTypeMapper::class), |
||||
103 | ]); |
||||
104 | }, |
||||
105 | GlobTypeMapper::class => function(ContainerInterface $container) { |
||||
106 | return new GlobTypeMapper('TheCodingMachine\\GraphQL\\Controllers\\Fixtures\\Integration\\Types', |
||||
107 | $container->get(TypeGenerator::class), |
||||
108 | $container->get(InputTypeGenerator::class), |
||||
109 | $container->get(InputTypeUtils::class), |
||||
110 | $container->get(BasicAutoWiringContainer::class), |
||||
111 | $container->get(AnnotationReader::class), |
||||
112 | $container->get(NamingStrategyInterface::class), |
||||
113 | new ArrayCache() |
||||
114 | ); |
||||
115 | }, |
||||
116 | GlobTypeMapper::class.'2' => function(ContainerInterface $container) { |
||||
117 | return new GlobTypeMapper('TheCodingMachine\\GraphQL\\Controllers\\Fixtures\\Integration\\Models', |
||||
118 | $container->get(TypeGenerator::class), |
||||
119 | $container->get(InputTypeGenerator::class), |
||||
120 | $container->get(InputTypeUtils::class), |
||||
121 | $container->get(BasicAutoWiringContainer::class), |
||||
122 | $container->get(AnnotationReader::class), |
||||
123 | $container->get(NamingStrategyInterface::class), |
||||
124 | new ArrayCache() |
||||
125 | ); |
||||
126 | }, |
||||
127 | PorpaginasTypeMapper::class => function() { |
||||
128 | return new PorpaginasTypeMapper(); |
||||
129 | }, |
||||
130 | TypeGenerator::class => function(ContainerInterface $container) { |
||||
131 | return new TypeGenerator( |
||||
132 | $container->get(AnnotationReader::class), |
||||
133 | $container->get(FieldsBuilderFactory::class), |
||||
134 | $container->get(NamingStrategyInterface::class), |
||||
135 | $container->get(TypeRegistry::class), |
||||
136 | $container->get(BasicAutoWiringContainer::class) |
||||
137 | ); |
||||
138 | }, |
||||
139 | TypeRegistry::class => function() { |
||||
140 | return new TypeRegistry(); |
||||
141 | }, |
||||
142 | InputTypeGenerator::class => function(ContainerInterface $container) { |
||||
143 | return new InputTypeGenerator( |
||||
144 | $container->get(InputTypeUtils::class), |
||||
145 | $container->get(FieldsBuilderFactory::class), |
||||
146 | $container->get(HydratorInterface::class) |
||||
147 | ); |
||||
148 | }, |
||||
149 | InputTypeUtils::class => function(ContainerInterface $container) { |
||||
150 | return new InputTypeUtils( |
||||
151 | $container->get(AnnotationReader::class), |
||||
152 | $container->get(NamingStrategyInterface::class) |
||||
153 | ); |
||||
154 | }, |
||||
155 | AnnotationReader::class => function(ContainerInterface $container) { |
||||
0 ignored issues
–
show
The parameter
$container 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
156 | return new AnnotationReader(new DoctrineAnnotationReader()); |
||||
157 | }, |
||||
158 | HydratorInterface::class => function(ContainerInterface $container) { |
||||
0 ignored issues
–
show
The parameter
$container 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
159 | return new FactoryHydrator(); |
||||
160 | }, |
||||
161 | NamingStrategyInterface::class => function() { |
||||
162 | return new NamingStrategy(); |
||||
163 | }, |
||||
164 | CachedDocBlockFactory::class => function() { |
||||
165 | return new CachedDocBlockFactory(new ArrayCache()); |
||||
166 | } |
||||
167 | ]); |
||||
168 | |||||
169 | $this->mainContainer->get(TypeResolver::class)->registerSchema($this->mainContainer->get(Schema::class)); |
||||
170 | } |
||||
171 | |||||
172 | public function testEndToEnd() |
||||
173 | { |
||||
174 | /** |
||||
175 | * @var Schema $schema |
||||
176 | */ |
||||
177 | $schema = $this->mainContainer->get(Schema::class); |
||||
178 | |||||
179 | $schema->assertValid(); |
||||
180 | |||||
181 | $queryString = ' |
||||
182 | query { |
||||
183 | contacts { |
||||
184 | name |
||||
185 | uppercaseName |
||||
186 | ... on User { |
||||
187 | |||||
188 | } |
||||
189 | } |
||||
190 | } |
||||
191 | '; |
||||
192 | |||||
193 | $result = GraphQL::executeQuery( |
||||
194 | $schema, |
||||
195 | $queryString |
||||
196 | ); |
||||
197 | |||||
198 | $this->assertSame([ |
||||
199 | 'contacts' => [ |
||||
200 | [ |
||||
201 | 'name' => 'Joe', |
||||
202 | 'uppercaseName' => 'JOE' |
||||
203 | ], |
||||
204 | [ |
||||
205 | 'name' => 'Bill', |
||||
206 | 'uppercaseName' => 'BILL', |
||||
207 | 'email' => '[email protected]' |
||||
208 | ] |
||||
209 | |||||
210 | ] |
||||
211 | ], $result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']); |
||||
212 | |||||
213 | // Let's redo this to test cache. |
||||
214 | $result = GraphQL::executeQuery( |
||||
215 | $schema, |
||||
216 | $queryString |
||||
217 | ); |
||||
218 | |||||
219 | $this->assertSame([ |
||||
220 | 'contacts' => [ |
||||
221 | [ |
||||
222 | 'name' => 'Joe', |
||||
223 | 'uppercaseName' => 'JOE' |
||||
224 | ], |
||||
225 | [ |
||||
226 | 'name' => 'Bill', |
||||
227 | 'uppercaseName' => 'BILL', |
||||
228 | 'email' => '[email protected]' |
||||
229 | ] |
||||
230 | |||||
231 | ] |
||||
232 | ], $result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']); |
||||
233 | } |
||||
234 | |||||
235 | public function testEndToEndInputType() |
||||
236 | { |
||||
237 | /** |
||||
238 | * @var Schema $schema |
||||
239 | */ |
||||
240 | $schema = $this->mainContainer->get(Schema::class); |
||||
241 | $queryString = ' |
||||
242 | mutation { |
||||
243 | saveContact( |
||||
244 | contact: { |
||||
245 | name: "foo", |
||||
246 | birthDate: "1942-12-24 00:00:00", |
||||
247 | relations: [ |
||||
248 | { |
||||
249 | name: "bar" |
||||
250 | birthDate: "1942-12-24 00:00:00", |
||||
251 | } |
||||
252 | ] |
||||
253 | } |
||||
254 | ) { |
||||
255 | name, |
||||
256 | birthDate, |
||||
257 | relations { |
||||
258 | name, |
||||
259 | birthDate |
||||
260 | } |
||||
261 | } |
||||
262 | } |
||||
263 | '; |
||||
264 | |||||
265 | $result = GraphQL::executeQuery( |
||||
266 | $schema, |
||||
267 | $queryString |
||||
268 | ); |
||||
269 | |||||
270 | $this->assertSame([ |
||||
271 | 'saveContact' => [ |
||||
272 | 'name' => 'foo', |
||||
273 | 'birthDate' => '1942-12-24T00:00:00+00:00', |
||||
274 | 'relations' => [ |
||||
275 | [ |
||||
276 | 'name' => 'bar', |
||||
277 | 'birthDate' => '1942-12-24T00:00:00+00:00' |
||||
278 | ] |
||||
279 | ] |
||||
280 | ] |
||||
281 | ], $result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']); |
||||
282 | } |
||||
283 | |||||
284 | public function testEndToEndPorpaginas() |
||||
285 | { |
||||
286 | /** |
||||
287 | * @var Schema $schema |
||||
288 | */ |
||||
289 | $schema = $this->mainContainer->get(Schema::class); |
||||
290 | |||||
291 | $queryString = ' |
||||
292 | query { |
||||
293 | contactsIterator { |
||||
294 | items(limit: 1, offset: 1) { |
||||
295 | name |
||||
296 | uppercaseName |
||||
297 | ... on User { |
||||
298 | |||||
299 | } |
||||
300 | } |
||||
301 | count |
||||
302 | } |
||||
303 | } |
||||
304 | '; |
||||
305 | |||||
306 | $result = GraphQL::executeQuery( |
||||
307 | $schema, |
||||
308 | $queryString |
||||
309 | ); |
||||
310 | |||||
311 | $this->assertSame([ |
||||
312 | 'contactsIterator' => [ |
||||
313 | 'items' => [ |
||||
314 | [ |
||||
315 | 'name' => 'Bill', |
||||
316 | 'uppercaseName' => 'BILL', |
||||
317 | 'email' => '[email protected]' |
||||
318 | ] |
||||
319 | ], |
||||
320 | 'count' => 2 |
||||
321 | ] |
||||
322 | ], $result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']); |
||||
323 | |||||
324 | // Let's redo this to test cache. |
||||
325 | $result = GraphQL::executeQuery( |
||||
326 | $schema, |
||||
327 | $queryString |
||||
328 | ); |
||||
329 | |||||
330 | $this->assertSame([ |
||||
331 | 'contactsIterator' => [ |
||||
332 | 'items' => [ |
||||
333 | [ |
||||
334 | 'name' => 'Bill', |
||||
335 | 'uppercaseName' => 'BILL', |
||||
336 | 'email' => '[email protected]' |
||||
337 | ] |
||||
338 | ], |
||||
339 | 'count' => 2 |
||||
340 | ] |
||||
341 | ], $result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']); |
||||
342 | |||||
343 | // Let's run a query with no limit but an offset |
||||
344 | $invalidQueryString = ' |
||||
345 | query { |
||||
346 | contactsIterator { |
||||
347 | items(offset: 1) { |
||||
348 | name |
||||
349 | ... on User { |
||||
350 | |||||
351 | } |
||||
352 | } |
||||
353 | count |
||||
354 | } |
||||
355 | } |
||||
356 | '; |
||||
357 | |||||
358 | $result = GraphQL::executeQuery( |
||||
359 | $schema, |
||||
360 | $invalidQueryString |
||||
361 | ); |
||||
362 | |||||
363 | $this->assertSame('In the items field of a result set, you cannot add a "offset" without also adding a "limit"', $result->toArray(Debug::RETHROW_UNSAFE_EXCEPTIONS)['errors'][0]['message']); |
||||
364 | |||||
365 | |||||
366 | // Let's run a query with no limit offset |
||||
367 | $invalidQueryString = ' |
||||
368 | query { |
||||
369 | contactsIterator { |
||||
370 | items { |
||||
371 | name |
||||
372 | ... on User { |
||||
373 | |||||
374 | } |
||||
375 | } |
||||
376 | count |
||||
377 | } |
||||
378 | } |
||||
379 | '; |
||||
380 | |||||
381 | $result = GraphQL::executeQuery( |
||||
382 | $schema, |
||||
383 | $invalidQueryString |
||||
384 | ); |
||||
385 | |||||
386 | $this->assertSame([ |
||||
387 | 'contactsIterator' => [ |
||||
388 | 'items' => [ |
||||
389 | [ |
||||
390 | 'name' => 'Joe', |
||||
391 | ], |
||||
392 | [ |
||||
393 | 'name' => 'Bill', |
||||
394 | 'email' => '[email protected]' |
||||
395 | ] |
||||
396 | ], |
||||
397 | 'count' => 2 |
||||
398 | ] |
||||
399 | ], $result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']); |
||||
400 | } |
||||
401 | |||||
402 | /** |
||||
403 | * This tests is used to be sure that the PorpaginasIterator types are not mixed up when cached (because it has a subtype) |
||||
404 | */ |
||||
405 | public function testEndToEnd2Iterators() |
||||
406 | { |
||||
407 | /** |
||||
408 | * @var Schema $schema |
||||
409 | */ |
||||
410 | $schema = $this->mainContainer->get(Schema::class); |
||||
411 | |||||
412 | $queryString = ' |
||||
413 | query { |
||||
414 | contactsIterator { |
||||
415 | items(limit: 1, offset: 1) { |
||||
416 | name |
||||
417 | uppercaseName |
||||
418 | ... on User { |
||||
419 | |||||
420 | } |
||||
421 | } |
||||
422 | count |
||||
423 | } |
||||
424 | |||||
425 | products { |
||||
426 | items { |
||||
427 | name |
||||
428 | price |
||||
429 | } |
||||
430 | count |
||||
431 | } |
||||
432 | } |
||||
433 | '; |
||||
434 | |||||
435 | $result = GraphQL::executeQuery( |
||||
436 | $schema, |
||||
437 | $queryString |
||||
438 | ); |
||||
439 | |||||
440 | $this->assertSame([ |
||||
441 | 'contactsIterator' => [ |
||||
442 | 'items' => [ |
||||
443 | [ |
||||
444 | 'name' => 'Bill', |
||||
445 | 'uppercaseName' => 'BILL', |
||||
446 | 'email' => '[email protected]' |
||||
447 | ] |
||||
448 | ], |
||||
449 | 'count' => 2 |
||||
450 | ], |
||||
451 | 'products' => [ |
||||
452 | 'items' => [ |
||||
453 | [ |
||||
454 | 'name' => 'Foo', |
||||
455 | 'price' => 42.0, |
||||
456 | ] |
||||
457 | ], |
||||
458 | 'count' => 1 |
||||
459 | ] |
||||
460 | ], $result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']); |
||||
461 | |||||
462 | } |
||||
463 | } |
||||
464 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.