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

View File

@ -1,23 +1,28 @@
<?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\GraphQL;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Human;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
/**
* Execute: Handles execution of abstract types
*/
class AbstractTest extends TestCase
{
// Execute: Handles execution of abstract types
/**
* @see it('isTypeOf used to resolve runtime type for Interface')
*/
@ -27,19 +32,21 @@ class AbstractTest extends TestCase
$petType = new InterfaceType([
'name' => 'Pet',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
// Added to interface type when defined
$dogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
]
'woofs' => ['type' => Type::boolean()],
],
]);
$catType = new ObjectType([
@ -51,7 +58,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -62,11 +69,11 @@ class AbstractTest extends TestCase
'type' => Type::listOf($petType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
}
]
]
},
],
],
]),
'types' => [$catType, $dogType]
'types' => [$catType, $dogType],
]);
$query = '{
@ -84,8 +91,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
['name' => 'Garfield', 'meows' => false],
],
]);
$result = Executor::execute($schema, Parser::parse($query));
@ -99,11 +106,13 @@ class AbstractTest extends TestCase
{
$dogType = new ObjectType([
'name' => 'Dog',
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
]
'woofs' => ['type' => Type::boolean()],
],
]);
$catType = new ObjectType([
@ -114,12 +123,12 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$petType = new UnionType([
'name' => 'Pet',
'types' => [$dogType, $catType]
'types' => [$dogType, $catType],
]);
$schema = new Schema([
@ -128,12 +137,12 @@ class AbstractTest extends TestCase
'fields' => [
'pets' => [
'type' => Type::listOf($petType),
'resolve' => function() {
return [ new Dog('Odie', true), new Cat('Garfield', false) ];
}
]
]
])
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
},
],
],
]),
]);
$query = '{
@ -151,8 +160,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
['name' => 'Garfield', 'meows' => false],
],
]);
$this->assertEquals($expected, Executor::execute($schema, Parser::parse($query)));
@ -161,7 +170,7 @@ class AbstractTest extends TestCase
/**
* @see it('resolveType on Interface yields useful error')
*/
function testResolveTypeOnInterfaceYieldsUsefulError()
public function testResolveTypeOnInterfaceYieldsUsefulError() : void
{
$DogType = null;
$CatType = null;
@ -179,18 +188,19 @@ class AbstractTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
},
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$HumanType = new ObjectType([
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
@ -199,7 +209,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -208,7 +218,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
@ -221,16 +231,15 @@ class AbstractTest extends TestCase
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
},
],
],
]),
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$query = '{
pets {
name
@ -248,14 +257,15 @@ class AbstractTest extends TestCase
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
null
]
null,
],
],
'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2]
]]
'path' => ['pets', 2],
],
],
];
$actual = GraphQL::executeQuery($schema, $query)->toArray(true);
@ -271,7 +281,7 @@ class AbstractTest extends TestCase
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
@ -279,7 +289,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
@ -287,7 +297,7 @@ class AbstractTest extends TestCase
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
@ -303,7 +313,7 @@ class AbstractTest extends TestCase
return $HumanType;
}
},
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
@ -316,12 +326,12 @@ class AbstractTest extends TestCase
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -341,18 +351,23 @@ class AbstractTest extends TestCase
$expected = [
'data' => [
'pets' => [
['name' => 'Odie',
'woofs' => true],
['name' => 'Garfield',
'meows' => false],
null
]
[
'name' => 'Odie',
'woofs' => true,
],
[
'name' => 'Garfield',
'meows' => false,
],
null,
],
],
'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2]
]]
'path' => ['pets', 2],
],
],
];
$this->assertArraySubset($expected, $result);
}
@ -419,32 +434,37 @@ class AbstractTest extends TestCase
{
$PetType = new InterfaceType([
'name' => 'Pet',
'resolveType' => function($obj) {
if ($obj instanceof Dog) return 'Dog';
if ($obj instanceof Cat) return 'Cat';
'resolveType' => function ($obj) {
if ($obj instanceof Dog) {
return 'Dog';
}
if ($obj instanceof Cat) {
return 'Cat';
}
return null;
},
'fields' => [
'name' => [ 'type' => Type::string() ]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [ $PetType ],
'interfaces' => [$PetType],
'fields' => [
'name' => [ 'type' => Type::string() ],
'woofs' => [ 'type' => Type::boolean() ],
]
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'interfaces' => [ $PetType ],
'interfaces' => [$PetType],
'fields' => [
'name' => [ 'type' => Type::string() ],
'meows' => [ 'type' => Type::boolean() ],
]
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
],
]);
$schema = new Schema([
@ -453,16 +473,16 @@ class AbstractTest extends TestCase
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'resolve' => function() {
'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([
$this->assertEquals(
[
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
]
], $result);
['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()
'a' => Type::string(),
],
'interfaces' => function() use ($iface) {
'interfaces' => function () use ($iface) {
return [$iface];
}
},
]);
};
$iface = new InterfaceType([
'name' => 'Node',
'fields' => [
'a' => Type::string()
'a' => Type::string(),
],
'resolveType' => function() use (&$createTest) {
'resolveType' => function () use (&$createTest) {
return $createTest();
}
},
]);
$query = new ObjectType([
'name' => 'Query',
'fields' => [
'node' => $iface,
'test' => $createTest()
]
'test' => $createTest(),
],
]);
$schema = new Schema([
'query' => $query,
]);
$schema = new Schema(['query' => $query]);
$schema->assertValid();
$query = '
@ -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,36 +65,41 @@ 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->userType = new ObjectType([
'name' => 'User',
'fields' => function() {
'fields' => function () {
return [
'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) {
'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 Utils::find(
$this->userDataSource,
function ($entry) use ($user) {
return $entry['id'] === $user['bestFriendId'];
});
});
}
]
);
});
},
],
];
}
},
]);
$this->storyType = new ObjectType([
@ -91,25 +107,30 @@ class DeferredFieldsTest extends TestCase
'fields' => [
'title' => [
'type' => Type::string(),
'resolve' => function($entry, $args, $context, ResolveInfo $info) {
'resolve' => function ($entry, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $entry['title'];
}
},
],
'author' => [
'type' => $this->userType,
'resolve' => function($story, $args, $context, ResolveInfo $info) {
'resolve' => function ($story, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function() use ($story) {
return new Deferred(function () use ($story) {
$this->path[] = 'deferred-for-story-' . $story['id'] . '-author';
return Utils::find($this->userDataSource, function($entry) use ($story) {
return Utils::find(
$this->userDataSource,
function ($entry) use ($story) {
return $entry['id'] === $story['authorId'];
});
});
}
]
]
);
});
},
],
],
]);
$this->categoryType = new ObjectType([
@ -117,35 +138,44 @@ class DeferredFieldsTest extends TestCase
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function($category, $args, $context, ResolveInfo $info) {
'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) {
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) use ($category) {
return Utils::filter(
$this->storyDataSource,
function ($story) use ($category) {
return in_array($category['id'], $story['categoryIds']);
});
}
);
},
],
'topStory' => [
'type' => $this->storyType,
'resolve' => function($category, $args, $context, ResolveInfo $info) {
'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 Utils::find(
$this->storyDataSource,
function ($story) use ($category) {
return $story['id'] === $category['topStoryId'];
});
});
}
]
]
);
});
},
],
],
]);
$this->queryType = new ObjectType([
@ -153,28 +183,34 @@ class DeferredFieldsTest extends TestCase
'fields' => [
'topStories' => [
'type' => Type::listOf($this->storyType),
'resolve' => function($val, $args, $context, ResolveInfo $info) {
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) {
return Utils::filter(
$this->storyDataSource,
function ($story) {
return $story['id'] % 2 === 1;
});
}
);
},
],
'featuredCategory' => [
'type' => $this->categoryType,
'resolve' => function($val, $args, $context, ResolveInfo $info) {
'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) {
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $this->categoryDataSource;
}
]
]
},
],
],
]);
parent::setUp();
@ -202,7 +238,7 @@ class DeferredFieldsTest extends TestCase
');
$schema = new Schema([
'query' => $this->queryType
'query' => $this->queryType,
]);
$expected = [
@ -220,9 +256,9 @@ class DeferredFieldsTest extends TestCase
['title' => 'Story #4', 'author' => ['name' => 'Joe']],
['title' => 'Story #6', 'author' => ['name' => 'Jane']],
['title' => 'Story #8', 'author' => ['name' => 'John']],
]
]
]
],
],
],
];
$result = Executor::execute($schema, $query);
@ -292,7 +328,7 @@ class DeferredFieldsTest extends TestCase
');
$schema = new Schema([
'query' => $this->queryType
'query' => $this->queryType,
]);
$author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']];
@ -306,8 +342,8 @@ class DeferredFieldsTest extends TestCase
['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]],
['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]],
['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]],
]
]
],
],
];
$result = Executor::execute($schema, $query);
@ -353,51 +389,53 @@ class DeferredFieldsTest extends TestCase
{
$complexType = new ObjectType([
'name' => 'ComplexType',
'fields' => function() use (&$complexType) {
'fields' => function () use (&$complexType) {
return [
'sync' => [
'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return 'sync';
}
},
],
'deferred' => [
'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'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 for: ', $info->path];
return 'deferred';
});
}
},
],
'nest' => [
'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return [];
}
},
],
'deferredNest' => [
'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'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('
{
@ -435,26 +473,26 @@ class DeferredFieldsTest extends TestCase
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred'
'deferred' => 'deferred',
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred'
]
'deferred' => 'deferred',
],
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred'
'deferred' => 'deferred',
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred'
]
]
]
'deferred' => 'deferred',
],
],
],
];
$this->assertEquals($expected, $result->toArray());

View File

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

View File

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

View File

@ -1,44 +1,62 @@
<?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase;
use function count;
class ExecutorLazySchemaTest extends TestCase
{
public $SomeScalarType;
/** @var ScalarType */
public $someScalarType;
public $SomeObjectType;
/** @var ObjectType */
public $someObjectType;
public $OtherObjectType;
/** @var ObjectType */
public $otherObjectType;
public $DeeperObjectType;
/** @var ObjectType */
public $deeperObjectType;
public $SomeUnionType;
/** @var UnionType */
public $someUnionType;
public $SomeInterfaceType;
/** @var InterfaceType */
public $someInterfaceType;
public $SomeEnumType;
/** @var EnumType */
public $someEnumType;
public $SomeInputObjectType;
/** @var InputObjectType */
public $someInputObjectType;
public $QueryType;
/** @var ObjectType */
public $queryType;
/** @var string[] */
public $calls = [];
/** @var bool[] */
public $loadedTypes = [];
public function testWarnsAboutSlowIsTypeOfForLazySchema() : void
@ -46,24 +64,26 @@ class ExecutorLazySchemaTest extends TestCase
// isTypeOf used to resolve runtime type for Interface
$petType = new InterfaceType([
'name' => 'Pet',
'fields' => function() {
'fields' => function () {
return [
'name' => ['type' => Type::string()]
'name' => ['type' => Type::string()],
];
}
},
]);
// Added to interface type when defined
$dogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'fields' => function() {
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => function () {
return [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
'woofs' => ['type' => Type::boolean()],
];
}
},
]);
$catType = new ObjectType([
@ -72,12 +92,12 @@ class ExecutorLazySchemaTest extends TestCase
'isTypeOf' => function ($obj) {
return $obj instanceof Cat;
},
'fields' => function() {
'fields' => function () {
return [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
];
}
},
]);
$schema = new Schema([
@ -88,12 +108,12 @@ class ExecutorLazySchemaTest extends TestCase
'type' => Type::listOf($petType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
}
]
]
},
],
],
]),
'types' => [$catType, $dogType],
'typeLoader' => function($name) use ($dogType, $petType, $catType) {
'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) {
$typeLoader = function ($name) use (&$calls) {
$calls[] = $name;
switch ($name) {
case 'Test':
return new ObjectType([
'name' => 'Test',
'fields' => function() {
'fields' => function () {
return [
'test' => Type::string(),
];
}
},
]);
default:
return null;
}
};
$query = new ObjectType([
'name' => 'Query',
'fields' => function() use ($typeLoader) {
'fields' => function () use ($typeLoader) {
return [
'test' => $typeLoader('Test')
'test' => $typeLoader('Test'),
];
}
},
]);
$schema = new Schema([
'query' => $query,
'typeLoader' => $typeLoader
'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()
);
@ -201,9 +224,9 @@ class ExecutorLazySchemaTest extends TestCase
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '{ object { string } }';
@ -219,19 +242,126 @@ class ExecutorLazySchemaTest extends TestCase
$expectedExecutorCalls = [
'Query.fields',
'SomeObject',
'SomeObject.fields'
'SomeObject.fields',
];
$this->assertEquals($expected, $result->toArray(true));
$this->assertEquals($expectedExecutorCalls, $this->calls);
}
public function loadType($name, $isExecutorCall = false)
{
if ($isExecutorCall) {
$this->calls[] = $name;
}
$this->loadedTypes[$name] = true;
switch ($name) {
case 'Query':
return $this->queryType ?: $this->queryType = new ObjectType([
'name' => 'Query',
'fields' => function () {
$this->calls[] = 'Query.fields';
return [
'object' => ['type' => $this->loadType('SomeObject')],
'other' => ['type' => $this->loadType('OtherObject')],
];
},
]);
case 'SomeObject':
return $this->someObjectType ?: $this->someObjectType = new ObjectType([
'name' => 'SomeObject',
'fields' => function () {
$this->calls[] = 'SomeObject.fields';
return [
'string' => ['type' => Type::string()],
'object' => ['type' => $this->someObjectType],
];
},
'interfaces' => function () {
$this->calls[] = 'SomeObject.interfaces';
return [
$this->loadType('SomeInterface'),
];
},
]);
case 'OtherObject':
return $this->otherObjectType ?: $this->otherObjectType = new ObjectType([
'name' => 'OtherObject',
'fields' => function () {
$this->calls[] = 'OtherObject.fields';
return [
'union' => ['type' => $this->loadType('SomeUnion')],
'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))],
];
},
]);
case 'DeeperObject':
return $this->deeperObjectType ?: $this->deeperObjectType = new ObjectType([
'name' => 'DeeperObject',
'fields' => function () {
return [
'scalar' => ['type' => $this->loadType('SomeScalar')],
];
},
]);
case 'SomeScalar':
return $this->someScalarType ?: $this->someScalarType = new CustomScalarType([
'name' => 'SomeScalar',
'serialize' => function ($value) {
return $value;
},
'parseValue' => function ($value) {
return $value;
},
'parseLiteral' => function () {
},
]);
case 'SomeUnion':
return $this->someUnionType ?: $this->someUnionType = new UnionType([
'name' => 'SomeUnion',
'resolveType' => function () {
$this->calls[] = 'SomeUnion.resolveType';
return $this->loadType('DeeperObject');
},
'types' => function () {
$this->calls[] = 'SomeUnion.types';
return [$this->loadType('DeeperObject')];
},
]);
case 'SomeInterface':
return $this->someInterfaceType ?: $this->someInterfaceType = new InterfaceType([
'name' => 'SomeInterface',
'resolveType' => function () {
$this->calls[] = 'SomeInterface.resolveType';
return $this->loadType('SomeObject');
},
'fields' => function () {
$this->calls[] = 'SomeInterface.fields';
return [
'string' => ['type' => Type::string()],
];
},
]);
default:
return null;
}
}
public function testDeepQuery() : void
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '{ object { object { object { string } } } }';
@ -242,12 +372,12 @@ class ExecutorLazySchemaTest extends TestCase
);
$expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]]
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]],
];
$expectedLoadedTypes = [
'Query' => true,
'SomeObject' => true,
'OtherObject' => true
'OtherObject' => true,
];
$this->assertEquals($expected, $result->toArray(true));
@ -256,7 +386,7 @@ class ExecutorLazySchemaTest extends TestCase
$expectedExecutorCalls = [
'Query.fields',
'SomeObject',
'SomeObject.fields'
'SomeObject.fields',
];
$this->assertEquals($expectedExecutorCalls, $this->calls);
}
@ -265,9 +395,9 @@ class ExecutorLazySchemaTest extends TestCase
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '
@ -313,98 +443,4 @@ class ExecutorLazySchemaTest extends TestCase
];
$this->assertEquals($expectedCalls, $this->calls);
}
public function loadType($name, $isExecutorCall = false)
{
if ($isExecutorCall) {
$this->calls[] = $name;
}
$this->loadedTypes[$name] = true;
switch ($name) {
case 'Query':
return $this->QueryType ?: $this->QueryType = new ObjectType([
'name' => 'Query',
'fields' => function() {
$this->calls[] = 'Query.fields';
return [
'object' => ['type' => $this->loadType('SomeObject')],
'other' => ['type' => $this->loadType('OtherObject')],
];
}
]);
case 'SomeObject':
return $this->SomeObjectType ?: $this->SomeObjectType = new ObjectType([
'name' => 'SomeObject',
'fields' => function() {
$this->calls[] = 'SomeObject.fields';
return [
'string' => ['type' => Type::string()],
'object' => ['type' => $this->SomeObjectType]
];
},
'interfaces' => function() {
$this->calls[] = 'SomeObject.interfaces';
return [
$this->loadType('SomeInterface')
];
}
]);
case 'OtherObject':
return $this->OtherObjectType ?: $this->OtherObjectType = new ObjectType([
'name' => 'OtherObject',
'fields' => function() {
$this->calls[] = 'OtherObject.fields';
return [
'union' => ['type' => $this->loadType('SomeUnion')],
'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))],
];
}
]);
case 'DeeperObject':
return $this->DeeperObjectType ?: $this->DeeperObjectType = new ObjectType([
'name' => 'DeeperObject',
'fields' => function() {
return [
'scalar' => ['type' => $this->loadType('SomeScalar')],
];
}
]);
case 'SomeScalar';
return $this->SomeScalarType ?: $this->SomeScalarType = new CustomScalarType([
'name' => 'SomeScalar',
'serialize' => function($value) {return $value;},
'parseValue' => function($value) {return $value;},
'parseLiteral' => function() {}
]);
case 'SomeUnion':
return $this->SomeUnionType ?: $this->SomeUnionType = new UnionType([
'name' => 'SomeUnion',
'resolveType' => function() {
$this->calls[] = 'SomeUnion.resolveType';
return $this->loadType('DeeperObject');
},
'types' => function() {
$this->calls[] = 'SomeUnion.types';
return [ $this->loadType('DeeperObject') ];
}
]);
case 'SomeInterface':
return $this->SomeInterfaceType ?: $this->SomeInterfaceType = new InterfaceType([
'name' => 'SomeInterface',
'resolveType' => function() {
$this->calls[] = 'SomeInterface.resolveType';
return $this->loadType('SomeObject');
},
'fields' => function() {
$this->calls[] = 'SomeInterface.fields';
return [
'string' => ['type' => Type::string() ]
];
}
]);
default:
return null;
}
}
}

View File

@ -1,17 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function sprintf;
class ExecutorSchemaTest extends TestCase
{
// Execute: Handles execution with a complex schema
/**
* @see it('executes using a schema')
*/
@ -24,12 +27,12 @@ class ExecutorSchemaTest extends TestCase
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()],
]
],
]);
$BlogAuthor = new ObjectType([
'name' => 'Author',
'fields' => function() use (&$BlogArticle, &$BlogImage) {
'fields' => function () use (&$BlogArticle, &$BlogImage) {
return [
'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
@ -38,11 +41,11 @@ class ExecutorSchemaTest extends TestCase
'type' => $BlogImage,
'resolve' => function ($obj, $args) {
return $obj['pic']($args['width'], $args['height']);
}
},
],
'recentArticle' => $BlogArticle
'recentArticle' => $BlogArticle,
];
}
},
]);
$BlogArticle = new ObjectType([
@ -53,8 +56,8 @@ class ExecutorSchemaTest extends TestCase
'author' => ['type' => $BlogAuthor],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'keywords' => ['type' => Type::listOf(Type::string())]
]
'keywords' => ['type' => Type::listOf(Type::string())],
],
]);
$BlogQuery = new ObjectType([
@ -65,7 +68,7 @@ class ExecutorSchemaTest extends TestCase
'args' => ['id' => ['type' => Type::id()]],
'resolve' => function ($_, $args) {
return $this->article($args['id']);
}
},
],
'feed' => [
'type' => Type::listOf($BlogArticle),
@ -80,16 +83,15 @@ class ExecutorSchemaTest extends TestCase
$this->article(7),
$this->article(8),
$this->article(9),
$this->article(10)
$this->article(10),
];
}
]
]
},
],
],
]);
$BlogSchema = new Schema(['query' => $BlogQuery]);
$request = '
{
feed {
@ -127,26 +129,46 @@ class ExecutorSchemaTest extends TestCase
$expected = [
'data' => [
'feed' => [
['id' => '1',
'title' => 'My Article 1'],
['id' => '2',
'title' => 'My Article 2'],
['id' => '3',
'title' => 'My Article 3'],
['id' => '4',
'title' => 'My Article 4'],
['id' => '5',
'title' => 'My Article 5'],
['id' => '6',
'title' => 'My Article 6'],
['id' => '7',
'title' => 'My Article 7'],
['id' => '8',
'title' => 'My Article 8'],
['id' => '9',
'title' => 'My Article 9'],
['id' => '10',
'title' => 'My Article 10']
[
'id' => '1',
'title' => 'My Article 1',
],
[
'id' => '2',
'title' => 'My Article 2',
],
[
'id' => '3',
'title' => 'My Article 3',
],
[
'id' => '4',
'title' => 'My Article 4',
],
[
'id' => '5',
'title' => 'My Article 5',
],
[
'id' => '6',
'title' => 'My Article 6',
],
[
'id' => '7',
'title' => 'My Article 7',
],
[
'id' => '8',
'title' => 'My Article 8',
],
[
'id' => '9',
'title' => 'My Article 9',
],
[
'id' => '10',
'title' => 'My Article 10',
],
],
'article' => [
'id' => '1',
@ -159,18 +181,18 @@ class ExecutorSchemaTest extends TestCase
'pic' => [
'url' => 'cdn://123',
'width' => 640,
'height' => 480
'height' => 480,
],
'recentArticle' => [
'id' => '1',
'isPublished' => true,
'title' => 'My Article 1',
'body' => 'This is a post',
'keywords' => ['foo', 'bar', '1', 'true', null]
]
]
]
]
'keywords' => ['foo', 'bar', '1', 'true', null],
],
],
],
],
];
$this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray());
@ -179,7 +201,7 @@ class ExecutorSchemaTest extends TestCase
private function article($id)
{
$johnSmith = null;
$article = function($id) use (&$johnSmith) {
$article = function ($id) use (&$johnSmith) {
return [
'id' => $id,
'isPublished' => 'true',
@ -187,22 +209,24 @@ class ExecutorSchemaTest extends TestCase
'title' => 'My Article ' . $id,
'body' => 'This is a post',
'hidden' => 'This data is not exposed in the schema',
'keywords' => ['foo', 'bar', 1, true, null]
'keywords' => ['foo', 'bar', 1, true, null],
];
};
$getPic = function($uid, $width, $height) {
$getPic = function ($uid, $width, $height) {
return [
'url' => "cdn://$uid",
'url' => sprintf('cdn://%s', $uid),
'width' => $width,
'height' => $height
'height' => $height,
];
};
$johnSmith = [
'id' => 123,
'name' => 'John Smith',
'pic' => function($width, $height) use ($getPic) {return $getPic(123, $width, $height);},
'pic' => function ($width, $height) use ($getPic) {
return $getPic(123, $width, $height);
},
'recentArticle' => $article(1),
];

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
@ -83,9 +116,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -98,46 +131,53 @@ 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;
})
}),
];
},
[
@ -146,9 +186,9 @@ class ListsTest extends TestCase
[
'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,7 +271,7 @@ class ListsTest extends TestCase
// Rejected
$this->checkHandlesNonNullableLists(
function () {
return new Deferred(function() {
return new Deferred(function () {
throw new UserError('bad');
});
},
@ -235,9 +281,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -250,45 +296,45 @@ 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;
})
}),
];
},
[
@ -297,9 +343,9 @@ class ListsTest extends TestCase
[
'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,41 +396,41 @@ 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');
});
},
@ -388,9 +440,9 @@ class ListsTest extends TestCase
[
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -403,39 +455,45 @@ 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;
})
}),
];
},
[
@ -444,9 +502,9 @@ class ListsTest extends TestCase
[
'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',
'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',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
private function checkHandlesNullableLists($testData, $expected)
{
$testType = Type::listOf(Type::int());
$this->check($testType, $testData, $expected);
}
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
private function checkHandlesListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::listOf(Type::nonNull(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$this->check($testType, $testData, $expected, $debug);
}
private function check($testType, $testData, $expected, $debug = false)
{
$data = ['test' => $testData];
$dataType = null;
$dataType = new ObjectType([
'name' => 'DataType',
'fields' => function () use (&$testType, &$dataType, $data) {
return [
'test' => [
'type' => $testType
],
'nest' => [
'type' => $dataType,
'resolve' => function () use ($data) {
return $data;
}
]
];
}
]);
$schema = new Schema([
'query' => $dataType
]);
$ast = Parser::parse('{ nest { test } }');
$result = Executor::execute($schema, $ast, $data);
$this->assertArraySubset($expected, $result->toArray($debug));
}
}

View File

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

View File

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

View File

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

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Deferred;
@ -10,9 +13,7 @@ use PHPUnit\Framework\TestCase;
class SyncPromiseAdapterTest extends TestCase
{
/**
* @var SyncPromiseAdapter
*/
/** @var SyncPromiseAdapter */
private $promises;
public function setUp()
@ -22,7 +23,11 @@ class SyncPromiseAdapterTest extends TestCase
public function testIsThenable() : void
{
$this->assertEquals(true, $this->promises->isThenable(new Deferred(function() {})));
$this->assertEquals(
true,
$this->promises->isThenable(new Deferred(function () {
}))
);
$this->assertEquals(false, $this->promises->isThenable(false));
$this->assertEquals(false, $this->promises->isThenable(true));
$this->assertEquals(false, $this->promises->isThenable(1));
@ -35,7 +40,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testConvert() : void
{
$dfd = new Deferred(function() {});
$dfd = new Deferred(function () {
});
$result = $this->promises->convertThenable($dfd);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $result);
@ -48,7 +54,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testThen() : void
{
$dfd = new Deferred(function() {});
$dfd = new Deferred(function () {
});
$promise = $this->promises->convertThenable($dfd);
$result = $this->promises->then($promise);
@ -59,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];
$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) {
$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,18 +259,22 @@ class SyncPromiseTest extends TestCase
try {
$promise->reject(new \Exception('other-reason'));
$this->fail('Expected exception not thrown');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertEquals('Cannot change rejection reason', $e->getMessage());
}
try {
$promise->resolve('anything');
$this->fail('Expected exception not thrown');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertEquals('Cannot resolve rejected promise', $e->getMessage());
}
$nextPromise = $promise->then(function() {}, null);
$nextPromise = $promise->then(
function () {
},
null
);
$this->assertSame($promise, $nextPromise);
$onFulfilledCalled = false;
@ -266,7 +315,7 @@ class SyncPromiseTest extends TestCase
try {
$promise->resolve($promise);
$this->fail('Expected exception not thrown');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertEquals('Cannot resolve promise with self', $e->getMessage());
$this->assertEquals(SyncPromise::PENDING, $promise->state);
}
@ -299,24 +348,25 @@ class SyncPromiseTest extends TestCase
throw $e;
} catch (\Throwable $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state);
} catch (\Exception $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state);
}
$promise->reject(new \Exception("Rejected Reason"));
$this->assertValidPromise($promise, "Rejected Reason", null, SyncPromise::REJECTED);
$promise->reject(new \Exception('Rejected Reason'));
$this->assertValidPromise($promise, 'Rejected Reason', null, SyncPromise::REJECTED);
$promise = new SyncPromise();
$promise2 = $promise->then(null, function() {
$promise2 = $promise->then(
null,
function () {
return 'value';
});
$promise->reject(new \Exception("Rejected Again"));
}
);
$promise->reject(new \Exception('Rejected Again'));
$this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED);
$promise = new SyncPromise();
$promise2 = $promise->then();
$promise->reject(new \Exception("Rejected Once Again"));
$this->assertValidPromise($promise2, "Rejected Once Again", null, SyncPromise::REJECTED);
$promise->reject(new \Exception('Rejected Once Again'));
$this->assertValidPromise($promise2, 'Rejected Once Again', null, SyncPromise::REJECTED);
}
public function testPendingPromiseThen() : void
@ -332,11 +382,13 @@ class SyncPromiseTest extends TestCase
// Make sure that it queues derivative promises until resolution:
$onFulfilledCount = 0;
$onRejectedCount = 0;
$onFulfilled = function($value) use (&$onFulfilledCount) {
$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,6 +32,16 @@ class ResolveTest extends TestCase
);
}
private function buildSchema($testField)
{
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['test' => $testField],
]),
]);
}
/**
* @see it('default function calls methods')
*/
@ -51,9 +51,9 @@ class ResolveTest extends TestCase
$_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()],
],
]);
@ -92,7 +92,7 @@ class ResolveTest extends TestCase
],
'resolve' => function ($source, $args) {
return json_encode([$source, $args]);
}
},
]);
$this->assertEquals(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Values;
@ -10,19 +13,97 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function count;
use function var_export;
use const PHP_EOL;
class ValuesTest extends TestCase
{
/** @var Schema */
private static $schema;
public function testGetIDVariableValues() : void
{
$this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']);
$this->assertEquals(
['errors'=> [], 'coerced' => ['idInput' => '123456789']],
self::runTestCase(['idInput' => 123456789]),
['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,9 +31,7 @@ class VariablesTest extends TestCase
');
$expected = [
'data' => [
'fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'
]
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
];
$this->assertEquals($expected, $result->toArray());
@ -77,8 +77,9 @@ class VariablesTest extends TestCase
'errors' => [[
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
'path' => ['fieldWithObjectInput'],
'locations' => [['line' => 3, 'column' => 39]]
]]
'locations' => [['line' => 3, 'column' => 39]],
],
],
];
$this->assertArraySubset($expected, $result->toArray());
@ -94,6 +95,77 @@ class VariablesTest extends TestCase
);
}
private function executeQuery($query, $variableValues = null)
{
$document = Parser::parse($query);
return Executor::execute($this->schema(), $document, null, null, $variableValues);
}
/**
* Describe: Handles nullable scalars
*/
public function schema() : Schema
{
$ComplexScalarType = ComplexScalar::create();
$TestInputObject = new InputObjectType([
'name' => 'TestInputObject',
'fields' => [
'a' => ['type' => Type::string()],
'b' => ['type' => Type::listOf(Type::string())],
'c' => ['type' => Type::nonNull(Type::string())],
'd' => ['type' => $ComplexScalarType],
],
]);
$TestNestedInputObject = new InputObjectType([
'name' => 'TestNestedInputObject',
'fields' => [
'na' => ['type' => Type::nonNull($TestInputObject)],
'nb' => ['type' => Type::nonNull(Type::string())],
],
]);
$TestType = new ObjectType([
'name' => 'TestType',
'fields' => [
'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]),
'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]),
'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]),
'fieldWithDefaultArgumentValue' => $this->fieldWithInputArg([
'type' => Type::string(),
'defaultValue' => 'Hello World',
]),
'fieldWithNestedInputObject' => $this->fieldWithInputArg([
'type' => $TestNestedInputObject,
'defaultValue' => 'Hello World',
]),
'list' => $this->fieldWithInputArg(['type' => Type::listOf(Type::string())]),
'nnList' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::string()))]),
'listNN' => $this->fieldWithInputArg(['type' => Type::listOf(Type::nonNull(Type::string()))]),
'nnListNN' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]),
],
]);
return new Schema(['query' => $TestType]);
}
private function fieldWithInputArg($inputArg)
{
return [
'type' => Type::string(),
'args' => ['input' => $inputArg],
'resolve' => function ($_, $args) {
if (isset($args['input'])) {
return json_encode($args['input']);
}
return null;
},
];
}
public function testUsingVariables() : void
{
$doc = '
@ -119,7 +191,7 @@ class VariablesTest extends TestCase
');
$expected = [
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}']
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
];
$this->assertEquals($expected, $result->toArray());
@ -132,12 +204,10 @@ class VariablesTest extends TestCase
);
// executes with complex scalar input:
$params = [ 'input' => [ 'c' => 'foo', 'd' => 'SerializedValue' ] ];
$params = ['input' => ['c' => 'foo', 'd' => 'SerializedValue']];
$result = $this->executeQuery($doc, $params);
$expected = [
'data' => [
'fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'
]
'data' => ['fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'],
];
$this->assertEquals($expected, $result->toArray());
@ -152,15 +222,15 @@ class VariablesTest extends TestCase
'{"a":"foo","b":"bar","c":null}; ' .
'Expected non-nullable type String! not to be null at value.c.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql'
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
// errors on incorrect type:
$params = [ 'input' => 'foo bar' ];
$params = ['input' => 'foo bar'];
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
@ -168,10 +238,10 @@ class VariablesTest extends TestCase
'message' =>
'Variable "$input" got invalid value "foo bar"; ' .
'Expected type TestInputObject to be an object.',
'locations' => [ [ 'line' => 2, 'column' => 21 ] ],
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
@ -183,12 +253,12 @@ class VariablesTest extends TestCase
'errors' => [
[
'message' =>
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; '.
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; ' .
'Field value.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
@ -198,7 +268,7 @@ class VariablesTest extends TestCase
fieldWithNestedObjectInput(input: $input)
}
';
$params = [ 'input' => [ 'na' => [ 'a' => 'foo' ] ] ];
$params = ['input' => ['na' => ['a' => 'foo']]];
$result = $this->executeQuery($nestedDoc, $params);
$expected = [
@ -216,14 +286,13 @@ class VariablesTest extends TestCase
'Field value.nb of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
// errors on addition of unknown input field
$params = ['input' => [ 'a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog' ]];
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog']];
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
@ -234,14 +303,12 @@ class VariablesTest extends TestCase
'Field "extra" is not defined by type TestInputObject.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles nullable scalars
/**
* @see it('allows nullable inputs to be omitted')
*/
@ -253,7 +320,7 @@ class VariablesTest extends TestCase
}
');
$expected = [
'data' => ['fieldWithNullableStringInput' => null]
'data' => ['fieldWithNullableStringInput' => null],
];
$this->assertEquals($expected, $result->toArray());
@ -288,6 +355,9 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles non-nullable scalars
/**
* @see it('allows nullable inputs to be set to null in a variable')
*/
@ -332,9 +402,6 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles non-nullable scalars
/**
* @see it('allows non-nullable inputs to be omitted given a default')
*/
@ -346,7 +413,7 @@ class VariablesTest extends TestCase
}
');
$expected = [
'data' => ['fieldWithNonNullableStringInput' => '"default"']
'data' => ['fieldWithNonNullableStringInput' => '"default"'],
];
$this->assertEquals($expected, $result->toArray());
}
@ -367,9 +434,9 @@ class VariablesTest extends TestCase
[
'message' => 'Variable "$value" of required type "String!" was not provided.',
'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql'
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -393,8 +460,8 @@ class VariablesTest extends TestCase
'Expected non-nullable type String! not to be null.',
'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -442,20 +509,22 @@ class VariablesTest extends TestCase
'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[
'message' => 'Argument "input" of required type "String!" was not provided.',
'locations' => [ [ 'line' => 3, 'column' => 9 ] ],
'path' => [ 'fieldWithNonNullableStringInput' ],
'locations' => [['line' => 3, 'column' => 9]],
'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql',
]]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles lists and nullability
/**
* @see it('reports error for array passed into string input')
*/
public function testReportsErrorForArrayPassedIntoStringInput() : void
{
$doc = '
query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value)
@ -471,9 +540,10 @@ class VariablesTest extends TestCase
'String; String cannot represent an array value: [1,2,3]',
'category' => 'graphql',
'locations' => [
['line' => 2, 'column' => 31]
]
]]
['line' => 2, 'column' => 31],
],
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -512,13 +582,12 @@ class VariablesTest extends TestCase
'locations' => [['line' => 3, 'column' => 48]],
'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql',
]]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles lists and nullability
/**
* @see it('allows lists to be null')
*/
@ -560,7 +629,7 @@ class VariablesTest extends TestCase
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());
}
@ -584,8 +653,8 @@ class VariablesTest extends TestCase
'Expected non-nullable type [String]! not to be null.',
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -615,7 +684,7 @@ class VariablesTest extends TestCase
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());
}
@ -667,10 +736,10 @@ class VariablesTest extends TestCase
'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] ],
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -692,10 +761,10 @@ class VariablesTest extends TestCase
'message' =>
'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String!]! not to be null.',
'locations' => [ ['line' => 2, 'column' => 17] ],
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -715,6 +784,8 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
}
// Describe: Execute: Uses argument default values
/**
* @see it('does not allow non-null lists of non-nulls to contain null')
*/
@ -732,10 +803,10 @@ class VariablesTest extends TestCase
'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] ],
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -750,7 +821,7 @@ class VariablesTest extends TestCase
fieldWithObjectInput(input: $input)
}
';
$vars = [ 'input' => [ 'list' => [ 'A', 'B' ] ] ];
$vars = ['input' => ['list' => ['A', 'B']]];
$result = $this->executeQuery($doc, $vars);
$expected = [
'errors' => [
@ -760,8 +831,8 @@ class VariablesTest extends TestCase
'be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -787,13 +858,12 @@ class VariablesTest extends TestCase
'cannot be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql',
]
]
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Execute: Uses argument default values
/**
* @see it('when no argument provided')
*/
@ -838,80 +908,13 @@ class VariablesTest extends TestCase
'errors' => [[
'message' =>
'Argument "input" has invalid value WRONG_TYPE.',
'locations' => [ [ 'line' => 2, 'column' => 50 ] ],
'path' => [ 'fieldWithDefaultArgumentValue' ],
'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);
}
}