mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-06 07:49:24 +03:00
Type loader tests
This commit is contained in:
parent
20f8cab943
commit
884a8967f3
@ -185,44 +185,51 @@ class Schema
|
|||||||
*/
|
*/
|
||||||
public function getType($name)
|
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()
|
private function collectAllTypes()
|
||||||
{
|
{
|
||||||
$initialTypes = array_merge(
|
$initialTypes = $this->resolvedTypes;
|
||||||
array_values($this->resolvedTypes),
|
|
||||||
[Introspection::_schema()]
|
|
||||||
);
|
|
||||||
|
|
||||||
$typeMap = [];
|
$typeMap = [];
|
||||||
foreach ($initialTypes as $type) {
|
foreach ($initialTypes as $type) {
|
||||||
$typeMap = TypeInfo::extractTypes($type, $typeMap);
|
$typeMap = TypeInfo::extractTypes($type, $typeMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->config->types) {
|
||||||
$types = $this->config->types;
|
$types = $this->config->types;
|
||||||
|
|
||||||
if (is_callable($types)) {
|
if (is_callable($types)) {
|
||||||
$types = $types();
|
$types = $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 (!empty($types)) {
|
if (!is_array($types) && !$types instanceof \Traversable) {
|
||||||
foreach ($types as $type) {
|
throw new InvariantViolation(sprintf(
|
||||||
Utils::invariant(
|
'Schema types callable must return array or instance of Traversable but got: %s',
|
||||||
$type instanceof Type,
|
|
||||||
'Each entry of schema types must be instance of GraphQL\Type\Definition\Type but got: %s',
|
|
||||||
Utils::getVariableType($types)
|
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);
|
$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)
|
public function getPossibleTypes(AbstractType $abstractType)
|
||||||
{
|
{
|
||||||
$possibleTypeMap = $this->getPossibleTypeMap();
|
$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 -
|
* @param $typeName
|
||||||
* will load it first.
|
|
||||||
*
|
|
||||||
* @param $typeOrName
|
|
||||||
* @return Type
|
* @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)
|
private function loadType($typeName)
|
||||||
{
|
{
|
||||||
$typeLoader = $this->config->typeLoader;
|
$typeLoader = $this->config->typeLoader;
|
||||||
@ -290,7 +280,18 @@ class Schema
|
|||||||
}
|
}
|
||||||
|
|
||||||
$type = $typeLoader($typeName);
|
$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;
|
return $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
319
tests/Type/TypeLoaderTest.php
Normal file
319
tests/Type/TypeLoaderTest.php
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user