thecodingmachine /
graphql-controllers
| 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. Loading history...
|
|||||
| 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. Loading history...
|
|||||
| 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. Loading history...
|
|||||
| 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. Loading history...
|
|||||
| 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. Loading history...
|
|||||
| 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.