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,23 +1,27 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Error\UserError;
use GraphQL\Error\Warning;
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\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
/**
* DESCRIBE: Execute: Handles execution of abstract types with promises
*/
class AbstractPromiseTest extends TestCase
{
// DESCRIBE: Execute: Handles execution of abstract types with promises
/**
* @see it('isTypeOf used to resolve runtime type for Interface')
*/
@ -26,8 +30,8 @@ class AbstractPromiseTest extends TestCase
$PetType = new InterfaceType([
'name' => 'Pet',
'fields' => [
'name' => [ 'type' => Type::string() ]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
@ -41,7 +45,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -55,7 +59,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -67,13 +71,13 @@ class AbstractPromiseTest extends TestCase
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [ $CatType, $DogType ]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -94,9 +98,9 @@ class AbstractPromiseTest extends TestCase
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
[ 'name' => 'Garfield', 'meows' => false ]
]
]
['name' => 'Garfield', 'meows' => false],
],
],
];
$this->assertEquals($expected, $result);
@ -107,12 +111,11 @@ class AbstractPromiseTest extends TestCase
*/
public function testIsTypeOfCanBeRejected() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
@ -126,7 +129,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -140,7 +143,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -152,13 +155,13 @@ class AbstractPromiseTest extends TestCase
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -177,7 +180,7 @@ class AbstractPromiseTest extends TestCase
$expected = [
'data' => [
'pets' => [null, null]
'pets' => [null, null],
],
'errors' => [
[
@ -188,9 +191,9 @@ class AbstractPromiseTest extends TestCase
[
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 1]
]
]
'path' => ['pets', 1],
],
],
];
$this->assertArraySubset($expected, $result);
@ -201,7 +204,6 @@ class AbstractPromiseTest extends TestCase
*/
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
{
$DogType = new ObjectType([
'name' => 'Dog',
'isTypeOf' => function ($obj) {
@ -212,7 +214,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -225,12 +227,12 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
'name' => 'Pet',
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
@ -241,10 +243,10 @@ class AbstractPromiseTest extends TestCase
'type' => Type::listOf($PetType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -266,9 +268,9 @@ class AbstractPromiseTest extends TestCase
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
]
['name' => 'Garfield', 'meows' => false],
],
],
];
$this->assertEquals($expected, $result);
@ -292,19 +294,20 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
});
},
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$HumanType = new ObjectType([
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
@ -313,7 +316,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -322,7 +325,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -336,14 +339,14 @@ class AbstractPromiseTest extends TestCase
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
});
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -365,16 +368,16 @@ class AbstractPromiseTest extends TestCase
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
null
]
null,
],
],
'errors' => [
[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2]
'path' => ['pets', 2],
],
],
]
];
$this->assertArraySubset($expected, $result);
@ -385,12 +388,11 @@ class AbstractPromiseTest extends TestCase
*/
public function testResolveTypeOnUnionYieldsUsefulError() : void
{
$HumanType = new ObjectType([
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
@ -398,7 +400,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -406,7 +408,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
@ -422,10 +424,11 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
});
},
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
@ -438,12 +441,12 @@ class AbstractPromiseTest extends TestCase
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -466,16 +469,16 @@ class AbstractPromiseTest extends TestCase
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
null
]
null,
],
],
'errors' => [
[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2]
]
]
'path' => ['pets', 2],
],
],
];
$this->assertArraySubset($expected, $result);
@ -496,22 +499,22 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Cat) {
return 'Cat';
}
return null;
});
},
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -520,7 +523,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -532,13 +535,13 @@ class AbstractPromiseTest extends TestCase
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -560,8 +563,8 @@ class AbstractPromiseTest extends TestCase
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
]
]
],
],
];
$this->assertEquals($expected, $result);
}
@ -571,7 +574,6 @@ class AbstractPromiseTest extends TestCase
*/
public function testResolveTypeCanBeCaught() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'resolveType' => function () {
@ -580,8 +582,8 @@ class AbstractPromiseTest extends TestCase
});
},
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
@ -590,7 +592,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -599,7 +601,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -611,13 +613,13 @@ class AbstractPromiseTest extends TestCase
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -636,20 +638,20 @@ class AbstractPromiseTest extends TestCase
$expected = [
'data' => [
'pets' => [null, null]
'pets' => [null, null],
],
'errors' => [
[
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 0]
'path' => ['pets', 0],
],
[
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 1]
]
]
'path' => ['pets', 1],
],
],
];
$this->assertArraySubset($expected, $result);

View File

@ -1,23 +1,28 @@
<?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\Executor;
use GraphQL\GraphQL;
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\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
/**
* Execute: Handles execution of abstract types
*/
class AbstractTest extends TestCase
{
// Execute: Handles execution of abstract types
/**
* @see it('isTypeOf used to resolve runtime type for Interface')
*/
@ -27,19 +32,21 @@ class AbstractTest extends TestCase
$petType = new InterfaceType([
'name' => 'Pet',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
// Added to interface type when defined
$dogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
]
'woofs' => ['type' => Type::boolean()],
],
]);
$catType = new ObjectType([
@ -51,7 +58,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -62,11 +69,11 @@ class AbstractTest extends TestCase
'type' => Type::listOf($petType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
}
]
]
},
],
],
]),
'types' => [$catType, $dogType]
'types' => [$catType, $dogType],
]);
$query = '{
@ -84,8 +91,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
['name' => 'Garfield', 'meows' => false],
],
]);
$result = Executor::execute($schema, Parser::parse($query));
@ -99,11 +106,13 @@ class AbstractTest extends TestCase
{
$dogType = new ObjectType([
'name' => 'Dog',
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
]
'woofs' => ['type' => Type::boolean()],
],
]);
$catType = new ObjectType([
@ -114,12 +123,12 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$petType = new UnionType([
'name' => 'Pet',
'types' => [$dogType, $catType]
'types' => [$dogType, $catType],
]);
$schema = new Schema([
@ -130,10 +139,10 @@ class AbstractTest extends TestCase
'type' => Type::listOf($petType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -151,8 +160,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
['name' => 'Garfield', 'meows' => false],
],
]);
$this->assertEquals($expected, Executor::execute($schema, Parser::parse($query)));
@ -161,7 +170,7 @@ class AbstractTest extends TestCase
/**
* @see it('resolveType on Interface yields useful error')
*/
function testResolveTypeOnInterfaceYieldsUsefulError()
public function testResolveTypeOnInterfaceYieldsUsefulError() : void
{
$DogType = null;
$CatType = null;
@ -179,18 +188,19 @@ class AbstractTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
},
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$HumanType = new ObjectType([
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
@ -199,7 +209,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -208,7 +218,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -221,16 +231,15 @@ class AbstractTest extends TestCase
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
},
],
],
]),
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$query = '{
pets {
name
@ -248,14 +257,15 @@ class AbstractTest extends TestCase
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
null
]
null,
],
],
'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2]
]]
'path' => ['pets', 2],
],
],
];
$actual = GraphQL::executeQuery($schema, $query)->toArray(true);
@ -271,7 +281,7 @@ class AbstractTest extends TestCase
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
@ -279,7 +289,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -287,7 +297,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
@ -303,7 +313,7 @@ class AbstractTest extends TestCase
return $HumanType;
}
},
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
@ -316,12 +326,12 @@ class AbstractTest extends TestCase
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -341,18 +351,23 @@ class AbstractTest extends TestCase
$expected = [
'data' => [
'pets' => [
['name' => 'Odie',
'woofs' => true],
['name' => 'Garfield',
'meows' => false],
null
]
[
'name' => 'Odie',
'woofs' => true,
],
[
'name' => 'Garfield',
'meows' => false,
],
null,
],
],
'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2]
]]
'path' => ['pets', 2],
],
],
];
$this->assertArraySubset($expected, $result);
}
@ -420,13 +435,18 @@ class AbstractTest extends TestCase
$PetType = new InterfaceType([
'name' => 'Pet',
'resolveType' => function ($obj) {
if ($obj instanceof Dog) return 'Dog';
if ($obj instanceof Cat) return 'Cat';
if ($obj instanceof Dog) {
return 'Dog';
}
if ($obj instanceof Cat) {
return 'Cat';
}
return null;
},
'fields' => [
'name' => [ 'type' => Type::string() ]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
@ -435,7 +455,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -444,7 +464,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -456,13 +476,13 @@ class AbstractTest extends TestCase
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [ $CatType, $DogType ]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -479,14 +499,17 @@ class AbstractTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray();
$this->assertEquals([
$this->assertEquals(
[
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
]
], $result);
['name' => 'Garfield', 'meows' => false],
],
],
],
$result
);
}
public function testHintsOnConflictingTypeInstancesInResolveType() : void
@ -495,35 +518,33 @@ class AbstractTest extends TestCase
return new ObjectType([
'name' => 'Test',
'fields' => [
'a' => Type::string()
'a' => Type::string(),
],
'interfaces' => function () use ($iface) {
return [$iface];
}
},
]);
};
$iface = new InterfaceType([
'name' => 'Node',
'fields' => [
'a' => Type::string()
'a' => Type::string(),
],
'resolveType' => function () use (&$createTest) {
return $createTest();
}
},
]);
$query = new ObjectType([
'name' => 'Query',
'fields' => [
'node' => $iface,
'test' => $createTest()
]
'test' => $createTest(),
],
]);
$schema = new Schema([
'query' => $query,
]);
$schema = new Schema(['query' => $query]);
$schema->assertValid();
$query = '

View File

@ -1,33 +1,44 @@
<?php
namespace GraphQL\Tests\Executor;
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase;
use function in_array;
class DeferredFieldsTest extends TestCase
{
/** @var ObjectType */
private $userType;
/** @var ObjectType */
private $storyType;
/** @var ObjectType */
private $categoryType;
/** @var */
private $path;
/** @var mixed[][] */
private $storyDataSource;
/** @var mixed[][] */
private $userDataSource;
/** @var mixed[][] */
private $categoryDataSource;
/** @var ObjectType */
private $queryType;
public function setUp()
@ -54,7 +65,7 @@ class DeferredFieldsTest extends TestCase
$this->categoryDataSource = [
['id' => 1, 'name' => 'Category #1', 'topStoryId' => 8],
['id' => 2, 'name' => 'Category #2', 'topStoryId' => 3],
['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9]
['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9],
];
$this->path = [];
@ -66,8 +77,9 @@ class DeferredFieldsTest extends TestCase
'type' => Type::string(),
'resolve' => function ($user, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $user['name'];
}
},
],
'bestFriend' => [
'type' => $this->userType,
@ -76,14 +88,18 @@ class DeferredFieldsTest extends TestCase
return new Deferred(function () use ($user) {
$this->path[] = 'deferred-for-best-friend-of-' . $user['id'];
return Utils::find($this->userDataSource, function($entry) use ($user) {
return Utils::find(
$this->userDataSource,
function ($entry) use ($user) {
return $entry['id'] === $user['bestFriendId'];
});
});
}
]
);
});
},
],
];
}
},
]);
$this->storyType = new ObjectType([
@ -93,8 +109,9 @@ class DeferredFieldsTest extends TestCase
'type' => Type::string(),
'resolve' => function ($entry, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $entry['title'];
}
},
],
'author' => [
'type' => $this->userType,
@ -103,13 +120,17 @@ class DeferredFieldsTest extends TestCase
return new Deferred(function () use ($story) {
$this->path[] = 'deferred-for-story-' . $story['id'] . '-author';
return Utils::find($this->userDataSource, function($entry) use ($story) {
return Utils::find(
$this->userDataSource,
function ($entry) use ($story) {
return $entry['id'] === $story['authorId'];
});
});
}
]
]
);
});
},
],
],
]);
$this->categoryType = new ObjectType([
@ -119,18 +140,23 @@ class DeferredFieldsTest extends TestCase
'type' => Type::string(),
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $category['name'];
}
},
],
'stories' => [
'type' => Type::listOf($this->storyType),
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) use ($category) {
return Utils::filter(
$this->storyDataSource,
function ($story) use ($category) {
return in_array($category['id'], $story['categoryIds']);
});
}
);
},
],
'topStory' => [
'type' => $this->storyType,
@ -139,13 +165,17 @@ class DeferredFieldsTest extends TestCase
return new Deferred(function () use ($category) {
$this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory';
return Utils::find($this->storyDataSource, function($story) use ($category) {
return Utils::find(
$this->storyDataSource,
function ($story) use ($category) {
return $story['id'] === $category['topStoryId'];
});
});
}
]
]
);
});
},
],
],
]);
$this->queryType = new ObjectType([
@ -155,26 +185,32 @@ class DeferredFieldsTest extends TestCase
'type' => Type::listOf($this->storyType),
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) {
return Utils::filter(
$this->storyDataSource,
function ($story) {
return $story['id'] % 2 === 1;
});
}
);
},
],
'featuredCategory' => [
'type' => $this->categoryType,
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $this->categoryDataSource[0];
}
},
],
'categories' => [
'type' => Type::listOf($this->categoryType),
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $this->categoryDataSource;
}
]
]
},
],
],
]);
parent::setUp();
@ -202,7 +238,7 @@ class DeferredFieldsTest extends TestCase
');
$schema = new Schema([
'query' => $this->queryType
'query' => $this->queryType,
]);
$expected = [
@ -220,9 +256,9 @@ class DeferredFieldsTest extends TestCase
['title' => 'Story #4', 'author' => ['name' => 'Joe']],
['title' => 'Story #6', 'author' => ['name' => 'Jane']],
['title' => 'Story #8', 'author' => ['name' => 'John']],
]
]
]
],
],
],
];
$result = Executor::execute($schema, $query);
@ -292,7 +328,7 @@ class DeferredFieldsTest extends TestCase
');
$schema = new Schema([
'query' => $this->queryType
'query' => $this->queryType,
]);
$author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']];
@ -306,8 +342,8 @@ class DeferredFieldsTest extends TestCase
['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]],
['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]],
['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]],
]
]
],
],
];
$result = Executor::execute($schema, $query);
@ -359,8 +395,9 @@ class DeferredFieldsTest extends TestCase
'type' => Type::string(),
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return 'sync';
}
},
],
'deferred' => [
'type' => Type::string(),
@ -369,16 +406,18 @@ class DeferredFieldsTest extends TestCase
return new Deferred(function () use ($info) {
$this->path[] = ['!dfd for: ', $info->path];
return 'deferred';
});
}
},
],
'nest' => [
'type' => $complexType,
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return [];
}
},
],
'deferredNest' => [
'type' => $complexType,
@ -387,17 +426,16 @@ class DeferredFieldsTest extends TestCase
return new Deferred(function () use ($info) {
$this->path[] = ['!dfd nest for: ', $info->path];
return [];
});
}
]
},
],
];
}
},
]);
$schema = new Schema([
'query' => $complexType
]);
$schema = new Schema(['query' => $complexType]);
$query = Parser::parse('
{
@ -435,26 +473,26 @@ class DeferredFieldsTest extends TestCase
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred'
'deferred' => 'deferred',
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred'
]
'deferred' => 'deferred',
],
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred'
'deferred' => 'deferred',
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred'
]
]
]
'deferred' => 'deferred',
],
],
],
];
$this->assertEquals($expected, $result->toArray());

View File

@ -1,16 +1,27 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Language\Source;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
/**
* Describe: Execute: handles directives
*/
class DirectivesTest extends TestCase
{
// Describe: Execute: handles directives
/** @var Schema */
private static $schema;
/** @var string[] */
private static $data;
/**
* @see it('basic query works')
@ -20,10 +31,50 @@ class DirectivesTest extends TestCase
$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
{
// 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
$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) }')
);
}
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
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\ExecutionResult;

View File

@ -1,44 +1,62 @@
<?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\Warning;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
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\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase;
use function count;
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 = [];
/** @var bool[] */
public $loadedTypes = [];
public function testWarnsAboutSlowIsTypeOfForLazySchema() : void
@ -48,22 +66,24 @@ class ExecutorLazySchemaTest extends TestCase
'name' => 'Pet',
'fields' => function () {
return [
'name' => ['type' => Type::string()]
'name' => ['type' => Type::string()],
];
}
},
]);
// Added to interface type when defined
$dogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => function () {
return [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
'woofs' => ['type' => Type::boolean()],
];
}
},
]);
$catType = new ObjectType([
@ -77,7 +97,7 @@ class ExecutorLazySchemaTest extends TestCase
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
];
}
},
]);
$schema = new Schema([
@ -88,9 +108,9 @@ class ExecutorLazySchemaTest extends TestCase
'type' => Type::listOf($petType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
}
]
]
},
],
],
]),
'types' => [$catType, $dogType],
'typeLoader' => function ($name) use ($dogType, $petType, $catType) {
@ -102,7 +122,7 @@ class ExecutorLazySchemaTest extends TestCase
case 'Cat':
return $catType;
}
}
},
]);
$query = '{
@ -120,7 +140,7 @@ class ExecutorLazySchemaTest extends TestCase
$expected = new ExecutionResult([
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
['name' => 'Garfield', 'meows' => false],
],
]);
@ -135,10 +155,11 @@ class ExecutorLazySchemaTest extends TestCase
$this->assertEquals(
'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. ' .
'Make sure your `resolveType` always returns valid implementation or throws.',
$result->errors[0]->getMessage());
$result->errors[0]->getMessage()
);
}
public function testHintsOnConflictingTypeInstancesInDefinitions() : void
@ -154,23 +175,25 @@ class ExecutorLazySchemaTest extends TestCase
return [
'test' => Type::string(),
];
}
},
]);
default:
return null;
}
};
$query = new ObjectType([
'name' => 'Query',
'fields' => function () use ($typeLoader) {
return [
'test' => $typeLoader('Test')
'test' => $typeLoader('Test'),
];
}
},
]);
$schema = new Schema([
'query' => $query,
'typeLoader' => $typeLoader
'typeLoader' => $typeLoader,
]);
$query = '
@ -203,7 +226,7 @@ class ExecutorLazySchemaTest extends TestCase
'query' => $this->loadType('Query'),
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '{ object { string } }';
@ -219,19 +242,126 @@ class ExecutorLazySchemaTest extends TestCase
$expectedExecutorCalls = [
'Query.fields',
'SomeObject',
'SomeObject.fields'
'SomeObject.fields',
];
$this->assertEquals($expected, $result->toArray(true));
$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
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '{ object { object { object { string } } } }';
@ -242,12 +372,12 @@ class ExecutorLazySchemaTest extends TestCase
);
$expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]]
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]],
];
$expectedLoadedTypes = [
'Query' => true,
'SomeObject' => true,
'OtherObject' => true
'OtherObject' => true,
];
$this->assertEquals($expected, $result->toArray(true));
@ -256,7 +386,7 @@ class ExecutorLazySchemaTest extends TestCase
$expectedExecutorCalls = [
'Query.fields',
'SomeObject',
'SomeObject.fields'
'SomeObject.fields',
];
$this->assertEquals($expectedExecutorCalls, $this->calls);
}
@ -267,7 +397,7 @@ class ExecutorLazySchemaTest extends TestCase
'query' => $this->loadType('Query'),
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '
@ -313,98 +443,4 @@ class ExecutorLazySchemaTest extends TestCase
];
$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,17 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function sprintf;
class ExecutorSchemaTest extends TestCase
{
// Execute: Handles execution with a complex schema
/**
* @see it('executes using a schema')
*/
@ -24,7 +27,7 @@ class ExecutorSchemaTest extends TestCase
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()],
]
],
]);
$BlogAuthor = new ObjectType([
@ -38,11 +41,11 @@ class ExecutorSchemaTest extends TestCase
'type' => $BlogImage,
'resolve' => function ($obj, $args) {
return $obj['pic']($args['width'], $args['height']);
}
},
],
'recentArticle' => $BlogArticle
'recentArticle' => $BlogArticle,
];
}
},
]);
$BlogArticle = new ObjectType([
@ -53,8 +56,8 @@ class ExecutorSchemaTest extends TestCase
'author' => ['type' => $BlogAuthor],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'keywords' => ['type' => Type::listOf(Type::string())]
]
'keywords' => ['type' => Type::listOf(Type::string())],
],
]);
$BlogQuery = new ObjectType([
@ -65,7 +68,7 @@ class ExecutorSchemaTest extends TestCase
'args' => ['id' => ['type' => Type::id()]],
'resolve' => function ($_, $args) {
return $this->article($args['id']);
}
},
],
'feed' => [
'type' => Type::listOf($BlogArticle),
@ -80,16 +83,15 @@ class ExecutorSchemaTest extends TestCase
$this->article(7),
$this->article(8),
$this->article(9),
$this->article(10)
$this->article(10),
];
}
]
]
},
],
],
]);
$BlogSchema = new Schema(['query' => $BlogQuery]);
$request = '
{
feed {
@ -127,26 +129,46 @@ class ExecutorSchemaTest extends TestCase
$expected = [
'data' => [
'feed' => [
['id' => '1',
'title' => 'My Article 1'],
['id' => '2',
'title' => 'My Article 2'],
['id' => '3',
'title' => 'My Article 3'],
['id' => '4',
'title' => 'My Article 4'],
['id' => '5',
'title' => 'My Article 5'],
['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']
[
'id' => '1',
'title' => 'My Article 1',
],
[
'id' => '2',
'title' => 'My Article 2',
],
[
'id' => '3',
'title' => 'My Article 3',
],
[
'id' => '4',
'title' => 'My Article 4',
],
[
'id' => '5',
'title' => 'My Article 5',
],
[
'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' => [
'id' => '1',
@ -159,18 +181,18 @@ class ExecutorSchemaTest extends TestCase
'pic' => [
'url' => 'cdn://123',
'width' => 640,
'height' => 480
'height' => 480,
],
'recentArticle' => [
'id' => '1',
'isPublished' => true,
'title' => 'My Article 1',
'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());
@ -187,22 +209,24 @@ class ExecutorSchemaTest extends TestCase
'title' => 'My Article ' . $id,
'body' => 'This is a post',
'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) {
return [
'url' => "cdn://$uid",
'url' => sprintf('cdn://%s', $uid),
'width' => $width,
'height' => $height
'height' => $height,
];
};
$johnSmith = [
'id' => 123,
'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),
];

View File

@ -1,20 +1,25 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Deferred;
use GraphQL\Error\Error;
use GraphQL\Error\UserError;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\NotSpecial;
use GraphQL\Tests\Executor\TestClasses\Special;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function array_keys;
use function count;
use function json_encode;
class ExecutorTest extends TestCase
{
@ -40,11 +45,21 @@ class ExecutorTest extends TestCase
};
$data = [
'a' => function () { return 'Apple';},
'b' => function () {return 'Banana';},
'c' => function () {return 'Cookie';},
'd' => function () {return 'Donut';},
'e' => function () {return 'Egg';},
'a' => function () {
return 'Apple';
},
'b' => function () {
return 'Banana';
},
'c' => function () {
return 'Cookie';
},
'd' => function () {
return 'Donut';
},
'e' => function () {
return 'Egg';
},
'f' => 'Fish',
'pic' => function ($size = 50) {
return 'Pic of size: ' . $size;
@ -54,21 +69,25 @@ class ExecutorTest extends TestCase
},
'deep' => function () use (&$deepData) {
return $deepData;
}
},
];
// Required for that & reference above
$deepData = [
'a' => function () { return 'Already Been Done'; },
'b' => function () { return 'Boring'; },
'a' => function () {
return 'Already Been Done';
},
'b' => function () {
return 'Boring';
},
'c' => function () {
return ['Contrived', null, 'Confusing'];
},
'deeper' => function () use (&$data) {
return [$data, null, $data];
}
},
];
$doc = '
query Example($size: Int) {
a,
@ -109,9 +128,7 @@ class ExecutorTest extends TestCase
'e' => 'Egg',
'f' => 'Fish',
'pic' => 'Pic of size: 100',
'promise' => [
'a' => 'Apple'
],
'promise' => ['a' => 'Apple'],
'deep' => [
'a' => 'Already Been Done',
'b' => 'Boring',
@ -119,10 +136,10 @@ class ExecutorTest extends TestCase
'deeper' => [
['a' => 'Apple', 'b' => 'Banana'],
null,
[ 'a' => 'Apple', 'b' => 'Banana' ]
]
]
]
['a' => 'Apple', 'b' => 'Banana'],
],
],
],
];
$deepDataType = null;
@ -141,26 +158,30 @@ class ExecutorTest extends TestCase
'type' => Type::string(),
'resolve' => function ($obj, $args) {
return $obj['pic']($args['size']);
}
},
],
'promise' => ['type' => $dataType],
'deep' => ['type' => $deepDataType],
];
}
},
]);
// Required for that & reference above
$deepDataType = new ObjectType([
'name' => 'DeepDataType',
'fields' => [
'a' => ['type' => Type::string()],
'b' => ['type' => Type::string()],
'c' => ['type' => Type::listOf(Type::string())],
'deeper' => [ 'type' => Type::listOf($dataType) ]
]
'deeper' => ['type' => Type::listOf($dataType)],
],
]);
$schema = new Schema(['query' => $dataType]);
$this->assertEquals($expected, Executor::execute($schema, $ast, $data, null, ['size' => 100], 'Example')->toArray());
$this->assertEquals(
$expected,
Executor::execute($schema, $ast, $data, null, ['size' => 100], 'Example')->toArray()
);
}
/**
@ -186,24 +207,34 @@ class ExecutorTest extends TestCase
'name' => 'Type',
'fields' => function () use (&$Type) {
return [
'a' => ['type' => Type::string(), 'resolve' => function () {
'a' => [
'type' => Type::string(),
'resolve' => function () {
return 'Apple';
}],
'b' => ['type' => Type::string(), 'resolve' => function () {
},
],
'b' => [
'type' => Type::string(),
'resolve' => function () {
return 'Banana';
}],
'c' => ['type' => Type::string(), 'resolve' => function () {
},
],
'c' => [
'type' => Type::string(),
'resolve' => function () {
return 'Cherry';
}],
},
],
'deep' => [
'type' => $Type,
'resolve' => function () {
return [];
}
]
},
],
];
}
},
]);
$schema = new Schema(['query' => $Type]);
$expected = [
'data' => [
@ -215,10 +246,10 @@ class ExecutorTest extends TestCase
'c' => 'Cherry',
'deeper' => [
'b' => 'Banana',
'c' => 'Cherry'
]
]
]
'c' => 'Cherry',
],
],
],
];
$this->assertEquals($expected, Executor::execute($schema, $ast)->toArray());
@ -241,17 +272,18 @@ class ExecutorTest extends TestCase
'type' => Type::string(),
'resolve' => function ($val, $args, $ctx, $_info) use (&$info) {
$info = $_info;
}
]
]
])
},
],
],
]),
]);
$rootValue = ['root' => 'val'];
Executor::execute($schema, $ast, $rootValue, null, ['var' => '123']);
$this->assertEquals([
$this->assertEquals(
[
'fieldName',
'fieldNodes',
'returnType',
@ -262,7 +294,9 @@ class ExecutorTest extends TestCase
'rootValue',
'operation',
'variableValues',
], array_keys((array) $info));
],
array_keys((array) $info)
);
$this->assertEquals('test', $info->fieldName);
$this->assertEquals(1, count($info->fieldNodes));
@ -286,9 +320,7 @@ class ExecutorTest extends TestCase
$gotHere = false;
$data = [
'contextThing' => 'thing',
];
$data = ['contextThing' => 'thing'];
$ast = Parser::parse($doc);
$schema = new Schema([
@ -300,10 +332,10 @@ class ExecutorTest extends TestCase
'resolve' => function ($context) use ($doc, &$gotHere) {
$this->assertEquals('thing', $context['contextThing']);
$gotHere = true;
}
]
]
])
},
],
],
]),
]);
Executor::execute($schema, $ast, $data, null, [], 'Example');
@ -331,17 +363,17 @@ class ExecutorTest extends TestCase
'b' => [
'args' => [
'numArg' => ['type' => Type::int()],
'stringArg' => ['type' => Type::string()]
'stringArg' => ['type' => Type::string()],
],
'type' => Type::string(),
'resolve' => function ($_, $args) use (&$gotHere) {
$this->assertEquals(123, $args['numArg']);
$this->assertEquals('foo', $args['stringArg']);
$gotHere = true;
}
]
]
])
},
],
],
]),
]);
Executor::execute($schema, $docAst, null, null, [], 'Example');
$this->assertSame($gotHere, true);
@ -387,14 +419,18 @@ class ExecutorTest extends TestCase
'sync0',
new UserError('Error getting syncReturnErrorList1'),
'sync2',
new UserError('Error getting syncReturnErrorList3')
new UserError('Error getting syncReturnErrorList3'),
];
},
'async' => function () {
return new Deferred(function() { return 'async'; });
return new Deferred(function () {
return 'async';
});
},
'asyncReject' => function () {
return new Deferred(function() { throw new UserError('Error getting asyncReject'); });
return new Deferred(function () {
throw new UserError('Error getting asyncReject');
});
},
'asyncRawReject' => function () {
return new Deferred(function () {
@ -442,8 +478,8 @@ class ExecutorTest extends TestCase
'asyncError' => ['type' => Type::string()],
'asyncRawError' => ['type' => Type::string()],
'asyncReturnError' => ['type' => Type::string()],
]
])
],
]),
]);
$expected = [
@ -465,59 +501,59 @@ class ExecutorTest extends TestCase
[
'message' => 'Error getting syncError',
'locations' => [['line' => 3, 'column' => 7]],
'path' => ['syncError']
'path' => ['syncError'],
],
[
'message' => 'Error getting syncRawError',
'locations' => [['line' => 4, 'column' => 7]],
'path'=> [ 'syncRawError' ]
'path' => ['syncRawError'],
],
[
'message' => 'Error getting syncReturnError',
'locations' => [['line' => 5, 'column' => 7]],
'path' => ['syncReturnError']
'path' => ['syncReturnError'],
],
[
'message' => 'Error getting syncReturnErrorList1',
'locations' => [['line' => 6, 'column' => 7]],
'path' => ['syncReturnErrorList', 1]
'path' => ['syncReturnErrorList', 1],
],
[
'message' => 'Error getting syncReturnErrorList3',
'locations' => [['line' => 6, 'column' => 7]],
'path' => ['syncReturnErrorList', 3]
'path' => ['syncReturnErrorList', 3],
],
[
'message' => 'Error getting asyncReject',
'locations' => [['line' => 8, 'column' => 7]],
'path' => ['asyncReject']
'path' => ['asyncReject'],
],
[
'message' => 'Error getting asyncRawReject',
'locations' => [['line' => 9, 'column' => 7]],
'path' => ['asyncRawReject']
'path' => ['asyncRawReject'],
],
[
'message' => 'An unknown error occurred.',
'locations' => [['line' => 10, 'column' => 7]],
'path' => ['asyncEmptyReject']
'path' => ['asyncEmptyReject'],
],
[
'message' => 'Error getting asyncError',
'locations' => [['line' => 11, 'column' => 7]],
'path' => ['asyncError']
'path' => ['asyncError'],
],
[
'message' => 'Error getting asyncRawError',
'locations' => [['line' => 12, 'column' => 7]],
'path' => [ 'asyncRawError' ]
'path' => ['asyncRawError'],
],
[
'message' => 'Error getting asyncReturnError',
'locations' => [['line' => 13, 'column' => 7]],
'path' => ['asyncReturnError']
'path' => ['asyncReturnError'],
],
],
]
];
$result = Executor::execute($schema, $docAst, $data)->toArray();
@ -538,8 +574,8 @@ class ExecutorTest extends TestCase
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
],
]),
]);
$ex = Executor::execute($schema, $ast, $data);
@ -560,8 +596,8 @@ class ExecutorTest extends TestCase
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
],
]),
]);
$ex = Executor::execute($schema, $ast, $data);
@ -581,8 +617,8 @@ class ExecutorTest extends TestCase
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
],
]),
]);
$result = Executor::execute($schema, $ast, $data, null, null, 'OtherExample');
@ -602,17 +638,15 @@ class ExecutorTest extends TestCase
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
],
]),
]);
$result = Executor::execute($schema, $ast, $data);
$expected = [
'errors' => [
[
'message' => 'Must provide an operation.',
]
]
['message' => 'Must provide an operation.'],
],
];
$this->assertArraySubset($expected, $result->toArray());
@ -631,18 +665,16 @@ class ExecutorTest extends TestCase
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
],
]),
]);
$result = Executor::execute($schema, $ast, $data);
$expected = [
'errors' => [
[
'message' => 'Must provide operation name if query contains multiple operations.',
]
]
['message' => 'Must provide operation name if query contains multiple operations.'],
],
];
$this->assertArraySubset($expected, $result->toArray());
@ -660,11 +692,10 @@ class ExecutorTest extends TestCase
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
],
]),
]);
$result = Executor::execute(
$schema,
$ast,
@ -676,10 +707,8 @@ class ExecutorTest extends TestCase
$expected = [
'errors' => [
[
'message' => 'Unknown operation named "UnknownExample".',
]
]
['message' => 'Unknown operation named "UnknownExample".'],
],
];
@ -699,14 +728,14 @@ class ExecutorTest extends TestCase
'name' => 'Q',
'fields' => [
'a' => ['type' => Type::string()],
]
],
]),
'mutation' => new ObjectType([
'name' => 'M',
'fields' => [
'c' => ['type' => Type::string()],
]
])
],
]),
]);
$queryResult = Executor::execute($schema, $ast, $data, null, [], 'Q');
@ -726,14 +755,14 @@ class ExecutorTest extends TestCase
'name' => 'Q',
'fields' => [
'a' => ['type' => Type::string()],
]
],
]),
'mutation' => new ObjectType([
'name' => 'M',
'fields' => [
'c' => ['type' => Type::string()],
]
])
],
]),
]);
$mutationResult = Executor::execute($schema, $ast, $data, null, [], 'M');
$this->assertEquals(['data' => ['c' => 'd']], $mutationResult->toArray());
@ -752,14 +781,14 @@ class ExecutorTest extends TestCase
'name' => 'Q',
'fields' => [
'a' => ['type' => Type::string()],
]
],
]),
'subscription' => new ObjectType([
'name' => 'S',
'fields' => [
'a' => ['type' => Type::string()],
]
])
],
]),
]);
$subscriptionResult = Executor::execute($schema, $ast, $data, null, [], 'S');
@ -780,13 +809,17 @@ class ExecutorTest extends TestCase
return 'a';
},
'b' => function () {
return new Deferred(function () { return 'b'; });
return new Deferred(function () {
return 'b';
});
},
'c' => function () {
return 'c';
},
'd' => function () {
return new Deferred(function () { return 'd'; });
return new Deferred(function () {
return 'd';
});
},
'e' => function () {
return 'e';
@ -803,7 +836,7 @@ class ExecutorTest extends TestCase
'c' => ['type' => Type::string()],
'd' => ['type' => Type::string()],
'e' => ['type' => Type::string()],
]
],
]);
$schema = new Schema(['query' => $queryType]);
@ -814,7 +847,7 @@ class ExecutorTest extends TestCase
'c' => 'c',
'd' => 'd',
'e' => 'e',
]
],
];
$this->assertEquals($expected, Executor::execute($schema, $ast, $data)->toArray());
@ -844,8 +877,8 @@ class ExecutorTest extends TestCase
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
],
]),
]);
$queryResult = Executor::execute($schema, $ast, $data, null, [], 'Q');
@ -866,14 +899,14 @@ class ExecutorTest extends TestCase
'name' => 'Q',
'fields' => [
'a' => ['type' => Type::string()],
]
],
]),
'mutation' => new ObjectType([
'name' => 'M',
'fields' => [
'c' => ['type' => Type::string()],
]
])
],
]),
]);
$mutationResult = Executor::execute($schema, $ast);
$this->assertEquals(['data' => []], $mutationResult->toArray());
@ -890,25 +923,25 @@ class ExecutorTest extends TestCase
'fields' => [
'field' => [
'type' => Type::string(),
'resolve' => function($data, $args) {return $args ? json_encode($args) : '';},
'resolve' => function ($data, $args) {
return $args ? json_encode($args) : '';
},
'args' => [
'a' => ['type' => Type::boolean()],
'b' => ['type' => Type::boolean()],
'c' => ['type' => Type::boolean()],
'd' => ['type' => Type::int()],
'e' => ['type' => Type::int()]
]
]
]
])
'e' => ['type' => Type::int()],
],
],
],
]),
]);
$query = Parser::parse('{ field(a: true, c: false, e: 0) }');
$result = Executor::execute($schema, $query);
$expected = [
'data' => [
'field' => '{"a":true,"c":false,"e":0}'
]
'data' => ['field' => '{"a":true,"c":false,"e":0}'],
];
$this->assertEquals($expected, $result->toArray());
@ -925,8 +958,8 @@ class ExecutorTest extends TestCase
return $obj instanceof Special;
},
'fields' => [
'value' => ['type' => Type::string()]
]
'value' => ['type' => Type::string()],
],
]);
$schema = new Schema([
@ -937,31 +970,37 @@ class ExecutorTest extends TestCase
'type' => Type::listOf($SpecialType),
'resolve' => function ($rootValue) {
return $rootValue['specials'];
}
]
]
])
},
],
],
]),
]);
$query = Parser::parse('{ specials { value } }');
$value = [
'specials' => [ new Special('foo'), new NotSpecial('bar') ]
'specials' => [new Special('foo'), new NotSpecial('bar')],
];
$result = Executor::execute($schema, $query, $value);
$this->assertEquals([
$this->assertEquals(
[
'specials' => [
['value' => 'foo'],
null
]
], $result->data);
null,
],
],
$result->data
);
$this->assertEquals(1, count($result->errors));
$this->assertEquals([
'message' => 'Expected value of type "SpecialType" but got: instance of GraphQL\Tests\Executor\NotSpecial.',
$this->assertEquals(
[
'message' => 'Expected value of type "SpecialType" but got: instance of GraphQL\Tests\Executor\TestClasses\NotSpecial.',
'locations' => [['line' => 1, 'column' => 3]],
'path' => ['specials', 1]
], $result->errors[0]->toSerializableArray());
'path' => ['specials', 1],
],
$result->errors[0]->toSerializableArray()
);
}
/**
@ -979,18 +1018,15 @@ class ExecutorTest extends TestCase
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'foo' => ['type' => Type::string()]
]
])
'foo' => ['type' => Type::string()],
],
]),
]);
$result = Executor::execute($schema, $query);
$expected = [
'data' => [
'foo' => null,
],
'data' => ['foo' => null],
];
$this->assertArraySubset($expected, $result->toArray());
@ -1007,9 +1043,9 @@ class ExecutorTest extends TestCase
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'foo' => ['type' => Type::string()]
]
])
'foo' => ['type' => Type::string()],
],
]),
]);
// For the purposes of test, just return the name of the field!
@ -1028,7 +1064,7 @@ class ExecutorTest extends TestCase
);
$expected = [
'data' => ['foo' => 'foo']
'data' => ['foo' => 'foo'],
];
$this->assertEquals($expected, $result->toArray());
@ -1042,7 +1078,9 @@ class ExecutorTest extends TestCase
'fields' => [
'field' => [
'type' => Type::string(),
'resolve' => function($data, $args) {return $args ? json_encode($args) : '';},
'resolve' => function ($data, $args) {
return $args ? json_encode($args) : '';
},
'args' => [
'a' => ['type' => Type::boolean(), 'defaultValue' => 1],
'b' => ['type' => Type::boolean(), 'defaultValue' => null],
@ -1051,25 +1089,25 @@ class ExecutorTest extends TestCase
'e' => ['type' => Type::int(), 'defaultValue' => '0'],
'f' => ['type' => Type::int(), 'defaultValue' => 'some-string'],
'g' => ['type' => Type::boolean()],
'h' => ['type' => new InputObjectType([
'h' => [
'type' => new InputObjectType([
'name' => 'ComplexType',
'fields' => [
'a' => ['type' => Type::int()],
'b' => ['type' => Type::string()]
]
]), 'defaultValue' => ['a' => 1, 'b' => 'test']]
]
]
]
])
'b' => ['type' => Type::string()],
],
]), 'defaultValue' => ['a' => 1, 'b' => 'test'],
],
],
],
],
]),
]);
$query = Parser::parse('{ field }');
$result = Executor::execute($schema, $query);
$expected = [
'data' => [
'field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"}}'
]
'data' => ['field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"}}'],
];
$this->assertEquals($expected, $result->toArray());
@ -1085,41 +1123,41 @@ class ExecutorTest extends TestCase
$a = new ObjectType([
'name' => 'A',
'fields' => [
'id' => Type::id()
'id' => Type::id(),
],
'interfaces' => function () use (&$iface) {
return [$iface];
}
},
]);
$b = new ObjectType([
'name' => 'B',
'fields' => [
'id' => Type::id()
'id' => Type::id(),
],
'interfaces' => function () use (&$iface) {
return [$iface];
}
},
]);
$iface = new InterfaceType([
'name' => 'Iface',
'fields' => [
'id' => Type::id()
'id' => Type::id(),
],
'resolveType' => function ($v) use ($a, $b) {
return $v['type'] === 'A' ? $a : $b;
}
},
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'ab' => Type::listOf($iface)
]
'ab' => Type::listOf($iface),
],
]),
'types' => [$a, $b]
'types' => [$a, $b],
]);
$data = [
@ -1127,8 +1165,8 @@ class ExecutorTest extends TestCase
['id' => 1, 'type' => 'A'],
['id' => 2, 'type' => 'A'],
['id' => 3, 'type' => 'B'],
['id' => 4, 'type' => 'B']
]
['id' => 4, 'type' => 'B'],
],
];
$query = Parser::parse('
@ -1143,15 +1181,18 @@ class ExecutorTest extends TestCase
$result = Executor::execute($schema, $query, $data, null);
$this->assertEquals([
$this->assertEquals(
[
'data' => [
'ab' => [
['id' => '1'],
['id' => '2'],
new \stdClass(),
new \stdClass()
]
]
], $result->toArray());
new \stdClass(),
],
],
],
$result->toArray()
);
}
}

View File

@ -1,36 +1,58 @@
<?php
declare(strict_types=1);
/**
* @author: Ivo Meißner
* Date: 03.05.16
* Time: 13:14
*/
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class LazyInterfaceTest extends TestCase
{
/**
* @var Schema
*/
/** @var Schema */
protected $schema;
/**
* @var InterfaceType
*/
/** @var InterfaceType */
protected $lazyInterface;
/**
* @var ObjectType
*/
/** @var ObjectType */
protected $testObject;
/**
* Handles execution of a lazily created interface
*/
public function testReturnsFragmentsWithLazyCreatedInterface() : void
{
$request = '
{
lazyInterface {
... on TestObject {
name
}
}
}
';
$expected = [
'data' => [
'lazyInterface' => ['name' => 'testname'],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray());
}
/**
* Setup schema
*/
@ -44,10 +66,10 @@ class LazyInterfaceTest extends TestCase
'type' => $this->getLazyInterfaceType(),
'resolve' => function () {
return [];
}
]
},
],
];
}
},
]);
$this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]);
@ -64,7 +86,7 @@ class LazyInterfaceTest extends TestCase
$this->lazyInterface = new InterfaceType([
'name' => 'LazyInterface',
'fields' => [
'a' => Type::string()
'a' => Type::string(),
],
'resolveType' => function () {
return $this->getTestObjectType();
@ -89,39 +111,13 @@ class LazyInterfaceTest extends TestCase
'type' => Type::string(),
'resolve' => function () {
return 'testname';
}
]
},
],
'interfaces' => [$this->getLazyInterfaceType()]
],
'interfaces' => [$this->getLazyInterfaceType()],
]);
}
return $this->testObject;
}
/**
* Handles execution of a lazily created interface
*/
public function testReturnsFragmentsWithLazyCreatedInterface() : void
{
$request = '
{
lazyInterface {
... on TestObject {
name
}
}
}
';
$expected = [
'data' => [
'lazyInterface' => [
'name' => 'testname'
]
]
];
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray());
}
}

View File

@ -1,22 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Error\UserError;
use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class ListsTest extends TestCase
{
// Describe: Execute: Handles list nullability
/**
* [T]
*/
@ -41,6 +40,40 @@ class ListsTest extends TestCase
);
}
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]
*/
@ -83,9 +116,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -103,16 +136,23 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})],
}),
],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNullableLists(
[
new Deferred(function() {return 1;}),
new Deferred(function() {return null;}),
new Deferred(function() {return 2;})
new Deferred(function () {
return 1;
}),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
],
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
@ -137,7 +177,7 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})
}),
];
},
[
@ -146,9 +186,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1]
]
]
'path' => ['nest', 'test', 1],
],
],
]
);
}
@ -178,14 +218,20 @@ class ListsTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
}
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/**
* [T]!
*/
@ -215,9 +261,9 @@ class ListsTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -235,9 +281,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -255,7 +301,7 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})
}),
],
['data' => ['nest' => ['test' => [1, 2]]]]
);
@ -271,7 +317,7 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})
}),
],
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
@ -288,7 +334,7 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})
}),
];
},
[
@ -297,9 +343,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1]
]
]
'path' => ['nest', 'test', 1],
],
],
]
);
}
@ -323,9 +369,9 @@ class ListsTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -337,6 +383,12 @@ class ListsTest extends TestCase
);
}
private function checkHandlesListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::listOf(Type::nonNull(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/**
* [T!]
*/
@ -360,9 +412,9 @@ class ListsTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -388,9 +440,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -408,7 +460,7 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})
}),
],
['data' => ['nest' => ['test' => [1, 2]]]]
);
@ -416,9 +468,15 @@ class ListsTest extends TestCase
// Contains null
$this->checkHandlesListOfNonNulls(
[
new Deferred(function() {return 1;}),
new Deferred(function() {return null;}),
new Deferred(function() {return 2;})
new Deferred(function () {
return 1;
}),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
],
['data' => ['nest' => ['test' => null]]]
);
@ -435,7 +493,7 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})
}),
];
},
[
@ -444,9 +502,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1]
]
]
'path' => ['nest', 'test', 1],
],
],
]
);
}
@ -462,7 +520,6 @@ class ListsTest extends TestCase
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullListOfNonNulls(
[1, null, 2],
@ -471,9 +528,9 @@ class ListsTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10 ]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -486,14 +543,20 @@ class ListsTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
}
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$this->check($testType, $testData, $expected, $debug);
}
/**
* [T!]!
*/
@ -517,9 +580,9 @@ class ListsTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -534,9 +597,9 @@ class ListsTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -554,9 +617,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -574,7 +637,7 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})
}),
],
['data' => ['nest' => ['test' => [1, 2]]]]
@ -591,16 +654,16 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})
}),
],
[
'data' => ['nest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -617,7 +680,7 @@ class ListsTest extends TestCase
}),
new Deferred(function () {
return 2;
})
}),
];
},
[
@ -626,66 +689,10 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'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,21 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\Root;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class MutationsTest extends TestCase
{
// Execute: Handles mutation execution ordering
/**
* @see it('evaluates mutations serially')
*/
@ -42,26 +41,69 @@ class MutationsTest extends TestCase
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [
'data' => [
'first' => [
'theNumber' => 1
'first' => ['theNumber' => 1],
'second' => ['theNumber' => 2],
'third' => ['theNumber' => 3],
'fourth' => ['theNumber' => 4],
'fifth' => ['theNumber' => 5],
],
'second' => [
'theNumber' => 2
],
'third' => [
'theNumber' => 3
],
'fourth' => [
'theNumber' => 4
],
'fifth' => [
'theNumber' => 5
]
]
];
$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')
*/
@ -91,143 +133,24 @@ class MutationsTest extends TestCase
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [
'data' => [
'first' => [
'theNumber' => 1
],
'second' => [
'theNumber' => 2
],
'first' => ['theNumber' => 1],
'second' => ['theNumber' => 2],
'third' => null,
'fourth' => [
'theNumber' => 4
],
'fifth' => [
'theNumber' => 5
],
'fourth' => ['theNumber' => 4],
'fifth' => ['theNumber' => 5],
'sixth' => null,
],
'errors' => [
[
'debugMessage' => 'Cannot change the number',
'locations' => [['line' => 8, 'column' => 7]]
'locations' => [['line' => 8, 'column' => 7]],
],
[
'debugMessage' => 'Cannot change the number',
'locations' => [['line' => 17, 'column' => 7]]
]
]
'locations' => [['line' => 17, 'column' => 7]],
],
],
];
$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
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Error\FormattedError;
use GraphQL\Error\UserError;
use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class NonNullTest extends TestCase
@ -27,8 +29,13 @@ class NonNullTest extends TestCase
/** @var \Exception */
public $promiseNonNullError;
/** @var callable[] */
public $throwingData;
/** @var callable[] */
public $nullingData;
/** @var Schema */
public $schema;
public function setUp()
@ -121,7 +128,7 @@ class NonNullTest extends TestCase
'promiseNest' => $dataType,
'promiseNonNullNest' => Type::nonNull($dataType),
];
}
},
]);
$this->schema = new Schema(['query' => $dataType]);
@ -143,17 +150,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'sync' => null,
],
'data' => ['sync' => null],
'errors' => [
FormattedError::create(
$this->syncError->getMessage(),
[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
@ -167,18 +175,19 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'promise' => null,
],
'data' => ['promise' => null],
'errors' => [
FormattedError::create(
$this->promiseError->getMessage(),
[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
@ -195,14 +204,15 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'syncNest' => null
],
'data' => ['syncNest' => null],
'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
@ -218,15 +228,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'syncNest' => null
],
'data' => ['syncNest' => null],
'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
@ -242,15 +253,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'promiseNest' => null
],
'data' => ['promiseNest' => null],
'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
@ -266,15 +278,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'promiseNest' => null
],
'data' => ['promiseNest' => null],
'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()
);
}
/**
@ -353,10 +366,13 @@ class NonNullTest extends TestCase
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(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
@ -424,10 +440,13 @@ class NonNullTest extends TestCase
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(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
@ -441,11 +460,12 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'sync' => null,
]
'data' => ['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
@ -459,12 +479,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'promise' => null,
]
'data' => ['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
@ -480,17 +501,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'syncNest' => null
],
'data' => ['syncNest' => null],
'errors' => [
[
'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
@ -506,15 +528,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'syncNest' => null,
],
'data' => ['syncNest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 4, 'column' => 11]]
'locations' => [['line' => 4, 'column' => 11]],
],
],
]
];
$this->assertArraySubset(
@ -536,15 +556,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'promiseNest' => null,
],
'data' => ['promiseNest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
'locations' => [['line' => 4, 'column' => 11]]
'locations' => [['line' => 4, 'column' => 11]],
],
],
]
];
$this->assertArraySubset(
@ -566,15 +584,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'promiseNest' => null,
],
'data' => ['promiseNest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 4, 'column' => 11]]
'locations' => [['line' => 4, 'column' => 11]],
],
],
]
];
$this->assertArraySubset(
@ -628,7 +644,7 @@ class NonNullTest extends TestCase
'promiseNest' => [
'sync' => null,
'promise' => null,
]
],
],
'promiseNest' => [
'sync' => null,
@ -640,9 +656,9 @@ class NonNullTest extends TestCase
'promiseNest' => [
'sync' => null,
'promise' => null,
]
]
]
],
],
],
];
$actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray();
@ -714,7 +730,7 @@ class NonNullTest extends TestCase
['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' => 41, 'column' => 19]]],
]
],
];
$this->assertArraySubset(
@ -734,8 +750,8 @@ class NonNullTest extends TestCase
$expected = [
'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();
$this->assertArraySubset($expected, $actual);
@ -752,10 +768,13 @@ class NonNullTest extends TestCase
$expected = [
'errors' => [
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
@ -769,9 +788,9 @@ class NonNullTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
'locations' => [['line' => 2, 'column' => 17]]
'locations' => [['line' => 2, 'column' => 17]],
],
],
]
];
$this->assertArraySubset(
$expected,
@ -791,9 +810,9 @@ class NonNullTest extends TestCase
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 2, 'column' => 17]]
'locations' => [['line' => 2, 'column' => 17]],
],
],
]
];
$this->assertArraySubset(

View File

@ -1,17 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use PHPUnit\Framework\TestCase;
use React\Promise\Deferred;
use React\Promise\FulfilledPromise;
use React\Promise\LazyPromise;
use React\Promise\Promise as ReactPromise;
use React\Promise\RejectedPromise;
use function class_exists;
/**
* @group ReactPromise
@ -20,19 +20,29 @@ class ReactPromiseAdapterTest extends TestCase
{
public function setUp()
{
if(! class_exists('React\Promise\Promise')) {
$this->markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest');
if (class_exists('React\Promise\Promise')) {
return;
}
$this->markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest');
}
public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven() : void
{
$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 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(true));
$this->assertSame(false, $reactAdapter->isThenable(1));
@ -62,9 +72,12 @@ class ReactPromiseAdapterTest extends TestCase
$result = null;
$resultPromise = $reactAdapter->then($promise, function ($value) use (&$result) {
$resultPromise = $reactAdapter->then(
$promise,
function ($value) use (&$result) {
$result = $value;
});
}
);
$this->assertSame(1, $result);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise);
@ -117,9 +130,12 @@ class ReactPromiseAdapterTest extends TestCase
$exception = null;
$rejectedPromise->then(null, function ($error) use (&$exception) {
$rejectedPromise->then(
null,
function ($error) use (&$exception) {
$exception = $error;
});
}
);
$this->assertInstanceOf('\Exception', $exception);
$this->assertEquals('I am a bad promise', $exception->getMessage());

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Deferred;
@ -10,9 +13,7 @@ use PHPUnit\Framework\TestCase;
class SyncPromiseAdapterTest extends TestCase
{
/**
* @var SyncPromiseAdapter
*/
/** @var SyncPromiseAdapter */
private $promises;
public function setUp()
@ -22,7 +23,11 @@ class SyncPromiseAdapterTest extends TestCase
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(true));
$this->assertEquals(false, $this->promises->isThenable(1));
@ -35,7 +40,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testConvert() : void
{
$dfd = new Deferred(function() {});
$dfd = new Deferred(function () {
});
$result = $this->promises->convertThenable($dfd);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $result);
@ -48,7 +54,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testThen() : void
{
$dfd = new Deferred(function() {});
$dfd = new Deferred(function () {
});
$promise = $this->promises->convertThenable($dfd);
$result = $this->promises->then($promise);
@ -59,7 +66,8 @@ class SyncPromiseAdapterTest extends TestCase
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\Adapter\SyncPromise', $promise->adoptedPromise);
@ -71,6 +79,42 @@ class SyncPromiseAdapterTest extends TestCase
$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
{
$promise = $this->promises->createFulfilled('test');
@ -105,7 +149,7 @@ class SyncPromiseAdapterTest extends TestCase
new Promise($promise2, $this->promises),
3,
new Promise($promise3, $this->promises),
[]
[],
];
$promise = $this->promises->all($data);
@ -114,7 +158,12 @@ class SyncPromiseAdapterTest extends TestCase
$promise1->resolve('value1');
$this->assertValidPromise($promise, null, null, SyncPromise::PENDING);
$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
@ -123,10 +172,12 @@ class SyncPromiseAdapterTest extends TestCase
$deferred1 = new Deferred(function () use (&$called) {
$called[] = 1;
return 1;
});
$deferred2 = new Deferred(function () use (&$called) {
$called[] = 2;
return 2;
});
@ -136,14 +187,17 @@ class SyncPromiseAdapterTest extends TestCase
$p3 = $p2->then(function () use (&$called) {
$dfd = new Deferred(function () use (&$called) {
$called[] = 3;
return 3;
});
return $this->promises->convertThenable($dfd);
});
$p4 = $p3->then(function () use (&$called) {
return new Deferred(function () use (&$called) {
$called[] = 4;
return 4;
});
});
@ -162,40 +216,4 @@ class SyncPromiseAdapterTest extends TestCase
$this->assertEquals([1, 2, 3, 4], $called);
$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,9 +1,13 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase;
use function uniqid;
class SyncPromiseTest extends TestCase
{
@ -12,14 +16,17 @@ class SyncPromiseTest extends TestCase
$onFulfilledReturnsNull = function () {
return null;
};
$onFulfilledReturnsSameValue = function ($value) {
return $value;
};
$onFulfilledReturnsOtherValue = function ($value) {
return 'other-' . $value;
};
$onFulfilledThrows = function ($value) {
throw new \Exception("onFulfilled throws this!");
throw new \Exception('onFulfilled throws this!');
};
return [
@ -28,7 +35,7 @@ class SyncPromiseTest extends TestCase
[uniqid(), $onFulfilledReturnsNull, null, null, SyncPromise::FULFILLED],
['test-value', $onFulfilledReturnsSameValue, 'test-value', 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,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -63,8 +69,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -85,21 +90,27 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
$promise->resolve($resolvedValue);
$this->assertEquals(SyncPromise::FULFILLED, $promise->state);
$nextPromise = $promise->then(null, function() {});
$nextPromise = $promise->then(
null,
function () {
}
);
$this->assertSame($promise, $nextPromise);
$onRejectedCalled = false;
$nextPromise = $promise->then($onFulfilled, function () use (&$onRejectedCalled) {
$nextPromise = $promise->then(
$onFulfilled,
function () use (&$onRejectedCalled) {
$onRejectedCalled = true;
});
}
);
if ($onFulfilled) {
$this->assertNotSame($promise, $nextPromise);
@ -124,19 +135,57 @@ class SyncPromiseTest extends TestCase
$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()
{
$onRejectedReturnsNull = function () {
return null;
};
$onRejectedReturnsSomeValue = function ($reason) {
return 'some-value';
};
$onRejectedThrowsSameReason = function ($reason) {
throw $reason;
};
$onRejectedThrowsOtherReason = function ($value) {
throw new \Exception("onRejected throws other!");
throw new \Exception('onRejected throws other!');
};
return [
@ -158,8 +207,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -169,7 +217,6 @@ class SyncPromiseTest extends TestCase
$this->expectException(\Throwable::class);
$this->expectExceptionMessage('Cannot change rejection reason');
$promise->reject(new \Exception('other-reason'));
}
/**
@ -181,8 +228,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -203,8 +249,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -214,18 +259,22 @@ class SyncPromiseTest extends TestCase
try {
$promise->reject(new \Exception('other-reason'));
$this->fail('Expected exception not thrown');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertEquals('Cannot change rejection reason', $e->getMessage());
}
try {
$promise->resolve('anything');
$this->fail('Expected exception not thrown');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertEquals('Cannot resolve rejected promise', $e->getMessage());
}
$nextPromise = $promise->then(function() {}, null);
$nextPromise = $promise->then(
function () {
},
null
);
$this->assertSame($promise, $nextPromise);
$onFulfilledCalled = false;
@ -266,7 +315,7 @@ class SyncPromiseTest extends TestCase
try {
$promise->resolve($promise);
$this->fail('Expected exception not thrown');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertEquals('Cannot resolve promise with self', $e->getMessage());
$this->assertEquals(SyncPromise::PENDING, $promise->state);
}
@ -299,24 +348,25 @@ class SyncPromiseTest extends TestCase
throw $e;
} catch (\Throwable $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state);
} catch (\Exception $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state);
}
$promise->reject(new \Exception("Rejected Reason"));
$this->assertValidPromise($promise, "Rejected Reason", null, SyncPromise::REJECTED);
$promise->reject(new \Exception('Rejected Reason'));
$this->assertValidPromise($promise, 'Rejected Reason', null, SyncPromise::REJECTED);
$promise = new SyncPromise();
$promise2 = $promise->then(null, function() {
$promise2 = $promise->then(
null,
function () {
return 'value';
});
$promise->reject(new \Exception("Rejected Again"));
}
);
$promise->reject(new \Exception('Rejected Again'));
$this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED);
$promise = new SyncPromise();
$promise2 = $promise->then();
$promise->reject(new \Exception("Rejected Once Again"));
$this->assertValidPromise($promise2, "Rejected Once Again", null, SyncPromise::REJECTED);
$promise->reject(new \Exception('Rejected Once Again'));
$this->assertValidPromise($promise2, 'Rejected Once Again', null, SyncPromise::REJECTED);
}
public function testPendingPromiseThen() : void
@ -334,8 +384,10 @@ class SyncPromiseTest extends TestCase
$onRejectedCount = 0;
$onFulfilled = function ($value) use (&$onFulfilledCount) {
$onFulfilledCount++;
return $onFulfilledCount;
};
$onRejected = function ($reason) use (&$onRejectedCount) {
$onRejectedCount++;
throw $reason;
@ -367,35 +419,4 @@ class SyncPromiseTest extends TestCase
$this->assertValidPromise($nextPromise3, null, 2, 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
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\Adder;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function json_encode;
use function uniqid;
class ResolveTest extends TestCase
{
// Execute: resolve function
private function buildSchema($testField)
{
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'test' => $testField
]
])
]);
}
/**
* @see it('default function accesses properties')
*/
@ -32,9 +24,7 @@ class ResolveTest extends TestCase
{
$schema = $this->buildSchema(['type' => Type::string()]);
$source = [
'test' => 'testValue'
];
$source = ['test' => 'testValue'];
$this->assertEquals(
['data' => ['test' => 'testValue']],
@ -42,6 +32,16 @@ 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')
*/
@ -53,7 +53,7 @@ class ResolveTest extends TestCase
$source = [
'test' => function () use ($_secret) {
return $_secret;
}
},
];
$this->assertEquals(
['data' => ['test' => $_secret]],
@ -92,7 +92,7 @@ class ResolveTest extends TestCase
],
'resolve' => function ($source, $args) {
return json_encode([$source, $args]);
}
},
]);
$this->assertEquals(

View File

@ -1,8 +1,10 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
@ -36,7 +38,7 @@ class SyncTest extends TestCase
'type' => Type::string(),
'resolve' => function ($rootValue) {
return $rootValue;
}
},
],
'asyncField' => [
'type' => Type::string(),
@ -44,9 +46,9 @@ class SyncTest extends TestCase
return new Deferred(function () use ($rootValue) {
return $rootValue;
});
}
]
]
},
],
],
]),
'mutation' => new ObjectType([
'name' => 'Mutation',
@ -55,11 +57,12 @@ class SyncTest extends TestCase
'type' => Type::string(),
'resolve' => function ($rootValue) {
return $rootValue;
}
]
]
])
},
],
],
]),
]);
$this->promiseAdapter = new SyncPromiseAdapter();
}
@ -79,6 +82,26 @@ class SyncTest extends TestCase
$this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result);
}
private function execute($schema, $doc, $rootValue = null)
{
return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
}
private function assertSync($expectedFinalArray, $actualResult)
{
$message = 'Failed assertion that execution was synchronous';
$this->assertInstanceOf(Promise::class, $actualResult, $message);
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
$this->assertEquals(SyncPromise::FULFILLED, $actualResult->adoptedPromise->state, $message);
$this->assertInstanceOf(ExecutionResult::class, $actualResult->adoptedPromise->result, $message);
$this->assertArraySubset(
$expectedFinalArray,
$actualResult->adoptedPromise->result->toArray(),
false,
$message
);
}
/**
* @see it('does not return a Promise if fields are all synchronous')
*/
@ -93,6 +116,8 @@ class SyncTest extends TestCase
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
// Describe: graphqlSync
/**
* @see it('does not return a Promise if mutation fields are all synchronous')
*/
@ -121,7 +146,16 @@ class SyncTest extends TestCase
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
}
// Describe: graphqlSync
private function assertAsync($expectedFinalArray, $actualResult)
{
$message = 'Failed assertion that execution was asynchronous';
$this->assertInstanceOf(Promise::class, $actualResult, $message);
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
$this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message);
$resolvedResult = $this->promiseAdapter->wait($actualResult);
$this->assertInstanceOf(ExecutionResult::class, $resolvedResult, $message);
$this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), false, $message);
}
/**
* @see it('does not return a Promise for syntax errors')
@ -133,12 +167,22 @@ class SyncTest extends TestCase
$this->schema,
$doc
);
$this->assertSync([
$this->assertSync(
[
'errors' => [
['message' => 'Syntax Error: Expected Name, found {',
'locations' => [['line' => 1, 'column' => 29]]]
]
], $result);
[
'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);
}
/**
@ -153,9 +197,12 @@ class SyncTest extends TestCase
$doc
);
$expected = [
'errors' => Utils::map($validationErrors, function ($e) {
'errors' => Utils::map(
$validationErrors,
function ($e) {
return FormattedError::createFromException($e);
})
}
),
];
$this->assertSync($expected, $result);
}
@ -173,35 +220,4 @@ class SyncTest extends TestCase
);
$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)
{
return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
}
private function assertSync($expectedFinalArray, $actualResult)
{
$message = 'Failed assertion that execution was synchronous';
$this->assertInstanceOf(Promise::class, $actualResult, $message);
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
$this->assertEquals(SyncPromise::FULFILLED, $actualResult->adoptedPromise->state, $message);
$this->assertInstanceOf(ExecutionResult::class, $actualResult->adoptedPromise->result, $message);
$this->assertArraySubset($expectedFinalArray, $actualResult->adoptedPromise->result->toArray(), $message);
}
private function assertAsync($expectedFinalArray, $actualResult)
{
$message = 'Failed assertion that execution was asynchronous';
$this->assertInstanceOf(Promise::class, $actualResult, $message);
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
$this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message);
$resolvedResult = $this->promiseAdapter->wait($actualResult);
$this->assertInstanceOf(ExecutionResult::class, $resolvedResult, $message);
$this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), $message);
}
}

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,26 +1,38 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Error\Warning;
use GraphQL\Executor\Executor;
use GraphQL\GraphQL;
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\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class UnionInterfaceTest extends TestCase
{
/** @var */
public $schema;
/** @var Cat */
public $garfield;
/** @var Dog */
public $odie;
/** @var Person */
public $liz;
/** @var Person */
public $john;
public function setUp()
@ -28,8 +40,8 @@ class UnionInterfaceTest extends TestCase
$NamedType = new InterfaceType([
'name' => 'Named',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
@ -37,11 +49,11 @@ class UnionInterfaceTest extends TestCase
'interfaces' => [$NamedType],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
'woofs' => ['type' => Type::boolean()],
],
'isTypeOf' => function ($value) {
return $value instanceof Dog;
}
},
]);
$CatType = new ObjectType([
@ -49,11 +61,11 @@ class UnionInterfaceTest extends TestCase
'interfaces' => [$NamedType],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()]
'meows' => ['type' => Type::boolean()],
],
'isTypeOf' => function ($value) {
return $value instanceof Cat;
}
},
]);
$PetType = new UnionType([
@ -66,7 +78,7 @@ class UnionInterfaceTest extends TestCase
if ($value instanceof Cat) {
return $CatType;
}
}
},
]);
$PersonType = new ObjectType([
@ -75,23 +87,22 @@ class UnionInterfaceTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'pets' => ['type' => Type::listOf($PetType)],
'friends' => ['type' => Type::listOf($NamedType)]
'friends' => ['type' => Type::listOf($NamedType)],
],
'isTypeOf' => function ($value) {
return $value instanceof Person;
}
},
]);
$this->schema = new Schema([
'query' => $PersonType,
'types' => [ $PetType ]
'types' => [$PetType],
]);
$this->garfield = new Cat('Garfield', false);
$this->odie = new Dog('Odie', true);
$this->liz = new Person('Liz');
$this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]);
}
// Execute: Union and intersection types
@ -101,7 +112,6 @@ class UnionInterfaceTest extends TestCase
*/
public function testCanIntrospectOnUnionAndIntersectionTypes() : void
{
$ast = Parser::parse('
{
Named: __type(name: "Named") {
@ -131,16 +141,16 @@ class UnionInterfaceTest extends TestCase
'kind' => 'INTERFACE',
'name' => 'Named',
'fields' => [
['name' => 'name']
['name' => 'name'],
],
'interfaces' => null,
'possibleTypes' => [
['name' => 'Person'],
['name' => 'Dog'],
['name' => 'Cat']
['name' => 'Cat'],
],
'enumValues' => null,
'inputFields' => null
'inputFields' => null,
],
'Pet' => [
'kind' => 'UNION',
@ -149,12 +159,12 @@ class UnionInterfaceTest extends TestCase
'interfaces' => null,
'possibleTypes' => [
['name' => 'Dog'],
['name' => 'Cat']
['name' => 'Cat'],
],
'enumValues' => null,
'inputFields' => null
]
]
'inputFields' => null,
],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray());
}
@ -183,9 +193,9 @@ class UnionInterfaceTest extends TestCase
'name' => 'John',
'pets' => [
['__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());
@ -220,10 +230,10 @@ class UnionInterfaceTest extends TestCase
'name' => 'John',
'pets' => [
['__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());
}
@ -252,9 +262,9 @@ class UnionInterfaceTest extends TestCase
'name' => 'John',
'friends' => [
['__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());
@ -288,9 +298,9 @@ class UnionInterfaceTest extends TestCase
'name' => 'John',
'friends' => [
['__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));
@ -339,13 +349,13 @@ class UnionInterfaceTest extends TestCase
'name' => 'John',
'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
],
'friends' => [
['__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());
@ -364,14 +374,25 @@ class UnionInterfaceTest extends TestCase
$NamedType2 = new InterfaceType([
'name' => 'Named',
'fields' => [
'name' => ['type' => Type::string()]
'name' => ['type' => Type::string()],
],
'resolveType' => function ($obj, $context, ResolveInfo $info) use (&$encounteredContext, &$encounteredSchema, &$encounteredRootValue, &$PersonType2) {
'resolveType' => function (
$obj,
$context,
ResolveInfo $info
) use (
&$encounteredContext,
&
$encounteredSchema,
&$encounteredRootValue,
&$PersonType2
) {
$encounteredContext = $context;
$encounteredSchema = $info->schema;
$encounteredRootValue = $info->rootValue;
return $PersonType2;
}
},
]);
$PersonType2 = new ObjectType([
@ -383,9 +404,7 @@ class UnionInterfaceTest extends TestCase
],
]);
$schema2 = new Schema([
'query' => $PersonType2
]);
$schema2 = new Schema(['query' => $PersonType2]);
$john2 = new Person('John', [], [$this->liz]);

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Values;
@ -10,19 +13,97 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function count;
use function var_export;
use const PHP_EOL;
class ValuesTest extends TestCase
{
/** @var Schema */
private static $schema;
public function testGetIDVariableValues() : void
{
$this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']);
$this->assertEquals(
['errors' => [], 'coerced' => ['idInput' => '123456789']],
self::runTestCase(['idInput' => 123456789]),
$this->runTestCase(['idInput' => 123456789]),
'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
{
$this->expectInputVariablesMatchOutputVariables(['boolInput' => true]);
@ -64,11 +145,20 @@ class ValuesTest extends TestCase
$this->expectGraphQLError(['idInput' => true]);
}
private function expectGraphQLError($variables) : void
{
$result = $this->runTestCase($variables);
$this->assertGreaterThan(0, count($result['errors']));
}
public function testFloatForIDVariableThrowsError() : void
{
$this->expectGraphQLError(['idInput' => 1.0]);
}
/**
* Helpers for running test cases and making assertions
*/
public function testStringForBooleanVariableThrowsError() : void
{
$this->expectGraphQLError(['boolInput' => 'true']);
@ -98,77 +188,4 @@ class ValuesTest extends TestCase
{
$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
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Error\Error;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\ComplexScalar;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function json_encode;
/**
* Execute: Handles inputs
@ -18,7 +21,6 @@ use PHPUnit\Framework\TestCase;
*/
class VariablesTest extends TestCase
{
public function testUsingInlineStructs() : void
{
// executes with complex input:
@ -29,9 +31,7 @@ class VariablesTest extends TestCase
');
$expected = [
'data' => [
'fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'
]
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
];
$this->assertEquals($expected, $result->toArray());
@ -77,8 +77,9 @@ class VariablesTest extends TestCase
'errors' => [[
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
'path' => ['fieldWithObjectInput'],
'locations' => [['line' => 3, 'column' => 39]]
]]
'locations' => [['line' => 3, 'column' => 39]],
],
],
];
$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
{
$doc = '
@ -119,7 +191,7 @@ class VariablesTest extends TestCase
');
$expected = [
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}']
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
];
$this->assertEquals($expected, $result->toArray());
@ -135,9 +207,7 @@ class VariablesTest extends TestCase
$params = ['input' => ['c' => 'foo', 'd' => 'SerializedValue']];
$result = $this->executeQuery($doc, $params);
$expected = [
'data' => [
'fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'
]
'data' => ['fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'],
];
$this->assertEquals($expected, $result->toArray());
@ -152,9 +222,9 @@ class VariablesTest extends TestCase
'{"a":"foo","b":"bar","c":null}; ' .
'Expected non-nullable type String! not to be null at value.c.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql'
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
@ -170,8 +240,8 @@ class VariablesTest extends TestCase
'Expected type TestInputObject to be an object.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
@ -187,8 +257,8 @@ class VariablesTest extends TestCase
'Field value.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
@ -216,12 +286,11 @@ class VariablesTest extends TestCase
'Field value.nb of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
// errors on addition of unknown input field
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog']];
$result = $this->executeQuery($doc, $params);
@ -234,14 +303,12 @@ class VariablesTest extends TestCase
'Field "extra" is not defined by type TestInputObject.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles nullable scalars
/**
* @see it('allows nullable inputs to be omitted')
*/
@ -253,7 +320,7 @@ class VariablesTest extends TestCase
}
');
$expected = [
'data' => ['fieldWithNullableStringInput' => null]
'data' => ['fieldWithNullableStringInput' => null],
];
$this->assertEquals($expected, $result->toArray());
@ -288,6 +355,9 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles non-nullable scalars
/**
* @see it('allows nullable inputs to be set to null in a variable')
*/
@ -332,9 +402,6 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles non-nullable scalars
/**
* @see it('allows non-nullable inputs to be omitted given a default')
*/
@ -346,7 +413,7 @@ class VariablesTest extends TestCase
}
');
$expected = [
'data' => ['fieldWithNonNullableStringInput' => '"default"']
'data' => ['fieldWithNonNullableStringInput' => '"default"'],
];
$this->assertEquals($expected, $result->toArray());
}
@ -367,9 +434,9 @@ class VariablesTest extends TestCase
[
'message' => 'Variable "$value" of required type "String!" was not provided.',
'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql'
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -393,8 +460,8 @@ class VariablesTest extends TestCase
'Expected non-nullable type String! not to be null.',
'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -445,17 +512,19 @@ class VariablesTest extends TestCase
'locations' => [['line' => 3, 'column' => 9]],
'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql',
]]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles lists and nullability
/**
* @see it('reports error for array passed into string input')
*/
public function testReportsErrorForArrayPassedIntoStringInput() : void
{
$doc = '
query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value)
@ -471,9 +540,10 @@ class VariablesTest extends TestCase
'String; String cannot represent an array value: [1,2,3]',
'category' => 'graphql',
'locations' => [
['line' => 2, 'column' => 31]
]
]]
['line' => 2, 'column' => 31],
],
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -512,13 +582,12 @@ class VariablesTest extends TestCase
'locations' => [['line' => 3, 'column' => 48]],
'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql',
]]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles lists and nullability
/**
* @see it('allows lists to be null')
*/
@ -584,8 +653,8 @@ class VariablesTest extends TestCase
'Expected non-nullable type [String]! not to be null.',
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -669,8 +738,8 @@ class VariablesTest extends TestCase
'Expected non-nullable type String! not to be null at value[1].',
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -694,8 +763,8 @@ class VariablesTest extends TestCase
'Expected non-nullable type [String!]! not to be null.',
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -715,6 +784,8 @@ class VariablesTest extends TestCase
$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')
*/
@ -734,8 +805,8 @@ class VariablesTest extends TestCase
'Expected non-nullable type String! not to be null at value[1].',
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -760,8 +831,8 @@ class VariablesTest extends TestCase
'be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -787,13 +858,12 @@ class VariablesTest extends TestCase
'cannot be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Execute: Uses argument default values
/**
* @see it('when no argument provided')
*/
@ -841,77 +911,10 @@ class VariablesTest extends TestCase
'locations' => [['line' => 2, 'column' => 50]],
'path' => ['fieldWithDefaultArgumentValue'],
'category' => 'graphql',
]]
],
],
];
$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);
}
}