Merge pull request #338 from simPod/cs-executor-test

Fix CS in tests/Executor
This commit is contained in:
Vladimir Razuvaev 2018-09-02 21:20:54 +07:00 committed by GitHub
commit 7f99bf478f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 3033 additions and 2662 deletions

View File

@ -1,79 +1,83 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\UserError; use GraphQL\Error\UserError;
use GraphQL\Error\Warning;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Human;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
require_once __DIR__ . '/TestClasses.php';
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/**
* DESCRIBE: Execute: Handles execution of abstract types with promises
*/
class AbstractPromiseTest extends TestCase class AbstractPromiseTest extends TestCase
{ {
// DESCRIBE: Execute: Handles execution of abstract types with promises
/** /**
* @see it('isTypeOf used to resolve runtime type for Interface') * @see it('isTypeOf used to resolve runtime type for Interface')
*/ */
public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void
{ {
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [ $PetType ], 'interfaces' => [$PetType],
'isTypeOf' => function($obj) { 'isTypeOf' => function ($obj) {
return new Deferred(function() use ($obj) { return new Deferred(function () use ($obj) {
return $obj instanceof Dog; return $obj instanceof Dog;
}); });
}, },
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ], 'name' => ['type' => Type::string()],
'woofs' => [ 'type' => Type::boolean() ], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [ $PetType ], 'interfaces' => [$PetType],
'isTypeOf' => function($obj) { 'isTypeOf' => function ($obj) {
return new Deferred(function() use ($obj) { return new Deferred(function () use ($obj) {
return $obj instanceof Cat; return $obj instanceof Cat;
}); });
}, },
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ], 'name' => ['type' => Type::string()],
'meows' => [ 'type' => Type::boolean() ], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function() { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [ $CatType, $DogType ] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -93,10 +97,10 @@ class AbstractPromiseTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [ 'pets' => [
[ 'name' => 'Odie', 'woofs' => true ], ['name' => 'Odie', 'woofs' => true],
[ 'name' => 'Garfield', 'meows' => false ] ['name' => 'Garfield', 'meows' => false],
] ],
] ],
]; ];
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
@ -107,58 +111,57 @@ class AbstractPromiseTest extends TestCase
*/ */
public function testIsTypeOfCanBeRejected() : void public function testIsTypeOfCanBeRejected() : void
{ {
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'isTypeOf' => function () { 'isTypeOf' => function () {
return new Deferred(function () { return new Deferred(function () {
throw new UserError('We are testing this error'); throw new UserError('We are testing this error');
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'isTypeOf' => function ($obj) { 'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) { return new Deferred(function () use ($obj) {
return $obj instanceof Cat; return $obj instanceof Cat;
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function () { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [$CatType, $DogType] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -176,21 +179,21 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(); $result = GraphQL::executeQuery($schema, $query)->toArray();
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [null, null] 'pets' => [null, null],
], ],
'errors' => [ 'errors' => [
[ [
'message' => 'We are testing this error', 'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 0], 'path' => ['pets', 0],
], ],
[ [
'message' => 'We are testing this error', 'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 1] 'path' => ['pets', 1],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
@ -201,50 +204,49 @@ class AbstractPromiseTest extends TestCase
*/ */
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
{ {
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'isTypeOf' => function ($obj) { 'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) { return new Deferred(function () use ($obj) {
return $obj instanceof Dog; return $obj instanceof Dog;
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'isTypeOf' => function ($obj) { 'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) { return new Deferred(function () use ($obj) {
return $obj instanceof Cat; return $obj instanceof Cat;
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$PetType = new UnionType([ $PetType = new UnionType([
'name' => 'Pet', 'name' => 'Pet',
'types' => [$DogType, $CatType] 'types' => [$DogType, $CatType],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function () { 'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)]; return [new Dog('Odie', true), new Cat('Garfield', false)];
} },
] ],
] ],
]) ]),
]); ]);
$query = '{ $query = '{
@ -266,9 +268,9 @@ class AbstractPromiseTest extends TestCase
'data' => [ 'data' => [
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false] ['name' => 'Garfield', 'meows' => false],
] ],
] ],
]; ];
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
@ -280,7 +282,7 @@ class AbstractPromiseTest extends TestCase
public function testResolveTypeOnInterfaceYieldsUsefulError() : void public function testResolveTypeOnInterfaceYieldsUsefulError() : void
{ {
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) { 'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) { return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
if ($obj instanceof Dog) { if ($obj instanceof Dog) {
@ -292,58 +294,59 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) { if ($obj instanceof Human) {
return $HumanType; return $HumanType;
} }
return null; return null;
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$HumanType = new ObjectType([ $HumanType = new ObjectType([
'name' => 'Human', 'name' => 'Human',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function () { 'resolve' => function () {
return new Deferred(function () { return new Deferred(function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false), new Cat('Garfield', false),
new Human('Jon') new Human('Jon'),
]; ];
}); });
} },
] ],
] ],
]), ]),
'types' => [$CatType, $DogType] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -361,20 +364,20 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(true); $result = GraphQL::executeQuery($schema, $query)->toArray(true);
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false], ['name' => 'Garfield', 'meows' => false],
null null,
] ],
], ],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 7]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2] 'path' => ['pets', 2],
], ],
] ],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
@ -385,32 +388,31 @@ class AbstractPromiseTest extends TestCase
*/ */
public function testResolveTypeOnUnionYieldsUsefulError() : void public function testResolveTypeOnUnionYieldsUsefulError() : void
{ {
$HumanType = new ObjectType([ $HumanType = new ObjectType([
'name' => 'Human', 'name' => 'Human',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$PetType = new UnionType([ $PetType = new UnionType([
'name' => 'Pet', 'name' => 'Pet',
'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) { 'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) {
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) { return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
if ($obj instanceof Dog) { if ($obj instanceof Dog) {
@ -422,28 +424,29 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) { if ($obj instanceof Human) {
return $HumanType; return $HumanType;
} }
return null; return null;
}); });
}, },
'types' => [$DogType, $CatType] 'types' => [$DogType, $CatType],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function () { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false), new Cat('Garfield', false),
new Human('Jon') new Human('Jon'),
]; ];
} },
] ],
] ],
]) ]),
]); ]);
$query = '{ $query = '{
@ -462,20 +465,20 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(true); $result = GraphQL::executeQuery($schema, $query)->toArray(true);
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false], ['name' => 'Garfield', 'meows' => false],
null null,
] ],
], ],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 7]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2] 'path' => ['pets', 2],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
@ -487,7 +490,7 @@ class AbstractPromiseTest extends TestCase
public function testResolveTypeAllowsResolvingWithTypeName() : void public function testResolveTypeAllowsResolvingWithTypeName() : void
{ {
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'resolveType' => function ($obj) { 'resolveType' => function ($obj) {
return new Deferred(function () use ($obj) { return new Deferred(function () use ($obj) {
if ($obj instanceof Dog) { if ($obj instanceof Dog) {
@ -496,49 +499,49 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Cat) { if ($obj instanceof Cat) {
return 'Cat'; return 'Cat';
} }
return null; return null;
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function () { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [$CatType, $DogType] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -560,8 +563,8 @@ class AbstractPromiseTest extends TestCase
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false], ['name' => 'Garfield', 'meows' => false],
] ],
] ],
]; ];
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
} }
@ -571,53 +574,52 @@ class AbstractPromiseTest extends TestCase
*/ */
public function testResolveTypeCanBeCaught() : void public function testResolveTypeCanBeCaught() : void
{ {
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'resolveType' => function () { 'resolveType' => function () {
return new Deferred(function () { return new Deferred(function () {
throw new UserError('We are testing this error'); throw new UserError('We are testing this error');
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function () { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [$CatType, $DogType] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -635,21 +637,21 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(); $result = GraphQL::executeQuery($schema, $query)->toArray();
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [null, null] 'pets' => [null, null],
], ],
'errors' => [ 'errors' => [
[ [
'message' => 'We are testing this error', 'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 0] 'path' => ['pets', 0],
], ],
[ [
'message' => 'We are testing this error', 'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 1] 'path' => ['pets', 1],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);

View File

@ -1,23 +1,28 @@
<?php <?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php'; declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Human;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/**
* Execute: Handles execution of abstract types
*/
class AbstractTest extends TestCase class AbstractTest extends TestCase
{ {
// Execute: Handles execution of abstract types
/** /**
* @see it('isTypeOf used to resolve runtime type for Interface') * @see it('isTypeOf used to resolve runtime type for Interface')
*/ */
@ -25,48 +30,50 @@ class AbstractTest extends TestCase
{ {
// isTypeOf used to resolve runtime type for Interface // isTypeOf used to resolve runtime type for Interface
$petType = new InterfaceType([ $petType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
// Added to interface type when defined // Added to interface type when defined
$dogType = new ObjectType([ $dogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$petType], 'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; }, 'isTypeOf' => function ($obj) {
'fields' => [ return $obj instanceof Dog;
'name' => ['type' => Type::string()], },
'woofs' => ['type' => Type::boolean()] 'fields' => [
] 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]); ]);
$catType = new ObjectType([ $catType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$petType], 'interfaces' => [$petType],
'isTypeOf' => function ($obj) { 'isTypeOf' => function ($obj) {
return $obj instanceof Cat; return $obj instanceof Cat;
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($petType), 'type' => Type::listOf($petType),
'resolve' => function () { 'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)]; return [new Dog('Odie', true), new Cat('Garfield', false)];
} },
] ],
] ],
]), ]),
'types' => [$catType, $dogType] 'types' => [$catType, $dogType],
]); ]);
$query = '{ $query = '{
@ -84,8 +91,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([ $expected = new ExecutionResult([
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false] ['name' => 'Garfield', 'meows' => false],
] ],
]); ]);
$result = Executor::execute($schema, Parser::parse($query)); $result = Executor::execute($schema, Parser::parse($query));
@ -98,42 +105,44 @@ class AbstractTest extends TestCase
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
{ {
$dogType = new ObjectType([ $dogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'isTypeOf' => function($obj) { return $obj instanceof Dog; }, 'isTypeOf' => function ($obj) {
'fields' => [ return $obj instanceof Dog;
'name' => ['type' => Type::string()], },
'woofs' => ['type' => Type::boolean()] 'fields' => [
] 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]); ]);
$catType = new ObjectType([ $catType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'isTypeOf' => function ($obj) { 'isTypeOf' => function ($obj) {
return $obj instanceof Cat; return $obj instanceof Cat;
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$petType = new UnionType([ $petType = new UnionType([
'name' => 'Pet', 'name' => 'Pet',
'types' => [$dogType, $catType] 'types' => [$dogType, $catType],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($petType), 'type' => Type::listOf($petType),
'resolve' => function() { 'resolve' => function () {
return [ new Dog('Odie', true), new Cat('Garfield', false) ]; return [new Dog('Odie', true), new Cat('Garfield', false)];
} },
] ],
] ],
]) ]),
]); ]);
$query = '{ $query = '{
@ -151,8 +160,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([ $expected = new ExecutionResult([
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false] ['name' => 'Garfield', 'meows' => false],
] ],
]); ]);
$this->assertEquals($expected, Executor::execute($schema, Parser::parse($query))); $this->assertEquals($expected, Executor::execute($schema, Parser::parse($query)));
@ -161,14 +170,14 @@ class AbstractTest extends TestCase
/** /**
* @see it('resolveType on Interface yields useful error') * @see it('resolveType on Interface yields useful error')
*/ */
function testResolveTypeOnInterfaceYieldsUsefulError() public function testResolveTypeOnInterfaceYieldsUsefulError() : void
{ {
$DogType = null; $DogType = null;
$CatType = null; $CatType = null;
$HumanType = null; $HumanType = null;
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) { 'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
if ($obj instanceof Dog) { if ($obj instanceof Dog) {
return $DogType; return $DogType;
@ -179,58 +188,58 @@ class AbstractTest extends TestCase
if ($obj instanceof Human) { if ($obj instanceof Human) {
return $HumanType; return $HumanType;
} }
return null; return null;
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$HumanType = new ObjectType([ $HumanType = new ObjectType([
'name' => 'Human', 'name' => 'Human',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function () { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false), new Cat('Garfield', false),
new Human('Jon') new Human('Jon'),
]; ];
} },
] ],
], ],
]), ]),
'types' => [$DogType, $CatType] 'types' => [$DogType, $CatType],
]); ]);
$query = '{ $query = '{
pets { pets {
name name
@ -244,20 +253,21 @@ class AbstractTest extends TestCase
}'; }';
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false], ['name' => 'Garfield', 'meows' => false],
null null,
] ],
], ],
'errors' => [[ 'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]], 'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2] 'path' => ['pets', 2],
]] ],
],
]; ];
$actual = GraphQL::executeQuery($schema, $query)->toArray(true); $actual = GraphQL::executeQuery($schema, $query)->toArray(true);
$this->assertArraySubset($expected, $actual); $this->assertArraySubset($expected, $actual);
} }
@ -268,30 +278,30 @@ class AbstractTest extends TestCase
public function testResolveTypeOnUnionYieldsUsefulError() : void public function testResolveTypeOnUnionYieldsUsefulError() : void
{ {
$HumanType = new ObjectType([ $HumanType = new ObjectType([
'name' => 'Human', 'name' => 'Human',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$PetType = new UnionType([ $PetType = new UnionType([
'name' => 'Pet', 'name' => 'Pet',
'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) { 'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) {
if ($obj instanceof Dog) { if ($obj instanceof Dog) {
return $DogType; return $DogType;
@ -303,25 +313,25 @@ class AbstractTest extends TestCase
return $HumanType; return $HumanType;
} }
}, },
'types' => [$DogType, $CatType] 'types' => [$DogType, $CatType],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function () { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false), new Cat('Garfield', false),
new Human('Jon') new Human('Jon'),
]; ];
} },
] ],
] ],
]) ]),
]); ]);
$query = '{ $query = '{
@ -337,22 +347,27 @@ class AbstractTest extends TestCase
} }
}'; }';
$result = GraphQL::executeQuery($schema, $query)->toArray(true); $result = GraphQL::executeQuery($schema, $query)->toArray(true);
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [ 'pets' => [
['name' => 'Odie', [
'woofs' => true], 'name' => 'Odie',
['name' => 'Garfield', 'woofs' => true,
'meows' => false], ],
null [
] 'name' => 'Garfield',
'meows' => false,
],
null,
],
], ],
'errors' => [[ 'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]], 'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2] 'path' => ['pets', 2],
]] ],
],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
} }
@ -363,25 +378,25 @@ class AbstractTest extends TestCase
public function testReturningInvalidValueFromResolveTypeYieldsUsefulError() : void public function testReturningInvalidValueFromResolveTypeYieldsUsefulError() : void
{ {
$fooInterface = new InterfaceType([ $fooInterface = new InterfaceType([
'name' => 'FooInterface', 'name' => 'FooInterface',
'fields' => ['bar' => ['type' => Type::string()]], 'fields' => ['bar' => ['type' => Type::string()]],
'resolveType' => function () { 'resolveType' => function () {
return []; return [];
}, },
]); ]);
$fooObject = new ObjectType([ $fooObject = new ObjectType([
'name' => 'FooObject', 'name' => 'FooObject',
'fields' => ['bar' => ['type' => Type::string()]], 'fields' => ['bar' => ['type' => Type::string()]],
'interfaces' => [$fooInterface], 'interfaces' => [$fooInterface],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'foo' => [ 'foo' => [
'type' => $fooInterface, 'type' => $fooInterface,
'resolve' => function () { 'resolve' => function () {
return 'dummy'; return 'dummy';
}, },
@ -394,18 +409,18 @@ class AbstractTest extends TestCase
$result = GraphQL::executeQuery($schema, '{ foo { bar } }'); $result = GraphQL::executeQuery($schema, '{ foo { bar } }');
$expected = [ $expected = [
'data' => ['foo' => null], 'data' => ['foo' => null],
'errors' => [ 'errors' => [
[ [
'message' => 'Internal server error', 'message' => 'Internal server error',
'debugMessage' => 'debugMessage' =>
'Abstract type FooInterface must resolve to an Object type at ' . 'Abstract type FooInterface must resolve to an Object type at ' .
'runtime for field Query.foo with value "dummy", received "[]". ' . 'runtime for field Query.foo with value "dummy", received "[]". ' .
'Either the FooInterface type should provide a "resolveType" ' . 'Either the FooInterface type should provide a "resolveType" ' .
'function or each possible type should provide an "isTypeOf" function.', 'function or each possible type should provide an "isTypeOf" function.',
'locations' => [['line' => 1, 'column' => 3]], 'locations' => [['line' => 1, 'column' => 3]],
'path' => ['foo'], 'path' => ['foo'],
'category' => 'internal', 'category' => 'internal',
], ],
], ],
]; ];
@ -418,51 +433,56 @@ class AbstractTest extends TestCase
public function testResolveTypeAllowsResolvingWithTypeName() : void public function testResolveTypeAllowsResolvingWithTypeName() : void
{ {
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'resolveType' => function($obj) { 'resolveType' => function ($obj) {
if ($obj instanceof Dog) return 'Dog'; if ($obj instanceof Dog) {
if ($obj instanceof Cat) return 'Cat'; return 'Dog';
}
if ($obj instanceof Cat) {
return 'Cat';
}
return null; return null;
}, },
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [ $PetType ], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ], 'name' => ['type' => Type::string()],
'woofs' => [ 'type' => Type::boolean() ], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [ $PetType ], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ], 'name' => ['type' => Type::string()],
'meows' => [ 'type' => Type::boolean() ], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function() { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [ $CatType, $DogType ] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -479,51 +499,52 @@ class AbstractTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(); $result = GraphQL::executeQuery($schema, $query)->toArray();
$this->assertEquals([ $this->assertEquals(
'data' => [ [
'pets' => [ 'data' => [
['name' => 'Odie', 'woofs' => true], 'pets' => [
['name' => 'Garfield', 'meows' => false] ['name' => 'Odie', 'woofs' => true],
] ['name' => 'Garfield', 'meows' => false],
] ],
], $result); ],
],
$result
);
} }
public function testHintsOnConflictingTypeInstancesInResolveType() : void public function testHintsOnConflictingTypeInstancesInResolveType() : void
{ {
$createTest = function() use (&$iface) { $createTest = function () use (&$iface) {
return new ObjectType([ return new ObjectType([
'name' => 'Test', 'name' => 'Test',
'fields' => [ 'fields' => [
'a' => Type::string() 'a' => Type::string(),
], ],
'interfaces' => function() use ($iface) { 'interfaces' => function () use ($iface) {
return [$iface]; return [$iface];
} },
]); ]);
}; };
$iface = new InterfaceType([ $iface = new InterfaceType([
'name' => 'Node', 'name' => 'Node',
'fields' => [ 'fields' => [
'a' => Type::string() 'a' => Type::string(),
], ],
'resolveType' => function() use (&$createTest) { 'resolveType' => function () use (&$createTest) {
return $createTest(); return $createTest();
} },
]); ]);
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'node' => $iface, 'node' => $iface,
'test' => $createTest() 'test' => $createTest(),
] ],
]); ]);
$schema = new Schema([ $schema = new Schema(['query' => $query]);
'query' => $query,
]);
$schema->assertValid(); $schema->assertValid();
$query = ' $query = '
@ -537,9 +558,9 @@ class AbstractTest extends TestCase
$result = Executor::execute($schema, Parser::parse($query), ['node' => ['a' => 'value']]); $result = Executor::execute($schema, Parser::parse($query), ['node' => ['a' => 'value']]);
$this->assertEquals( $this->assertEquals(
'Schema must contain unique named types but contains multiple types named "Test". '. 'Schema must contain unique named types but contains multiple types named "Test". ' .
'Make sure that `resolveType` function of abstract type "Node" returns the same type instance '. 'Make sure that `resolveType` function of abstract type "Node" returns the same type instance ' .
'as referenced anywhere else within the schema '. 'as referenced anywhere else within the schema ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).', '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$result->errors[0]->getMessage() $result->errors[0]->getMessage()
); );

View File

@ -1,33 +1,44 @@
<?php <?php
namespace GraphQL\Tests\Executor;
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function in_array;
class DeferredFieldsTest extends TestCase class DeferredFieldsTest extends TestCase
{ {
/** @var ObjectType */
private $userType; private $userType;
/** @var ObjectType */
private $storyType; private $storyType;
/** @var ObjectType */
private $categoryType; private $categoryType;
/** @var */
private $path; private $path;
/** @var mixed[][] */
private $storyDataSource; private $storyDataSource;
/** @var mixed[][] */
private $userDataSource; private $userDataSource;
/** @var mixed[][] */
private $categoryDataSource; private $categoryDataSource;
/** @var ObjectType */
private $queryType; private $queryType;
public function setUp() public function setUp()
@ -54,127 +65,152 @@ class DeferredFieldsTest extends TestCase
$this->categoryDataSource = [ $this->categoryDataSource = [
['id' => 1, 'name' => 'Category #1', 'topStoryId' => 8], ['id' => 1, 'name' => 'Category #1', 'topStoryId' => 8],
['id' => 2, 'name' => 'Category #2', 'topStoryId' => 3], ['id' => 2, 'name' => 'Category #2', 'topStoryId' => 3],
['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9] ['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9],
]; ];
$this->path = []; $this->path = [];
$this->userType = new ObjectType([ $this->userType = new ObjectType([
'name' => 'User', 'name' => 'User',
'fields' => function() { 'fields' => function () {
return [ return [
'name' => [ 'name' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($user, $args, $context, ResolveInfo $info) { 'resolve' => function ($user, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return $user['name']; return $user['name'];
} },
], ],
'bestFriend' => [ 'bestFriend' => [
'type' => $this->userType, 'type' => $this->userType,
'resolve' => function($user, $args, $context, ResolveInfo $info) { 'resolve' => function ($user, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return new Deferred(function() use ($user) { return new Deferred(function () use ($user) {
$this->path[] = 'deferred-for-best-friend-of-' . $user['id']; $this->path[] = 'deferred-for-best-friend-of-' . $user['id'];
return Utils::find($this->userDataSource, function($entry) use ($user) {
return $entry['id'] === $user['bestFriendId']; return Utils::find(
}); $this->userDataSource,
function ($entry) use ($user) {
return $entry['id'] === $user['bestFriendId'];
}
);
}); });
} },
] ],
]; ];
} },
]); ]);
$this->storyType = new ObjectType([ $this->storyType = new ObjectType([
'name' => 'Story', 'name' => 'Story',
'fields' => [ 'fields' => [
'title' => [ 'title' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($entry, $args, $context, ResolveInfo $info) { 'resolve' => function ($entry, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $entry['title'];
}
],
'author' => [
'type' => $this->userType,
'resolve' => function($story, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return new Deferred(function() use ($story) { return $entry['title'];
},
],
'author' => [
'type' => $this->userType,
'resolve' => function ($story, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function () use ($story) {
$this->path[] = 'deferred-for-story-' . $story['id'] . '-author'; $this->path[] = 'deferred-for-story-' . $story['id'] . '-author';
return Utils::find($this->userDataSource, function($entry) use ($story) {
return $entry['id'] === $story['authorId']; return Utils::find(
}); $this->userDataSource,
function ($entry) use ($story) {
return $entry['id'] === $story['authorId'];
}
);
}); });
} },
] ],
] ],
]); ]);
$this->categoryType = new ObjectType([ $this->categoryType = new ObjectType([
'name' => 'Category', 'name' => 'Category',
'fields' => [ 'fields' => [
'name' => [ 'name' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($category, $args, $context, ResolveInfo $info) { 'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return $category['name']; return $category['name'];
} },
], ],
'stories' => [ 'stories' => [
'type' => Type::listOf($this->storyType), 'type' => Type::listOf($this->storyType),
'resolve' => function($category, $args, $context, ResolveInfo $info) { 'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) use ($category) {
return in_array($category['id'], $story['categoryIds']); return Utils::filter(
}); $this->storyDataSource,
} function ($story) use ($category) {
return in_array($category['id'], $story['categoryIds']);
}
);
},
], ],
'topStory' => [ 'topStory' => [
'type' => $this->storyType, 'type' => $this->storyType,
'resolve' => function($category, $args, $context, ResolveInfo $info) { 'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return new Deferred(function () use ($category) { return new Deferred(function () use ($category) {
$this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory'; $this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory';
return Utils::find($this->storyDataSource, function($story) use ($category) {
return $story['id'] === $category['topStoryId']; return Utils::find(
}); $this->storyDataSource,
function ($story) use ($category) {
return $story['id'] === $category['topStoryId'];
}
);
}); });
} },
] ],
] ],
]); ]);
$this->queryType = new ObjectType([ $this->queryType = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'topStories' => [ 'topStories' => [
'type' => Type::listOf($this->storyType), 'type' => Type::listOf($this->storyType),
'resolve' => function($val, $args, $context, ResolveInfo $info) { 'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) {
return $story['id'] % 2 === 1; return Utils::filter(
}); $this->storyDataSource,
} function ($story) {
return $story['id'] % 2 === 1;
}
);
},
], ],
'featuredCategory' => [ 'featuredCategory' => [
'type' => $this->categoryType, 'type' => $this->categoryType,
'resolve' => function($val, $args, $context, ResolveInfo $info) { 'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return $this->categoryDataSource[0]; return $this->categoryDataSource[0];
} },
], ],
'categories' => [ 'categories' => [
'type' => Type::listOf($this->categoryType), 'type' => Type::listOf($this->categoryType),
'resolve' => function($val, $args, $context, ResolveInfo $info) { 'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return $this->categoryDataSource; return $this->categoryDataSource;
} },
] ],
] ],
]); ]);
parent::setUp(); parent::setUp();
@ -202,12 +238,12 @@ class DeferredFieldsTest extends TestCase
'); ');
$schema = new Schema([ $schema = new Schema([
'query' => $this->queryType 'query' => $this->queryType,
]); ]);
$expected = [ $expected = [
'data' => [ 'data' => [
'topStories' => [ 'topStories' => [
['title' => 'Story #1', 'author' => ['name' => 'John']], ['title' => 'Story #1', 'author' => ['name' => 'John']],
['title' => 'Story #3', 'author' => ['name' => 'Joe']], ['title' => 'Story #3', 'author' => ['name' => 'Joe']],
['title' => 'Story #5', 'author' => ['name' => 'John']], ['title' => 'Story #5', 'author' => ['name' => 'John']],
@ -220,9 +256,9 @@ class DeferredFieldsTest extends TestCase
['title' => 'Story #4', 'author' => ['name' => 'Joe']], ['title' => 'Story #4', 'author' => ['name' => 'Joe']],
['title' => 'Story #6', 'author' => ['name' => 'Jane']], ['title' => 'Story #6', 'author' => ['name' => 'Jane']],
['title' => 'Story #8', 'author' => ['name' => 'John']], ['title' => 'Story #8', 'author' => ['name' => 'John']],
] ],
] ],
] ],
]; ];
$result = Executor::execute($schema, $query); $result = Executor::execute($schema, $query);
@ -292,12 +328,12 @@ class DeferredFieldsTest extends TestCase
'); ');
$schema = new Schema([ $schema = new Schema([
'query' => $this->queryType 'query' => $this->queryType,
]); ]);
$author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']]; $author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']];
$author2 = ['name' => 'Jane', 'bestFriend' => ['name' => 'Joe']]; $author2 = ['name' => 'Jane', 'bestFriend' => ['name' => 'Joe']];
$author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']]; $author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']];
$author4 = ['name' => 'Dirk', 'bestFriend' => ['name' => 'John']]; $author4 = ['name' => 'Dirk', 'bestFriend' => ['name' => 'John']];
$expected = [ $expected = [
@ -306,8 +342,8 @@ class DeferredFieldsTest extends TestCase
['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]], ['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]],
['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]], ['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]],
['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]], ['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]],
] ],
] ],
]; ];
$result = Executor::execute($schema, $query); $result = Executor::execute($schema, $query);
@ -352,54 +388,56 @@ class DeferredFieldsTest extends TestCase
public function testComplexRecursiveDeferredFields() : void public function testComplexRecursiveDeferredFields() : void
{ {
$complexType = new ObjectType([ $complexType = new ObjectType([
'name' => 'ComplexType', 'name' => 'ComplexType',
'fields' => function() use (&$complexType) { 'fields' => function () use (&$complexType) {
return [ return [
'sync' => [ 'sync' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) { 'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return 'sync';
}
],
'deferred' => [
'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return new Deferred(function() use ($info) { return 'sync';
},
],
'deferred' => [
'type' => Type::string(),
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function () use ($info) {
$this->path[] = ['!dfd for: ', $info->path]; $this->path[] = ['!dfd for: ', $info->path];
return 'deferred'; return 'deferred';
}); });
} },
], ],
'nest' => [ 'nest' => [
'type' => $complexType, 'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) { 'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return []; return [];
} },
], ],
'deferredNest' => [ 'deferredNest' => [
'type' => $complexType, 'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) { 'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return new Deferred(function() use ($info) { return new Deferred(function () use ($info) {
$this->path[] = ['!dfd nest for: ', $info->path]; $this->path[] = ['!dfd nest for: ', $info->path];
return []; return [];
}); });
} },
] ],
]; ];
} },
]); ]);
$schema = new Schema([ $schema = new Schema(['query' => $complexType]);
'query' => $complexType
]);
$query = Parser::parse(' $query = Parser::parse('
{ {
nest { nest {
sync sync
@ -427,34 +465,34 @@ class DeferredFieldsTest extends TestCase
} }
} }
'); ');
$result = Executor::execute($schema, $query); $result = Executor::execute($schema, $query);
$expected = [ $expected = [
'data' => [ 'data' => [
'nest' => [ 'nest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred', 'deferred' => 'deferred',
'nest' => [ 'nest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred' 'deferred' => 'deferred',
], ],
'deferredNest' => [ 'deferredNest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred' 'deferred' => 'deferred',
] ],
], ],
'deferredNest' => [ 'deferredNest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred', 'deferred' => 'deferred',
'nest' => [ 'nest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred' 'deferred' => 'deferred',
], ],
'deferredNest' => [ 'deferredNest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred' 'deferred' => 'deferred',
] ],
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());

View File

@ -1,16 +1,27 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Language\Source;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/**
* Describe: Execute: handles directives
*/
class DirectivesTest extends TestCase class DirectivesTest extends TestCase
{ {
// Describe: Execute: handles directives /** @var Schema */
private static $schema;
/** @var string[] */
private static $data;
/** /**
* @see it('basic query works') * @see it('basic query works')
@ -20,10 +31,50 @@ class DirectivesTest extends TestCase
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b }')); $this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b }'));
} }
/**
* @param Source|string $doc
* @return mixed[]
*/
private function executeTestQuery($doc) : array
{
return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray();
}
private static function getSchema() : Schema
{
if (! self::$schema) {
self::$schema = new Schema([
'query' => new ObjectType([
'name' => 'TestType',
'fields' => [
'a' => ['type' => Type::string()],
'b' => ['type' => Type::string()],
],
]),
]);
}
return self::$schema;
}
/**
* @return string[]
*/
private static function getData() : array
{
return self::$data ?: (self::$data = [
'a' => 'a',
'b' => 'b',
]);
}
public function testWorksOnScalars() : void public function testWorksOnScalars() : void
{ {
// if true includes scalar // if true includes scalar
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b @include(if: true) }')); $this->assertEquals(
['data' => ['a' => 'a', 'b' => 'b']],
$this->executeTestQuery('{ a, b @include(if: true) }')
);
// if false omits on scalar // if false omits on scalar
$this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @include(if: false) }')); $this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @include(if: false) }'));
@ -200,37 +251,4 @@ class DirectivesTest extends TestCase
$this->executeTestQuery('{ a, b @include(if: false) @skip(if: false) }') $this->executeTestQuery('{ a, b @include(if: false) @skip(if: false) }')
); );
} }
private static $schema;
private static $data;
private static function getSchema()
{
if (!self::$schema) {
self::$schema = new Schema([
'query' => new ObjectType([
'name' => 'TestType',
'fields' => [
'a' => ['type' => Type::string()],
'b' => ['type' => Type::string()]
]
])
]);
}
return self::$schema;
}
private static function getData()
{
return self::$data ?: (self::$data = [
'a' => 'a',
'b' => 'b'
]);
}
private function executeTestQuery($doc)
{
return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray();
}
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;

View File

@ -1,99 +1,119 @@
<?php <?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php'; declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning; use GraphQL\Error\Warning;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use PHPUnit\Framework\Error\Error; use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
class ExecutorLazySchemaTest extends TestCase class ExecutorLazySchemaTest extends TestCase
{ {
public $SomeScalarType; /** @var ScalarType */
public $someScalarType;
public $SomeObjectType; /** @var ObjectType */
public $someObjectType;
public $OtherObjectType; /** @var ObjectType */
public $otherObjectType;
public $DeeperObjectType; /** @var ObjectType */
public $deeperObjectType;
public $SomeUnionType; /** @var UnionType */
public $someUnionType;
public $SomeInterfaceType; /** @var InterfaceType */
public $someInterfaceType;
public $SomeEnumType; /** @var EnumType */
public $someEnumType;
public $SomeInputObjectType; /** @var InputObjectType */
public $someInputObjectType;
public $QueryType; /** @var ObjectType */
public $queryType;
/** @var string[] */
public $calls = []; public $calls = [];
/** @var bool[] */
public $loadedTypes = []; public $loadedTypes = [];
public function testWarnsAboutSlowIsTypeOfForLazySchema() : void public function testWarnsAboutSlowIsTypeOfForLazySchema() : void
{ {
// isTypeOf used to resolve runtime type for Interface // isTypeOf used to resolve runtime type for Interface
$petType = new InterfaceType([ $petType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'fields' => function() { 'fields' => function () {
return [ return [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
]; ];
} },
]); ]);
// Added to interface type when defined // Added to interface type when defined
$dogType = new ObjectType([ $dogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$petType], 'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; }, 'isTypeOf' => function ($obj) {
'fields' => function() { return $obj instanceof Dog;
},
'fields' => function () {
return [ return [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()] 'woofs' => ['type' => Type::boolean()],
]; ];
} },
]); ]);
$catType = new ObjectType([ $catType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$petType], 'interfaces' => [$petType],
'isTypeOf' => function ($obj) { 'isTypeOf' => function ($obj) {
return $obj instanceof Cat; return $obj instanceof Cat;
}, },
'fields' => function() { 'fields' => function () {
return [ return [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
]; ];
} },
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($petType), 'type' => Type::listOf($petType),
'resolve' => function () { 'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)]; return [new Dog('Odie', true), new Cat('Garfield', false)];
} },
] ],
] ],
]), ]),
'types' => [$catType, $dogType], 'types' => [$catType, $dogType],
'typeLoader' => function($name) use ($dogType, $petType, $catType) { 'typeLoader' => function ($name) use ($dogType, $petType, $catType) {
switch ($name) { switch ($name) {
case 'Dog': case 'Dog':
return $dogType; return $dogType;
@ -102,7 +122,7 @@ class ExecutorLazySchemaTest extends TestCase
case 'Cat': case 'Cat':
return $catType; return $catType;
} }
} },
]); ]);
$query = '{ $query = '{
@ -120,7 +140,7 @@ class ExecutorLazySchemaTest extends TestCase
$expected = new ExecutionResult([ $expected = new ExecutionResult([
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false] ['name' => 'Garfield', 'meows' => false],
], ],
]); ]);
@ -134,43 +154,46 @@ class ExecutorLazySchemaTest extends TestCase
$this->assertInstanceOf(Error::class, $result->errors[0]->getPrevious()); $this->assertInstanceOf(Error::class, $result->errors[0]->getPrevious());
$this->assertEquals( $this->assertEquals(
'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of '. 'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of ' .
'GraphQL\Tests\Executor\Dog. Switching to slow resolution method using `isTypeOf` of all possible '. 'GraphQL\Tests\Executor\TestClasses\Dog. Switching to slow resolution method using `isTypeOf` of all possible ' .
'implementations. It requires full schema scan and degrades query performance significantly. '. 'implementations. It requires full schema scan and degrades query performance significantly. ' .
'Make sure your `resolveType` always returns valid implementation or throws.', 'Make sure your `resolveType` always returns valid implementation or throws.',
$result->errors[0]->getMessage()); $result->errors[0]->getMessage()
);
} }
public function testHintsOnConflictingTypeInstancesInDefinitions() : void public function testHintsOnConflictingTypeInstancesInDefinitions() : void
{ {
$calls = []; $calls = [];
$typeLoader = function($name) use (&$calls) { $typeLoader = function ($name) use (&$calls) {
$calls[] = $name; $calls[] = $name;
switch ($name) { switch ($name) {
case 'Test': case 'Test':
return new ObjectType([ return new ObjectType([
'name' => 'Test', 'name' => 'Test',
'fields' => function() { 'fields' => function () {
return [ return [
'test' => Type::string(), 'test' => Type::string(),
]; ];
} },
]); ]);
default: default:
return null; return null;
} }
}; };
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => function() use ($typeLoader) { 'fields' => function () use ($typeLoader) {
return [ return [
'test' => $typeLoader('Test') 'test' => $typeLoader('Test'),
]; ];
} },
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => $query, 'query' => $query,
'typeLoader' => $typeLoader 'typeLoader' => $typeLoader,
]); ]);
$query = ' $query = '
@ -186,8 +209,8 @@ class ExecutorLazySchemaTest extends TestCase
$this->assertEquals(['Test', 'Test'], $calls); $this->assertEquals(['Test', 'Test'], $calls);
$this->assertEquals( $this->assertEquals(
'Schema must contain unique named types but contains multiple types named "Test". '. 'Schema must contain unique named types but contains multiple types named "Test". ' .
'Make sure that type loader returns the same instance as defined in Query.test '. 'Make sure that type loader returns the same instance as defined in Query.test ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).', '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$result->errors[0]->getMessage() $result->errors[0]->getMessage()
); );
@ -200,54 +223,161 @@ class ExecutorLazySchemaTest extends TestCase
public function testSimpleQuery() : void public function testSimpleQuery() : void
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->loadType('Query'), 'query' => $this->loadType('Query'),
'typeLoader' => function($name) { 'typeLoader' => function ($name) {
return $this->loadType($name, true); return $this->loadType($name, true);
} },
]); ]);
$query = '{ object { string } }'; $query = '{ object { string } }';
$result = Executor::execute( $result = Executor::execute(
$schema, $schema,
Parser::parse($query), Parser::parse($query),
['object' => ['string' => 'test']] ['object' => ['string' => 'test']]
); );
$expected = [ $expected = [
'data' => ['object' => ['string' => 'test']], 'data' => ['object' => ['string' => 'test']],
]; ];
$expectedExecutorCalls = [ $expectedExecutorCalls = [
'Query.fields', 'Query.fields',
'SomeObject', 'SomeObject',
'SomeObject.fields' 'SomeObject.fields',
]; ];
$this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expected, $result->toArray(true));
$this->assertEquals($expectedExecutorCalls, $this->calls); $this->assertEquals($expectedExecutorCalls, $this->calls);
} }
public function loadType($name, $isExecutorCall = false)
{
if ($isExecutorCall) {
$this->calls[] = $name;
}
$this->loadedTypes[$name] = true;
switch ($name) {
case 'Query':
return $this->queryType ?: $this->queryType = new ObjectType([
'name' => 'Query',
'fields' => function () {
$this->calls[] = 'Query.fields';
return [
'object' => ['type' => $this->loadType('SomeObject')],
'other' => ['type' => $this->loadType('OtherObject')],
];
},
]);
case 'SomeObject':
return $this->someObjectType ?: $this->someObjectType = new ObjectType([
'name' => 'SomeObject',
'fields' => function () {
$this->calls[] = 'SomeObject.fields';
return [
'string' => ['type' => Type::string()],
'object' => ['type' => $this->someObjectType],
];
},
'interfaces' => function () {
$this->calls[] = 'SomeObject.interfaces';
return [
$this->loadType('SomeInterface'),
];
},
]);
case 'OtherObject':
return $this->otherObjectType ?: $this->otherObjectType = new ObjectType([
'name' => 'OtherObject',
'fields' => function () {
$this->calls[] = 'OtherObject.fields';
return [
'union' => ['type' => $this->loadType('SomeUnion')],
'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))],
];
},
]);
case 'DeeperObject':
return $this->deeperObjectType ?: $this->deeperObjectType = new ObjectType([
'name' => 'DeeperObject',
'fields' => function () {
return [
'scalar' => ['type' => $this->loadType('SomeScalar')],
];
},
]);
case 'SomeScalar':
return $this->someScalarType ?: $this->someScalarType = new CustomScalarType([
'name' => 'SomeScalar',
'serialize' => function ($value) {
return $value;
},
'parseValue' => function ($value) {
return $value;
},
'parseLiteral' => function () {
},
]);
case 'SomeUnion':
return $this->someUnionType ?: $this->someUnionType = new UnionType([
'name' => 'SomeUnion',
'resolveType' => function () {
$this->calls[] = 'SomeUnion.resolveType';
return $this->loadType('DeeperObject');
},
'types' => function () {
$this->calls[] = 'SomeUnion.types';
return [$this->loadType('DeeperObject')];
},
]);
case 'SomeInterface':
return $this->someInterfaceType ?: $this->someInterfaceType = new InterfaceType([
'name' => 'SomeInterface',
'resolveType' => function () {
$this->calls[] = 'SomeInterface.resolveType';
return $this->loadType('SomeObject');
},
'fields' => function () {
$this->calls[] = 'SomeInterface.fields';
return [
'string' => ['type' => Type::string()],
];
},
]);
default:
return null;
}
}
public function testDeepQuery() : void public function testDeepQuery() : void
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->loadType('Query'), 'query' => $this->loadType('Query'),
'typeLoader' => function($name) { 'typeLoader' => function ($name) {
return $this->loadType($name, true); return $this->loadType($name, true);
} },
]); ]);
$query = '{ object { object { object { string } } } }'; $query = '{ object { object { object { string } } } }';
$result = Executor::execute( $result = Executor::execute(
$schema, $schema,
Parser::parse($query), Parser::parse($query),
['object' => ['object' => ['object' => ['string' => 'test']]]] ['object' => ['object' => ['object' => ['string' => 'test']]]]
); );
$expected = [ $expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]] 'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]],
]; ];
$expectedLoadedTypes = [ $expectedLoadedTypes = [
'Query' => true, 'Query' => true,
'SomeObject' => true, 'SomeObject' => true,
'OtherObject' => true 'OtherObject' => true,
]; ];
$this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expected, $result->toArray(true));
@ -256,7 +386,7 @@ class ExecutorLazySchemaTest extends TestCase
$expectedExecutorCalls = [ $expectedExecutorCalls = [
'Query.fields', 'Query.fields',
'SomeObject', 'SomeObject',
'SomeObject.fields' 'SomeObject.fields',
]; ];
$this->assertEquals($expectedExecutorCalls, $this->calls); $this->assertEquals($expectedExecutorCalls, $this->calls);
} }
@ -264,13 +394,13 @@ class ExecutorLazySchemaTest extends TestCase
public function testResolveUnion() : void public function testResolveUnion() : void
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->loadType('Query'), 'query' => $this->loadType('Query'),
'typeLoader' => function($name) { 'typeLoader' => function ($name) {
return $this->loadType($name, true); return $this->loadType($name, true);
} },
]); ]);
$query = ' $query = '
{ {
other { other {
union { union {
@ -285,17 +415,17 @@ class ExecutorLazySchemaTest extends TestCase
['other' => ['union' => ['scalar' => 'test']]] ['other' => ['union' => ['scalar' => 'test']]]
); );
$expected = [ $expected = [
'data' => ['other' => ['union' => ['scalar' => 'test']]], 'data' => ['other' => ['union' => ['scalar' => 'test']]],
]; ];
$expectedLoadedTypes = [ $expectedLoadedTypes = [
'Query' => true, 'Query' => true,
'SomeObject' => true, 'SomeObject' => true,
'OtherObject' => true, 'OtherObject' => true,
'SomeUnion' => true, 'SomeUnion' => true,
'SomeInterface' => true, 'SomeInterface' => true,
'DeeperObject' => true, 'DeeperObject' => true,
'SomeScalar' => true, 'SomeScalar' => true,
]; ];
$this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expected, $result->toArray(true));
@ -313,98 +443,4 @@ class ExecutorLazySchemaTest extends TestCase
]; ];
$this->assertEquals($expectedCalls, $this->calls); $this->assertEquals($expectedCalls, $this->calls);
} }
public function loadType($name, $isExecutorCall = false)
{
if ($isExecutorCall) {
$this->calls[] = $name;
}
$this->loadedTypes[$name] = true;
switch ($name) {
case 'Query':
return $this->QueryType ?: $this->QueryType = new ObjectType([
'name' => 'Query',
'fields' => function() {
$this->calls[] = 'Query.fields';
return [
'object' => ['type' => $this->loadType('SomeObject')],
'other' => ['type' => $this->loadType('OtherObject')],
];
}
]);
case 'SomeObject':
return $this->SomeObjectType ?: $this->SomeObjectType = new ObjectType([
'name' => 'SomeObject',
'fields' => function() {
$this->calls[] = 'SomeObject.fields';
return [
'string' => ['type' => Type::string()],
'object' => ['type' => $this->SomeObjectType]
];
},
'interfaces' => function() {
$this->calls[] = 'SomeObject.interfaces';
return [
$this->loadType('SomeInterface')
];
}
]);
case 'OtherObject':
return $this->OtherObjectType ?: $this->OtherObjectType = new ObjectType([
'name' => 'OtherObject',
'fields' => function() {
$this->calls[] = 'OtherObject.fields';
return [
'union' => ['type' => $this->loadType('SomeUnion')],
'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))],
];
}
]);
case 'DeeperObject':
return $this->DeeperObjectType ?: $this->DeeperObjectType = new ObjectType([
'name' => 'DeeperObject',
'fields' => function() {
return [
'scalar' => ['type' => $this->loadType('SomeScalar')],
];
}
]);
case 'SomeScalar';
return $this->SomeScalarType ?: $this->SomeScalarType = new CustomScalarType([
'name' => 'SomeScalar',
'serialize' => function($value) {return $value;},
'parseValue' => function($value) {return $value;},
'parseLiteral' => function() {}
]);
case 'SomeUnion':
return $this->SomeUnionType ?: $this->SomeUnionType = new UnionType([
'name' => 'SomeUnion',
'resolveType' => function() {
$this->calls[] = 'SomeUnion.resolveType';
return $this->loadType('DeeperObject');
},
'types' => function() {
$this->calls[] = 'SomeUnion.types';
return [ $this->loadType('DeeperObject') ];
}
]);
case 'SomeInterface':
return $this->SomeInterfaceType ?: $this->SomeInterfaceType = new InterfaceType([
'name' => 'SomeInterface',
'resolveType' => function() {
$this->calls[] = 'SomeInterface.resolveType';
return $this->loadType('SomeObject');
},
'fields' => function() {
$this->calls[] = 'SomeInterface.fields';
return [
'string' => ['type' => Type::string() ]
];
}
]);
default:
return null;
}
}
} }

View File

@ -1,74 +1,77 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function sprintf;
class ExecutorSchemaTest extends TestCase class ExecutorSchemaTest extends TestCase
{ {
// Execute: Handles execution with a complex schema // Execute: Handles execution with a complex schema
/** /**
* @see it('executes using a schema') * @see it('executes using a schema')
*/ */
public function testExecutesUsingASchema() : void public function testExecutesUsingASchema() : void
{ {
$BlogArticle = null; $BlogArticle = null;
$BlogImage = new ObjectType([ $BlogImage = new ObjectType([
'name' => 'Image', 'name' => 'Image',
'fields' => [ 'fields' => [
'url' => ['type' => Type::string()], 'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()], 'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()], 'height' => ['type' => Type::int()],
] ],
]); ]);
$BlogAuthor = new ObjectType([ $BlogAuthor = new ObjectType([
'name' => 'Author', 'name' => 'Author',
'fields' => function() use (&$BlogArticle, &$BlogImage) { 'fields' => function () use (&$BlogArticle, &$BlogImage) {
return [ return [
'id' => ['type' => Type::string()], 'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'pic' => [ 'pic' => [
'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]], 'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]],
'type' => $BlogImage, 'type' => $BlogImage,
'resolve' => function ($obj, $args) { 'resolve' => function ($obj, $args) {
return $obj['pic']($args['width'], $args['height']); return $obj['pic']($args['width'], $args['height']);
} },
], ],
'recentArticle' => $BlogArticle 'recentArticle' => $BlogArticle,
]; ];
} },
]); ]);
$BlogArticle = new ObjectType([ $BlogArticle = new ObjectType([
'name' => 'Article', 'name' => 'Article',
'fields' => [ 'fields' => [
'id' => ['type' => Type::nonNull(Type::string())], 'id' => ['type' => Type::nonNull(Type::string())],
'isPublished' => ['type' => Type::boolean()], 'isPublished' => ['type' => Type::boolean()],
'author' => ['type' => $BlogAuthor], 'author' => ['type' => $BlogAuthor],
'title' => ['type' => Type::string()], 'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()], 'body' => ['type' => Type::string()],
'keywords' => ['type' => Type::listOf(Type::string())] 'keywords' => ['type' => Type::listOf(Type::string())],
] ],
]); ]);
$BlogQuery = new ObjectType([ $BlogQuery = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'article' => [ 'article' => [
'type' => $BlogArticle, 'type' => $BlogArticle,
'args' => ['id' => ['type' => Type::id()]], 'args' => ['id' => ['type' => Type::id()]],
'resolve' => function ($_, $args) { 'resolve' => function ($_, $args) {
return $this->article($args['id']); return $this->article($args['id']);
} },
], ],
'feed' => [ 'feed' => [
'type' => Type::listOf($BlogArticle), 'type' => Type::listOf($BlogArticle),
'resolve' => function () { 'resolve' => function () {
return [ return [
$this->article(1), $this->article(1),
@ -80,16 +83,15 @@ class ExecutorSchemaTest extends TestCase
$this->article(7), $this->article(7),
$this->article(8), $this->article(8),
$this->article(9), $this->article(9),
$this->article(10) $this->article(10),
]; ];
} },
] ],
] ],
]); ]);
$BlogSchema = new Schema(['query' => $BlogQuery]); $BlogSchema = new Schema(['query' => $BlogQuery]);
$request = ' $request = '
{ {
feed { feed {
@ -126,51 +128,71 @@ class ExecutorSchemaTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'feed' => [ 'feed' => [
['id' => '1', [
'title' => 'My Article 1'], 'id' => '1',
['id' => '2', 'title' => 'My Article 1',
'title' => 'My Article 2'], ],
['id' => '3', [
'title' => 'My Article 3'], 'id' => '2',
['id' => '4', 'title' => 'My Article 2',
'title' => 'My Article 4'], ],
['id' => '5', [
'title' => 'My Article 5'], 'id' => '3',
['id' => '6', 'title' => 'My Article 3',
'title' => 'My Article 6'], ],
['id' => '7', [
'title' => 'My Article 7'], 'id' => '4',
['id' => '8', 'title' => 'My Article 4',
'title' => 'My Article 8'], ],
['id' => '9', [
'title' => 'My Article 9'], 'id' => '5',
['id' => '10', 'title' => 'My Article 5',
'title' => 'My Article 10'] ],
[
'id' => '6',
'title' => 'My Article 6',
],
[
'id' => '7',
'title' => 'My Article 7',
],
[
'id' => '8',
'title' => 'My Article 8',
],
[
'id' => '9',
'title' => 'My Article 9',
],
[
'id' => '10',
'title' => 'My Article 10',
],
], ],
'article' => [ 'article' => [
'id' => '1', 'id' => '1',
'isPublished' => true, 'isPublished' => true,
'title' => 'My Article 1', 'title' => 'My Article 1',
'body' => 'This is a post', 'body' => 'This is a post',
'author' => [ 'author' => [
'id' => '123', 'id' => '123',
'name' => 'John Smith', 'name' => 'John Smith',
'pic' => [ 'pic' => [
'url' => 'cdn://123', 'url' => 'cdn://123',
'width' => 640, 'width' => 640,
'height' => 480 'height' => 480,
], ],
'recentArticle' => [ 'recentArticle' => [
'id' => '1', 'id' => '1',
'isPublished' => true, 'isPublished' => true,
'title' => 'My Article 1', 'title' => 'My Article 1',
'body' => 'This is a post', 'body' => 'This is a post',
'keywords' => ['foo', 'bar', '1', 'true', null] 'keywords' => ['foo', 'bar', '1', 'true', null],
] ],
] ],
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray()); $this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray());
@ -179,30 +201,32 @@ class ExecutorSchemaTest extends TestCase
private function article($id) private function article($id)
{ {
$johnSmith = null; $johnSmith = null;
$article = function($id) use (&$johnSmith) { $article = function ($id) use (&$johnSmith) {
return [ return [
'id' => $id, 'id' => $id,
'isPublished' => 'true', 'isPublished' => 'true',
'author' => $johnSmith, 'author' => $johnSmith,
'title' => 'My Article ' . $id, 'title' => 'My Article ' . $id,
'body' => 'This is a post', 'body' => 'This is a post',
'hidden' => 'This data is not exposed in the schema', 'hidden' => 'This data is not exposed in the schema',
'keywords' => ['foo', 'bar', 1, true, null] 'keywords' => ['foo', 'bar', 1, true, null],
]; ];
}; };
$getPic = function($uid, $width, $height) { $getPic = function ($uid, $width, $height) {
return [ return [
'url' => "cdn://$uid", 'url' => sprintf('cdn://%s', $uid),
'width' => $width, 'width' => $width,
'height' => $height 'height' => $height,
]; ];
}; };
$johnSmith = [ $johnSmith = [
'id' => 123, 'id' => 123,
'name' => 'John Smith', 'name' => 'John Smith',
'pic' => function($width, $height) use ($getPic) {return $getPic(123, $width, $height);}, 'pic' => function ($width, $height) use ($getPic) {
return $getPic(123, $width, $height);
},
'recentArticle' => $article(1), 'recentArticle' => $article(1),
]; ];

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +1,34 @@
<?php <?php
declare(strict_types=1);
/** /**
* @author: Ivo Meißner * @author: Ivo Meißner
* Date: 03.05.16 * Date: 03.05.16
* Time: 13:14 * Time: 13:14
*/ */
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class LazyInterfaceTest extends TestCase class LazyInterfaceTest extends TestCase
{ {
/** /** @var Schema */
* @var Schema
*/
protected $schema; protected $schema;
/** /** @var InterfaceType */
* @var InterfaceType
*/
protected $lazyInterface; protected $lazyInterface;
/** /** @var ObjectType */
* @var ObjectType
*/
protected $testObject; protected $testObject;
/**
* Setup schema
*/
protected function setUp()
{
$query = new ObjectType([
'name' => 'query',
'fields' => function () {
return [
'lazyInterface' => [
'type' => $this->getLazyInterfaceType(),
'resolve' => function() {
return [];
}
]
];
}
]);
$this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]);
}
/**
* Returns the LazyInterface
*
* @return InterfaceType
*/
protected function getLazyInterfaceType()
{
if (!$this->lazyInterface) {
$this->lazyInterface = new InterfaceType([
'name' => 'LazyInterface',
'fields' => [
'a' => Type::string()
],
'resolveType' => function() {
return $this->getTestObjectType();
},
]);
}
return $this->lazyInterface;
}
/**
* Returns the test ObjectType
* @return ObjectType
*/
protected function getTestObjectType()
{
if (!$this->testObject) {
$this->testObject = new ObjectType([
'name' => 'TestObject',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function() {
return 'testname';
}
]
],
'interfaces' => [$this->getLazyInterfaceType()]
]);
}
return $this->testObject;
}
/** /**
* Handles execution of a lazily created interface * Handles execution of a lazily created interface
*/ */
@ -116,12 +46,78 @@ class LazyInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'lazyInterface' => [ 'lazyInterface' => ['name' => 'testname'],
'name' => 'testname' ],
]
]
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray()); $this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray());
} }
/**
* Setup schema
*/
protected function setUp()
{
$query = new ObjectType([
'name' => 'query',
'fields' => function () {
return [
'lazyInterface' => [
'type' => $this->getLazyInterfaceType(),
'resolve' => function () {
return [];
},
],
];
},
]);
$this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]);
}
/**
* Returns the LazyInterface
*
* @return InterfaceType
*/
protected function getLazyInterfaceType()
{
if (! $this->lazyInterface) {
$this->lazyInterface = new InterfaceType([
'name' => 'LazyInterface',
'fields' => [
'a' => Type::string(),
],
'resolveType' => function () {
return $this->getTestObjectType();
},
]);
}
return $this->lazyInterface;
}
/**
* Returns the test ObjectType
* @return ObjectType
*/
protected function getTestObjectType()
{
if (! $this->testObject) {
$this->testObject = new ObjectType([
'name' => 'TestObject',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function () {
return 'testname';
},
],
],
'interfaces' => [$this->getLazyInterfaceType()],
]);
}
return $this->testObject;
}
} }

View File

@ -1,22 +1,21 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\UserError; use GraphQL\Error\UserError;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ListsTest extends TestCase class ListsTest extends TestCase
{ {
// Describe: Execute: Handles list nullability // Describe: Execute: Handles list nullability
/** /**
* [T] * [T]
*/ */
@ -24,23 +23,57 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ 1, null, 2 ], [1, null, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
null, null,
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
} }
private function checkHandlesNullableLists($testData, $expected)
{
$testType = Type::listOf(Type::int());
$this->check($testType, $testData, $expected);
}
private function check($testType, $testData, $expected, $debug = false)
{
$data = ['test' => $testData];
$dataType = null;
$dataType = new ObjectType([
'name' => 'DataType',
'fields' => function () use (&$testType, &$dataType, $data) {
return [
'test' => ['type' => $testType],
'nest' => [
'type' => $dataType,
'resolve' => function () use ($data) {
return $data;
},
],
];
},
]);
$schema = new Schema(['query' => $dataType]);
$ast = Parser::parse('{ nest { test } }');
$result = Executor::execute($schema, $ast, $data);
$this->assertArraySubset($expected, $result->toArray($debug));
}
/** /**
* [T] * [T]
*/ */
@ -48,26 +81,26 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return [1,2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Rejected // Rejected
@ -78,14 +111,14 @@ class ListsTest extends TestCase
}); });
}, },
[ [
'data' => ['nest' => ['test' => null]], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -98,57 +131,64 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
})], }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ],
['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ [
new Deferred(function() {return 1;}), new Deferred(function () {
new Deferred(function() {return null;}), return 1;
new Deferred(function() {return 2;}) }),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Contains reject // Contains reject
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
'data' => ['nest' => ['test' => [1, null, 2]]], 'data' => ['nest' => ['test' => [1, null, 2]]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1] 'path' => ['nest', 'test', 1],
] ],
] ],
] ]
); );
} }
@ -160,32 +200,38 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ 1, null, 2 ], [1, null, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
null, null,
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
} }
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/** /**
* [T]! * [T]!
*/ */
@ -193,31 +239,31 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
new Deferred(function() { new Deferred(function () {
return [1,2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
null, null,
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -225,19 +271,19 @@ class ListsTest extends TestCase
// Rejected // Rejected
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
function () { function () {
return new Deferred(function() { return new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}); });
}, },
[ [
'data' => ['nest' => null], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -250,56 +296,56 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Contains reject // Contains reject
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
'data' => ['nest' => ['test' => [1, null, 2]]], 'data' => ['nest' => ['test' => [1, null, 2]]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1] 'path' => ['nest', 'test', 1],
] ],
] ],
] ]
); );
} }
@ -311,21 +357,21 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ 1, null, 2 ], [1, null, 2],
[ [
'data' => [ 'nest' => [ 'test' => null ] ], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -333,10 +379,16 @@ class ListsTest extends TestCase
// Returns null // Returns null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
null, null,
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
} }
private function checkHandlesListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::listOf(Type::nonNull(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/** /**
* [T!] * [T!]
*/ */
@ -344,53 +396,53 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, 2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ [
'data' => [ 'nest' => [ 'test' => null ] ], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
// Returns null // Returns null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Rejected // Rejected
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
function () { function () {
return new Deferred(function() { return new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}); });
}, },
[ [
'data' => ['nest' => ['test' => null]], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -403,50 +455,56 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ [
new Deferred(function() {return 1;}), new Deferred(function () {
new Deferred(function() {return null;}), return 1;
new Deferred(function() {return 2;}) }),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
], ],
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Contains reject // Contains reject
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
'data' => ['nest' => ['test' => null]], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1] 'path' => ['nest', 'test', 1],
] ],
] ],
] ]
); );
} }
@ -458,22 +516,21 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ 1, null, 2 ], [1, null, 2],
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10 ]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -482,18 +539,24 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
null, null,
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
} }
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$this->check($testType, $testData, $expected, $debug);
}
/** /**
* [T!]! * [T!]!
*/ */
@ -501,42 +564,42 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, 2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
// Returns null // Returns null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -544,19 +607,19 @@ class ListsTest extends TestCase
// Rejected // Rejected
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
function () { function () {
return new Deferred(function() { return new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}); });
}, },
[ [
'data' => ['nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -569,38 +632,38 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -609,83 +672,27 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
'data' => ['nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
private function checkHandlesNullableLists($testData, $expected)
{
$testType = Type::listOf(Type::int());
$this->check($testType, $testData, $expected);
}
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
private function checkHandlesListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::listOf(Type::nonNull(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$this->check($testType, $testData, $expected, $debug);
}
private function check($testType, $testData, $expected, $debug = false)
{
$data = ['test' => $testData];
$dataType = null;
$dataType = new ObjectType([
'name' => 'DataType',
'fields' => function () use (&$testType, &$dataType, $data) {
return [
'test' => [
'type' => $testType
],
'nest' => [
'type' => $dataType,
'resolve' => function () use ($data) {
return $data;
}
]
];
}
]);
$schema = new Schema([
'query' => $dataType
]);
$ast = Parser::parse('{ nest { test } }');
$result = Executor::execute($schema, $ast, $data);
$this->assertArraySubset($expected, $result->toArray($debug));
}
} }

View File

@ -1,27 +1,26 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation; use GraphQL\Tests\Executor\TestClasses\Root;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class MutationsTest extends TestCase class MutationsTest extends TestCase
{ {
// Execute: Handles mutation execution ordering // Execute: Handles mutation execution ordering
/** /**
* @see it('evaluates mutations serially') * @see it('evaluates mutations serially')
*/ */
public function testEvaluatesMutationsSerially() : void public function testEvaluatesMutationsSerially() : void
{ {
$doc = 'mutation M { $doc = 'mutation M {
first: immediatelyChangeTheNumber(newNumber: 1) { first: immediatelyChangeTheNumber(newNumber: 1) {
theNumber theNumber
}, },
@ -38,36 +37,79 @@ class MutationsTest extends TestCase
theNumber theNumber
} }
}'; }';
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6)); $mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [ $expected = [
'data' => [ 'data' => [
'first' => [ 'first' => ['theNumber' => 1],
'theNumber' => 1 'second' => ['theNumber' => 2],
], 'third' => ['theNumber' => 3],
'second' => [ 'fourth' => ['theNumber' => 4],
'theNumber' => 2 'fifth' => ['theNumber' => 5],
], ],
'third' => [
'theNumber' => 3
],
'fourth' => [
'theNumber' => 4
],
'fifth' => [
'theNumber' => 5
]
]
]; ];
$this->assertEquals($expected, $mutationResult->toArray()); $this->assertEquals($expected, $mutationResult->toArray());
} }
private function schema() : Schema
{
$numberHolderType = new ObjectType([
'fields' => [
'theNumber' => ['type' => Type::int()],
],
'name' => 'NumberHolder',
]);
$schema = new Schema([
'query' => new ObjectType([
'fields' => [
'numberHolder' => ['type' => $numberHolderType],
],
'name' => 'Query',
]),
'mutation' => new ObjectType([
'fields' => [
'immediatelyChangeTheNumber' => [
'type' => $numberHolderType,
'args' => ['newNumber' => ['type' => Type::int()]],
'resolve' => function (Root $obj, $args) {
return $obj->immediatelyChangeTheNumber($args['newNumber']);
},
],
'promiseToChangeTheNumber' => [
'type' => $numberHolderType,
'args' => ['newNumber' => ['type' => Type::int()]],
'resolve' => function (Root $obj, $args) {
return $obj->promiseToChangeTheNumber($args['newNumber']);
},
],
'failToChangeTheNumber' => [
'type' => $numberHolderType,
'args' => ['newNumber' => ['type' => Type::int()]],
'resolve' => function (Root $obj, $args) {
$obj->failToChangeTheNumber();
},
],
'promiseAndFailToChangeTheNumber' => [
'type' => $numberHolderType,
'args' => ['newNumber' => ['type' => Type::int()]],
'resolve' => function (Root $obj, $args) {
return $obj->promiseAndFailToChangeTheNumber();
},
],
],
'name' => 'Mutation',
]),
]);
return $schema;
}
/** /**
* @see it('evaluates mutations correctly in the presense of a failed mutation') * @see it('evaluates mutations correctly in the presense of a failed mutation')
*/ */
public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() : void public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() : void
{ {
$doc = 'mutation M { $doc = 'mutation M {
first: immediatelyChangeTheNumber(newNumber: 1) { first: immediatelyChangeTheNumber(newNumber: 1) {
theNumber theNumber
}, },
@ -87,147 +129,28 @@ class MutationsTest extends TestCase
theNumber theNumber
} }
}'; }';
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6)); $mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [ $expected = [
'data' => [ 'data' => [
'first' => [ 'first' => ['theNumber' => 1],
'theNumber' => 1 'second' => ['theNumber' => 2],
], 'third' => null,
'second' => [ 'fourth' => ['theNumber' => 4],
'theNumber' => 2 'fifth' => ['theNumber' => 5],
], 'sixth' => null,
'third' => null,
'fourth' => [
'theNumber' => 4
],
'fifth' => [
'theNumber' => 5
],
'sixth' => null,
], ],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot change the number', 'debugMessage' => 'Cannot change the number',
'locations' => [['line' => 8, 'column' => 7]] 'locations' => [['line' => 8, 'column' => 7]],
], ],
[ [
'debugMessage' => 'Cannot change the number', 'debugMessage' => 'Cannot change the number',
'locations' => [['line' => 17, 'column' => 7]] 'locations' => [['line' => 17, 'column' => 7]],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $mutationResult->toArray(true)); $this->assertArraySubset($expected, $mutationResult->toArray(true));
} }
private function schema()
{
$numberHolderType = new ObjectType([
'fields' => [
'theNumber' => ['type' => Type::int()],
],
'name' => 'NumberHolder',
]);
$schema = new Schema([
'query' => new ObjectType([
'fields' => [
'numberHolder' => ['type' => $numberHolderType],
],
'name' => 'Query',
]),
'mutation' => new ObjectType([
'fields' => [
'immediatelyChangeTheNumber' => [
'type' => $numberHolderType,
'args' => ['newNumber' => ['type' => Type::int()]],
'resolve' => (function (Root $obj, $args) {
return $obj->immediatelyChangeTheNumber($args['newNumber']);
})
],
'promiseToChangeTheNumber' => [
'type' => $numberHolderType,
'args' => ['newNumber' => ['type' => Type::int()]],
'resolve' => (function (Root $obj, $args) {
return $obj->promiseToChangeTheNumber($args['newNumber']);
})
],
'failToChangeTheNumber' => [
'type' => $numberHolderType,
'args' => ['newNumber' => ['type' => Type::int()]],
'resolve' => (function (Root $obj, $args) {
return $obj->failToChangeTheNumber($args['newNumber']);
})
],
'promiseAndFailToChangeTheNumber' => [
'type' => $numberHolderType,
'args' => ['newNumber' => ['type' => Type::int()]],
'resolve' => (function (Root $obj, $args) {
return $obj->promiseAndFailToChangeTheNumber($args['newNumber']);
})
]
],
'name' => 'Mutation',
])
]);
return $schema;
}
}
class NumberHolder
{
public $theNumber;
public function __construct($originalNumber)
{
$this->theNumber = $originalNumber;
}
}
class Root {
public $numberHolder;
public function __construct($originalNumber)
{
$this->numberHolder = new NumberHolder($originalNumber);
}
/**
* @param $newNumber
* @return NumberHolder
*/
public function immediatelyChangeTheNumber($newNumber)
{
$this->numberHolder->theNumber = $newNumber;
return $this->numberHolder;
}
/**
* @param $newNumber
*
* @return Deferred
*/
public function promiseToChangeTheNumber($newNumber)
{
return new Deferred(function () use ($newNumber) {
return $this->immediatelyChangeTheNumber($newNumber);
});
}
/**
* @throws \Exception
*/
public function failToChangeTheNumber()
{
throw new \Exception('Cannot change the number');
}
/**
* @return Deferred
*/
public function promiseAndFailToChangeTheNumber()
{
return new Deferred(function () {
throw new \Exception("Cannot change the number");
});
}
} }

View File

@ -1,16 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\FormattedError;
use GraphQL\Error\UserError; use GraphQL\Error\UserError;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class NonNullTest extends TestCase class NonNullTest extends TestCase
@ -27,41 +29,46 @@ class NonNullTest extends TestCase
/** @var \Exception */ /** @var \Exception */
public $promiseNonNullError; public $promiseNonNullError;
/** @var callable[] */
public $throwingData; public $throwingData;
/** @var callable[] */
public $nullingData; public $nullingData;
/** @var Schema */
public $schema; public $schema;
public function setUp() public function setUp()
{ {
$this->syncError = new UserError('sync'); $this->syncError = new UserError('sync');
$this->syncNonNullError = new UserError('syncNonNull'); $this->syncNonNullError = new UserError('syncNonNull');
$this->promiseError = new UserError('promise'); $this->promiseError = new UserError('promise');
$this->promiseNonNullError = new UserError('promiseNonNull'); $this->promiseNonNullError = new UserError('promiseNonNull');
$this->throwingData = [ $this->throwingData = [
'sync' => function () { 'sync' => function () {
throw $this->syncError; throw $this->syncError;
}, },
'syncNonNull' => function () { 'syncNonNull' => function () {
throw $this->syncNonNullError; throw $this->syncNonNullError;
}, },
'promise' => function () { 'promise' => function () {
return new Deferred(function () { return new Deferred(function () {
throw $this->promiseError; throw $this->promiseError;
}); });
}, },
'promiseNonNull' => function () { 'promiseNonNull' => function () {
return new Deferred(function () { return new Deferred(function () {
throw $this->promiseNonNullError; throw $this->promiseNonNullError;
}); });
}, },
'syncNest' => function () { 'syncNest' => function () {
return $this->throwingData; return $this->throwingData;
}, },
'syncNonNullNest' => function () { 'syncNonNullNest' => function () {
return $this->throwingData; return $this->throwingData;
}, },
'promiseNest' => function () { 'promiseNest' => function () {
return new Deferred(function () { return new Deferred(function () {
return $this->throwingData; return $this->throwingData;
}); });
@ -74,29 +81,29 @@ class NonNullTest extends TestCase
]; ];
$this->nullingData = [ $this->nullingData = [
'sync' => function () { 'sync' => function () {
return null; return null;
}, },
'syncNonNull' => function () { 'syncNonNull' => function () {
return null; return null;
}, },
'promise' => function () { 'promise' => function () {
return new Deferred(function () { return new Deferred(function () {
return null; return null;
}); });
}, },
'promiseNonNull' => function () { 'promiseNonNull' => function () {
return new Deferred(function () { return new Deferred(function () {
return null; return null;
}); });
}, },
'syncNest' => function () { 'syncNest' => function () {
return $this->nullingData; return $this->nullingData;
}, },
'syncNonNullNest' => function () { 'syncNonNullNest' => function () {
return $this->nullingData; return $this->nullingData;
}, },
'promiseNest' => function () { 'promiseNest' => function () {
return new Deferred(function () { return new Deferred(function () {
return $this->nullingData; return $this->nullingData;
}); });
@ -109,19 +116,19 @@ class NonNullTest extends TestCase
]; ];
$dataType = new ObjectType([ $dataType = new ObjectType([
'name' => 'DataType', 'name' => 'DataType',
'fields' => function() use (&$dataType) { 'fields' => function () use (&$dataType) {
return [ return [
'sync' => ['type' => Type::string()], 'sync' => ['type' => Type::string()],
'syncNonNull' => ['type' => Type::nonNull(Type::string())], 'syncNonNull' => ['type' => Type::nonNull(Type::string())],
'promise' => Type::string(), 'promise' => Type::string(),
'promiseNonNull' => Type::nonNull(Type::string()), 'promiseNonNull' => Type::nonNull(Type::string()),
'syncNest' => $dataType, 'syncNest' => $dataType,
'syncNonNullNest' => Type::nonNull($dataType), 'syncNonNullNest' => Type::nonNull($dataType),
'promiseNest' => $dataType, 'promiseNest' => $dataType,
'promiseNonNullNest' => Type::nonNull($dataType), 'promiseNonNullNest' => Type::nonNull($dataType),
]; ];
} },
]); ]);
$this->schema = new Schema(['query' => $dataType]); $this->schema = new Schema(['query' => $dataType]);
@ -143,17 +150,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['sync' => null],
'sync' => null,
],
'errors' => [ 'errors' => [
FormattedError::create( FormattedError::create(
$this->syncError->getMessage(), $this->syncError->getMessage(),
[new SourceLocation(3, 9)] [new SourceLocation(3, 9)]
) ),
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
);
} }
public function testNullsANullableFieldThatThrowsInAPromise() : void public function testNullsANullableFieldThatThrowsInAPromise() : void
@ -167,18 +175,19 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promise' => null],
'promise' => null,
],
'errors' => [ 'errors' => [
FormattedError::create( FormattedError::create(
$this->promiseError->getMessage(), $this->promiseError->getMessage(),
[new SourceLocation(3, 9)] [new SourceLocation(3, 9)]
) ),
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
);
} }
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously() : void public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously() : void
@ -195,14 +204,15 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null
],
'errors' => [ 'errors' => [
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]) FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]),
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
);
} }
public function testNullsAsynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsInAPromise() : void public function testNullsAsynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsInAPromise() : void
@ -218,15 +228,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null
],
'errors' => [ 'errors' => [
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]) FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]),
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
);
} }
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsSynchronously() : void public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsSynchronously() : void
@ -242,15 +253,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null
],
'errors' => [ 'errors' => [
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]) FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]),
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
);
} }
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsInAPromise() : void public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsInAPromise() : void
@ -266,15 +278,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null
],
'errors' => [ 'errors' => [
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]) FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]),
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
);
} }
/** /**
@ -314,28 +327,28 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => [
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
], ],
@ -353,10 +366,13 @@ class NonNullTest extends TestCase
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(17, 11)]), FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(17, 11)]),
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(20, 13)]), FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(20, 13)]),
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(24, 13)]), FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(24, 13)]),
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
);
} }
public function testNullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfFieldsThatAreNonNull() : void public function testNullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfFieldsThatAreNonNull() : void
@ -413,10 +429,10 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => [
'syncNest' => null, 'syncNest' => null,
'promiseNest' => null, 'promiseNest' => null,
'anotherNest' => null, 'anotherNest' => null,
'anotherPromiseNest' => null, 'anotherPromiseNest' => null,
], ],
'errors' => [ 'errors' => [
@ -424,10 +440,13 @@ class NonNullTest extends TestCase
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(19, 19)]), FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(19, 19)]),
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(30, 19)]), FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(30, 19)]),
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(41, 19)]), FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(41, 19)]),
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
);
} }
public function testNullsANullableFieldThatSynchronouslyReturnsNull() : void public function testNullsANullableFieldThatSynchronouslyReturnsNull() : void
@ -441,11 +460,12 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['sync' => null],
'sync' => null,
]
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); $this->assertEquals(
$expected,
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()
);
} }
public function testNullsANullableFieldThatReturnsNullInAPromise() : void public function testNullsANullableFieldThatReturnsNullInAPromise() : void
@ -459,12 +479,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promise' => null],
'promise' => null,
]
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()
);
} }
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullSynchronously() : void public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullSynchronously() : void
@ -480,17 +501,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
] ],
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true)); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true)
);
} }
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise() : void public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise() : void
@ -506,15 +528,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null,
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -536,15 +556,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null,
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -566,15 +584,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null,
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -618,31 +634,31 @@ class NonNullTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
] ],
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
] ],
] ],
] ],
]; ];
$actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(); $actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray();
@ -703,18 +719,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => [
'syncNest' => null, 'syncNest' => null,
'promiseNest' => null, 'promiseNest' => null,
'anotherNest' => null, 'anotherNest' => null,
'anotherPromiseNest' => null, 'anotherPromiseNest' => null,
], ],
'errors' => [ 'errors' => [
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [ ['line' => 8, 'column' => 19]]], ['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 8, 'column' => 19]]],
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [ ['line' => 19, 'column' => 19]]], ['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 19, 'column' => 19]]],
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [ ['line' => 30, 'column' => 19]]], ['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 30, 'column' => 19]]],
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [ ['line' => 41, 'column' => 19]]], ['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 41, 'column' => 19]]],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -734,10 +750,10 @@ class NonNullTest extends TestCase
$expected = [ $expected = [
'errors' => [ 'errors' => [
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(2, 17)]) FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(2, 17)]),
] ],
]; ];
$actual = Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray(); $actual = Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray();
$this->assertArraySubset($expected, $actual); $this->assertArraySubset($expected, $actual);
} }
@ -752,10 +768,13 @@ class NonNullTest extends TestCase
$expected = [ $expected = [
'errors' => [ 'errors' => [
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(2, 17)]), FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(2, 17)]),
] ],
]; ];
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); $this->assertArraySubset(
$expected,
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
);
} }
public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() : void public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() : void
@ -769,9 +788,9 @@ class NonNullTest extends TestCase
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
'locations' => [['line' => 2, 'column' => 17]] 'locations' => [['line' => 2, 'column' => 17]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
$expected, $expected,
@ -791,9 +810,9 @@ class NonNullTest extends TestCase
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 2, 'column' => 17]] 'locations' => [['line' => 2, 'column' => 17]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(

View File

@ -1,17 +1,17 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise; namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter; use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use React\Promise\Deferred; use React\Promise\Deferred;
use React\Promise\FulfilledPromise; use React\Promise\FulfilledPromise;
use React\Promise\LazyPromise; use React\Promise\LazyPromise;
use React\Promise\Promise as ReactPromise; use React\Promise\Promise as ReactPromise;
use React\Promise\RejectedPromise; use React\Promise\RejectedPromise;
use function class_exists;
/** /**
* @group ReactPromise * @group ReactPromise
@ -20,19 +20,29 @@ class ReactPromiseAdapterTest extends TestCase
{ {
public function setUp() public function setUp()
{ {
if(! class_exists('React\Promise\Promise')) { if (class_exists('React\Promise\Promise')) {
$this->markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest'); return;
} }
$this->markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest');
} }
public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven() : void public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven() : void
{ {
$reactAdapter = new ReactPromiseAdapter(); $reactAdapter = new ReactPromiseAdapter();
$this->assertSame(true, $reactAdapter->isThenable(new ReactPromise(function() {}))); $this->assertSame(
true,
$reactAdapter->isThenable(new ReactPromise(function () {
}))
);
$this->assertSame(true, $reactAdapter->isThenable(new FulfilledPromise())); $this->assertSame(true, $reactAdapter->isThenable(new FulfilledPromise()));
$this->assertSame(true, $reactAdapter->isThenable(new RejectedPromise())); $this->assertSame(true, $reactAdapter->isThenable(new RejectedPromise()));
$this->assertSame(true, $reactAdapter->isThenable(new LazyPromise(function() {}))); $this->assertSame(
true,
$reactAdapter->isThenable(new LazyPromise(function () {
}))
);
$this->assertSame(false, $reactAdapter->isThenable(false)); $this->assertSame(false, $reactAdapter->isThenable(false));
$this->assertSame(false, $reactAdapter->isThenable(true)); $this->assertSame(false, $reactAdapter->isThenable(true));
$this->assertSame(false, $reactAdapter->isThenable(1)); $this->assertSame(false, $reactAdapter->isThenable(1));
@ -58,13 +68,16 @@ class ReactPromiseAdapterTest extends TestCase
{ {
$reactAdapter = new ReactPromiseAdapter(); $reactAdapter = new ReactPromiseAdapter();
$reactPromise = new FulfilledPromise(1); $reactPromise = new FulfilledPromise(1);
$promise = $reactAdapter->convertThenable($reactPromise); $promise = $reactAdapter->convertThenable($reactPromise);
$result = null; $result = null;
$resultPromise = $reactAdapter->then($promise, function ($value) use (&$result) { $resultPromise = $reactAdapter->then(
$result = $value; $promise,
}); function ($value) use (&$result) {
$result = $value;
}
);
$this->assertSame(1, $result); $this->assertSame(1, $result);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise);
@ -73,9 +86,9 @@ class ReactPromiseAdapterTest extends TestCase
public function testCreate() : void public function testCreate() : void
{ {
$reactAdapter = new ReactPromiseAdapter(); $reactAdapter = new ReactPromiseAdapter();
$resolvedPromise = $reactAdapter->create(function ($resolve) { $resolvedPromise = $reactAdapter->create(function ($resolve) {
$resolve(1); $resolve(1);
}); });
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resolvedPromise); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resolvedPromise);
@ -84,7 +97,7 @@ class ReactPromiseAdapterTest extends TestCase
$result = null; $result = null;
$resolvedPromise->then(function ($value) use (&$result) { $resolvedPromise->then(function ($value) use (&$result) {
$result = $value; $result = $value;
}); });
$this->assertSame(1, $result); $this->assertSame(1, $result);
@ -92,7 +105,7 @@ class ReactPromiseAdapterTest extends TestCase
public function testCreateFulfilled() : void public function testCreateFulfilled() : void
{ {
$reactAdapter = new ReactPromiseAdapter(); $reactAdapter = new ReactPromiseAdapter();
$fulfilledPromise = $reactAdapter->createFulfilled(1); $fulfilledPromise = $reactAdapter->createFulfilled(1);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $fulfilledPromise); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $fulfilledPromise);
@ -109,7 +122,7 @@ class ReactPromiseAdapterTest extends TestCase
public function testCreateRejected() : void public function testCreateRejected() : void
{ {
$reactAdapter = new ReactPromiseAdapter(); $reactAdapter = new ReactPromiseAdapter();
$rejectedPromise = $reactAdapter->createRejected(new \Exception('I am a bad promise')); $rejectedPromise = $reactAdapter->createRejected(new \Exception('I am a bad promise'));
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $rejectedPromise); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $rejectedPromise);
@ -117,9 +130,12 @@ class ReactPromiseAdapterTest extends TestCase
$exception = null; $exception = null;
$rejectedPromise->then(null, function ($error) use (&$exception) { $rejectedPromise->then(
$exception = $error; null,
}); function ($error) use (&$exception) {
$exception = $error;
}
);
$this->assertInstanceOf('\Exception', $exception); $this->assertInstanceOf('\Exception', $exception);
$this->assertEquals('I am a bad promise', $exception->getMessage()); $this->assertEquals('I am a bad promise', $exception->getMessage());
@ -128,7 +144,7 @@ class ReactPromiseAdapterTest extends TestCase
public function testAll() : void public function testAll() : void
{ {
$reactAdapter = new ReactPromiseAdapter(); $reactAdapter = new ReactPromiseAdapter();
$promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)]; $promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)];
$allPromise = $reactAdapter->all($promises); $allPromise = $reactAdapter->all($promises);
@ -138,7 +154,7 @@ class ReactPromiseAdapterTest extends TestCase
$result = null; $result = null;
$allPromise->then(function ($values) use (&$result) { $allPromise->then(function ($values) use (&$result) {
$result = $values; $result = $values;
}); });
$this->assertSame([1, 2, 3], $result); $this->assertSame([1, 2, 3], $result);
@ -147,9 +163,9 @@ class ReactPromiseAdapterTest extends TestCase
public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromises() : void public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromises() : void
{ {
$reactAdapter = new ReactPromiseAdapter(); $reactAdapter = new ReactPromiseAdapter();
$deferred = new Deferred(); $deferred = new Deferred();
$promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)]; $promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)];
$result = null; $result = null;
$reactAdapter->all($promises)->then(function ($values) use (&$result) { $reactAdapter->all($promises)->then(function ($values) use (&$result) {
$result = $values; $result = $values;

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise; namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Deferred; use GraphQL\Deferred;
@ -10,9 +13,7 @@ use PHPUnit\Framework\TestCase;
class SyncPromiseAdapterTest extends TestCase class SyncPromiseAdapterTest extends TestCase
{ {
/** /** @var SyncPromiseAdapter */
* @var SyncPromiseAdapter
*/
private $promises; private $promises;
public function setUp() public function setUp()
@ -22,7 +23,11 @@ class SyncPromiseAdapterTest extends TestCase
public function testIsThenable() : void public function testIsThenable() : void
{ {
$this->assertEquals(true, $this->promises->isThenable(new Deferred(function() {}))); $this->assertEquals(
true,
$this->promises->isThenable(new Deferred(function () {
}))
);
$this->assertEquals(false, $this->promises->isThenable(false)); $this->assertEquals(false, $this->promises->isThenable(false));
$this->assertEquals(false, $this->promises->isThenable(true)); $this->assertEquals(false, $this->promises->isThenable(true));
$this->assertEquals(false, $this->promises->isThenable(1)); $this->assertEquals(false, $this->promises->isThenable(1));
@ -35,7 +40,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testConvert() : void public function testConvert() : void
{ {
$dfd = new Deferred(function() {}); $dfd = new Deferred(function () {
});
$result = $this->promises->convertThenable($dfd); $result = $this->promises->convertThenable($dfd);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $result); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $result);
@ -48,7 +54,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testThen() : void public function testThen() : void
{ {
$dfd = new Deferred(function() {}); $dfd = new Deferred(function () {
});
$promise = $this->promises->convertThenable($dfd); $promise = $this->promises->convertThenable($dfd);
$result = $this->promises->then($promise); $result = $this->promises->then($promise);
@ -59,18 +66,55 @@ class SyncPromiseAdapterTest extends TestCase
public function testCreatePromise() : void public function testCreatePromise() : void
{ {
$promise = $this->promises->create(function($resolve, $reject) {}); $promise = $this->promises->create(function ($resolve, $reject) {
});
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise);
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise); $this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise);
$promise = $this->promises->create(function($resolve, $reject) { $promise = $this->promises->create(function ($resolve, $reject) {
$resolve('A'); $resolve('A');
}); });
$this->assertValidPromise($promise, null, 'A', SyncPromise::FULFILLED); $this->assertValidPromise($promise, null, 'A', SyncPromise::FULFILLED);
} }
private function assertValidPromise($promise, $expectedNextReason, $expectedNextValue, $expectedNextState)
{
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise);
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise);
$actualNextValue = null;
$actualNextReason = null;
$onFulfilledCalled = false;
$onRejectedCalled = false;
$promise->then(
function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
$onFulfilledCalled = true;
$actualNextValue = $nextValue;
},
function (\Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) {
$onRejectedCalled = true;
$actualNextReason = $reason->getMessage();
}
);
$this->assertSame($onFulfilledCalled, false);
$this->assertSame($onRejectedCalled, false);
SyncPromise::runQueue();
if ($expectedNextState !== SyncPromise::PENDING) {
$this->assertSame(! $expectedNextReason, $onFulfilledCalled);
$this->assertSame(! ! $expectedNextReason, $onRejectedCalled);
}
$this->assertSame($expectedNextValue, $actualNextValue);
$this->assertSame($expectedNextReason, $actualNextReason);
$this->assertSame($expectedNextState, $promise->adoptedPromise->state);
}
public function testCreateFulfilledPromise() : void public function testCreateFulfilledPromise() : void
{ {
$promise = $this->promises->createFulfilled('test'); $promise = $this->promises->createFulfilled('test');
@ -94,8 +138,8 @@ class SyncPromiseAdapterTest extends TestCase
$promise1 = new SyncPromise(); $promise1 = new SyncPromise();
$promise2 = new SyncPromise(); $promise2 = new SyncPromise();
$promise3 = $promise2->then( $promise3 = $promise2->then(
function($value) { function ($value) {
return $value .'-value3'; return $value . '-value3';
} }
); );
@ -105,7 +149,7 @@ class SyncPromiseAdapterTest extends TestCase
new Promise($promise2, $this->promises), new Promise($promise2, $this->promises),
3, 3,
new Promise($promise3, $this->promises), new Promise($promise3, $this->promises),
[] [],
]; ];
$promise = $this->promises->all($data); $promise = $this->promises->all($data);
@ -114,36 +158,46 @@ class SyncPromiseAdapterTest extends TestCase
$promise1->resolve('value1'); $promise1->resolve('value1');
$this->assertValidPromise($promise, null, null, SyncPromise::PENDING); $this->assertValidPromise($promise, null, null, SyncPromise::PENDING);
$promise2->resolve('value2'); $promise2->resolve('value2');
$this->assertValidPromise($promise, null, ['1', 'value1', 'value2', 3, 'value2-value3', []], SyncPromise::FULFILLED); $this->assertValidPromise(
$promise,
null,
['1', 'value1', 'value2', 3, 'value2-value3', []],
SyncPromise::FULFILLED
);
} }
public function testWait() : void public function testWait() : void
{ {
$called = []; $called = [];
$deferred1 = new Deferred(function() use (&$called) { $deferred1 = new Deferred(function () use (&$called) {
$called[] = 1; $called[] = 1;
return 1; return 1;
}); });
$deferred2 = new Deferred(function() use (&$called) { $deferred2 = new Deferred(function () use (&$called) {
$called[] = 2; $called[] = 2;
return 2; return 2;
}); });
$p1 = $this->promises->convertThenable($deferred1); $p1 = $this->promises->convertThenable($deferred1);
$p2 = $this->promises->convertThenable($deferred2); $p2 = $this->promises->convertThenable($deferred2);
$p3 = $p2->then(function() use (&$called) { $p3 = $p2->then(function () use (&$called) {
$dfd = new Deferred(function() use (&$called) { $dfd = new Deferred(function () use (&$called) {
$called[] = 3; $called[] = 3;
return 3; return 3;
}); });
return $this->promises->convertThenable($dfd); return $this->promises->convertThenable($dfd);
}); });
$p4 = $p3->then(function() use (&$called) { $p4 = $p3->then(function () use (&$called) {
return new Deferred(function() use (&$called) { return new Deferred(function () use (&$called) {
$called[] = 4; $called[] = 4;
return 4; return 4;
}); });
}); });
@ -156,46 +210,10 @@ class SyncPromiseAdapterTest extends TestCase
$this->assertEquals(SyncPromise::PENDING, $all->adoptedPromise->state); $this->assertEquals(SyncPromise::PENDING, $all->adoptedPromise->state);
$this->assertEquals([1, 2], $called); $this->assertEquals([1, 2], $called);
$expectedResult = [0,1,2,3,4]; $expectedResult = [0, 1, 2, 3, 4];
$result = $this->promises->wait($all); $result = $this->promises->wait($all);
$this->assertEquals($expectedResult, $result); $this->assertEquals($expectedResult, $result);
$this->assertEquals([1, 2, 3, 4], $called); $this->assertEquals([1, 2, 3, 4], $called);
$this->assertValidPromise($all, null, [0,1,2,3,4], SyncPromise::FULFILLED); $this->assertValidPromise($all, null, [0, 1, 2, 3, 4], SyncPromise::FULFILLED);
}
private function assertValidPromise($promise, $expectedNextReason, $expectedNextValue, $expectedNextState)
{
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise);
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise);
$actualNextValue = null;
$actualNextReason = null;
$onFulfilledCalled = false;
$onRejectedCalled = false;
$promise->then(
function($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
$onFulfilledCalled = true;
$actualNextValue = $nextValue;
},
function(\Exception $reason) use (&$actualNextReason, &$onRejectedCalled) {
$onRejectedCalled = true;
$actualNextReason = $reason->getMessage();
}
);
$this->assertSame($onFulfilledCalled, false);
$this->assertSame($onRejectedCalled, false);
SyncPromise::runQueue();
if ($expectedNextState !== SyncPromise::PENDING) {
$this->assertSame(!$expectedNextReason, $onFulfilledCalled);
$this->assertSame(!!$expectedNextReason, $onRejectedCalled);
}
$this->assertSame($expectedNextValue, $actualNextValue);
$this->assertSame($expectedNextReason, $actualNextReason);
$this->assertSame($expectedNextState, $promise->adoptedPromise->state);
} }
} }

View File

@ -1,25 +1,32 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise; namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\SyncPromise; use GraphQL\Executor\Promise\Adapter\SyncPromise;
use PHPUnit\Framework\Error\Error; use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function uniqid;
class SyncPromiseTest extends TestCase class SyncPromiseTest extends TestCase
{ {
public function getFulfilledPromiseResolveData() public function getFulfilledPromiseResolveData()
{ {
$onFulfilledReturnsNull = function() { $onFulfilledReturnsNull = function () {
return null; return null;
}; };
$onFulfilledReturnsSameValue = function($value) {
$onFulfilledReturnsSameValue = function ($value) {
return $value; return $value;
}; };
$onFulfilledReturnsOtherValue = function($value) {
$onFulfilledReturnsOtherValue = function ($value) {
return 'other-' . $value; return 'other-' . $value;
}; };
$onFulfilledThrows = function($value) {
throw new \Exception("onFulfilled throws this!"); $onFulfilledThrows = function ($value) {
throw new \Exception('onFulfilled throws this!');
}; };
return [ return [
@ -28,7 +35,7 @@ class SyncPromiseTest extends TestCase
[uniqid(), $onFulfilledReturnsNull, null, null, SyncPromise::FULFILLED], [uniqid(), $onFulfilledReturnsNull, null, null, SyncPromise::FULFILLED],
['test-value', $onFulfilledReturnsSameValue, 'test-value', null, SyncPromise::FULFILLED], ['test-value', $onFulfilledReturnsSameValue, 'test-value', null, SyncPromise::FULFILLED],
['test-value-2', $onFulfilledReturnsOtherValue, 'other-test-value-2', null, SyncPromise::FULFILLED], ['test-value-2', $onFulfilledReturnsOtherValue, 'other-test-value-2', null, SyncPromise::FULFILLED],
['test-value-3', $onFulfilledThrows, null, "onFulfilled throws this!", SyncPromise::REJECTED], ['test-value-3', $onFulfilledThrows, null, 'onFulfilled throws this!', SyncPromise::REJECTED],
]; ];
} }
@ -41,8 +48,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -63,8 +69,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -85,21 +90,27 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
$promise->resolve($resolvedValue); $promise->resolve($resolvedValue);
$this->assertEquals(SyncPromise::FULFILLED, $promise->state); $this->assertEquals(SyncPromise::FULFILLED, $promise->state);
$nextPromise = $promise->then(null, function() {}); $nextPromise = $promise->then(
null,
function () {
}
);
$this->assertSame($promise, $nextPromise); $this->assertSame($promise, $nextPromise);
$onRejectedCalled = false; $onRejectedCalled = false;
$nextPromise = $promise->then($onFulfilled, function () use (&$onRejectedCalled) { $nextPromise = $promise->then(
$onRejectedCalled = true; $onFulfilled,
}); function () use (&$onRejectedCalled) {
$onRejectedCalled = true;
}
);
if ($onFulfilled) { if ($onFulfilled) {
$this->assertNotSame($promise, $nextPromise); $this->assertNotSame($promise, $nextPromise);
@ -124,19 +135,57 @@ class SyncPromiseTest extends TestCase
$this->assertValidPromise($nextPromise3, $expectedNextReason, $expectedNextValue, $expectedNextState); $this->assertValidPromise($nextPromise3, $expectedNextReason, $expectedNextValue, $expectedNextState);
} }
private function assertValidPromise(
SyncPromise $promise,
$expectedNextReason,
$expectedNextValue,
$expectedNextState
) {
$actualNextValue = null;
$actualNextReason = null;
$onFulfilledCalled = false;
$onRejectedCalled = false;
$promise->then(
function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
$onFulfilledCalled = true;
$actualNextValue = $nextValue;
},
function (\Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) {
$onRejectedCalled = true;
$actualNextReason = $reason->getMessage();
}
);
$this->assertEquals($onFulfilledCalled, false);
$this->assertEquals($onRejectedCalled, false);
SyncPromise::runQueue();
$this->assertEquals(! $expectedNextReason, $onFulfilledCalled);
$this->assertEquals(! ! $expectedNextReason, $onRejectedCalled);
$this->assertEquals($expectedNextValue, $actualNextValue);
$this->assertEquals($expectedNextReason, $actualNextReason);
$this->assertEquals($expectedNextState, $promise->state);
}
public function getRejectedPromiseData() public function getRejectedPromiseData()
{ {
$onRejectedReturnsNull = function() { $onRejectedReturnsNull = function () {
return null; return null;
}; };
$onRejectedReturnsSomeValue = function($reason) {
$onRejectedReturnsSomeValue = function ($reason) {
return 'some-value'; return 'some-value';
}; };
$onRejectedThrowsSameReason = function($reason) {
$onRejectedThrowsSameReason = function ($reason) {
throw $reason; throw $reason;
}; };
$onRejectedThrowsOtherReason = function($value) {
throw new \Exception("onRejected throws other!"); $onRejectedThrowsOtherReason = function ($value) {
throw new \Exception('onRejected throws other!');
}; };
return [ return [
@ -158,8 +207,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -169,7 +217,6 @@ class SyncPromiseTest extends TestCase
$this->expectException(\Throwable::class); $this->expectException(\Throwable::class);
$this->expectExceptionMessage('Cannot change rejection reason'); $this->expectExceptionMessage('Cannot change rejection reason');
$promise->reject(new \Exception('other-reason')); $promise->reject(new \Exception('other-reason'));
} }
/** /**
@ -181,8 +228,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -203,8 +249,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -214,22 +259,26 @@ class SyncPromiseTest extends TestCase
try { try {
$promise->reject(new \Exception('other-reason')); $promise->reject(new \Exception('other-reason'));
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertEquals('Cannot change rejection reason', $e->getMessage()); $this->assertEquals('Cannot change rejection reason', $e->getMessage());
} }
try { try {
$promise->resolve('anything'); $promise->resolve('anything');
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertEquals('Cannot resolve rejected promise', $e->getMessage()); $this->assertEquals('Cannot resolve rejected promise', $e->getMessage());
} }
$nextPromise = $promise->then(function() {}, null); $nextPromise = $promise->then(
function () {
},
null
);
$this->assertSame($promise, $nextPromise); $this->assertSame($promise, $nextPromise);
$onFulfilledCalled = false; $onFulfilledCalled = false;
$nextPromise = $promise->then( $nextPromise = $promise->then(
function () use (&$onFulfilledCalled) { function () use (&$onFulfilledCalled) {
$onFulfilledCalled = true; $onFulfilledCalled = true;
}, },
@ -266,7 +315,7 @@ class SyncPromiseTest extends TestCase
try { try {
$promise->resolve($promise); $promise->resolve($promise);
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertEquals('Cannot resolve promise with self', $e->getMessage()); $this->assertEquals('Cannot resolve promise with self', $e->getMessage());
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
} }
@ -299,24 +348,25 @@ class SyncPromiseTest extends TestCase
throw $e; throw $e;
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
} catch (\Exception $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state);
} }
$promise->reject(new \Exception("Rejected Reason")); $promise->reject(new \Exception('Rejected Reason'));
$this->assertValidPromise($promise, "Rejected Reason", null, SyncPromise::REJECTED); $this->assertValidPromise($promise, 'Rejected Reason', null, SyncPromise::REJECTED);
$promise = new SyncPromise(); $promise = new SyncPromise();
$promise2 = $promise->then(null, function() { $promise2 = $promise->then(
return 'value'; null,
}); function () {
$promise->reject(new \Exception("Rejected Again")); return 'value';
}
);
$promise->reject(new \Exception('Rejected Again'));
$this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED); $this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED);
$promise = new SyncPromise(); $promise = new SyncPromise();
$promise2 = $promise->then(); $promise2 = $promise->then();
$promise->reject(new \Exception("Rejected Once Again")); $promise->reject(new \Exception('Rejected Once Again'));
$this->assertValidPromise($promise2, "Rejected Once Again", null, SyncPromise::REJECTED); $this->assertValidPromise($promise2, 'Rejected Once Again', null, SyncPromise::REJECTED);
} }
public function testPendingPromiseThen() : void public function testPendingPromiseThen() : void
@ -331,12 +381,14 @@ class SyncPromiseTest extends TestCase
// Make sure that it queues derivative promises until resolution: // Make sure that it queues derivative promises until resolution:
$onFulfilledCount = 0; $onFulfilledCount = 0;
$onRejectedCount = 0; $onRejectedCount = 0;
$onFulfilled = function($value) use (&$onFulfilledCount) { $onFulfilled = function ($value) use (&$onFulfilledCount) {
$onFulfilledCount++; $onFulfilledCount++;
return $onFulfilledCount; return $onFulfilledCount;
}; };
$onRejected = function($reason) use (&$onRejectedCount) {
$onRejected = function ($reason) use (&$onRejectedCount) {
$onRejectedCount++; $onRejectedCount++;
throw $reason; throw $reason;
}; };
@ -367,35 +419,4 @@ class SyncPromiseTest extends TestCase
$this->assertValidPromise($nextPromise3, null, 2, SyncPromise::FULFILLED); $this->assertValidPromise($nextPromise3, null, 2, SyncPromise::FULFILLED);
$this->assertValidPromise($nextPromise4, null, 3, SyncPromise::FULFILLED); $this->assertValidPromise($nextPromise4, null, 3, SyncPromise::FULFILLED);
} }
private function assertValidPromise(SyncPromise $promise, $expectedNextReason, $expectedNextValue, $expectedNextState)
{
$actualNextValue = null;
$actualNextReason = null;
$onFulfilledCalled = false;
$onRejectedCalled = false;
$promise->then(
function($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
$onFulfilledCalled = true;
$actualNextValue = $nextValue;
},
function(\Exception $reason) use (&$actualNextReason, &$onRejectedCalled) {
$onRejectedCalled = true;
$actualNextReason = $reason->getMessage();
}
);
$this->assertEquals($onFulfilledCalled, false);
$this->assertEquals($onRejectedCalled, false);
SyncPromise::runQueue();
$this->assertEquals(!$expectedNextReason, $onFulfilledCalled);
$this->assertEquals(!!$expectedNextReason, $onRejectedCalled);
$this->assertEquals($expectedNextValue, $actualNextValue);
$this->assertEquals($expectedNextReason, $actualNextReason);
$this->assertEquals($expectedNextState, $promise->state);
}
} }

View File

@ -1,30 +1,22 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\Adder;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
require_once __DIR__ . '/TestClasses.php';
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function json_encode;
use function uniqid;
class ResolveTest extends TestCase class ResolveTest extends TestCase
{ {
// Execute: resolve function // Execute: resolve function
private function buildSchema($testField)
{
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'test' => $testField
]
])
]);
}
/** /**
* @see it('default function accesses properties') * @see it('default function accesses properties')
*/ */
@ -32,9 +24,7 @@ class ResolveTest extends TestCase
{ {
$schema = $this->buildSchema(['type' => Type::string()]); $schema = $this->buildSchema(['type' => Type::string()]);
$source = [ $source = ['test' => 'testValue'];
'test' => 'testValue'
];
$this->assertEquals( $this->assertEquals(
['data' => ['test' => 'testValue']], ['data' => ['test' => 'testValue']],
@ -42,18 +32,28 @@ class ResolveTest extends TestCase
); );
} }
private function buildSchema($testField)
{
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['test' => $testField],
]),
]);
}
/** /**
* @see it('default function calls methods') * @see it('default function calls methods')
*/ */
public function testDefaultFunctionCallsClosures() : void public function testDefaultFunctionCallsClosures() : void
{ {
$schema = $this->buildSchema(['type' => Type::string()]); $schema = $this->buildSchema(['type' => Type::string()]);
$_secret = 'secretValue' . uniqid(); $_secret = 'secretValue' . uniqid();
$source = [ $source = [
'test' => function() use ($_secret) { 'test' => function () use ($_secret) {
return $_secret; return $_secret;
} },
]; ];
$this->assertEquals( $this->assertEquals(
['data' => ['test' => $_secret]], ['data' => ['test' => $_secret]],
@ -69,7 +69,7 @@ class ResolveTest extends TestCase
$schema = $this->buildSchema([ $schema = $this->buildSchema([
'type' => Type::int(), 'type' => Type::int(),
'args' => [ 'args' => [
'addend1' => [ 'type' => Type::int() ], 'addend1' => ['type' => Type::int()],
], ],
]); ]);
@ -85,14 +85,14 @@ class ResolveTest extends TestCase
public function testUsesProvidedResolveFunction() : void public function testUsesProvidedResolveFunction() : void
{ {
$schema = $this->buildSchema([ $schema = $this->buildSchema([
'type' => Type::string(), 'type' => Type::string(),
'args' => [ 'args' => [
'aStr' => ['type' => Type::string()], 'aStr' => ['type' => Type::string()],
'aInt' => ['type' => Type::int()], 'aInt' => ['type' => Type::int()],
], ],
'resolve' => function ($source, $args) { 'resolve' => function ($source, $args) {
return json_encode([$source, $args]); return json_encode([$source, $args]);
} },
]); ]);
$this->assertEquals( $this->assertEquals(

View File

@ -1,8 +1,10 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError; use GraphQL\Error\FormattedError;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
@ -29,37 +31,38 @@ class SyncTest extends TestCase
public function setUp() public function setUp()
{ {
$this->schema = new Schema([ $this->schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'syncField' => [ 'syncField' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($rootValue) { 'resolve' => function ($rootValue) {
return $rootValue; return $rootValue;
} },
], ],
'asyncField' => [ 'asyncField' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($rootValue) { 'resolve' => function ($rootValue) {
return new Deferred(function () use ($rootValue) { return new Deferred(function () use ($rootValue) {
return $rootValue; return $rootValue;
}); });
} },
] ],
] ],
]), ]),
'mutation' => new ObjectType([ 'mutation' => new ObjectType([
'name' => 'Mutation', 'name' => 'Mutation',
'fields' => [ 'fields' => [
'syncMutationField' => [ 'syncMutationField' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($rootValue) { 'resolve' => function ($rootValue) {
return $rootValue; return $rootValue;
} },
] ],
] ],
]) ]),
]); ]);
$this->promiseAdapter = new SyncPromiseAdapter(); $this->promiseAdapter = new SyncPromiseAdapter();
} }
@ -70,7 +73,7 @@ class SyncTest extends TestCase
*/ */
public function testDoesNotReturnAPromiseForInitialErrors() : void public function testDoesNotReturnAPromiseForInitialErrors() : void
{ {
$doc = 'fragment Example on Query { syncField }'; $doc = 'fragment Example on Query { syncField }';
$result = $this->execute( $result = $this->execute(
$this->schema, $this->schema,
Parser::parse($doc), Parser::parse($doc),
@ -79,106 +82,6 @@ class SyncTest extends TestCase
$this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result); $this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result);
} }
/**
* @see it('does not return a Promise if fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void
{
$doc = 'query Example { syncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
/**
* @see it('does not return a Promise if mutation fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void
{
$doc = 'mutation Example { syncMutationField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result);
}
/**
* @see it('returns a Promise if any field is asynchronous')
*/
public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void
{
$doc = 'query Example { syncField, asyncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
}
// Describe: graphqlSync
/**
* @see it('does not return a Promise for syntax errors')
*/
public function testDoesNotReturnAPromiseForSyntaxErrors() : void
{
$doc = 'fragment Example on Query { { { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc
);
$this->assertSync([
'errors' => [
['message' => 'Syntax Error: Expected Name, found {',
'locations' => [['line' => 1, 'column' => 29]]]
]
], $result);
}
/**
* @see it('does not return a Promise for validation errors')
*/
public function testDoesNotReturnAPromiseForValidationErrors() : void
{
$doc = 'fragment Example on Query { unknownField }';
$validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc));
$result = $this->graphqlSync(
$this->schema,
$doc
);
$expected = [
'errors' => Utils::map($validationErrors, function ($e) {
return FormattedError::createFromException($e);
})
];
$this->assertSync($expected, $result);
}
/**
* @see it('does not return a Promise for sync execution')
*/
public function testDoesNotReturnAPromiseForSyncExecution() : void
{
$doc = 'query Example { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc,
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
private function graphqlSync($schema, $doc, $rootValue = null)
{
return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
}
private function execute($schema, $doc, $rootValue = null) private function execute($schema, $doc, $rootValue = null)
{ {
return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue); return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
@ -191,7 +94,56 @@ class SyncTest extends TestCase
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message); $this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
$this->assertEquals(SyncPromise::FULFILLED, $actualResult->adoptedPromise->state, $message); $this->assertEquals(SyncPromise::FULFILLED, $actualResult->adoptedPromise->state, $message);
$this->assertInstanceOf(ExecutionResult::class, $actualResult->adoptedPromise->result, $message); $this->assertInstanceOf(ExecutionResult::class, $actualResult->adoptedPromise->result, $message);
$this->assertArraySubset($expectedFinalArray, $actualResult->adoptedPromise->result->toArray(), $message); $this->assertArraySubset(
$expectedFinalArray,
$actualResult->adoptedPromise->result->toArray(),
false,
$message
);
}
/**
* @see it('does not return a Promise if fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void
{
$doc = 'query Example { syncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
// Describe: graphqlSync
/**
* @see it('does not return a Promise if mutation fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void
{
$doc = 'mutation Example { syncMutationField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result);
}
/**
* @see it('returns a Promise if any field is asynchronous')
*/
public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void
{
$doc = 'query Example { syncField, asyncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
} }
private function assertAsync($expectedFinalArray, $actualResult) private function assertAsync($expectedFinalArray, $actualResult)
@ -202,6 +154,70 @@ class SyncTest extends TestCase
$this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message); $this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message);
$resolvedResult = $this->promiseAdapter->wait($actualResult); $resolvedResult = $this->promiseAdapter->wait($actualResult);
$this->assertInstanceOf(ExecutionResult::class, $resolvedResult, $message); $this->assertInstanceOf(ExecutionResult::class, $resolvedResult, $message);
$this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), $message); $this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), false, $message);
}
/**
* @see it('does not return a Promise for syntax errors')
*/
public function testDoesNotReturnAPromiseForSyntaxErrors() : void
{
$doc = 'fragment Example on Query { { { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc
);
$this->assertSync(
[
'errors' => [
[
'message' => 'Syntax Error: Expected Name, found {',
'locations' => [['line' => 1, 'column' => 29]],
],
],
],
$result
);
}
private function graphqlSync($schema, $doc, $rootValue = null)
{
return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
}
/**
* @see it('does not return a Promise for validation errors')
*/
public function testDoesNotReturnAPromiseForValidationErrors() : void
{
$doc = 'fragment Example on Query { unknownField }';
$validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc));
$result = $this->graphqlSync(
$this->schema,
$doc
);
$expected = [
'errors' => Utils::map(
$validationErrors,
function ($e) {
return FormattedError::createFromException($e);
}
),
];
$this->assertSync($expected, $result);
}
/**
* @see it('does not return a Promise for sync execution')
*/
public function testDoesNotReturnAPromiseForSyncExecution() : void
{
$doc = 'query Example { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc,
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
} }
} }

View File

@ -1,128 +0,0 @@
<?php
namespace GraphQL\Tests\Executor;
use GraphQL\Error\Error;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
class Dog
{
function __construct($name, $woofs)
{
$this->name = $name;
$this->woofs = $woofs;
}
}
class Cat
{
function __construct($name, $meows)
{
$this->name = $name;
$this->meows = $meows;
}
}
class Human
{
function __construct($name)
{
$this->name = $name;
}
}
class Person
{
public $name;
public $pets;
public $friends;
function __construct($name, $pets = null, $friends = null)
{
$this->name = $name;
$this->pets = $pets;
$this->friends = $friends;
}
}
class ComplexScalar extends ScalarType
{
public static function create()
{
return new self();
}
public $name = 'ComplexScalar';
/**
* {@inheritdoc}
*/
public function serialize($value)
{
if ($value === 'DeserializedValue') {
return 'SerializedValue';
}
throw new Error("Cannot serialize value as ComplexScalar: " . Utils::printSafe($value));
}
/**
* {@inheritdoc}
*/
public function parseValue($value)
{
if ($value === 'SerializedValue') {
return 'DeserializedValue';
}
throw new Error("Cannot represent value as ComplexScalar: " . Utils::printSafe($value));
}
/**
* {@inheritdoc}
*/
public function parseLiteral($valueNode, array $variables = null)
{
if ($valueNode->value === 'SerializedValue') {
return 'DeserializedValue';
}
throw new Error("Cannot represent literal as ComplexScalar: " . Utils::printSafe($valueNode->value));
}
}
class Special
{
public $value;
public function __construct($value)
{
$this->value = $value;
}
}
class NotSpecial
{
public $value;
public function __construct($value)
{
$this->value = $value;
}
}
class Adder
{
public $num;
public $test;
public function __construct($num)
{
$this->num = $num;
$this->test = function($source, $args, $context) {
return $this->num + $args['addend1'] + $context['addend2'];
};
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Adder
{
/** @var float */
public $num;
/** @var callable */
public $test;
public function __construct(float $num)
{
$this->num = $num;
$this->test = function ($source, $args, $context) {
return $this->num + $args['addend1'] + $context['addend2'];
};
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Cat
{
/** @var string */
public $name;
/** @var bool */
public $meows;
public function __construct(string $name, bool $meows)
{
$this->name = $name;
$this->meows = $meows;
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
use GraphQL\Error\Error;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
class ComplexScalar extends ScalarType
{
/** @var string */
public $name = 'ComplexScalar';
public static function create() : self
{
return new self();
}
/**
* {@inheritdoc}
*/
public function serialize($value)
{
if ($value === 'DeserializedValue') {
return 'SerializedValue';
}
throw new Error('Cannot serialize value as ComplexScalar: ' . Utils::printSafe($value));
}
/**
* {@inheritdoc}
*/
public function parseValue($value)
{
if ($value === 'SerializedValue') {
return 'DeserializedValue';
}
throw new Error('Cannot represent value as ComplexScalar: ' . Utils::printSafe($value));
}
/**
* {@inheritdoc}
*/
public function parseLiteral($valueNode, ?array $variables = null)
{
if ($valueNode->value === 'SerializedValue') {
return 'DeserializedValue';
}
throw new Error('Cannot represent literal as ComplexScalar: ' . Utils::printSafe($valueNode->value));
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Dog
{
/** @var string */
public $name;
/** @var bool */
public $woofs;
public function __construct(string $name, bool $woofs)
{
$this->name = $name;
$this->woofs = $woofs;
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Human
{
/** @var string */
public $name;
public function __construct(string $name)
{
$this->name = $name;
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class NotSpecial
{
/** @var string */
public $value;
/**
* @param string $value
*/
public function __construct($value)
{
$this->value = $value;
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class NumberHolder
{
/** @var float */
public $theNumber;
public function __construct(float $originalNumber)
{
$this->theNumber = $originalNumber;
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Person
{
/** @var string */
public $name;
/** @var (Dog|Cat)[]|null */
public $pets;
/** @var (Dog|Cat|Person)[]|null */
public $friends;
/**
* @param (Cat|Dog)[]|null $pets
* @param (Cat|Dog|Person)[]|null $friends
*/
public function __construct(string $name, $pets = null, $friends = null)
{
$this->name = $name;
$this->pets = $pets;
$this->friends = $friends;
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
use GraphQL\Deferred;
class Root
{
/** @var NumberHolder */
public $numberHolder;
public function __construct(float $originalNumber)
{
$this->numberHolder = new NumberHolder($originalNumber);
}
public function promiseToChangeTheNumber($newNumber) : Deferred
{
return new Deferred(function () use ($newNumber) {
return $this->immediatelyChangeTheNumber($newNumber);
});
}
public function immediatelyChangeTheNumber($newNumber) : NumberHolder
{
$this->numberHolder->theNumber = $newNumber;
return $this->numberHolder;
}
public function failToChangeTheNumber() : void
{
throw new \Exception('Cannot change the number');
}
public function promiseAndFailToChangeTheNumber() : Deferred
{
return new Deferred(function () {
$this->failToChangeTheNumber();
});
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Special
{
/** @var string */
public $value;
/**
* @param string $value
*/
public function __construct($value)
{
$this->value = $value;
}
}

View File

@ -1,64 +1,76 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Error\Warning;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Person;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class UnionInterfaceTest extends TestCase class UnionInterfaceTest extends TestCase
{ {
/** @var */
public $schema; public $schema;
/** @var Cat */
public $garfield; public $garfield;
/** @var Dog */
public $odie; public $odie;
/** @var Person */
public $liz; public $liz;
/** @var Person */
public $john; public $john;
public function setUp() public function setUp()
{ {
$NamedType = new InterfaceType([ $NamedType = new InterfaceType([
'name' => 'Named', 'name' => 'Named',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$NamedType], 'interfaces' => [$NamedType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()] 'woofs' => ['type' => Type::boolean()],
], ],
'isTypeOf' => function ($value) { 'isTypeOf' => function ($value) {
return $value instanceof Dog; return $value instanceof Dog;
} },
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$NamedType], 'interfaces' => [$NamedType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()] 'meows' => ['type' => Type::boolean()],
], ],
'isTypeOf' => function ($value) { 'isTypeOf' => function ($value) {
return $value instanceof Cat; return $value instanceof Cat;
} },
]); ]);
$PetType = new UnionType([ $PetType = new UnionType([
'name' => 'Pet', 'name' => 'Pet',
'types' => [$DogType, $CatType], 'types' => [$DogType, $CatType],
'resolveType' => function ($value) use ($DogType, $CatType) { 'resolveType' => function ($value) use ($DogType, $CatType) {
if ($value instanceof Dog) { if ($value instanceof Dog) {
return $DogType; return $DogType;
@ -66,32 +78,31 @@ class UnionInterfaceTest extends TestCase
if ($value instanceof Cat) { if ($value instanceof Cat) {
return $CatType; return $CatType;
} }
} },
]); ]);
$PersonType = new ObjectType([ $PersonType = new ObjectType([
'name' => 'Person', 'name' => 'Person',
'interfaces' => [$NamedType], 'interfaces' => [$NamedType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'pets' => ['type' => Type::listOf($PetType)], 'pets' => ['type' => Type::listOf($PetType)],
'friends' => ['type' => Type::listOf($NamedType)] 'friends' => ['type' => Type::listOf($NamedType)],
], ],
'isTypeOf' => function ($value) { 'isTypeOf' => function ($value) {
return $value instanceof Person; return $value instanceof Person;
} },
]); ]);
$this->schema = new Schema([ $this->schema = new Schema([
'query' => $PersonType, 'query' => $PersonType,
'types' => [ $PetType ] 'types' => [$PetType],
]); ]);
$this->garfield = new Cat('Garfield', false); $this->garfield = new Cat('Garfield', false);
$this->odie = new Dog('Odie', true); $this->odie = new Dog('Odie', true);
$this->liz = new Person('Liz'); $this->liz = new Person('Liz');
$this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]); $this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]);
} }
// Execute: Union and intersection types // Execute: Union and intersection types
@ -101,7 +112,6 @@ class UnionInterfaceTest extends TestCase
*/ */
public function testCanIntrospectOnUnionAndIntersectionTypes() : void public function testCanIntrospectOnUnionAndIntersectionTypes() : void
{ {
$ast = Parser::parse(' $ast = Parser::parse('
{ {
Named: __type(name: "Named") { Named: __type(name: "Named") {
@ -128,33 +138,33 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'Named' => [ 'Named' => [
'kind' => 'INTERFACE', 'kind' => 'INTERFACE',
'name' => 'Named', 'name' => 'Named',
'fields' => [ 'fields' => [
['name' => 'name'] ['name' => 'name'],
], ],
'interfaces' => null, 'interfaces' => null,
'possibleTypes' => [ 'possibleTypes' => [
['name' => 'Person'], ['name' => 'Person'],
['name' => 'Dog'], ['name' => 'Dog'],
['name' => 'Cat'] ['name' => 'Cat'],
], ],
'enumValues' => null, 'enumValues' => null,
'inputFields' => null 'inputFields' => null,
], ],
'Pet' => [ 'Pet' => [
'kind' => 'UNION', 'kind' => 'UNION',
'name' => 'Pet', 'name' => 'Pet',
'fields' => null, 'fields' => null,
'interfaces' => null, 'interfaces' => null,
'possibleTypes' => [ 'possibleTypes' => [
['name' => 'Dog'], ['name' => 'Dog'],
['name' => 'Cat'] ['name' => 'Cat'],
], ],
'enumValues' => null, 'enumValues' => null,
'inputFields' => null 'inputFields' => null,
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray()); $this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray());
} }
@ -165,7 +175,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUsingUnionTypes() : void public function testExecutesUsingUnionTypes() : void
{ {
// NOTE: This is an *invalid* query, but it should be an *executable* query. // NOTE: This is an *invalid* query, but it should be an *executable* query.
$ast = Parser::parse(' $ast = Parser::parse('
{ {
__typename __typename
name name
@ -180,12 +190,12 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'pets' => [ 'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -197,7 +207,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUnionTypesWithInlineFragments() : void public function testExecutesUnionTypesWithInlineFragments() : void
{ {
// This is the valid version of the query in the above test. // This is the valid version of the query in the above test.
$ast = Parser::parse(' $ast = Parser::parse('
{ {
__typename __typename
name name
@ -217,13 +227,13 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'pets' => [ 'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
} }
@ -234,7 +244,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUsingInterfaceTypes() : void public function testExecutesUsingInterfaceTypes() : void
{ {
// NOTE: This is an *invalid* query, but it should be an *executable* query. // NOTE: This is an *invalid* query, but it should be an *executable* query.
$ast = Parser::parse(' $ast = Parser::parse('
{ {
__typename __typename
name name
@ -249,12 +259,12 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'friends' => [ 'friends' => [
['__typename' => 'Person', 'name' => 'Liz'], ['__typename' => 'Person', 'name' => 'Liz'],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -266,7 +276,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesInterfaceTypesWithInlineFragments() : void public function testExecutesInterfaceTypesWithInlineFragments() : void
{ {
// This is the valid version of the query in the above test. // This is the valid version of the query in the above test.
$ast = Parser::parse(' $ast = Parser::parse('
{ {
__typename __typename
name name
@ -285,12 +295,12 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'friends' => [ 'friends' => [
['__typename' => 'Person', 'name' => 'Liz'], ['__typename' => 'Person', 'name' => 'Liz'],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true)); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true));
@ -336,16 +346,16 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'pets' => [ 'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
], ],
'friends' => [ 'friends' => [
['__typename' => 'Person', 'name' => 'Liz'], ['__typename' => 'Person', 'name' => 'Liz'],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -356,36 +366,45 @@ class UnionInterfaceTest extends TestCase
*/ */
public function testGetsExecutionInfoInResolver() : void public function testGetsExecutionInfoInResolver() : void
{ {
$encounteredContext = null; $encounteredContext = null;
$encounteredSchema = null; $encounteredSchema = null;
$encounteredRootValue = null; $encounteredRootValue = null;
$PersonType2 = null; $PersonType2 = null;
$NamedType2 = new InterfaceType([ $NamedType2 = new InterfaceType([
'name' => 'Named', 'name' => 'Named',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
], ],
'resolveType' => function ($obj, $context, ResolveInfo $info) use (&$encounteredContext, &$encounteredSchema, &$encounteredRootValue, &$PersonType2) { 'resolveType' => function (
$encounteredContext = $context; $obj,
$encounteredSchema = $info->schema; $context,
ResolveInfo $info
) use (
&$encounteredContext,
&
$encounteredSchema,
&$encounteredRootValue,
&$PersonType2
) {
$encounteredContext = $context;
$encounteredSchema = $info->schema;
$encounteredRootValue = $info->rootValue; $encounteredRootValue = $info->rootValue;
return $PersonType2; return $PersonType2;
} },
]); ]);
$PersonType2 = new ObjectType([ $PersonType2 = new ObjectType([
'name' => 'Person', 'name' => 'Person',
'interfaces' => [$NamedType2], 'interfaces' => [$NamedType2],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'friends' => ['type' => Type::listOf($NamedType2)], 'friends' => ['type' => Type::listOf($NamedType2)],
], ],
]); ]);
$schema2 = new Schema([ $schema2 = new Schema(['query' => $PersonType2]);
'query' => $PersonType2
]);
$john2 = new Person('John', [], [$this->liz]); $john2 = new Person('John', [], [$this->liz]);

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Values; use GraphQL\Executor\Values;
@ -10,19 +13,97 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
use function var_export;
use const PHP_EOL;
class ValuesTest extends TestCase class ValuesTest extends TestCase
{ {
/** @var Schema */
private static $schema;
public function testGetIDVariableValues() : void public function testGetIDVariableValues() : void
{ {
$this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']); $this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']);
$this->assertEquals( $this->assertEquals(
['errors'=> [], 'coerced' => ['idInput' => '123456789']], ['errors' => [], 'coerced' => ['idInput' => '123456789']],
self::runTestCase(['idInput' => 123456789]), $this->runTestCase(['idInput' => 123456789]),
'Integer ID was not converted to string' 'Integer ID was not converted to string'
); );
} }
private function expectInputVariablesMatchOutputVariables($variables) : void
{
$this->assertEquals(
$variables,
$this->runTestCase($variables)['coerced'],
'Output variables did not match input variables' . PHP_EOL . var_export($variables, true) . PHP_EOL
);
}
/**
* @param mixed[] $variables
* @return mixed[]
*/
private function runTestCase($variables) : array
{
return Values::getVariableValues(self::getSchema(), self::getVariableDefinitionNodes(), $variables);
}
private static function getSchema() : Schema
{
if (! self::$schema) {
self::$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'test' => [
'type' => Type::boolean(),
'args' => [
'idInput' => Type::id(),
'boolInput' => Type::boolean(),
'intInput' => Type::int(),
'stringInput' => Type::string(),
'floatInput' => Type::float(),
],
],
],
]),
]);
}
return self::$schema;
}
/**
* @return VariableDefinitionNode[]
*/
private static function getVariableDefinitionNodes() : array
{
$idInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'idInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'ID'])]),
]);
$boolInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'boolInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Boolean'])]),
]);
$intInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'intInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Int'])]),
]);
$stringInputDefintion = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'stringInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'String'])]),
]);
$floatInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'floatInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Float'])]),
]);
return [$idInputDefinition, $boolInputDefinition, $intInputDefinition, $stringInputDefintion, $floatInputDefinition];
}
public function testGetBooleanVariableValues() : void public function testGetBooleanVariableValues() : void
{ {
$this->expectInputVariablesMatchOutputVariables(['boolInput' => true]); $this->expectInputVariablesMatchOutputVariables(['boolInput' => true]);
@ -64,11 +145,20 @@ class ValuesTest extends TestCase
$this->expectGraphQLError(['idInput' => true]); $this->expectGraphQLError(['idInput' => true]);
} }
private function expectGraphQLError($variables) : void
{
$result = $this->runTestCase($variables);
$this->assertGreaterThan(0, count($result['errors']));
}
public function testFloatForIDVariableThrowsError() : void public function testFloatForIDVariableThrowsError() : void
{ {
$this->expectGraphQLError(['idInput' => 1.0]); $this->expectGraphQLError(['idInput' => 1.0]);
} }
/**
* Helpers for running test cases and making assertions
*/
public function testStringForBooleanVariableThrowsError() : void public function testStringForBooleanVariableThrowsError() : void
{ {
$this->expectGraphQLError(['boolInput' => 'true']); $this->expectGraphQLError(['boolInput' => 'true']);
@ -98,77 +188,4 @@ class ValuesTest extends TestCase
{ {
$this->expectGraphQLError(['intInput' => -2147483649]); $this->expectGraphQLError(['intInput' => -2147483649]);
} }
// Helpers for running test cases and making assertions
private function expectInputVariablesMatchOutputVariables($variables)
{
$this->assertEquals(
$variables,
self::runTestCase($variables)['coerced'],
'Output variables did not match input variables' . PHP_EOL . var_export($variables, true) . PHP_EOL
);
}
private function expectGraphQLError($variables)
{
$result = self::runTestCase($variables);
$this->assertGreaterThan(0, count($result['errors']));
}
private static $schema;
private static function getSchema()
{
if (!self::$schema) {
self::$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'test' => [
'type' => Type::boolean(),
'args' => [
'idInput' => Type::id(),
'boolInput' => Type::boolean(),
'intInput' => Type::int(),
'stringInput' => Type::string(),
'floatInput' => Type::float()
]
],
]
])
]);
}
return self::$schema;
}
private static function getVariableDefinitionNodes()
{
$idInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'idInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'ID'])])
]);
$boolInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'boolInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Boolean'])])
]);
$intInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'intInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Int'])])
]);
$stringInputDefintion = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'stringInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'String'])])
]);
$floatInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'floatInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Float'])])
]);
return [$idInputDefinition, $boolInputDefinition, $intInputDefinition, $stringInputDefintion, $floatInputDefinition];
}
private function runTestCase($variables)
{
return Values::getVariableValues(self::getSchema(), self::getVariableDefinitionNodes(), $variables);
}
} }

View File

@ -1,16 +1,19 @@
<?php <?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php'; declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\ComplexScalar;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function json_encode;
/** /**
* Execute: Handles inputs * Execute: Handles inputs
@ -18,7 +21,6 @@ use PHPUnit\Framework\TestCase;
*/ */
class VariablesTest extends TestCase class VariablesTest extends TestCase
{ {
public function testUsingInlineStructs() : void public function testUsingInlineStructs() : void
{ {
// executes with complex input: // executes with complex input:
@ -29,14 +31,12 @@ class VariablesTest extends TestCase
'); ');
$expected = [ $expected = [
'data' => [ 'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
'fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'
]
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// properly parses single value to list: // properly parses single value to list:
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"})
} }
@ -46,7 +46,7 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// properly parses null value to null // properly parses null value to null
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null}) fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null})
} }
@ -56,7 +56,7 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// properly parses null value in list // properly parses null value in list
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"}) fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"})
} }
@ -73,12 +73,13 @@ class VariablesTest extends TestCase
'); ');
$expected = [ $expected = [
'data' => ['fieldWithObjectInput' => null], 'data' => ['fieldWithObjectInput' => null],
'errors' => [[ 'errors' => [[
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].', 'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
'path' => ['fieldWithObjectInput'], 'path' => ['fieldWithObjectInput'],
'locations' => [['line' => 3, 'column' => 39]] 'locations' => [['line' => 3, 'column' => 39]],
]] ],
],
]; ];
$this->assertArraySubset($expected, $result->toArray()); $this->assertArraySubset($expected, $result->toArray());
@ -94,6 +95,77 @@ class VariablesTest extends TestCase
); );
} }
private function executeQuery($query, $variableValues = null)
{
$document = Parser::parse($query);
return Executor::execute($this->schema(), $document, null, null, $variableValues);
}
/**
* Describe: Handles nullable scalars
*/
public function schema() : Schema
{
$ComplexScalarType = ComplexScalar::create();
$TestInputObject = new InputObjectType([
'name' => 'TestInputObject',
'fields' => [
'a' => ['type' => Type::string()],
'b' => ['type' => Type::listOf(Type::string())],
'c' => ['type' => Type::nonNull(Type::string())],
'd' => ['type' => $ComplexScalarType],
],
]);
$TestNestedInputObject = new InputObjectType([
'name' => 'TestNestedInputObject',
'fields' => [
'na' => ['type' => Type::nonNull($TestInputObject)],
'nb' => ['type' => Type::nonNull(Type::string())],
],
]);
$TestType = new ObjectType([
'name' => 'TestType',
'fields' => [
'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]),
'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]),
'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]),
'fieldWithDefaultArgumentValue' => $this->fieldWithInputArg([
'type' => Type::string(),
'defaultValue' => 'Hello World',
]),
'fieldWithNestedInputObject' => $this->fieldWithInputArg([
'type' => $TestNestedInputObject,
'defaultValue' => 'Hello World',
]),
'list' => $this->fieldWithInputArg(['type' => Type::listOf(Type::string())]),
'nnList' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::string()))]),
'listNN' => $this->fieldWithInputArg(['type' => Type::listOf(Type::nonNull(Type::string()))]),
'nnListNN' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]),
],
]);
return new Schema(['query' => $TestType]);
}
private function fieldWithInputArg($inputArg)
{
return [
'type' => Type::string(),
'args' => ['input' => $inputArg],
'resolve' => function ($_, $args) {
if (isset($args['input'])) {
return json_encode($args['input']);
}
return null;
},
];
}
public function testUsingVariables() : void public function testUsingVariables() : void
{ {
$doc = ' $doc = '
@ -119,7 +191,7 @@ class VariablesTest extends TestCase
'); ');
$expected = [ $expected = [
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'] 'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -132,63 +204,61 @@ class VariablesTest extends TestCase
); );
// executes with complex scalar input: // executes with complex scalar input:
$params = [ 'input' => [ 'c' => 'foo', 'd' => 'SerializedValue' ] ]; $params = ['input' => ['c' => 'foo', 'd' => 'SerializedValue']];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'data' => [ 'data' => ['fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'],
'fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'
]
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on null for nested non-null: // errors on null for nested non-null:
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]]; $params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value ' . 'Variable "$input" got invalid value ' .
'{"a":"foo","b":"bar","c":null}; ' . '{"a":"foo","b":"bar","c":null}; ' .
'Expected non-nullable type String! not to be null at value.c.', 'Expected non-nullable type String! not to be null at value.c.',
'locations' => [['line' => 2, 'column' => 21]], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on incorrect type: // errors on incorrect type:
$params = [ 'input' => 'foo bar' ]; $params = ['input' => 'foo bar'];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value "foo bar"; ' . 'Variable "$input" got invalid value "foo bar"; ' .
'Expected type TestInputObject to be an object.', 'Expected type TestInputObject to be an object.',
'locations' => [ [ 'line' => 2, 'column' => 21 ] ], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on omission of nested non-null: // errors on omission of nested non-null:
$params = ['input' => ['a' => 'foo', 'b' => 'bar']]; $params = ['input' => ['a' => 'foo', 'b' => 'bar']];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; '. 'Variable "$input" got invalid value {"a":"foo","b":"bar"}; ' .
'Field value.c of required type String! was not provided.', 'Field value.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 21]], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -198,62 +268,59 @@ class VariablesTest extends TestCase
fieldWithNestedObjectInput(input: $input) fieldWithNestedObjectInput(input: $input)
} }
'; ';
$params = [ 'input' => [ 'na' => [ 'a' => 'foo' ] ] ]; $params = ['input' => ['na' => ['a' => 'foo']]];
$result = $this->executeQuery($nestedDoc, $params); $result = $this->executeQuery($nestedDoc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' . 'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
'Field value.na.c of required type String! was not provided.', 'Field value.na.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]], 'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql', 'category' => 'graphql',
], ],
[ [
'message' => 'message' =>
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' . 'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
'Field value.nb of required type String! was not provided.', 'Field value.nb of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]], 'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on addition of unknown input field // errors on addition of unknown input field
$params = ['input' => [ 'a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog' ]]; $params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog']];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value ' . 'Variable "$input" got invalid value ' .
'{"a":"foo","b":"bar","c":"baz","extra":"dog"}; ' . '{"a":"foo","b":"bar","c":"baz","extra":"dog"}; ' .
'Field "extra" is not defined by type TestInputObject.', 'Field "extra" is not defined by type TestInputObject.',
'locations' => [['line' => 2, 'column' => 21]], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles nullable scalars
/** /**
* @see it('allows nullable inputs to be omitted') * @see it('allows nullable inputs to be omitted')
*/ */
public function testAllowsNullableInputsToBeOmitted() : void public function testAllowsNullableInputsToBeOmitted() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNullableStringInput fieldWithNullableStringInput
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNullableStringInput' => null] 'data' => ['fieldWithNullableStringInput' => null],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -264,7 +331,7 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNullableInputsToBeOmittedInAVariable() : void public function testAllowsNullableInputsToBeOmittedInAVariable() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
query SetsNullable($value: String) { query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value) fieldWithNullableStringInput(input: $value)
} }
@ -279,7 +346,7 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNullableInputsToBeOmittedInAnUnlistedVariable() : void public function testAllowsNullableInputsToBeOmittedInAnUnlistedVariable() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
query SetsNullable { query SetsNullable {
fieldWithNullableStringInput(input: $value) fieldWithNullableStringInput(input: $value)
} }
@ -288,12 +355,15 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles non-nullable scalars
/** /**
* @see it('allows nullable inputs to be set to null in a variable') * @see it('allows nullable inputs to be set to null in a variable')
*/ */
public function testAllowsNullableInputsToBeSetToNullInAVariable() : void public function testAllowsNullableInputsToBeSetToNullInAVariable() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
query SetsNullable($value: String) { query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value) fieldWithNullableStringInput(input: $value)
} }
@ -308,12 +378,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNullableInputsToBeSetToAValueInAVariable() : void public function testAllowsNullableInputsToBeSetToAValueInAVariable() : void
{ {
$doc = ' $doc = '
query SetsNullable($value: String) { query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value) fieldWithNullableStringInput(input: $value)
} }
'; ';
$result = $this->executeQuery($doc, ['value' => 'a']); $result = $this->executeQuery($doc, ['value' => 'a']);
$expected = ['data' => ['fieldWithNullableStringInput' => '"a"']]; $expected = ['data' => ['fieldWithNullableStringInput' => '"a"']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -323,7 +393,7 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNullableInputsToBeSetToAValueDirectly() : void public function testAllowsNullableInputsToBeSetToAValueDirectly() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNullableStringInput(input: "a") fieldWithNullableStringInput(input: "a")
} }
@ -332,21 +402,18 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles non-nullable scalars
/** /**
* @see it('allows non-nullable inputs to be omitted given a default') * @see it('allows non-nullable inputs to be omitted given a default')
*/ */
public function testAllowsNonNullableInputsToBeOmittedGivenADefault() : void public function testAllowsNonNullableInputsToBeOmittedGivenADefault() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
query SetsNonNullable($value: String = "default") { query SetsNonNullable($value: String = "default") {
fieldWithNonNullableStringInput(input: $value) fieldWithNonNullableStringInput(input: $value)
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNonNullableStringInput' => '"default"'] 'data' => ['fieldWithNonNullableStringInput' => '"default"'],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -365,11 +432,11 @@ class VariablesTest extends TestCase
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'Variable "$value" of required type "String!" was not provided.', 'message' => 'Variable "$value" of required type "String!" was not provided.',
'locations' => [['line' => 2, 'column' => 31]], 'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -379,22 +446,22 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable() : void public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable() : void
{ {
$doc = ' $doc = '
query SetsNonNullable($value: String!) { query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value) fieldWithNonNullableStringInput(input: $value)
} }
'; ';
$result = $this->executeQuery($doc, ['value' => null]); $result = $this->executeQuery($doc, ['value' => null]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$value" got invalid value null; ' . 'Variable "$value" got invalid value null; ' .
'Expected non-nullable type String! not to be null.', 'Expected non-nullable type String! not to be null.',
'locations' => [['line' => 2, 'column' => 31]], 'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -404,12 +471,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullableInputsToBeSetToAValueInAVariable() : void public function testAllowsNonNullableInputsToBeSetToAValueInAVariable() : void
{ {
$doc = ' $doc = '
query SetsNonNullable($value: String!) { query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value) fieldWithNonNullableStringInput(input: $value)
} }
'; ';
$result = $this->executeQuery($doc, ['value' => 'a']); $result = $this->executeQuery($doc, ['value' => 'a']);
$expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']]; $expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -419,7 +486,7 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullableInputsToBeSetToAValueDirectly() : void public function testAllowsNonNullableInputsToBeSetToAValueDirectly() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNonNullableStringInput(input: "a") fieldWithNonNullableStringInput(input: "a")
} }
@ -433,47 +500,50 @@ class VariablesTest extends TestCase
*/ */
public function testReportsErrorForMissingNonNullableInputs() : void public function testReportsErrorForMissingNonNullableInputs() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNonNullableStringInput fieldWithNonNullableStringInput
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNonNullableStringInput' => null], 'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[ 'errors' => [[
'message' => 'Argument "input" of required type "String!" was not provided.', 'message' => 'Argument "input" of required type "String!" was not provided.',
'locations' => [ [ 'line' => 3, 'column' => 9 ] ], 'locations' => [['line' => 3, 'column' => 9]],
'path' => [ 'fieldWithNonNullableStringInput' ], 'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql', 'category' => 'graphql',
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles lists and nullability
/** /**
* @see it('reports error for array passed into string input') * @see it('reports error for array passed into string input')
*/ */
public function testReportsErrorForArrayPassedIntoStringInput() : void public function testReportsErrorForArrayPassedIntoStringInput() : void
{ {
$doc = '
$doc = '
query SetsNonNullable($value: String!) { query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value) fieldWithNonNullableStringInput(input: $value)
} }
'; ';
$variables = ['value' => [1, 2, 3]]; $variables = ['value' => [1, 2, 3]];
$result = $this->executeQuery($doc, $variables); $result = $this->executeQuery($doc, $variables);
$expected = [ $expected = [
'errors' => [[ 'errors' => [[
'message' => 'message' =>
'Variable "$value" got invalid value [1,2,3]; Expected type ' . 'Variable "$value" got invalid value [1,2,3]; Expected type ' .
'String; String cannot represent an array value: [1,2,3]', 'String; String cannot represent an array value: [1,2,3]',
'category' => 'graphql', 'category' => 'graphql',
'locations' => [ 'locations' => [
['line' => 2, 'column' => 31] ['line' => 2, 'column' => 31],
] ],
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -498,38 +568,37 @@ class VariablesTest extends TestCase
// and are being run against a new schema which have introduced a breaking // and are being run against a new schema which have introduced a breaking
// change to make a formerly non-required argument required, this asserts // change to make a formerly non-required argument required, this asserts
// failure before allowing the underlying code to receive a non-null value. // failure before allowing the underlying code to receive a non-null value.
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNonNullableStringInput(input: $foo) fieldWithNonNullableStringInput(input: $foo)
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNonNullableStringInput' => null], 'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[ 'errors' => [[
'message' => 'message' =>
'Argument "input" of required type "String!" was provided the ' . 'Argument "input" of required type "String!" was provided the ' .
'variable "$foo" which was not provided a runtime value.', 'variable "$foo" which was not provided a runtime value.',
'locations' => [['line' => 3, 'column' => 48]], 'locations' => [['line' => 3, 'column' => 48]],
'path' => ['fieldWithNonNullableStringInput'], 'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql', 'category' => 'graphql',
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles lists and nullability
/** /**
* @see it('allows lists to be null') * @see it('allows lists to be null')
*/ */
public function testAllowsListsToBeNull() : void public function testAllowsListsToBeNull() : void
{ {
$doc = ' $doc = '
query q($input:[String]) { query q($input:[String]) {
list(input: $input) list(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => null]); $result = $this->executeQuery($doc, ['input' => null]);
$expected = ['data' => ['list' => null]]; $expected = ['data' => ['list' => null]];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -540,12 +609,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsListsToContainValues() : void public function testAllowsListsToContainValues() : void
{ {
$doc = ' $doc = '
query q($input:[String]) { query q($input:[String]) {
list(input: $input) list(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A']]); $result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['list' => '["A"]']]; $expected = ['data' => ['list' => '["A"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -555,12 +624,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsListsToContainNull() : void public function testAllowsListsToContainNull() : void
{ {
$doc = ' $doc = '
query q($input:[String]) { query q($input:[String]) {
list(input: $input) list(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A',null,'B']]); $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = ['data' => ['list' => '["A",null,"B"]']]; $expected = ['data' => ['list' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -570,22 +639,22 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowNonNullListsToBeNull() : void public function testDoesNotAllowNonNullListsToBeNull() : void
{ {
$doc = ' $doc = '
query q($input:[String]!) { query q($input:[String]!) {
nnList(input: $input) nnList(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => null]); $result = $this->executeQuery($doc, ['input' => null]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value null; ' . 'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String]! not to be null.', 'Expected non-nullable type [String]! not to be null.',
'locations' => [['line' => 2, 'column' => 17]], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -595,12 +664,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullListsToContainValues() : void public function testAllowsNonNullListsToContainValues() : void
{ {
$doc = ' $doc = '
query q($input:[String]!) { query q($input:[String]!) {
nnList(input: $input) nnList(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A']]); $result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['nnList' => '["A"]']]; $expected = ['data' => ['nnList' => '["A"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -610,12 +679,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullListsToContainNull() : void public function testAllowsNonNullListsToContainNull() : void
{ {
$doc = ' $doc = '
query q($input:[String]!) { query q($input:[String]!) {
nnList(input: $input) nnList(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A',null,'B']]); $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = ['data' => ['nnList' => '["A",null,"B"]']]; $expected = ['data' => ['nnList' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -625,12 +694,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsListsOfNonNullsToBeNull() : void public function testAllowsListsOfNonNullsToBeNull() : void
{ {
$doc = ' $doc = '
query q($input:[String!]) { query q($input:[String!]) {
listNN(input: $input) listNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => null]); $result = $this->executeQuery($doc, ['input' => null]);
$expected = ['data' => ['listNN' => null]]; $expected = ['data' => ['listNN' => null]];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -640,12 +709,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsListsOfNonNullsToContainValues() : void public function testAllowsListsOfNonNullsToContainValues() : void
{ {
$doc = ' $doc = '
query q($input:[String!]) { query q($input:[String!]) {
listNN(input: $input) listNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A']]); $result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['listNN' => '["A"]']]; $expected = ['data' => ['listNN' => '["A"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -655,22 +724,22 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowListsOfNonNullsToContainNull() : void public function testDoesNotAllowListsOfNonNullsToContainNull() : void
{ {
$doc = ' $doc = '
query q($input:[String!]) { query q($input:[String!]) {
listNN(input: $input) listNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' . 'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].', 'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -680,22 +749,22 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowNonNullListsOfNonNullsToBeNull() : void public function testDoesNotAllowNonNullListsOfNonNullsToBeNull() : void
{ {
$doc = ' $doc = '
query q($input:[String!]!) { query q($input:[String!]!) {
nnListNN(input: $input) nnListNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => null]); $result = $this->executeQuery($doc, ['input' => null]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value null; ' . 'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String!]! not to be null.', 'Expected non-nullable type [String!]! not to be null.',
'locations' => [ ['line' => 2, 'column' => 17] ], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -705,37 +774,39 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullListsOfNonNullsToContainValues() : void public function testAllowsNonNullListsOfNonNullsToContainValues() : void
{ {
$doc = ' $doc = '
query q($input:[String!]!) { query q($input:[String!]!) {
nnListNN(input: $input) nnListNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A']]); $result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['nnListNN' => '["A"]']]; $expected = ['data' => ['nnListNN' => '["A"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Execute: Uses argument default values
/** /**
* @see it('does not allow non-null lists of non-nulls to contain null') * @see it('does not allow non-null lists of non-nulls to contain null')
*/ */
public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() : void public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() : void
{ {
$doc = ' $doc = '
query q($input:[String!]!) { query q($input:[String!]!) {
nnListNN(input: $input) nnListNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' . 'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].', 'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -745,23 +816,23 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowInvalidTypesToBeUsedAsValues() : void public function testDoesNotAllowInvalidTypesToBeUsedAsValues() : void
{ {
$doc = ' $doc = '
query q($input: TestType!) { query q($input: TestType!) {
fieldWithObjectInput(input: $input) fieldWithObjectInput(input: $input)
} }
'; ';
$vars = [ 'input' => [ 'list' => [ 'A', 'B' ] ] ]; $vars = ['input' => ['list' => ['A', 'B']]];
$result = $this->executeQuery($doc, $vars); $result = $this->executeQuery($doc, $vars);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" expected value of type "TestType!" which cannot ' . 'Variable "$input" expected value of type "TestType!" which cannot ' .
'be used as an input type.', 'be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]], 'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -771,29 +842,28 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowUnknownTypesToBeUsedAsValues() : void public function testDoesNotAllowUnknownTypesToBeUsedAsValues() : void
{ {
$doc = ' $doc = '
query q($input: UnknownType!) { query q($input: UnknownType!) {
fieldWithObjectInput(input: $input) fieldWithObjectInput(input: $input)
} }
'; ';
$vars = ['input' => 'whoknows']; $vars = ['input' => 'whoknows'];
$result = $this->executeQuery($doc, $vars); $result = $this->executeQuery($doc, $vars);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" expected value of type "UnknownType!" which ' . 'Variable "$input" expected value of type "UnknownType!" which ' .
'cannot be used as an input type.', 'cannot be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]], 'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Execute: Uses argument default values
/** /**
* @see it('when no argument provided') * @see it('when no argument provided')
*/ */
@ -834,84 +904,17 @@ class VariablesTest extends TestCase
}'); }');
$expected = [ $expected = [
'data' => ['fieldWithDefaultArgumentValue' => null], 'data' => ['fieldWithDefaultArgumentValue' => null],
'errors' => [[ 'errors' => [[
'message' => 'message' =>
'Argument "input" has invalid value WRONG_TYPE.', 'Argument "input" has invalid value WRONG_TYPE.',
'locations' => [ [ 'line' => 2, 'column' => 50 ] ], 'locations' => [['line' => 2, 'column' => 50]],
'path' => [ 'fieldWithDefaultArgumentValue' ], 'path' => ['fieldWithDefaultArgumentValue'],
'category' => 'graphql', 'category' => 'graphql',
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
public function schema()
{
$ComplexScalarType = ComplexScalar::create();
$TestInputObject = new InputObjectType([
'name' => 'TestInputObject',
'fields' => [
'a' => ['type' => Type::string()],
'b' => ['type' => Type::listOf(Type::string())],
'c' => ['type' => Type::nonNull(Type::string())],
'd' => ['type' => $ComplexScalarType],
]
]);
$TestNestedInputObject = new InputObjectType([
'name' => 'TestNestedInputObject',
'fields' => [
'na' => [ 'type' => Type::nonNull($TestInputObject) ],
'nb' => [ 'type' => Type::nonNull(Type::string()) ],
],
]);
$TestType = new ObjectType([
'name' => 'TestType',
'fields' => [
'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]),
'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]),
'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]),
'fieldWithDefaultArgumentValue' => $this->fieldWithInputArg([
'type' => Type::string(),
'defaultValue' => 'Hello World',
]),
'fieldWithNestedInputObject' => $this->fieldWithInputArg([
'type' => $TestNestedInputObject,
'defaultValue' => 'Hello World'
]),
'list' => $this->fieldWithInputArg(['type' => Type::listOf(Type::string())]),
'nnList' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::string()))]),
'listNN' => $this->fieldWithInputArg(['type' => Type::listOf(Type::nonNull(Type::string()))]),
'nnListNN' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]),
]
]);
$schema = new Schema(['query' => $TestType]);
return $schema;
}
private function fieldWithInputArg($inputArg)
{
return [
'type' => Type::string(),
'args' => ['input' => $inputArg],
'resolve' => function ($_, $args) {
if (isset($args['input'])) {
return json_encode($args['input']);
}
return null;
},
];
}
private function executeQuery($query, $variableValues = null)
{
$document = Parser::parse($query);
return Executor::execute($this->schema(), $document, null, null, $variableValues);
}
} }