Tests for lazy type loading during query execution + related changed

This commit is contained in:
Vladimir Razuvaev 2017-08-15 01:49:56 +07:00
parent e4813c3a05
commit 9931cde6d4
5 changed files with 442 additions and 188 deletions

View File

@ -113,7 +113,7 @@ class Schema
); );
$this->config = $config; $this->config = $config;
$this->resolvedTypes = Type::getInternalTypes() + Introspection::getTypes();
$this->resolvedTypes[$config->query->name] = $config->query; $this->resolvedTypes[$config->query->name] = $config->query;
if ($config->mutation) { if ($config->mutation) {
@ -207,7 +207,7 @@ class Schema
foreach ($this->resolveAdditionalTypes() as $type) { foreach ($this->resolveAdditionalTypes() as $type) {
$typeMap = TypeInfo::extractTypes($type, $typeMap); $typeMap = TypeInfo::extractTypes($type, $typeMap);
} }
return $typeMap + Type::getInternalTypes() + Introspection::getTypes(); return $typeMap;
} }
/** /**
@ -373,7 +373,13 @@ class Schema
); );
} }
$internalTypes = Type::getInternalTypes() + Introspection::getTypes();
foreach ($this->getTypeMap() as $name => $type) { foreach ($this->getTypeMap() as $name => $type) {
if (isset($internalTypes[$name])) {
continue ;
}
$type->assertValid(); $type->assertValid();
if ($type instanceof AbstractType) { if ($type instanceof AbstractType) {

View File

@ -8,13 +8,37 @@ use GraphQL\Error\Warning;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
class ExecutorLazySchemaTest extends \PHPUnit_Framework_TestCase class ExecutorLazySchemaTest extends \PHPUnit_Framework_TestCase
{ {
public $SomeScalarType;
public $SomeObjectType;
public $OtherObjectType;
public $DeeperObjectType;
public $SomeUnionType;
public $SomeInterfaceType;
public $SomeEnumType;
public $SomeInputObjectType;
public $QueryType;
public $calls = [];
public $loadedTypes = [];
public function testWarnsAboutSlowIsTypeOfForLazySchema() public function testWarnsAboutSlowIsTypeOfForLazySchema()
{ {
// isTypeOf used to resolve runtime type for Interface // isTypeOf used to resolve runtime type for Interface
@ -169,4 +193,215 @@ class ExecutorLazySchemaTest extends \PHPUnit_Framework_TestCase
$result->errors[0]->getPrevious() $result->errors[0]->getPrevious()
); );
} }
public function testSimpleQuery()
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
return $this->loadType($name, true);
}
]);
$query = '{ object { string } }';
$result = Executor::execute(
$schema,
Parser::parse($query),
['object' => ['string' => 'test']]
);
$expected = [
'data' => ['object' => ['string' => 'test']],
];
$expectedExecutorCalls = [
'Query.fields',
'SomeObject',
'SomeObject.fields'
];
$this->assertEquals($expected, $result->toArray(true));
$this->assertEquals($expectedExecutorCalls, $this->calls);
}
public function testDeepQuery()
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
return $this->loadType($name, true);
}
]);
$query = '{ object { object { object { string } } } }';
$result = Executor::execute(
$schema,
Parser::parse($query),
['object' => ['object' => ['object' => ['string' => 'test']]]]
);
$expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]]
];
$expectedLoadedTypes = [
'Query' => true,
'SomeObject' => true,
'OtherObject' => true
];
$this->assertEquals($expected, $result->toArray(true));
$this->assertEquals($expectedLoadedTypes, $this->loadedTypes);
$expectedExecutorCalls = [
'Query.fields',
'SomeObject',
'SomeObject.fields'
];
$this->assertEquals($expectedExecutorCalls, $this->calls);
}
public function testResolveUnion()
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
return $this->loadType($name, true);
}
]);
$query = '
{
other {
union {
scalar
}
}
}
';
$result = Executor::execute(
$schema,
Parser::parse($query),
['other' => ['union' => ['scalar' => 'test']]]
);
$expected = [
'data' => ['other' => ['union' => ['scalar' => 'test']]],
];
$expectedLoadedTypes = [
'Query' => true,
'SomeObject' => true,
'OtherObject' => true,
'SomeUnion' => true,
'SomeInterface' => true,
'DeeperObject' => true,
'SomeScalar' => true,
];
$this->assertEquals($expected, $result->toArray(true));
$this->assertEquals($expectedLoadedTypes, $this->loadedTypes);
$expectedCalls = [
'Query.fields',
'OtherObject',
'OtherObject.fields',
'SomeUnion',
'SomeUnion.resolveType',
'SomeUnion.types',
'DeeperObject',
'SomeScalar',
];
$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

@ -26,24 +26,24 @@ class StarWarsIntrospectionTest extends \PHPUnit_Framework_TestCase
$expected = [ $expected = [
'__schema' => [ '__schema' => [
'types' => [ 'types' => [
['name' => 'Query'],
['name' => 'Episode'],
['name' => 'Character'],
['name' => 'String'],
['name' => 'Human'],
['name' => 'Droid'],
['name' => 'ID'], ['name' => 'ID'],
['name' => 'String'],
['name' => 'Float'], ['name' => 'Float'],
['name' => 'Int'], ['name' => 'Int'],
['name' => 'Boolean'], ['name' => 'Boolean'],
['name' => '__Schema'], ['name' => '__Schema'],
['name' => '__Type'], ['name' => '__Type'],
['name' => '__Directive'], ['name' => '__TypeKind'],
['name' => '__Field'], ['name' => '__Field'],
['name' => '__InputValue'], ['name' => '__InputValue'],
['name' => '__EnumValue'], ['name' => '__EnumValue'],
['name' => '__TypeKind'], ['name' => '__Directive'],
['name' => '__DirectiveLocation'], ['name' => '__DirectiveLocation'],
['name' => 'Query'],
['name' => 'Episode'],
['name' => 'Character'],
['name' => 'Human'],
['name' => 'Droid'],
] ]
] ]
]; ];

View File

@ -42,32 +42,9 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
), ),
'types' => 'types' =>
array ( array (
array (
'kind' => 'OBJECT',
'name' => 'QueryRoot',
'inputFields' => NULL,
'interfaces' =>
array (
),
'enumValues' => NULL,
'possibleTypes' => NULL,
'fields' => array (
array (
'name' => 'a',
'args' => array(),
'type' => array(
'kind' => 'SCALAR',
'name' => 'String',
'ofType' => null
),
'isDeprecated' => false,
'deprecationReason' => null,
)
)
),
array ( array (
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'name' => 'String', 'name' => 'ID',
'fields' => NULL, 'fields' => NULL,
'inputFields' => NULL, 'inputFields' => NULL,
'interfaces' => NULL, 'interfaces' => NULL,
@ -76,7 +53,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
), ),
array ( array (
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'name' => 'ID', 'name' => 'String',
'fields' => NULL, 'fields' => NULL,
'inputFields' => NULL, 'inputFields' => NULL,
'interfaces' => NULL, 'interfaces' => NULL,
@ -440,165 +417,62 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'possibleTypes' => NULL, 'possibleTypes' => NULL,
), ),
array ( array (
'kind' => 'OBJECT', 'kind' => 'ENUM',
'name' => '__Directive', 'name' => '__TypeKind',
'fields' => 'fields' => NULL,
'inputFields' => NULL,
'interfaces' => NULL,
'enumValues' =>
array ( array (
0 => 0 =>
array ( array (
'name' => 'name', 'name' => 'SCALAR',
'args' =>
array (
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'SCALAR',
'name' => 'String',
),
),
'isDeprecated' => false, 'isDeprecated' => false,
'deprecationReason' => NULL, 'deprecationReason' => NULL,
), ),
1 => 1 =>
array ( array (
'name' => 'description', 'name' => 'OBJECT',
'args' =>
array (
),
'type' =>
array (
'kind' => 'SCALAR',
'name' => 'String',
),
'isDeprecated' => false, 'isDeprecated' => false,
'deprecationReason' => NULL, 'deprecationReason' => NULL,
), ),
2 => 2 =>
array ( array (
'name' => 'locations', 'name' => 'INTERFACE',
'args' =>
array (
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'LIST',
'name' => NULL,
'ofType' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'ENUM',
'name' => '__DirectiveLocation',
),
),
),
),
'isDeprecated' => false, 'isDeprecated' => false,
'deprecationReason' => NULL, 'deprecationReason' => NULL,
), ),
3 => 3 =>
array ( array (
'name' => 'args', 'name' => 'UNION',
'args' =>
array (
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'LIST',
'name' => NULL,
'ofType' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'OBJECT',
'name' => '__InputValue',
),
),
),
),
'isDeprecated' => false, 'isDeprecated' => false,
'deprecationReason' => NULL, 'deprecationReason' => NULL,
), ),
4 => 4 =>
array ( array (
'name' => 'onOperation', 'name' => 'ENUM',
'args' => 'isDeprecated' => false,
array ( 'deprecationReason' => NULL,
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'SCALAR',
'name' => 'Boolean',
),
),
'isDeprecated' => true,
'deprecationReason' => 'Use `locations`.',
), ),
5 => 5 =>
array ( array (
'name' => 'onFragment', 'name' => 'INPUT_OBJECT',
'args' => 'isDeprecated' => false,
array ( 'deprecationReason' => NULL,
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'SCALAR',
'name' => 'Boolean',
),
),
'isDeprecated' => true,
'deprecationReason' => 'Use `locations`.',
), ),
6 => 6 =>
array ( array (
'name' => 'onField', 'name' => 'LIST',
'args' => 'isDeprecated' => false,
array ( 'deprecationReason' => NULL,
), ),
'type' => 7 =>
array ( array (
'kind' => 'NON_NULL', 'name' => 'NON_NULL',
'name' => NULL, 'isDeprecated' => false,
'ofType' => 'deprecationReason' => NULL,
array (
'kind' => 'SCALAR',
'name' => 'Boolean',
),
),
'isDeprecated' => true,
'deprecationReason' => 'Use `locations`.',
), ),
), ),
'inputFields' => NULL,
'interfaces' =>
array (
),
'enumValues' => NULL,
'possibleTypes' => NULL, 'possibleTypes' => NULL,
), ),
array ( array (
@ -887,62 +761,165 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'possibleTypes' => NULL, 'possibleTypes' => NULL,
), ),
array ( array (
'kind' => 'ENUM', 'kind' => 'OBJECT',
'name' => '__TypeKind', 'name' => '__Directive',
'fields' => NULL, 'fields' =>
'inputFields' => NULL,
'interfaces' => NULL,
'enumValues' =>
array ( array (
0 => 0 =>
array ( array (
'name' => 'SCALAR', 'name' => 'name',
'args' =>
array (
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'SCALAR',
'name' => 'String',
),
),
'isDeprecated' => false, 'isDeprecated' => false,
'deprecationReason' => NULL, 'deprecationReason' => NULL,
), ),
1 => 1 =>
array ( array (
'name' => 'OBJECT', 'name' => 'description',
'args' =>
array (
),
'type' =>
array (
'kind' => 'SCALAR',
'name' => 'String',
),
'isDeprecated' => false, 'isDeprecated' => false,
'deprecationReason' => NULL, 'deprecationReason' => NULL,
), ),
2 => 2 =>
array ( array (
'name' => 'INTERFACE', 'name' => 'locations',
'args' =>
array (
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'LIST',
'name' => NULL,
'ofType' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'ENUM',
'name' => '__DirectiveLocation',
),
),
),
),
'isDeprecated' => false, 'isDeprecated' => false,
'deprecationReason' => NULL, 'deprecationReason' => NULL,
), ),
3 => 3 =>
array ( array (
'name' => 'UNION', 'name' => 'args',
'args' =>
array (
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'LIST',
'name' => NULL,
'ofType' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'OBJECT',
'name' => '__InputValue',
),
),
),
),
'isDeprecated' => false, 'isDeprecated' => false,
'deprecationReason' => NULL, 'deprecationReason' => NULL,
), ),
4 => 4 =>
array ( array (
'name' => 'ENUM', 'name' => 'onOperation',
'isDeprecated' => false, 'args' =>
'deprecationReason' => NULL, array (
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'SCALAR',
'name' => 'Boolean',
),
),
'isDeprecated' => true,
'deprecationReason' => 'Use `locations`.',
), ),
5 => 5 =>
array ( array (
'name' => 'INPUT_OBJECT', 'name' => 'onFragment',
'isDeprecated' => false, 'args' =>
'deprecationReason' => NULL, array (
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'SCALAR',
'name' => 'Boolean',
),
),
'isDeprecated' => true,
'deprecationReason' => 'Use `locations`.',
), ),
6 => 6 =>
array ( array (
'name' => 'LIST', 'name' => 'onField',
'isDeprecated' => false, 'args' =>
'deprecationReason' => NULL, array (
), ),
7 => 'type' =>
array ( array (
'name' => 'NON_NULL', 'kind' => 'NON_NULL',
'isDeprecated' => false, 'name' => NULL,
'deprecationReason' => NULL, 'ofType' =>
array (
'kind' => 'SCALAR',
'name' => 'Boolean',
),
),
'isDeprecated' => true,
'deprecationReason' => 'Use `locations`.',
), ),
), ),
'inputFields' => NULL,
'interfaces' =>
array (
),
'enumValues' => NULL,
'possibleTypes' => NULL, 'possibleTypes' => NULL,
), ),
array ( array (
@ -998,6 +975,29 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
), ),
'possibleTypes' => NULL, 'possibleTypes' => NULL,
), ),
array (
'kind' => 'OBJECT',
'name' => 'QueryRoot',
'inputFields' => NULL,
'interfaces' =>
array (
),
'enumValues' => NULL,
'possibleTypes' => NULL,
'fields' => array (
array (
'name' => 'a',
'args' => array(),
'type' => array(
'kind' => 'SCALAR',
'name' => 'String',
'ofType' => null
),
'isDeprecated' => false,
'deprecationReason' => null,
)
)
),
), ),
'directives' => 'directives' =>
array ( array (

View File

@ -339,4 +339,17 @@ class TypeLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertSame($withoutLoader->getType('BlogStory'), $withLoader->getType('BlogStory')); $this->assertSame($withoutLoader->getType('BlogStory'), $withLoader->getType('BlogStory'));
$this->assertSame($withoutLoader->getDirectives(), $withLoader->getDirectives()); $this->assertSame($withoutLoader->getDirectives(), $withLoader->getDirectives());
} }
public function testSkipsLoaderForInternalTypes()
{
$schema = new Schema([
'query' => $this->query,
'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader
]);
$type = $schema->getType('ID');
$this->assertSame(Type::id(), $type);
$this->assertEquals([], $this->calls);
}
} }