Fix CS in tests/Executor

This commit is contained in:
Simon Podlipsky 2018-09-01 17:07:06 +02:00
parent ec54d6152b
commit b02d25e62c
No known key found for this signature in database
GPG Key ID: 725C2BD962B42663
31 changed files with 3033 additions and 2662 deletions

View File

@ -1,79 +1,83 @@
<?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')
*/
public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'fields' => [
'name' => [ 'type' => Type::string() ]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [ $PetType ],
'isTypeOf' => function($obj) {
return new Deferred(function() use ($obj) {
'name' => 'Dog',
'interfaces' => [$PetType],
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Dog;
});
},
'fields' => [
'name' => [ 'type' => Type::string() ],
'woofs' => [ 'type' => Type::boolean() ],
]
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'interfaces' => [ $PetType ],
'isTypeOf' => function($obj) {
return new Deferred(function() use ($obj) {
'name' => 'Cat',
'interfaces' => [$PetType],
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Cat;
});
},
'fields' => [
'name' => [ 'type' => Type::string() ],
'meows' => [ 'type' => Type::boolean() ],
]
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'resolve' => function() {
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [ $CatType, $DogType ]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -93,10 +97,10 @@ class AbstractPromiseTest extends TestCase
$expected = [
'data' => [
'pets' => [
[ 'name' => 'Odie', 'woofs' => true ],
[ 'name' => 'Garfield', 'meows' => false ]
]
]
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
],
],
];
$this->assertEquals($expected, $result);
@ -107,58 +111,57 @@ class AbstractPromiseTest extends TestCase
*/
public function testIsTypeOfCanBeRejected() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'isTypeOf' => function () {
'isTypeOf' => function () {
return new Deferred(function () {
throw new UserError('We are testing this error');
});
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'isTypeOf' => function ($obj) {
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Cat;
});
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -176,21 +179,21 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray();
$expected = [
'data' => [
'pets' => [null, null]
'data' => [
'pets' => [null, null],
],
'errors' => [
[
'message' => 'We are testing this error',
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 0],
'path' => ['pets', 0],
],
[
'message' => 'We are testing this error',
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 1]
]
]
'path' => ['pets', 1],
],
],
];
$this->assertArraySubset($expected, $result);
@ -201,50 +204,49 @@ class AbstractPromiseTest extends TestCase
*/
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
{
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Dog;
});
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Cat;
});
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
'name' => 'Pet',
'types' => [$DogType, $CatType]
'name' => 'Pet',
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'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);
@ -280,7 +282,7 @@ class AbstractPromiseTest extends TestCase
public function testResolveTypeOnInterfaceYieldsUsefulError() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
if ($obj instanceof Dog) {
@ -292,58 +294,59 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
});
},
'fields' => [
'name' => ['type' => Type::string()]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$HumanType = new ObjectType([
'name' => 'Human',
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return new Deferred(function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
});
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -361,20 +364,20 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
$expected = [
'data' => [
'data' => [
'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]
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2],
],
]
],
];
$this->assertArraySubset($expected, $result);
@ -385,32 +388,31 @@ class AbstractPromiseTest extends TestCase
*/
public function testResolveTypeOnUnionYieldsUsefulError() : void
{
$HumanType = new ObjectType([
'name' => 'Human',
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'fields' => [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'fields' => [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) {
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
if ($obj instanceof Dog) {
@ -422,28 +424,29 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
});
},
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -462,20 +465,20 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
$expected = [
'data' => [
'data' => [
'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]
]
]
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2],
],
],
];
$this->assertArraySubset($expected, $result);
@ -487,7 +490,7 @@ class AbstractPromiseTest extends TestCase
public function testResolveTypeAllowsResolvingWithTypeName() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) {
return new Deferred(function () use ($obj) {
if ($obj instanceof Dog) {
@ -496,49 +499,49 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Cat) {
return 'Cat';
}
return null;
});
},
'fields' => [
'name' => ['type' => Type::string()]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'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,53 +574,52 @@ class AbstractPromiseTest extends TestCase
*/
public function testResolveTypeCanBeCaught() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function () {
return new Deferred(function () {
throw new UserError('We are testing this error');
});
},
'fields' => [
'name' => ['type' => Type::string()]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -635,21 +637,21 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray();
$expected = [
'data' => [
'pets' => [null, null]
'data' => [
'pets' => [null, null],
],
'errors' => [
[
'message' => 'We are testing this error',
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 0]
'path' => ['pets', 0],
],
[
'message' => 'We are testing this error',
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'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')
*/
@ -25,48 +30,50 @@ class AbstractTest extends TestCase
{
// isTypeOf used to resolve runtime type for Interface
$petType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
// Added to interface type when defined
$dogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
]
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]);
$catType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$petType],
'isTypeOf' => function ($obj) {
'isTypeOf' => function ($obj) {
return $obj instanceof Cat;
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($petType),
'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));
@ -98,42 +105,44 @@ class AbstractTest extends TestCase
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
{
$dogType = new ObjectType([
'name' => 'Dog',
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
]
'name' => 'Dog',
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]);
$catType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'isTypeOf' => function ($obj) {
return $obj instanceof Cat;
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$petType = new UnionType([
'name' => 'Pet',
'types' => [$dogType, $catType]
'name' => 'Pet',
'types' => [$dogType, $catType],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($petType),
'resolve' => function() {
return [ new Dog('Odie', true), new Cat('Garfield', false) ];
}
]
]
])
'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,14 +170,14 @@ class AbstractTest extends TestCase
/**
* @see it('resolveType on Interface yields useful error')
*/
function testResolveTypeOnInterfaceYieldsUsefulError()
public function testResolveTypeOnInterfaceYieldsUsefulError() : void
{
$DogType = null;
$CatType = null;
$DogType = null;
$CatType = null;
$HumanType = null;
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
if ($obj instanceof Dog) {
return $DogType;
@ -179,58 +188,58 @@ class AbstractTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
},
'fields' => [
'name' => ['type' => Type::string()]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$HumanType = new ObjectType([
'name' => 'Human',
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
},
],
],
]),
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$query = '{
pets {
name
@ -244,20 +253,21 @@ class AbstractTest extends TestCase
}';
$expected = [
'data' => [
'data' => [
'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]
]]
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2],
],
],
];
$actual = GraphQL::executeQuery($schema, $query)->toArray(true);
$actual = GraphQL::executeQuery($schema, $query)->toArray(true);
$this->assertArraySubset($expected, $actual);
}
@ -268,30 +278,30 @@ class AbstractTest extends TestCase
public function testResolveTypeOnUnionYieldsUsefulError() : void
{
$HumanType = new ObjectType([
'name' => 'Human',
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'fields' => [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'fields' => [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) {
if ($obj instanceof Dog) {
return $DogType;
@ -303,25 +313,25 @@ class AbstractTest extends TestCase
return $HumanType;
}
},
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -337,22 +347,27 @@ class AbstractTest extends TestCase
}
}';
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
$expected = [
'data' => [
'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]
]]
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2],
],
],
];
$this->assertArraySubset($expected, $result);
}
@ -363,25 +378,25 @@ class AbstractTest extends TestCase
public function testReturningInvalidValueFromResolveTypeYieldsUsefulError() : void
{
$fooInterface = new InterfaceType([
'name' => 'FooInterface',
'fields' => ['bar' => ['type' => Type::string()]],
'name' => 'FooInterface',
'fields' => ['bar' => ['type' => Type::string()]],
'resolveType' => function () {
return [];
},
]);
$fooObject = new ObjectType([
'name' => 'FooObject',
'fields' => ['bar' => ['type' => Type::string()]],
'name' => 'FooObject',
'fields' => ['bar' => ['type' => Type::string()]],
'interfaces' => [$fooInterface],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'foo' => [
'type' => $fooInterface,
'type' => $fooInterface,
'resolve' => function () {
return 'dummy';
},
@ -394,18 +409,18 @@ class AbstractTest extends TestCase
$result = GraphQL::executeQuery($schema, '{ foo { bar } }');
$expected = [
'data' => ['foo' => null],
'data' => ['foo' => null],
'errors' => [
[
'message' => 'Internal server error',
'message' => 'Internal server error',
'debugMessage' =>
'Abstract type FooInterface must resolve to an Object type at ' .
'runtime for field Query.foo with value "dummy", received "[]". ' .
'Either the FooInterface type should provide a "resolveType" ' .
'function or each possible type should provide an "isTypeOf" function.',
'locations' => [['line' => 1, 'column' => 3]],
'path' => ['foo'],
'category' => 'internal',
'locations' => [['line' => 1, 'column' => 3]],
'path' => ['foo'],
'category' => 'internal',
],
],
];
@ -418,51 +433,56 @@ class AbstractTest extends TestCase
public function testResolveTypeAllowsResolvingWithTypeName() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'resolveType' => function($obj) {
if ($obj instanceof Dog) return 'Dog';
if ($obj instanceof Cat) return 'Cat';
'name' => 'Pet',
'resolveType' => function ($obj) {
if ($obj instanceof Dog) {
return 'Dog';
}
if ($obj instanceof Cat) {
return 'Cat';
}
return null;
},
'fields' => [
'name' => [ 'type' => Type::string() ]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [ $PetType ],
'fields' => [
'name' => [ 'type' => Type::string() ],
'woofs' => [ 'type' => Type::boolean() ],
]
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'interfaces' => [ $PetType ],
'fields' => [
'name' => [ 'type' => Type::string() ],
'meows' => [ 'type' => Type::boolean() ],
]
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'resolve' => function() {
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [ $CatType, $DogType ]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -479,51 +499,52 @@ class AbstractTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray();
$this->assertEquals([
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
]
], $result);
$this->assertEquals(
[
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
],
],
],
$result
);
}
public function testHintsOnConflictingTypeInstancesInResolveType() : void
{
$createTest = function() use (&$iface) {
$createTest = function () use (&$iface) {
return new ObjectType([
'name' => 'Test',
'fields' => [
'a' => Type::string()
'name' => 'Test',
'fields' => [
'a' => Type::string(),
],
'interfaces' => function() use ($iface) {
'interfaces' => function () use ($iface) {
return [$iface];
}
},
]);
};
$iface = new InterfaceType([
'name' => 'Node',
'fields' => [
'a' => Type::string()
'name' => 'Node',
'fields' => [
'a' => Type::string(),
],
'resolveType' => function() use (&$createTest) {
'resolveType' => function () use (&$createTest) {
return $createTest();
}
},
]);
$query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'node' => $iface,
'test' => $createTest()
]
'test' => $createTest(),
],
]);
$schema = new Schema([
'query' => $query,
]);
$schema = new Schema(['query' => $query]);
$schema->assertValid();
$query = '
@ -537,9 +558,9 @@ class AbstractTest extends TestCase
$result = Executor::execute($schema, Parser::parse($query), ['node' => ['a' => 'value']]);
$this->assertEquals(
'Schema must contain unique named types but contains multiple types named "Test". '.
'Make sure that `resolveType` function of abstract type "Node" returns the same type instance '.
'as referenced anywhere else within the schema '.
'Schema must contain unique named types but contains multiple types named "Test". ' .
'Make sure that `resolveType` function of abstract type "Node" returns the same type instance ' .
'as referenced anywhere else within the schema ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$result->errors[0]->getMessage()
);

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,127 +65,152 @@ 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 = [];
$this->path = [];
$this->userType = new ObjectType([
'name' => 'User',
'fields' => function() {
'name' => 'User',
'fields' => function () {
return [
'name' => [
'type' => Type::string(),
'name' => [
'type' => Type::string(),
'resolve' => function ($user, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $user['name'];
}
},
],
'bestFriend' => [
'type' => $this->userType,
'resolve' => function($user, $args, $context, ResolveInfo $info) {
'type' => $this->userType,
'resolve' => function ($user, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function() use ($user) {
return new Deferred(function () use ($user) {
$this->path[] = 'deferred-for-best-friend-of-' . $user['id'];
return Utils::find($this->userDataSource, function($entry) use ($user) {
return $entry['id'] === $user['bestFriendId'];
});
return Utils::find(
$this->userDataSource,
function ($entry) use ($user) {
return $entry['id'] === $user['bestFriendId'];
}
);
});
}
]
},
],
];
}
},
]);
$this->storyType = new ObjectType([
'name' => 'Story',
'name' => 'Story',
'fields' => [
'title' => [
'type' => Type::string(),
'resolve' => function($entry, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $entry['title'];
}
],
'author' => [
'type' => $this->userType,
'resolve' => function($story, $args, $context, ResolveInfo $info) {
'title' => [
'type' => Type::string(),
'resolve' => function ($entry, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function() use ($story) {
return $entry['title'];
},
],
'author' => [
'type' => $this->userType,
'resolve' => function ($story, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function () use ($story) {
$this->path[] = 'deferred-for-story-' . $story['id'] . '-author';
return Utils::find($this->userDataSource, function($entry) use ($story) {
return $entry['id'] === $story['authorId'];
});
return Utils::find(
$this->userDataSource,
function ($entry) use ($story) {
return $entry['id'] === $story['authorId'];
}
);
});
}
]
]
},
],
],
]);
$this->categoryType = new ObjectType([
'name' => 'Category',
'name' => 'Category',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function($category, $args, $context, ResolveInfo $info) {
'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) {
'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 in_array($category['id'], $story['categoryIds']);
});
}
return Utils::filter(
$this->storyDataSource,
function ($story) use ($category) {
return in_array($category['id'], $story['categoryIds']);
}
);
},
],
'topStory' => [
'type' => $this->storyType,
'resolve' => function($category, $args, $context, ResolveInfo $info) {
'type' => $this->storyType,
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function () use ($category) {
$this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory';
return Utils::find($this->storyDataSource, function($story) use ($category) {
return $story['id'] === $category['topStoryId'];
});
return Utils::find(
$this->storyDataSource,
function ($story) use ($category) {
return $story['id'] === $category['topStoryId'];
}
);
});
}
]
]
},
],
],
]);
$this->queryType = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'topStories' => [
'type' => Type::listOf($this->storyType),
'resolve' => function($val, $args, $context, ResolveInfo $info) {
'topStories' => [
'type' => Type::listOf($this->storyType),
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) {
return $story['id'] % 2 === 1;
});
}
return Utils::filter(
$this->storyDataSource,
function ($story) {
return $story['id'] % 2 === 1;
}
);
},
],
'featuredCategory' => [
'type' => $this->categoryType,
'resolve' => function($val, $args, $context, ResolveInfo $info) {
'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) {
'categories' => [
'type' => Type::listOf($this->categoryType),
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $this->categoryDataSource;
}
]
]
},
],
],
]);
parent::setUp();
@ -202,12 +238,12 @@ class DeferredFieldsTest extends TestCase
');
$schema = new Schema([
'query' => $this->queryType
'query' => $this->queryType,
]);
$expected = [
'data' => [
'topStories' => [
'topStories' => [
['title' => 'Story #1', 'author' => ['name' => 'John']],
['title' => 'Story #3', 'author' => ['name' => 'Joe']],
['title' => 'Story #5', 'author' => ['name' => 'John']],
@ -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,12 +328,12 @@ class DeferredFieldsTest extends TestCase
');
$schema = new Schema([
'query' => $this->queryType
'query' => $this->queryType,
]);
$author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']];
$author2 = ['name' => 'Jane', 'bestFriend' => ['name' => 'Joe']];
$author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']];
$author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']];
$author4 = ['name' => 'Dirk', 'bestFriend' => ['name' => 'John']];
$expected = [
@ -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);
@ -352,54 +388,56 @@ class DeferredFieldsTest extends TestCase
public function testComplexRecursiveDeferredFields() : void
{
$complexType = new ObjectType([
'name' => 'ComplexType',
'fields' => function() use (&$complexType) {
'name' => 'ComplexType',
'fields' => function () use (&$complexType) {
return [
'sync' => [
'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return 'sync';
}
],
'deferred' => [
'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'sync' => [
'type' => Type::string(),
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function() use ($info) {
return 'sync';
},
],
'deferred' => [
'type' => Type::string(),
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function () use ($info) {
$this->path[] = ['!dfd for: ', $info->path];
return 'deferred';
});
}
},
],
'nest' => [
'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'nest' => [
'type' => $complexType,
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return [];
}
},
],
'deferredNest' => [
'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'type' => $complexType,
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function() use ($info) {
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('
$query = Parser::parse('
{
nest {
sync
@ -427,34 +465,34 @@ class DeferredFieldsTest extends TestCase
}
}
');
$result = Executor::execute($schema, $query);
$result = Executor::execute($schema, $query);
$expected = [
'data' => [
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred'
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred',
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred'
]
'sync' => 'sync',
'deferred' => 'deferred',
],
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred'
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred',
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred'
]
]
]
'sync' => 'sync',
'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,99 +1,119 @@
<?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
{
// isTypeOf used to resolve runtime type for Interface
$petType = new InterfaceType([
'name' => 'Pet',
'fields' => function() {
'name' => 'Pet',
'fields' => function () {
return [
'name' => ['type' => Type::string()]
'name' => ['type' => Type::string()],
];
}
},
]);
// Added to interface type when defined
$dogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'fields' => function() {
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => function () {
return [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
];
}
},
]);
$catType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$petType],
'isTypeOf' => function ($obj) {
'isTypeOf' => function ($obj) {
return $obj instanceof Cat;
},
'fields' => function() {
'fields' => function () {
return [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
];
}
},
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($petType),
'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) {
'types' => [$catType, $dogType],
'typeLoader' => function ($name) use ($dogType, $petType, $catType) {
switch ($name) {
case 'Dog':
return $dogType;
@ -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],
],
]);
@ -134,43 +154,46 @@ class ExecutorLazySchemaTest extends TestCase
$this->assertInstanceOf(Error::class, $result->errors[0]->getPrevious());
$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 '.
'implementations. It requires full schema scan and degrades query performance significantly. '.
'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of ' .
'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
{
$calls = [];
$typeLoader = function($name) use (&$calls) {
$calls = [];
$typeLoader = function ($name) use (&$calls) {
$calls[] = $name;
switch ($name) {
case 'Test':
return new ObjectType([
'name' => 'Test',
'fields' => function() {
'name' => 'Test',
'fields' => function () {
return [
'test' => Type::string(),
];
}
},
]);
default:
return null;
}
};
$query = new ObjectType([
'name' => 'Query',
'fields' => function() use ($typeLoader) {
'name' => 'Query',
'fields' => function () use ($typeLoader) {
return [
'test' => $typeLoader('Test')
'test' => $typeLoader('Test'),
];
}
},
]);
$schema = new Schema([
'query' => $query,
'typeLoader' => $typeLoader
'query' => $query,
'typeLoader' => $typeLoader,
]);
$query = '
@ -186,8 +209,8 @@ class ExecutorLazySchemaTest extends TestCase
$this->assertEquals(['Test', 'Test'], $calls);
$this->assertEquals(
'Schema must contain unique named types but contains multiple types named "Test". '.
'Make sure that type loader returns the same instance as defined in Query.test '.
'Schema must contain unique named types but contains multiple types named "Test". ' .
'Make sure that type loader returns the same instance as defined in Query.test ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$result->errors[0]->getMessage()
);
@ -200,54 +223,161 @@ class ExecutorLazySchemaTest extends TestCase
public function testSimpleQuery() : void
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
'query' => $this->loadType('Query'),
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '{ object { string } }';
$query = '{ object { string } }';
$result = Executor::execute(
$schema,
Parser::parse($query),
['object' => ['string' => 'test']]
);
$expected = [
$expected = [
'data' => ['object' => ['string' => 'test']],
];
$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) {
'query' => $this->loadType('Query'),
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '{ object { object { object { string } } } }';
$query = '{ object { object { object { string } } } }';
$result = Executor::execute(
$schema,
Parser::parse($query),
['object' => ['object' => ['object' => ['string' => 'test']]]]
);
$expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]]
$expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]],
];
$expectedLoadedTypes = [
'Query' => true,
'SomeObject' => true,
'OtherObject' => true
'Query' => true,
'SomeObject' => 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);
}
@ -264,13 +394,13 @@ class ExecutorLazySchemaTest extends TestCase
public function testResolveUnion() : void
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
'query' => $this->loadType('Query'),
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '
$query = '
{
other {
union {
@ -285,17 +415,17 @@ class ExecutorLazySchemaTest extends TestCase
['other' => ['union' => ['scalar' => 'test']]]
);
$expected = [
$expected = [
'data' => ['other' => ['union' => ['scalar' => 'test']]],
];
$expectedLoadedTypes = [
'Query' => true,
'SomeObject' => true,
'OtherObject' => true,
'SomeUnion' => true,
'Query' => true,
'SomeObject' => true,
'OtherObject' => true,
'SomeUnion' => true,
'SomeInterface' => true,
'DeeperObject' => true,
'SomeScalar' => true,
'DeeperObject' => true,
'SomeScalar' => true,
];
$this->assertEquals($expected, $result->toArray(true));
@ -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,74 +1,77 @@
<?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')
*/
public function testExecutesUsingASchema() : void
{
$BlogArticle = null;
$BlogImage = new ObjectType([
'name' => 'Image',
$BlogImage = new ObjectType([
'name' => 'Image',
'fields' => [
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()],
]
],
]);
$BlogAuthor = new ObjectType([
'name' => 'Author',
'fields' => function() use (&$BlogArticle, &$BlogImage) {
'name' => 'Author',
'fields' => function () use (&$BlogArticle, &$BlogImage) {
return [
'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'pic' => [
'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]],
'type' => $BlogImage,
'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'pic' => [
'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]],
'type' => $BlogImage,
'resolve' => function ($obj, $args) {
return $obj['pic']($args['width'], $args['height']);
}
},
],
'recentArticle' => $BlogArticle
'recentArticle' => $BlogArticle,
];
}
},
]);
$BlogArticle = new ObjectType([
'name' => 'Article',
'name' => 'Article',
'fields' => [
'id' => ['type' => Type::nonNull(Type::string())],
'id' => ['type' => Type::nonNull(Type::string())],
'isPublished' => ['type' => Type::boolean()],
'author' => ['type' => $BlogAuthor],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'keywords' => ['type' => Type::listOf(Type::string())]
]
'author' => ['type' => $BlogAuthor],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'keywords' => ['type' => Type::listOf(Type::string())],
],
]);
$BlogQuery = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'article' => [
'type' => $BlogArticle,
'args' => ['id' => ['type' => Type::id()]],
'type' => $BlogArticle,
'args' => ['id' => ['type' => Type::id()]],
'resolve' => function ($_, $args) {
return $this->article($args['id']);
}
},
],
'feed' => [
'type' => Type::listOf($BlogArticle),
'feed' => [
'type' => Type::listOf($BlogArticle),
'resolve' => function () {
return [
$this->article(1),
@ -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 {
@ -126,51 +128,71 @@ 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']
'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',
],
],
'article' => [
'id' => '1',
'id' => '1',
'isPublished' => true,
'title' => 'My Article 1',
'body' => 'This is a post',
'author' => [
'id' => '123',
'name' => 'John Smith',
'pic' => [
'url' => 'cdn://123',
'width' => 640,
'height' => 480
'title' => 'My Article 1',
'body' => 'This is a post',
'author' => [
'id' => '123',
'name' => 'John Smith',
'pic' => [
'url' => 'cdn://123',
'width' => 640,
'height' => 480,
],
'recentArticle' => [
'id' => '1',
'id' => '1',
'isPublished' => true,
'title' => 'My Article 1',
'body' => 'This is a post',
'keywords' => ['foo', 'bar', '1', 'true', null]
]
]
]
]
'title' => 'My Article 1',
'body' => 'This is a post',
'keywords' => ['foo', 'bar', '1', 'true', null],
],
],
],
],
];
$this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray());
@ -179,30 +201,32 @@ class ExecutorSchemaTest extends TestCase
private function article($id)
{
$johnSmith = null;
$article = function($id) use (&$johnSmith) {
$article = function ($id) use (&$johnSmith) {
return [
'id' => $id,
'id' => $id,
'isPublished' => 'true',
'author' => $johnSmith,
'title' => 'My Article ' . $id,
'body' => 'This is a post',
'hidden' => 'This data is not exposed in the schema',
'keywords' => ['foo', 'bar', 1, true, null]
'author' => $johnSmith,
'title' => 'My Article ' . $id,
'body' => 'This is a post',
'hidden' => 'This data is not exposed in the schema',
'keywords' => ['foo', 'bar', 1, true, null],
];
};
$getPic = function($uid, $width, $height) {
$getPic = function ($uid, $width, $height) {
return [
'url' => "cdn://$uid",
'width' => $width,
'height' => $height
'url' => sprintf('cdn://%s', $uid),
'width' => $width,
'height' => $height,
];
};
$johnSmith = [
'id' => 123,
'name' => 'John Smith',
'pic' => function($width, $height) use ($getPic) {return $getPic(123, $width, $height);},
'id' => 123,
'name' => 'John Smith',
'pic' => function ($width, $height) use ($getPic) {
return $getPic(123, $width, $height);
},
'recentArticle' => $article(1),
];

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +1,34 @@
<?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;
/**
* Setup schema
*/
protected function setUp()
{
$query = new ObjectType([
'name' => 'query',
'fields' => function () {
return [
'lazyInterface' => [
'type' => $this->getLazyInterfaceType(),
'resolve' => function() {
return [];
}
]
];
}
]);
$this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]);
}
/**
* Returns the LazyInterface
*
* @return InterfaceType
*/
protected function getLazyInterfaceType()
{
if (!$this->lazyInterface) {
$this->lazyInterface = new InterfaceType([
'name' => 'LazyInterface',
'fields' => [
'a' => Type::string()
],
'resolveType' => function() {
return $this->getTestObjectType();
},
]);
}
return $this->lazyInterface;
}
/**
* Returns the test ObjectType
* @return ObjectType
*/
protected function getTestObjectType()
{
if (!$this->testObject) {
$this->testObject = new ObjectType([
'name' => 'TestObject',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function() {
return 'testname';
}
]
],
'interfaces' => [$this->getLazyInterfaceType()]
]);
}
return $this->testObject;
}
/**
* Handles execution of a lazily created interface
*/
@ -116,12 +46,78 @@ class LazyInterfaceTest extends TestCase
$expected = [
'data' => [
'lazyInterface' => [
'name' => 'testname'
]
]
'lazyInterface' => ['name' => 'testname'],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray());
}
/**
* Setup schema
*/
protected function setUp()
{
$query = new ObjectType([
'name' => 'query',
'fields' => function () {
return [
'lazyInterface' => [
'type' => $this->getLazyInterfaceType(),
'resolve' => function () {
return [];
},
],
];
},
]);
$this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]);
}
/**
* Returns the LazyInterface
*
* @return InterfaceType
*/
protected function getLazyInterfaceType()
{
if (! $this->lazyInterface) {
$this->lazyInterface = new InterfaceType([
'name' => 'LazyInterface',
'fields' => [
'a' => Type::string(),
],
'resolveType' => function () {
return $this->getTestObjectType();
},
]);
}
return $this->lazyInterface;
}
/**
* Returns the test ObjectType
* @return ObjectType
*/
protected function getTestObjectType()
{
if (! $this->testObject) {
$this->testObject = new ObjectType([
'name' => 'TestObject',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function () {
return 'testname';
},
],
],
'interfaces' => [$this->getLazyInterfaceType()],
]);
}
return $this->testObject;
}
}

View File

@ -1,22 +1,21 @@
<?php
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]
*/
@ -24,23 +23,57 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNullableLists(
[ 1, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
[1, 2],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNullableLists(
[ 1, null, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
[1, null, 2],
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNullableLists(
null,
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
}
private function checkHandlesNullableLists($testData, $expected)
{
$testType = Type::listOf(Type::int());
$this->check($testType, $testData, $expected);
}
private function check($testType, $testData, $expected, $debug = false)
{
$data = ['test' => $testData];
$dataType = null;
$dataType = new ObjectType([
'name' => 'DataType',
'fields' => function () use (&$testType, &$dataType, $data) {
return [
'test' => ['type' => $testType],
'nest' => [
'type' => $dataType,
'resolve' => function () use ($data) {
return $data;
},
],
];
},
]);
$schema = new Schema(['query' => $dataType]);
$ast = Parser::parse('{ nest { test } }');
$result = Executor::execute($schema, $ast, $data);
$this->assertArraySubset($expected, $result->toArray($debug));
}
/**
* [T]
*/
@ -48,26 +81,26 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNullableLists(
new Deferred(function() {
return [1,2];
new Deferred(function () {
return [1, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNullableLists(
new Deferred(function() {
new Deferred(function () {
return [1, null, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNullableLists(
new Deferred(function() {
new Deferred(function () {
return null;
}),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
// Rejected
@ -78,14 +111,14 @@ class ListsTest extends TestCase
});
},
[
'data' => ['nest' => ['test' => null]],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -98,57 +131,64 @@ class ListsTest extends TestCase
// Contains values
$this->checkHandlesNullableLists(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 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 ] ] ] ]
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNullableLists(
new Deferred(function() {
new Deferred(function () {
return null;
}),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
// Contains reject
$this->checkHandlesNullableLists(
function () {
return [
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
throw new UserError('bad');
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
];
},
[
'data' => ['nest' => ['test' => [1, null, 2]]],
'data' => ['nest' => ['test' => [1, null, 2]]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1]
]
]
'path' => ['nest', 'test', 1],
],
],
]
);
}
@ -160,32 +200,38 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNonNullableLists(
[ 1, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
[1, 2],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullableLists(
[ 1, null, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
[1, null, 2],
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNonNullableLists(
null,
[
'data' => [ 'nest' => null ],
'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
);
}
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/**
* [T]!
*/
@ -193,31 +239,31 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNonNullableLists(
new Deferred(function() {
return [1,2];
new Deferred(function () {
return [1, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullableLists(
new Deferred(function() {
new Deferred(function () {
return [1, null, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNonNullableLists(
null,
[
'data' => [ 'nest' => null ],
'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
);
@ -225,19 +271,19 @@ class ListsTest extends TestCase
// Rejected
$this->checkHandlesNonNullableLists(
function () {
return new Deferred(function() {
return new Deferred(function () {
throw new UserError('bad');
});
},
[
'data' => ['nest' => null],
'data' => ['nest' => null],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -250,56 +296,56 @@ class ListsTest extends TestCase
// Contains values
$this->checkHandlesNonNullableLists(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullableLists(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return null;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Contains reject
$this->checkHandlesNonNullableLists(
function () {
return [
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
throw new UserError('bad');
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
];
},
[
'data' => ['nest' => ['test' => [1, null, 2]]],
'data' => ['nest' => ['test' => [1, null, 2]]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1]
]
]
'path' => ['nest', 'test', 1],
],
],
]
);
}
@ -311,21 +357,21 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesListOfNonNulls(
[ 1, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
[1, 2],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesListOfNonNulls(
[ 1, null, 2 ],
[1, null, 2],
[
'data' => [ 'nest' => [ 'test' => null ] ],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -333,10 +379,16 @@ class ListsTest extends TestCase
// Returns null
$this->checkHandlesListOfNonNulls(
null,
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
}
private function checkHandlesListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::listOf(Type::nonNull(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/**
* [T!]
*/
@ -344,53 +396,53 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return [1, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return [1, null, 2];
}),
[
'data' => [ 'nest' => [ 'test' => null ] ],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
// Returns null
$this->checkHandlesListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return null;
}),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
// Rejected
$this->checkHandlesListOfNonNulls(
function () {
return new Deferred(function() {
return new Deferred(function () {
throw new UserError('bad');
});
},
[
'data' => ['nest' => ['test' => null]],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -403,50 +455,56 @@ class ListsTest extends TestCase
// Contains values
$this->checkHandlesListOfNonNulls(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// 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 ] ] ]
['data' => ['nest' => ['test' => null]]]
);
// Contains reject
$this->checkHandlesListOfNonNulls(
function () {
return [
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
throw new UserError('bad');
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
];
},
[
'data' => ['nest' => ['test' => null]],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1]
]
]
'path' => ['nest', 'test', 1],
],
],
]
);
}
@ -458,22 +516,21 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNonNullListOfNonNulls(
[ 1, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
[1, 2],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullListOfNonNulls(
[ 1, null, 2 ],
[1, null, 2],
[
'data' => [ 'nest' => null ],
'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
);
@ -482,18 +539,24 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls(
null,
[
'data' => [ 'nest' => null ],
'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
);
}
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$this->check($testType, $testData, $expected, $debug);
}
/**
* [T!]!
*/
@ -501,42 +564,42 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return [1, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return [1, null, 2];
}),
[
'data' => [ 'nest' => null ],
'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
);
// Returns null
$this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return null;
}),
[
'data' => [ 'nest' => null ],
'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
);
@ -544,19 +607,19 @@ class ListsTest extends TestCase
// Rejected
$this->checkHandlesNonNullListOfNonNulls(
function () {
return new Deferred(function() {
return new Deferred(function () {
throw new UserError('bad');
});
},
[
'data' => ['nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -569,38 +632,38 @@ class ListsTest extends TestCase
// Contains values
$this->checkHandlesNonNullListOfNonNulls(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullListOfNonNulls(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return null;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[
'data' => [ 'nest' => null ],
'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
);
@ -609,83 +672,27 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls(
function () {
return [
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
throw new UserError('bad');
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
];
},
[
'data' => ['nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'message' => 'bad',
'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,27 +1,26 @@
<?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')
*/
public function testEvaluatesMutationsSerially() : void
{
$doc = 'mutation M {
$doc = 'mutation M {
first: immediatelyChangeTheNumber(newNumber: 1) {
theNumber
},
@ -38,36 +37,79 @@ class MutationsTest extends TestCase
theNumber
}
}';
$ast = Parser::parse($doc);
$ast = Parser::parse($doc);
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [
$expected = [
'data' => [
'first' => [
'theNumber' => 1
],
'second' => [
'theNumber' => 2
],
'third' => [
'theNumber' => 3
],
'fourth' => [
'theNumber' => 4
],
'fifth' => [
'theNumber' => 5
]
]
'first' => ['theNumber' => 1],
'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')
*/
public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() : void
{
$doc = 'mutation M {
$doc = 'mutation M {
first: immediatelyChangeTheNumber(newNumber: 1) {
theNumber
},
@ -87,147 +129,28 @@ class MutationsTest extends TestCase
theNumber
}
}';
$ast = Parser::parse($doc);
$ast = Parser::parse($doc);
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [
'data' => [
'first' => [
'theNumber' => 1
],
'second' => [
'theNumber' => 2
],
'third' => null,
'fourth' => [
'theNumber' => 4
],
'fifth' => [
'theNumber' => 5
],
'sixth' => null,
$expected = [
'data' => [
'first' => ['theNumber' => 1],
'second' => ['theNumber' => 2],
'third' => null,
'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,41 +29,46 @@ class NonNullTest extends TestCase
/** @var \Exception */
public $promiseNonNullError;
/** @var callable[] */
public $throwingData;
/** @var callable[] */
public $nullingData;
/** @var Schema */
public $schema;
public function setUp()
{
$this->syncError = new UserError('sync');
$this->syncNonNullError = new UserError('syncNonNull');
$this->promiseError = new UserError('promise');
$this->syncError = new UserError('sync');
$this->syncNonNullError = new UserError('syncNonNull');
$this->promiseError = new UserError('promise');
$this->promiseNonNullError = new UserError('promiseNonNull');
$this->throwingData = [
'sync' => function () {
'sync' => function () {
throw $this->syncError;
},
'syncNonNull' => function () {
'syncNonNull' => function () {
throw $this->syncNonNullError;
},
'promise' => function () {
'promise' => function () {
return new Deferred(function () {
throw $this->promiseError;
});
},
'promiseNonNull' => function () {
'promiseNonNull' => function () {
return new Deferred(function () {
throw $this->promiseNonNullError;
});
},
'syncNest' => function () {
'syncNest' => function () {
return $this->throwingData;
},
'syncNonNullNest' => function () {
'syncNonNullNest' => function () {
return $this->throwingData;
},
'promiseNest' => function () {
'promiseNest' => function () {
return new Deferred(function () {
return $this->throwingData;
});
@ -74,29 +81,29 @@ class NonNullTest extends TestCase
];
$this->nullingData = [
'sync' => function () {
'sync' => function () {
return null;
},
'syncNonNull' => function () {
'syncNonNull' => function () {
return null;
},
'promise' => function () {
'promise' => function () {
return new Deferred(function () {
return null;
});
},
'promiseNonNull' => function () {
'promiseNonNull' => function () {
return new Deferred(function () {
return null;
});
},
'syncNest' => function () {
'syncNest' => function () {
return $this->nullingData;
},
'syncNonNullNest' => function () {
'syncNonNullNest' => function () {
return $this->nullingData;
},
'promiseNest' => function () {
'promiseNest' => function () {
return new Deferred(function () {
return $this->nullingData;
});
@ -109,19 +116,19 @@ class NonNullTest extends TestCase
];
$dataType = new ObjectType([
'name' => 'DataType',
'fields' => function() use (&$dataType) {
'name' => 'DataType',
'fields' => function () use (&$dataType) {
return [
'sync' => ['type' => Type::string()],
'syncNonNull' => ['type' => Type::nonNull(Type::string())],
'promise' => Type::string(),
'promiseNonNull' => Type::nonNull(Type::string()),
'syncNest' => $dataType,
'syncNonNullNest' => Type::nonNull($dataType),
'promiseNest' => $dataType,
'sync' => ['type' => Type::string()],
'syncNonNull' => ['type' => Type::nonNull(Type::string())],
'promise' => Type::string(),
'promiseNonNull' => Type::nonNull(Type::string()),
'syncNest' => $dataType,
'syncNonNullNest' => Type::nonNull($dataType),
'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()
);
}
/**
@ -314,28 +327,28 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'syncNest' => [
'sync' => null,
'promise' => null,
'syncNest' => [
'sync' => null,
'data' => [
'syncNest' => [
'sync' => null,
'promise' => null,
'syncNest' => [
'sync' => null,
'promise' => null,
],
'promiseNest' => [
'sync' => null,
'sync' => null,
'promise' => null,
],
],
'promiseNest' => [
'sync' => null,
'promise' => null,
'syncNest' => [
'sync' => null,
'sync' => null,
'promise' => null,
'syncNest' => [
'sync' => null,
'promise' => null,
],
'promiseNest' => [
'sync' => null,
'sync' => null,
'promise' => null,
],
],
@ -353,10 +366,13 @@ class NonNullTest extends TestCase
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(17, 11)]),
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(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
@ -413,10 +429,10 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'syncNest' => null,
'promiseNest' => null,
'anotherNest' => null,
'data' => [
'syncNest' => null,
'promiseNest' => null,
'anotherNest' => null,
'anotherPromiseNest' => null,
],
'errors' => [
@ -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(
@ -618,31 +634,31 @@ class NonNullTest extends TestCase
$expected = [
'data' => [
'syncNest' => [
'sync' => null,
'promise' => null,
'syncNest' => [
'sync' => null,
'syncNest' => [
'sync' => null,
'promise' => null,
'syncNest' => [
'sync' => null,
'promise' => null,
],
'promiseNest' => [
'sync' => null,
'sync' => null,
'promise' => null,
]
],
],
'promiseNest' => [
'sync' => null,
'promise' => null,
'syncNest' => [
'sync' => null,
'sync' => null,
'promise' => null,
'syncNest' => [
'sync' => null,
'promise' => null,
],
'promiseNest' => [
'sync' => null,
'sync' => null,
'promise' => null,
]
]
]
],
],
],
];
$actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray();
@ -703,18 +719,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc);
$expected = [
'data' => [
'syncNest' => null,
'promiseNest' => null,
'anotherNest' => null,
'data' => [
'syncNest' => null,
'promiseNest' => null,
'anotherNest' => null,
'anotherPromiseNest' => null,
],
'errors' => [
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [ ['line' => 8, 'column' => 19]]],
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [ ['line' => 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]]],
]
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 8, 'column' => 19]]],
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 19, 'column' => 19]]],
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 30, 'column' => 19]]],
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 41, 'column' => 19]]],
],
];
$this->assertArraySubset(
@ -734,10 +750,10 @@ 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();
$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));
@ -58,13 +68,16 @@ class ReactPromiseAdapterTest extends TestCase
{
$reactAdapter = new ReactPromiseAdapter();
$reactPromise = new FulfilledPromise(1);
$promise = $reactAdapter->convertThenable($reactPromise);
$promise = $reactAdapter->convertThenable($reactPromise);
$result = null;
$resultPromise = $reactAdapter->then($promise, function ($value) use (&$result) {
$result = $value;
});
$resultPromise = $reactAdapter->then(
$promise,
function ($value) use (&$result) {
$result = $value;
}
);
$this->assertSame(1, $result);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise);
@ -73,9 +86,9 @@ class ReactPromiseAdapterTest extends TestCase
public function testCreate() : void
{
$reactAdapter = new ReactPromiseAdapter();
$reactAdapter = new ReactPromiseAdapter();
$resolvedPromise = $reactAdapter->create(function ($resolve) {
$resolve(1);
$resolve(1);
});
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resolvedPromise);
@ -84,7 +97,7 @@ class ReactPromiseAdapterTest extends TestCase
$result = null;
$resolvedPromise->then(function ($value) use (&$result) {
$result = $value;
$result = $value;
});
$this->assertSame(1, $result);
@ -92,7 +105,7 @@ class ReactPromiseAdapterTest extends TestCase
public function testCreateFulfilled() : void
{
$reactAdapter = new ReactPromiseAdapter();
$reactAdapter = new ReactPromiseAdapter();
$fulfilledPromise = $reactAdapter->createFulfilled(1);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $fulfilledPromise);
@ -109,7 +122,7 @@ class ReactPromiseAdapterTest extends TestCase
public function testCreateRejected() : void
{
$reactAdapter = new ReactPromiseAdapter();
$reactAdapter = new ReactPromiseAdapter();
$rejectedPromise = $reactAdapter->createRejected(new \Exception('I am a bad promise'));
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $rejectedPromise);
@ -117,9 +130,12 @@ class ReactPromiseAdapterTest extends TestCase
$exception = null;
$rejectedPromise->then(null, function ($error) use (&$exception) {
$exception = $error;
});
$rejectedPromise->then(
null,
function ($error) use (&$exception) {
$exception = $error;
}
);
$this->assertInstanceOf('\Exception', $exception);
$this->assertEquals('I am a bad promise', $exception->getMessage());
@ -128,7 +144,7 @@ class ReactPromiseAdapterTest extends TestCase
public function testAll() : void
{
$reactAdapter = new ReactPromiseAdapter();
$promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)];
$promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)];
$allPromise = $reactAdapter->all($promises);
@ -138,7 +154,7 @@ class ReactPromiseAdapterTest extends TestCase
$result = null;
$allPromise->then(function ($values) use (&$result) {
$result = $values;
$result = $values;
});
$this->assertSame([1, 2, 3], $result);
@ -147,9 +163,9 @@ class ReactPromiseAdapterTest extends TestCase
public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromises() : void
{
$reactAdapter = new ReactPromiseAdapter();
$deferred = new Deferred();
$promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)];
$result = null;
$deferred = new Deferred();
$promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)];
$result = null;
$reactAdapter->all($promises)->then(function ($values) use (&$result) {
$result = $values;

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,18 +66,55 @@ 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);
$promise = $this->promises->create(function($resolve, $reject) {
$promise = $this->promises->create(function ($resolve, $reject) {
$resolve('A');
});
$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');
@ -94,8 +138,8 @@ class SyncPromiseAdapterTest extends TestCase
$promise1 = new SyncPromise();
$promise2 = new SyncPromise();
$promise3 = $promise2->then(
function($value) {
return $value .'-value3';
function ($value) {
return $value . '-value3';
}
);
@ -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,36 +158,46 @@ 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
{
$called = [];
$deferred1 = new Deferred(function() use (&$called) {
$deferred1 = new Deferred(function () use (&$called) {
$called[] = 1;
return 1;
});
$deferred2 = new Deferred(function() use (&$called) {
$deferred2 = new Deferred(function () use (&$called) {
$called[] = 2;
return 2;
});
$p1 = $this->promises->convertThenable($deferred1);
$p2 = $this->promises->convertThenable($deferred2);
$p3 = $p2->then(function() use (&$called) {
$dfd = new Deferred(function() use (&$called) {
$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) {
$p4 = $p3->then(function () use (&$called) {
return new Deferred(function () use (&$called) {
$called[] = 4;
return 4;
});
});
@ -156,46 +210,10 @@ class SyncPromiseAdapterTest extends TestCase
$this->assertEquals(SyncPromise::PENDING, $all->adoptedPromise->state);
$this->assertEquals([1, 2], $called);
$expectedResult = [0,1,2,3,4];
$result = $this->promises->wait($all);
$expectedResult = [0, 1, 2, 3, 4];
$result = $this->promises->wait($all);
$this->assertEquals($expectedResult, $result);
$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);
$this->assertValidPromise($all, null, [0, 1, 2, 3, 4], SyncPromise::FULFILLED);
}
}

View File

@ -1,25 +1,32 @@
<?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
{
public function getFulfilledPromiseResolveData()
{
$onFulfilledReturnsNull = function() {
$onFulfilledReturnsNull = function () {
return null;
};
$onFulfilledReturnsSameValue = function($value) {
$onFulfilledReturnsSameValue = function ($value) {
return $value;
};
$onFulfilledReturnsOtherValue = function($value) {
$onFulfilledReturnsOtherValue = function ($value) {
return 'other-' . $value;
};
$onFulfilledThrows = function($value) {
throw new \Exception("onFulfilled throws this!");
$onFulfilledThrows = function ($value) {
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) {
$onRejectedCalled = true;
});
$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() {
$onRejectedReturnsNull = function () {
return null;
};
$onRejectedReturnsSomeValue = function($reason) {
$onRejectedReturnsSomeValue = function ($reason) {
return 'some-value';
};
$onRejectedThrowsSameReason = function($reason) {
$onRejectedThrowsSameReason = function ($reason) {
throw $reason;
};
$onRejectedThrowsOtherReason = function($value) {
throw new \Exception("onRejected throws other!");
$onRejectedThrowsOtherReason = function ($value) {
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,22 +259,26 @@ 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;
$nextPromise = $promise->then(
$nextPromise = $promise->then(
function () use (&$onFulfilledCalled) {
$onFulfilledCalled = true;
},
@ -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() {
return 'value';
});
$promise->reject(new \Exception("Rejected Again"));
$promise = new SyncPromise();
$promise2 = $promise->then(
null,
function () {
return 'value';
}
);
$promise->reject(new \Exception('Rejected Again'));
$this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED);
$promise = new SyncPromise();
$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
@ -331,12 +381,14 @@ class SyncPromiseTest extends TestCase
// Make sure that it queues derivative promises until resolution:
$onFulfilledCount = 0;
$onRejectedCount = 0;
$onFulfilled = function($value) use (&$onFulfilledCount) {
$onRejectedCount = 0;
$onFulfilled = function ($value) use (&$onFulfilledCount) {
$onFulfilledCount++;
return $onFulfilledCount;
};
$onRejected = function($reason) use (&$onRejectedCount) {
$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,18 +32,28 @@ class ResolveTest extends TestCase
);
}
private function buildSchema($testField)
{
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['test' => $testField],
]),
]);
}
/**
* @see it('default function calls methods')
*/
public function testDefaultFunctionCallsClosures() : void
{
$schema = $this->buildSchema(['type' => Type::string()]);
$schema = $this->buildSchema(['type' => Type::string()]);
$_secret = 'secretValue' . uniqid();
$source = [
'test' => function() use ($_secret) {
'test' => function () use ($_secret) {
return $_secret;
}
},
];
$this->assertEquals(
['data' => ['test' => $_secret]],
@ -69,7 +69,7 @@ class ResolveTest extends TestCase
$schema = $this->buildSchema([
'type' => Type::int(),
'args' => [
'addend1' => [ 'type' => Type::int() ],
'addend1' => ['type' => Type::int()],
],
]);
@ -85,14 +85,14 @@ class ResolveTest extends TestCase
public function testUsesProvidedResolveFunction() : void
{
$schema = $this->buildSchema([
'type' => Type::string(),
'args' => [
'type' => Type::string(),
'args' => [
'aStr' => ['type' => Type::string()],
'aInt' => ['type' => Type::int()],
],
'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;
@ -29,37 +31,38 @@ class SyncTest extends TestCase
public function setUp()
{
$this->schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'syncField' => [
'type' => Type::string(),
'syncField' => [
'type' => Type::string(),
'resolve' => function ($rootValue) {
return $rootValue;
}
},
],
'asyncField' => [
'type' => Type::string(),
'type' => Type::string(),
'resolve' => function ($rootValue) {
return new Deferred(function () use ($rootValue) {
return $rootValue;
});
}
]
]
},
],
],
]),
'mutation' => new ObjectType([
'name' => 'Mutation',
'name' => 'Mutation',
'fields' => [
'syncMutationField' => [
'type' => Type::string(),
'type' => Type::string(),
'resolve' => function ($rootValue) {
return $rootValue;
}
]
]
])
},
],
],
]),
]);
$this->promiseAdapter = new SyncPromiseAdapter();
}
@ -70,7 +73,7 @@ class SyncTest extends TestCase
*/
public function testDoesNotReturnAPromiseForInitialErrors() : void
{
$doc = 'fragment Example on Query { syncField }';
$doc = 'fragment Example on Query { syncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
@ -79,106 +82,6 @@ class SyncTest extends TestCase
$this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result);
}
/**
* @see it('does not return a Promise if fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void
{
$doc = 'query Example { syncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
/**
* @see it('does not return a Promise if mutation fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void
{
$doc = 'mutation Example { syncMutationField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result);
}
/**
* @see it('returns a Promise if any field is asynchronous')
*/
public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void
{
$doc = 'query Example { syncField, asyncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
}
// Describe: graphqlSync
/**
* @see it('does not return a Promise for syntax errors')
*/
public function testDoesNotReturnAPromiseForSyntaxErrors() : void
{
$doc = 'fragment Example on Query { { { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc
);
$this->assertSync([
'errors' => [
['message' => 'Syntax Error: Expected Name, found {',
'locations' => [['line' => 1, 'column' => 29]]]
]
], $result);
}
/**
* @see it('does not return a Promise for validation errors')
*/
public function testDoesNotReturnAPromiseForValidationErrors() : void
{
$doc = 'fragment Example on Query { unknownField }';
$validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc));
$result = $this->graphqlSync(
$this->schema,
$doc
);
$expected = [
'errors' => Utils::map($validationErrors, function ($e) {
return FormattedError::createFromException($e);
})
];
$this->assertSync($expected, $result);
}
/**
* @see it('does not return a Promise for sync execution')
*/
public function testDoesNotReturnAPromiseForSyncExecution() : void
{
$doc = 'query Example { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc,
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
private function graphqlSync($schema, $doc, $rootValue = null)
{
return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
}
private function execute($schema, $doc, $rootValue = null)
{
return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
@ -191,7 +94,56 @@ class SyncTest extends TestCase
$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);
$this->assertArraySubset(
$expectedFinalArray,
$actualResult->adoptedPromise->result->toArray(),
false,
$message
);
}
/**
* @see it('does not return a Promise if fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void
{
$doc = 'query Example { syncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
// Describe: graphqlSync
/**
* @see it('does not return a Promise if mutation fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void
{
$doc = 'mutation Example { syncMutationField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result);
}
/**
* @see it('returns a Promise if any field is asynchronous')
*/
public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void
{
$doc = 'query Example { syncField, asyncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
}
private function assertAsync($expectedFinalArray, $actualResult)
@ -202,6 +154,70 @@ class SyncTest extends TestCase
$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);
$this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), false, $message);
}
/**
* @see it('does not return a Promise for syntax errors')
*/
public function testDoesNotReturnAPromiseForSyntaxErrors() : void
{
$doc = 'fragment Example on Query { { { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc
);
$this->assertSync(
[
'errors' => [
[
'message' => 'Syntax Error: Expected Name, found {',
'locations' => [['line' => 1, 'column' => 29]],
],
],
],
$result
);
}
private function graphqlSync($schema, $doc, $rootValue = null)
{
return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
}
/**
* @see it('does not return a Promise for validation errors')
*/
public function testDoesNotReturnAPromiseForValidationErrors() : void
{
$doc = 'fragment Example on Query { unknownField }';
$validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc));
$result = $this->graphqlSync(
$this->schema,
$doc
);
$expected = [
'errors' => Utils::map(
$validationErrors,
function ($e) {
return FormattedError::createFromException($e);
}
),
];
$this->assertSync($expected, $result);
}
/**
* @see it('does not return a Promise for sync execution')
*/
public function testDoesNotReturnAPromiseForSyncExecution() : void
{
$doc = 'query Example { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc,
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,64 +1,76 @@
<?php
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()
{
$NamedType = new InterfaceType([
'name' => 'Named',
'name' => 'Named',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$NamedType],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
'isTypeOf' => function ($value) {
'isTypeOf' => function ($value) {
return $value instanceof Dog;
}
},
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$NamedType],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()]
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
],
'isTypeOf' => function ($value) {
'isTypeOf' => function ($value) {
return $value instanceof Cat;
}
},
]);
$PetType = new UnionType([
'name' => 'Pet',
'types' => [$DogType, $CatType],
'name' => 'Pet',
'types' => [$DogType, $CatType],
'resolveType' => function ($value) use ($DogType, $CatType) {
if ($value instanceof Dog) {
return $DogType;
@ -66,32 +78,31 @@ class UnionInterfaceTest extends TestCase
if ($value instanceof Cat) {
return $CatType;
}
}
},
]);
$PersonType = new ObjectType([
'name' => 'Person',
'name' => 'Person',
'interfaces' => [$NamedType],
'fields' => [
'name' => ['type' => Type::string()],
'pets' => ['type' => Type::listOf($PetType)],
'friends' => ['type' => Type::listOf($NamedType)]
'fields' => [
'name' => ['type' => Type::string()],
'pets' => ['type' => Type::listOf($PetType)],
'friends' => ['type' => Type::listOf($NamedType)],
],
'isTypeOf' => function ($value) {
'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]);
$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") {
@ -128,33 +138,33 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'Named' => [
'kind' => 'INTERFACE',
'name' => 'Named',
'fields' => [
['name' => 'name']
'kind' => 'INTERFACE',
'name' => 'Named',
'fields' => [
['name' => 'name'],
],
'interfaces' => null,
'interfaces' => null,
'possibleTypes' => [
['name' => 'Person'],
['name' => 'Dog'],
['name' => 'Cat']
['name' => 'Cat'],
],
'enumValues' => null,
'inputFields' => null
'enumValues' => null,
'inputFields' => null,
],
'Pet' => [
'kind' => 'UNION',
'name' => 'Pet',
'fields' => null,
'interfaces' => null,
'Pet' => [
'kind' => 'UNION',
'name' => 'Pet',
'fields' => null,
'interfaces' => null,
'possibleTypes' => [
['name' => 'Dog'],
['name' => 'Cat']
['name' => 'Cat'],
],
'enumValues' => null,
'inputFields' => null
]
]
'enumValues' => null,
'inputFields' => null,
],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray());
}
@ -165,7 +175,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUsingUnionTypes() : void
{
// NOTE: This is an *invalid* query, but it should be an *executable* query.
$ast = Parser::parse('
$ast = Parser::parse('
{
__typename
name
@ -180,12 +190,12 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'pets' => [
'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());
@ -197,7 +207,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUnionTypesWithInlineFragments() : void
{
// This is the valid version of the query in the above test.
$ast = Parser::parse('
$ast = Parser::parse('
{
__typename
name
@ -217,13 +227,13 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'pets' => [
'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());
}
@ -234,7 +244,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUsingInterfaceTypes() : void
{
// NOTE: This is an *invalid* query, but it should be an *executable* query.
$ast = Parser::parse('
$ast = Parser::parse('
{
__typename
name
@ -249,12 +259,12 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'friends' => [
'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());
@ -266,7 +276,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesInterfaceTypesWithInlineFragments() : void
{
// This is the valid version of the query in the above test.
$ast = Parser::parse('
$ast = Parser::parse('
{
__typename
name
@ -285,12 +295,12 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'friends' => [
'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));
@ -336,16 +346,16 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'pets' => [
'name' => 'John',
'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
],
'friends' => [
'friends' => [
['__typename' => 'Person', 'name' => 'Liz'],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
]
]
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -356,36 +366,45 @@ class UnionInterfaceTest extends TestCase
*/
public function testGetsExecutionInfoInResolver() : void
{
$encounteredContext = null;
$encounteredSchema = null;
$encounteredContext = null;
$encounteredSchema = null;
$encounteredRootValue = null;
$PersonType2 = null;
$PersonType2 = null;
$NamedType2 = new InterfaceType([
'name' => 'Named',
'fields' => [
'name' => ['type' => Type::string()]
'name' => 'Named',
'fields' => [
'name' => ['type' => Type::string()],
],
'resolveType' => function ($obj, $context, ResolveInfo $info) use (&$encounteredContext, &$encounteredSchema, &$encounteredRootValue, &$PersonType2) {
$encounteredContext = $context;
$encounteredSchema = $info->schema;
'resolveType' => function (
$obj,
$context,
ResolveInfo $info
) use (
&$encounteredContext,
&
$encounteredSchema,
&$encounteredRootValue,
&$PersonType2
) {
$encounteredContext = $context;
$encounteredSchema = $info->schema;
$encounteredRootValue = $info->rootValue;
return $PersonType2;
}
},
]);
$PersonType2 = new ObjectType([
'name' => 'Person',
'name' => 'Person',
'interfaces' => [$NamedType2],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'friends' => ['type' => Type::listOf($NamedType2)],
],
]);
$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]),
['errors' => [], 'coerced' => ['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,14 +31,12 @@ 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());
// properly parses single value to list:
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"})
}
@ -46,7 +46,7 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
// properly parses null value to null
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null})
}
@ -56,7 +56,7 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
// properly parses null value in list
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"})
}
@ -73,12 +73,13 @@ class VariablesTest extends TestCase
');
$expected = [
'data' => ['fieldWithObjectInput' => null],
'data' => ['fieldWithObjectInput' => null],
'errors' => [[
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
'path' => ['fieldWithObjectInput'],
'locations' => [['line' => 3, 'column' => 39]]
]]
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
'path' => ['fieldWithObjectInput'],
'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());
@ -132,63 +204,61 @@ class VariablesTest extends TestCase
);
// executes with complex scalar input:
$params = [ 'input' => [ 'c' => 'foo', 'd' => 'SerializedValue' ] ];
$result = $this->executeQuery($doc, $params);
$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());
// errors on null for nested non-null:
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]];
$result = $this->executeQuery($doc, $params);
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]];
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value ' .
'{"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());
// errors on incorrect type:
$params = [ 'input' => 'foo bar' ];
$result = $this->executeQuery($doc, $params);
$params = ['input' => 'foo bar'];
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value "foo bar"; ' .
'Expected type TestInputObject to be an object.',
'locations' => [ [ 'line' => 2, 'column' => 21 ] ],
'category' => 'graphql',
]
]
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
// errors on omission of nested non-null:
$params = ['input' => ['a' => 'foo', 'b' => 'bar']];
$result = $this->executeQuery($doc, $params);
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
[
'message' =>
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; '.
'message' =>
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; ' .
'Field value.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
@ -198,62 +268,59 @@ class VariablesTest extends TestCase
fieldWithNestedObjectInput(input: $input)
}
';
$params = [ 'input' => [ 'na' => [ 'a' => 'foo' ] ] ];
$params = ['input' => ['na' => ['a' => 'foo']]];
$result = $this->executeQuery($nestedDoc, $params);
$result = $this->executeQuery($nestedDoc, $params);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
'Field value.na.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql',
'category' => 'graphql',
],
[
'message' =>
'message' =>
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
'Field value.nb of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql',
]
]
'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);
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog']];
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value ' .
'{"a":"foo","b":"bar","c":"baz","extra":"dog"}; ' .
'Field "extra" is not defined by type TestInputObject.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles nullable scalars
/**
* @see it('allows nullable inputs to be omitted')
*/
public function testAllowsNullableInputsToBeOmitted() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNullableStringInput
}
');
$expected = [
'data' => ['fieldWithNullableStringInput' => null]
'data' => ['fieldWithNullableStringInput' => null],
];
$this->assertEquals($expected, $result->toArray());
@ -264,7 +331,7 @@ class VariablesTest extends TestCase
*/
public function testAllowsNullableInputsToBeOmittedInAVariable() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value)
}
@ -279,7 +346,7 @@ class VariablesTest extends TestCase
*/
public function testAllowsNullableInputsToBeOmittedInAnUnlistedVariable() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
query SetsNullable {
fieldWithNullableStringInput(input: $value)
}
@ -288,12 +355,15 @@ 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')
*/
public function testAllowsNullableInputsToBeSetToNullInAVariable() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value)
}
@ -308,12 +378,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsNullableInputsToBeSetToAValueInAVariable() : void
{
$doc = '
$doc = '
query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value)
}
';
$result = $this->executeQuery($doc, ['value' => 'a']);
$result = $this->executeQuery($doc, ['value' => 'a']);
$expected = ['data' => ['fieldWithNullableStringInput' => '"a"']];
$this->assertEquals($expected, $result->toArray());
}
@ -323,7 +393,7 @@ class VariablesTest extends TestCase
*/
public function testAllowsNullableInputsToBeSetToAValueDirectly() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNullableStringInput(input: "a")
}
@ -332,21 +402,18 @@ 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')
*/
public function testAllowsNonNullableInputsToBeOmittedGivenADefault() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
query SetsNonNullable($value: String = "default") {
fieldWithNonNullableStringInput(input: $value)
}
');
$expected = [
'data' => ['fieldWithNonNullableStringInput' => '"default"']
'data' => ['fieldWithNonNullableStringInput' => '"default"'],
];
$this->assertEquals($expected, $result->toArray());
}
@ -365,11 +432,11 @@ class VariablesTest extends TestCase
$expected = [
'errors' => [
[
'message' => 'Variable "$value" of required type "String!" was not provided.',
'message' => 'Variable "$value" of required type "String!" was not provided.',
'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql'
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -379,22 +446,22 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable() : void
{
$doc = '
$doc = '
query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value)
}
';
$result = $this->executeQuery($doc, ['value' => null]);
$result = $this->executeQuery($doc, ['value' => null]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$value" got invalid value null; ' .
'Expected non-nullable type String! not to be null.',
'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -404,12 +471,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullableInputsToBeSetToAValueInAVariable() : void
{
$doc = '
$doc = '
query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value)
}
';
$result = $this->executeQuery($doc, ['value' => 'a']);
$result = $this->executeQuery($doc, ['value' => 'a']);
$expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']];
$this->assertEquals($expected, $result->toArray());
}
@ -419,7 +486,7 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullableInputsToBeSetToAValueDirectly() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNonNullableStringInput(input: "a")
}
@ -433,47 +500,50 @@ class VariablesTest extends TestCase
*/
public function testReportsErrorForMissingNonNullableInputs() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNonNullableStringInput
}
');
$expected = [
'data' => ['fieldWithNonNullableStringInput' => null],
'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[
'message' => 'Argument "input" of required type "String!" was not provided.',
'locations' => [ [ 'line' => 3, 'column' => 9 ] ],
'path' => [ 'fieldWithNonNullableStringInput' ],
'category' => 'graphql',
]]
'message' => 'Argument "input" of required type "String!" was not provided.',
'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 = '
$doc = '
query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value)
}
';
$variables = ['value' => [1, 2, 3]];
$result = $this->executeQuery($doc, $variables);
$result = $this->executeQuery($doc, $variables);
$expected = [
'errors' => [[
'message' =>
'message' =>
'Variable "$value" got invalid value [1,2,3]; Expected type ' .
'String; String cannot represent an array value: [1,2,3]',
'category' => 'graphql',
'category' => 'graphql',
'locations' => [
['line' => 2, 'column' => 31]
]
]]
['line' => 2, 'column' => 31],
],
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -498,38 +568,37 @@ class VariablesTest extends TestCase
// and are being run against a new schema which have introduced a breaking
// change to make a formerly non-required argument required, this asserts
// failure before allowing the underlying code to receive a non-null value.
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNonNullableStringInput(input: $foo)
}
');
$expected = [
'data' => ['fieldWithNonNullableStringInput' => null],
'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[
'message' =>
'message' =>
'Argument "input" of required type "String!" was provided the ' .
'variable "$foo" which was not provided a runtime value.',
'locations' => [['line' => 3, 'column' => 48]],
'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql',
]]
'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles lists and nullability
/**
* @see it('allows lists to be null')
*/
public function testAllowsListsToBeNull() : void
{
$doc = '
$doc = '
query q($input:[String]) {
list(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => null]);
$result = $this->executeQuery($doc, ['input' => null]);
$expected = ['data' => ['list' => null]];
$this->assertEquals($expected, $result->toArray());
@ -540,12 +609,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsListsToContainValues() : void
{
$doc = '
$doc = '
query q($input:[String]) {
list(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A']]);
$result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['list' => '["A"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -555,12 +624,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsListsToContainNull() : void
{
$doc = '
$doc = '
query q($input:[String]) {
list(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A',null,'B']]);
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = ['data' => ['list' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -570,22 +639,22 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowNonNullListsToBeNull() : void
{
$doc = '
$doc = '
query q($input:[String]!) {
nnList(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => null]);
$result = $this->executeQuery($doc, ['input' => null]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String]! not to be null.',
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -595,12 +664,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullListsToContainValues() : void
{
$doc = '
$doc = '
query q($input:[String]!) {
nnList(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A']]);
$result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['nnList' => '["A"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -610,12 +679,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullListsToContainNull() : void
{
$doc = '
$doc = '
query q($input:[String]!) {
nnList(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A',null,'B']]);
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = ['data' => ['nnList' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -625,12 +694,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsListsOfNonNullsToBeNull() : void
{
$doc = '
$doc = '
query q($input:[String!]) {
listNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => null]);
$result = $this->executeQuery($doc, ['input' => null]);
$expected = ['data' => ['listNN' => null]];
$this->assertEquals($expected, $result->toArray());
}
@ -640,12 +709,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsListsOfNonNullsToContainValues() : void
{
$doc = '
$doc = '
query q($input:[String!]) {
listNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A']]);
$result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['listNN' => '["A"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -655,22 +724,22 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowListsOfNonNullsToContainNull() : void
{
$doc = '
$doc = '
query q($input:[String!]) {
listNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ],
'category' => 'graphql',
]
]
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -680,22 +749,22 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowNonNullListsOfNonNullsToBeNull() : void
{
$doc = '
$doc = '
query q($input:[String!]!) {
nnListNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => null]);
$result = $this->executeQuery($doc, ['input' => null]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String!]! not to be null.',
'locations' => [ ['line' => 2, 'column' => 17] ],
'category' => 'graphql',
]
]
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -705,37 +774,39 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullListsOfNonNullsToContainValues() : void
{
$doc = '
$doc = '
query q($input:[String!]!) {
nnListNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A']]);
$result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['nnListNN' => '["A"]']];
$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')
*/
public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() : void
{
$doc = '
$doc = '
query q($input:[String!]!) {
nnListNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ],
'category' => 'graphql',
]
]
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -745,23 +816,23 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowInvalidTypesToBeUsedAsValues() : void
{
$doc = '
$doc = '
query q($input: TestType!) {
fieldWithObjectInput(input: $input)
}
';
$vars = [ 'input' => [ 'list' => [ 'A', 'B' ] ] ];
$result = $this->executeQuery($doc, $vars);
$vars = ['input' => ['list' => ['A', 'B']]];
$result = $this->executeQuery($doc, $vars);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" expected value of type "TestType!" which cannot ' .
'be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -771,29 +842,28 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowUnknownTypesToBeUsedAsValues() : void
{
$doc = '
$doc = '
query q($input: UnknownType!) {
fieldWithObjectInput(input: $input)
}
';
$vars = ['input' => 'whoknows'];
$result = $this->executeQuery($doc, $vars);
$result = $this->executeQuery($doc, $vars);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" expected value of type "UnknownType!" which ' .
'cannot be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Execute: Uses argument default values
/**
* @see it('when no argument provided')
*/
@ -834,84 +904,17 @@ class VariablesTest extends TestCase
}');
$expected = [
'data' => ['fieldWithDefaultArgumentValue' => null],
'data' => ['fieldWithDefaultArgumentValue' => null],
'errors' => [[
'message' =>
'message' =>
'Argument "input" has invalid value WRONG_TYPE.',
'locations' => [ [ 'line' => 2, 'column' => 50 ] ],
'path' => [ 'fieldWithDefaultArgumentValue' ],
'category' => 'graphql',
]]
'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);
}
}