Type loader tests

This commit is contained in:
Vladimir Razuvaev 2017-08-14 19:42:01 +07:00
parent 20f8cab943
commit 884a8967f3
2 changed files with 361 additions and 41 deletions

View File

@ -185,44 +185,51 @@ class Schema
*/
public function getType($name)
{
return $this->resolveType($name);
if (!isset($this->resolvedTypes[$name])) {
$this->resolvedTypes[$name] = $this->loadType($name);
}
return $this->resolvedTypes[$name];
}
/**
* @return array
*/
private function collectAllTypes()
{
$initialTypes = array_merge(
array_values($this->resolvedTypes),
[Introspection::_schema()]
);
$initialTypes = $this->resolvedTypes;
$typeMap = [];
foreach ($initialTypes as $type) {
$typeMap = TypeInfo::extractTypes($type, $typeMap);
}
$types = $this->config->types;
if (is_callable($types)) {
$types = $types();
if ($this->config->types) {
$types = $this->config->types;
Utils::invariant(
is_array($types) || $types instanceof \Traversable,
'Schema types callable must return array or instance of Traversable but got: %s',
Utils::getVariableType($types)
);
}
if (is_callable($types)) {
$types = $types();
}
if (!empty($types)) {
foreach ($types as $type) {
Utils::invariant(
$type instanceof Type,
'Each entry of schema types must be instance of GraphQL\Type\Definition\Type but got: %s',
if (!is_array($types) && !$types instanceof \Traversable) {
throw new InvariantViolation(sprintf(
'Schema types callable must return array or instance of Traversable but got: %s',
Utils::getVariableType($types)
);
));
}
foreach ($types as $index => $type) {
if (!$type instanceof Type) {
throw new InvariantViolation(
'Each entry of schema types must be instance of GraphQL\Type\Definition\Type but entry at %s is %s',
$index,
Utils::printSafe($type)
);
}
$typeMap = TypeInfo::extractTypes($type, $typeMap);
}
}
return $typeMap + Type::getInternalTypes();
return $typeMap + Type::getInternalTypes() + Introspection::getTypes();
}
/**
@ -235,7 +242,7 @@ class Schema
public function getPossibleTypes(AbstractType $abstractType)
{
$possibleTypeMap = $this->getPossibleTypeMap();
return array_values($possibleTypeMap[$abstractType->name]);
return isset($possibleTypeMap[$abstractType->name]) ? array_values($possibleTypeMap[$abstractType->name]) : [];
}
/**
@ -261,26 +268,9 @@ class Schema
}
/**
* Accepts name of type or type instance and returns type instance. If type with given name is not loaded yet -
* will load it first.
*
* @param $typeOrName
* @param $typeName
* @return Type
*/
public function resolveType($typeOrName)
{
if ($typeOrName instanceof Type) {
if ($typeOrName->name && !isset($this->resolvedTypes[$typeOrName->name])) {
$this->resolvedTypes[$typeOrName->name] = $typeOrName;
}
return $typeOrName;
}
if (!isset($this->resolvedTypes[$typeOrName])) {
$this->resolvedTypes[$typeOrName] = $this->loadType($typeOrName);
}
return $this->resolvedTypes[$typeOrName];
}
private function loadType($typeName)
{
$typeLoader = $this->config->typeLoader;
@ -290,7 +280,18 @@ class Schema
}
$type = $typeLoader($typeName);
// TODO: validate returned value
if (!$type instanceof Type) {
throw new InvariantViolation(
"Type loader is expected to return valid type \"$typeName\", but it returned " . Utils::printSafe($type)
);
}
if ($type->name !== $typeName) {
throw new InvariantViolation(
"Type loader is expected to return type \"$typeName\", but it returned \"{$type->name}\""
);
}
return $type;
}

View File

@ -0,0 +1,319 @@
<?php
namespace GraphQL\Tests\Type;
use GraphQL\Error\InvariantViolation;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
class TypeLoaderTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ObjectType
*/
private $query;
/**
* @var ObjectType
*/
private $mutation;
/**
* @var InterfaceType
*/
private $node;
/**
* @var InterfaceType
*/
private $content;
/**
* @var ObjectType
*/
private $blogStory;
/**
* @var ObjectType
*/
private $postStoryMutation;
/**
* @var InputObjectType
*/
private $postStoryMutationInput;
/**
* @var callable
*/
private $typeLoader;
/**
* @var array
*/
private $calls;
public function setUp()
{
$this->calls = [];
$this->node = new InterfaceType([
'name' => 'Node',
'fields' => function() {
$this->calls[] = 'Node.fields';
return [
'id' => Type::string()
];
},
'resolveType' => function() {}
]);
$this->content = new InterfaceType([
'name' => 'Content',
'fields' => function() {
$this->calls[] = 'Content.fields';
return [
'title' => Type::string(),
'body' => Type::string(),
];
},
'resolveType' => function() {}
]);
$this->blogStory = new ObjectType([
'name' => 'BlogStory',
'interfaces' => [
$this->node,
$this->content
],
'fields' => function() {
$this->calls[] = 'BlogStory.fields';
return [
$this->node->getField('id'),
$this->content->getField('title'),
$this->content->getField('body'),
];
},
]);
$this->query = new ObjectType([
'name' => 'Query',
'fields' => function() {
$this->calls[] = 'Query.fields';
return [
'latestContent' => $this->content,
'node' => $this->node,
];
}
]);
$this->mutation = new ObjectType([
'name' => 'Mutation',
'fields' => function() {
$this->calls[] = 'Mutation.fields';
return [
'postStory' => [
'type' => $this->postStoryMutation,
'args' => [
'input' => Type::nonNull($this->postStoryMutationInput),
'clientRequestId' => Type::string()
]
]
];
}
]);
$this->postStoryMutation = new ObjectType([
'name' => 'PostStoryMutation',
'fields' => [
'story' => $this->blogStory
]
]);
$this->postStoryMutationInput = new InputObjectType([
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id()
]
]);
$this->typeLoader = function($name) {
$this->calls[] = $name;
$prop = lcfirst($name);
return isset($this->{$prop}) ? $this->{$prop} : null;
};
}
public function testSchemaAcceptsTypeLoader()
{
new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['a' => Type::string()]
]),
'typeLoader' => function() {}
]);
}
public function testSchemaRejectsNonCallableTypeLoader()
{
$this->setExpectedException(
InvariantViolation::class,
'Schema type loader must be callable if provided but got: array(0)'
);
new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['a' => Type::string()]
]),
'typeLoader' => []
]);
}
public function testWorksWithoutTypeLoader()
{
$schema = new Schema([
'query' => $this->query,
'mutation' => $this->mutation,
'types' => [$this->blogStory]
]);
$expected = [
'Query.fields',
'Content.fields',
'Node.fields',
'Mutation.fields',
'BlogStory.fields',
];
$this->assertEquals($expected, $this->calls);
$this->assertSame($this->query, $schema->getType('Query'));
$this->assertSame($this->mutation, $schema->getType('Mutation'));
$this->assertSame($this->node, $schema->getType('Node'));
$this->assertSame($this->content, $schema->getType('Content'));
$this->assertSame($this->blogStory, $schema->getType('BlogStory'));
$this->assertSame($this->postStoryMutation, $schema->getType('PostStoryMutation'));
$this->assertSame($this->postStoryMutationInput, $schema->getType('PostStoryMutationInput'));
$expectedTypeMap = [
'Query' => $this->query,
'Mutation' => $this->mutation,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'BlogStory' => $this->blogStory,
'PostStoryMutationInput' => $this->postStoryMutationInput,
];
$this->assertArraySubset($expectedTypeMap, $schema->getTypeMap());
}
public function testWorksWithTypeLoader()
{
$schema = new Schema([
'query' => $this->query,
'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader
]);
$this->assertEquals([], $this->calls);
$node = $schema->getType('Node');
$this->assertSame($this->node, $node);
$this->assertEquals(['Node'], $this->calls);
$content = $schema->getType('Content');
$this->assertSame($this->content, $content);
$this->assertEquals(['Node', 'Content'], $this->calls);
$input = $schema->getType('PostStoryMutationInput');
$this->assertSame($this->postStoryMutationInput, $input);
$this->assertEquals(['Node', 'Content', 'PostStoryMutationInput'], $this->calls);
}
public function testOnlyCallsLoaderOnce()
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => $this->typeLoader
]);
$schema->getType('Node');
$this->assertEquals(['Node'], $this->calls);
$schema->getType('Node');
$this->assertEquals(['Node'], $this->calls);
}
public function testFailsOnNonExistentType()
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => function() {}
]);
$this->setExpectedException(
InvariantViolation::class,
'Type loader is expected to return valid type "NonExistingType", but it returned null'
);
$schema->getType('NonExistingType');
}
public function testFailsOnNonType()
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => function() {
return new \stdClass();
}
]);
$this->setExpectedException(
InvariantViolation::class,
'Type loader is expected to return valid type "Node", but it returned instance of stdClass'
);
$schema->getType('Node');
}
public function testFailsOnInvalidLoad()
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => function() {
return $this->content;
}
]);
$this->setExpectedException(
InvariantViolation::class,
'Type loader is expected to return type "Node", but it returned "Content"'
);
$schema->getType('Node');
}
public function testPassesThroughAnExceptionInLoader()
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => function() {
throw new \Exception("This is the exception we are looking for");
}
]);
$this->setExpectedException(
\Exception::class,
'This is the exception we are looking for'
);
$schema->getType('Node');
}
}