Merge branch 'master' into patch-1

This commit is contained in:
Benedikt Franke 2018-09-03 21:25:36 +02:00 committed by GitHub
commit e7513e356a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
135 changed files with 14159 additions and 11797 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
.phpcs-cache
composer.phar
composer.lock
phpcs.xml
phpstan.neon
vendor/

View File

@ -2,26 +2,19 @@ dist: trusty
language: php
php:
- 7.1
- 7.2
- nightly
matrix:
allow_failures:
- php: nightly
- 7.1
- 7.2
- nightly
cache:
directories:
- $HOME/.composer/cache
directories:
- $HOME/.composer/cache
before_install:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
- travis_retry composer self-update
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
- travis_retry composer self-update
install:
- composer require react/promise:2.*
- composer require psr/http-message:1.*
- travis_retry composer update --prefer-dist
install: travis_retry composer update --prefer-dist
script: ./vendor/bin/phpunit --group default,ReactPromise
@ -45,21 +38,16 @@ jobs:
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover clover.xml
- stage: Pull request coding standard
if: type = pull_request
- stage: Code Quality
php: 7.1
env: CODING_STANDARD
install: travis_retry composer install --prefer-dist
script:
- |
if [ $TRAVIS_BRANCH != "master" ]; then
git remote set-branches --add origin $TRAVIS_BRANCH;
git fetch origin $TRAVIS_BRANCH;
fi
- git merge-base origin/$TRAVIS_BRANCH $TRAVIS_PULL_REQUEST_SHA || git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge --unshallow
- wget https://github.com/diff-sniffer/git/releases/download/0.1.0/git-phpcs.phar
- php git-phpcs.phar origin/$TRAVIS_BRANCH...$TRAVIS_PULL_REQUEST_SHA
# - stage: Coding standard
# if: NOT type = pull_request
# php: 7.1
# install: travis_retry composer install --prefer-dist
# script:
# - ./vendor/bin/phpcs
- ./vendor/bin/phpcs
- stage: Code Quality
php: 7.1
env: STATIC_ANALYSIS
install: travis_retry composer install --prefer-dist
script: composer static-analysis

View File

@ -25,6 +25,8 @@ composer install
./vendor/bin/phpunit
```
Some tests have annotation `@see it('<description>')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description.
## Coding Standard
Coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). To run the inspection:

View File

@ -15,8 +15,11 @@
},
"require-dev": {
"doctrine/coding-standard": "^4.0",
"phpstan/phpstan": "^0.10.3",
"phpstan/phpstan-phpunit": "^0.10.0",
"phpunit/phpunit": "^7.2",
"psr/http-message": "^1.0"
"psr/http-message": "^1.0",
"react/promise": "2.*"
},
"config": {
"preferred-install": "dist",
@ -39,6 +42,7 @@
"psr/http-message": "To use standard GraphQL server"
},
"scripts": {
"lint" : "phpcs"
"lint" : "phpcs",
"static-analysis": "phpstan analyse --ansi -l 1 -c phpstan.neon.dist src tests"
}
}

View File

@ -4,7 +4,7 @@ first learn about GraphQL on [the official website](http://graphql.org/learn/).
# Installation
Using [composer](https://getcomposer.org/doc/00-intro.md), simply run:
Using [composer](https://getcomposer.org/doc/00-intro.md), run:
```sh
composer require webonyx/graphql-php

3
phpstan.neon.dist Normal file
View File

@ -0,0 +1,3 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon

View File

@ -1,23 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
class Deferred
{
/**
* @var \SplQueue
*/
/** @var \SplQueue */
private static $queue;
/**
* @var callable
*/
/** @var callable */
private $callback;
/**
* @var SyncPromise
*/
/** @var SyncPromise */
public $promise;
public static function getQueue()
@ -28,7 +25,7 @@ class Deferred
public static function runQueue()
{
$q = self::$queue;
while ($q && !$q->isEmpty()) {
while ($q && ! $q->isEmpty()) {
/** @var self $dfd */
$dfd = $q->dequeue();
$dfd->run();
@ -38,7 +35,7 @@ class Deferred
public function __construct(callable $callback)
{
$this->callback = $callback;
$this->promise = new SyncPromise();
$this->promise = new SyncPromise();
self::getQueue()->enqueue($this);
}
@ -47,7 +44,7 @@ class Deferred
return $this->promise->then($onFulfilled, $onRejected);
}
private function run()
public function run() : void
{
try {
$cb = $this->callback;

View File

@ -35,6 +35,7 @@ use GraphQL\Utils\TypeInfo;
use GraphQL\Utils\Utils;
use function array_keys;
use function array_merge;
use function array_reduce;
use function array_values;
use function get_class;
use function is_array;
@ -68,7 +69,7 @@ class Executor
self::$UNDEFINED = Utils::undefined();
}
$this->exeContext = $context;
$this->exeContext = $context;
$this->subFieldCache = new \SplObjectStorage();
}
@ -285,7 +286,7 @@ class Executor
// field and its descendants will be omitted, and sibling fields will still
// be executed. An execution which encounters errors will still result in a
// resolved Promise.
$data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue);
$data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue);
$result = $this->buildResponse($data);
// Note: we deviate here from the reference implementation a bit by always returning promise
@ -995,8 +996,7 @@ class Executor
* If the callback does not return a Promise, then this function will also not
* return a Promise.
*
* @param mixed[] $values
* @param \Closure $callback
* @param mixed[] $values
* @param Promise|mixed|null $initialValue
* @return mixed[]
*/
@ -1291,23 +1291,18 @@ class Executor
return $this->executeFields($returnType, $result, $path, $subFieldNodes);
}
/**
* @param ObjectType $returnType
* @param $fieldNodes
* @return ArrayObject
*/
private function collectSubFields(ObjectType $returnType, $fieldNodes): ArrayObject
private function collectSubFields(ObjectType $returnType, $fieldNodes) : ArrayObject
{
if (!isset($this->subFieldCache[$returnType])) {
if (! isset($this->subFieldCache[$returnType])) {
$this->subFieldCache[$returnType] = new \SplObjectStorage();
}
if (!isset($this->subFieldCache[$returnType][$fieldNodes])) {
if (! isset($this->subFieldCache[$returnType][$fieldNodes])) {
// Collect sub-fields to execute to complete this value.
$subFieldNodes = new \ArrayObject();
$subFieldNodes = new \ArrayObject();
$visitedFragmentNames = new \ArrayObject();
foreach ($fieldNodes as $fieldNode) {
if (!isset($fieldNode->selectionSet)) {
if (! isset($fieldNode->selectionSet)) {
continue;
}

View File

@ -6,6 +6,7 @@ namespace GraphQL\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use GraphQL\Utils\Utils;
use React\Promise\Promise as ReactPromise;
/**
* Convenience wrapper for promises represented by Promise Adapter

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL;
use GraphQL\Error\Error;
@ -6,15 +9,19 @@ use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema as SchemaType;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\ValidationRule;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\ValidationRule;
use function array_values;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* This is the primary facade for fulfilling GraphQL operations.
@ -58,32 +65,35 @@ class GraphQL
* queries which are validated before persisting and assumed valid during execution)
*
* @api
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @param array $validationRules
*
* @return ExecutionResult
* @param mixed $rootValue
* @param mixed $context
* @param mixed[]|null $variableValues
* @param ValidationRule[] $validationRules
*/
public static function executeQuery(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$context = null,
$variableValues = null,
$operationName = null,
callable $fieldResolver = null,
array $validationRules = null
)
{
?string $operationName = null,
?callable $fieldResolver = null,
?array $validationRules = null
) : ExecutionResult {
$promiseAdapter = new SyncPromiseAdapter();
$promise = self::promiseToExecute($promiseAdapter, $schema, $source, $rootValue, $context,
$variableValues, $operationName, $fieldResolver, $validationRules);
$promise = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver,
$validationRules
);
return $promiseAdapter->wait($promise);
}
@ -93,30 +103,23 @@ class GraphQL
* Useful for Async PHP platforms.
*
* @api
* @param PromiseAdapter $promiseAdapter
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @param array $validationRules
*
* @return Promise
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param mixed[]|null $variableValues
* @param ValidationRule[]|null $validationRules
*/
public static function promiseToExecute(
PromiseAdapter $promiseAdapter,
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$context = null,
$variableValues = null,
$operationName = null,
callable $fieldResolver = null,
array $validationRules = null
)
{
?string $operationName = null,
?callable $fieldResolver = null,
?array $validationRules = null
) : Promise {
try {
if ($source instanceof DocumentNode) {
$documentNode = $source;
@ -125,36 +128,38 @@ class GraphQL
}
// FIXME
if (!empty($validationRules)) {
foreach ($validationRules as $rule) {
if ($rule instanceof QueryComplexity) {
$rule->setRawVariableValues($variableValues);
}
}
} else {
if (empty($validationRules)) {
/** @var QueryComplexity $queryComplexity */
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
$queryComplexity->setRawVariableValues($variableValues);
} else {
foreach ($validationRules as $rule) {
if (! ($rule instanceof QueryComplexity)) {
continue;
}
$rule->setRawVariableValues($variableValues);
}
}
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
if (!empty($validationErrors)) {
if (! empty($validationErrors)) {
return $promiseAdapter->createFulfilled(
new ExecutionResult(null, $validationErrors)
);
} else {
return Executor::promiseToExecute(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver
);
}
return Executor::promiseToExecute(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver
);
} catch (Error $e) {
return $promiseAdapter->createFulfilled(
new ExecutionResult(null, [$e])
@ -165,29 +170,28 @@ class GraphQL
/**
* @deprecated Use executeQuery()->toArray() instead
*
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $contextValue
* @param array|null $variableValues
* @param string|null $operationName
* @return Promise|array
* @param mixed $rootValue
* @param mixed $contextValue
* @param mixed[]|null $variableValues
* @return Promise|mixed[]
*/
public static function execute(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null
)
{
?string $operationName = null
) {
trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
E_USER_DEPRECATED
);
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(),
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
@ -199,40 +203,40 @@ class GraphQL
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result)->toArray();
} else {
$result = $result->then(function(ExecutionResult $r) {
$result = $result->then(function (ExecutionResult $r) {
return $r->toArray();
});
}
return $result;
}
/**
* @deprecated renamed to executeQuery()
*
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $contextValue
* @param array|null $variableValues
* @param string|null $operationName
* @param mixed $rootValue
* @param mixed $contextValue
* @param mixed[]|null $variableValues
*
* @return ExecutionResult|Promise
*/
public static function executeAndReturnResult(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null
)
{
?string $operationName = null
) {
trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
E_USER_DEPRECATED
);
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(),
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
@ -240,9 +244,11 @@ class GraphQL
$variableValues,
$operationName
);
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result);
}
return $result;
}
@ -252,7 +258,7 @@ class GraphQL
* @api
* @return Directive[]
*/
public static function getStandardDirectives()
public static function getStandardDirectives() : array
{
return array_values(Directive::getInternalDirectives());
}
@ -263,7 +269,7 @@ class GraphQL
* @api
* @return Type[]
*/
public static function getStandardTypes()
public static function getStandardTypes() : array
{
return array_values(Type::getInternalTypes());
}
@ -274,23 +280,17 @@ class GraphQL
* @api
* @return ValidationRule[]
*/
public static function getStandardValidationRules()
public static function getStandardValidationRules() : array
{
return array_values(DocumentValidator::defaultRules());
}
/**
* @param callable $fn
*/
public static function setDefaultFieldResolver(callable $fn)
public static function setDefaultFieldResolver(callable $fn) : void
{
Executor::setDefaultFieldResolver($fn);
}
/**
* @param PromiseAdapter|null $promiseAdapter
*/
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) : void
{
Executor::setPromiseAdapter($promiseAdapter);
}
@ -301,7 +301,7 @@ class GraphQL
* @deprecated Renamed to getStandardDirectives
* @return Directive[]
*/
public static function getInternalDirectives()
public static function getInternalDirectives() : array
{
return self::getStandardDirectives();
}

View File

@ -263,7 +263,8 @@ class Visitor
$visitFn = self::getVisitFn($visitor, $node->kind, $isLeaving);
if ($visitFn) {
$result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
$result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
$editValue = null;
if ($result !== null) {
if ($result instanceof VisitorOperation) {

View File

@ -1,6 +1,12 @@
<?php
declare(strict_types=1);
namespace GraphQL;
use function trigger_error;
use const E_USER_DEPRECATED;
trigger_error(
'GraphQL\Schema is moved to GraphQL\Type\Schema',
E_USER_DEPRECATED

View File

@ -4,11 +4,11 @@ declare(strict_types=1);
namespace GraphQL\Server;
use const CASE_LOWER;
use function array_change_key_case;
use function is_string;
use function json_decode;
use function json_last_error;
use const CASE_LOWER;
/**
* Structure representing parsed HTTP parameters for GraphQL operation

View File

@ -8,7 +8,7 @@ use GraphQL\Error\FormattedError;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Utils;
use GraphQL\Utils\Utils;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;

View File

@ -21,7 +21,7 @@ class InputObjectField
/** @var string|null */
public $description;
/** @var callback|InputType */
/** @var callable|InputType */
public $type;
/** @var InputValueDefinitionNode|null */

View File

@ -12,7 +12,6 @@ use function is_array;
use function is_callable;
use function is_string;
use function sprintf;
use function spritnf;
/**
* Class InputObjectType
@ -70,7 +69,7 @@ class InputObjectType extends Type implements InputType, NamedType
if (! is_array($fields)) {
throw new InvariantViolation(
spritnf('%s fields must be an array or a callable which returns such an array.', $this->name)
sprintf('%s fields must be an array or a callable which returns such an array.', $this->name)
);
}

View File

@ -65,7 +65,7 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
}
$int = intval($num);
// int cast with == used for performance reasons
// @codingStandardsIgnoreLine
// phpcs:ignore
if ($int != $num) {
throw new Error(
'Int cannot represent non-integer value: ' .

View File

@ -255,7 +255,7 @@ class AST
}
if (is_float($serialized)) {
// int cast with == used for performance reasons
// @codingStandardsIgnoreLine
// phpcs:ignore
if ((int) $serialized == $serialized) {
return new IntValueNode(['value' => $serialized]);
}

View File

@ -7,7 +7,9 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Validator\ValidationContext;
use function count;
@ -59,7 +61,9 @@ class KnownDirectives extends ValidationRule
}
/**
* @param (Node|NodeList)[] $ancestors
* @param Node[]|NodeList[] $ancestors The type is actually (Node|NodeList)[] but this PSR-5 syntax is so far not supported by most of the tools
*
* @return string
*/
private function getDirectiveLocationForASTPath(array $ancestors)
{

View File

@ -44,10 +44,10 @@ class VariablesDefaultValueAllowed extends ValidationRule
return Visitor::skipNode();
},
NodeKind::SELECTION_SET => function (SelectionSetNode $node) use ($context) {
NodeKind::SELECTION_SET => function (SelectionSetNode $node) {
return Visitor::skipNode();
},
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) {
return Visitor::skipNode();
},
];

View File

@ -1,79 +1,83 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Error\UserError;
use GraphQL\Error\Warning;
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Human;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
/**
* DESCRIBE: Execute: Handles execution of abstract types with promises
*/
class AbstractPromiseTest extends TestCase
{
// DESCRIBE: Execute: Handles execution of abstract types with promises
/**
* @see it('isTypeOf used to resolve runtime type for Interface')
*/
public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'fields' => [
'name' => [ 'type' => Type::string() ]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [ $PetType ],
'isTypeOf' => function($obj) {
return new Deferred(function() use ($obj) {
'name' => 'Dog',
'interfaces' => [$PetType],
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Dog;
});
},
'fields' => [
'name' => [ 'type' => Type::string() ],
'woofs' => [ 'type' => Type::boolean() ],
]
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'interfaces' => [ $PetType ],
'isTypeOf' => function($obj) {
return new Deferred(function() use ($obj) {
'name' => 'Cat',
'interfaces' => [$PetType],
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Cat;
});
},
'fields' => [
'name' => [ 'type' => Type::string() ],
'meows' => [ 'type' => Type::boolean() ],
]
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'resolve' => function() {
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [ $CatType, $DogType ]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -93,10 +97,10 @@ class AbstractPromiseTest extends TestCase
$expected = [
'data' => [
'pets' => [
[ 'name' => 'Odie', 'woofs' => true ],
[ 'name' => 'Garfield', 'meows' => false ]
]
]
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
],
],
];
$this->assertEquals($expected, $result);
@ -107,58 +111,57 @@ class AbstractPromiseTest extends TestCase
*/
public function testIsTypeOfCanBeRejected() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'isTypeOf' => function () {
'isTypeOf' => function () {
return new Deferred(function () {
throw new UserError('We are testing this error');
});
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'isTypeOf' => function ($obj) {
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Cat;
});
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -176,21 +179,21 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray();
$expected = [
'data' => [
'pets' => [null, null]
'data' => [
'pets' => [null, null],
],
'errors' => [
[
'message' => 'We are testing this error',
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 0],
'path' => ['pets', 0],
],
[
'message' => 'We are testing this error',
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 1]
]
]
'path' => ['pets', 1],
],
],
];
$this->assertArraySubset($expected, $result);
@ -201,50 +204,49 @@ class AbstractPromiseTest extends TestCase
*/
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
{
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Dog;
});
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'isTypeOf' => function ($obj) {
return new Deferred(function () use ($obj) {
return $obj instanceof Cat;
});
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
'name' => 'Pet',
'types' => [$DogType, $CatType]
'name' => 'Pet',
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -266,9 +268,9 @@ class AbstractPromiseTest extends TestCase
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
]
['name' => 'Garfield', 'meows' => false],
],
],
];
$this->assertEquals($expected, $result);
@ -280,7 +282,7 @@ class AbstractPromiseTest extends TestCase
public function testResolveTypeOnInterfaceYieldsUsefulError() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
if ($obj instanceof Dog) {
@ -292,58 +294,59 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
});
},
'fields' => [
'name' => ['type' => Type::string()]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$HumanType = new ObjectType([
'name' => 'Human',
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return new Deferred(function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
});
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -361,20 +364,20 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
$expected = [
'data' => [
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
null
]
null,
],
],
'errors' => [
[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2]
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2],
],
]
],
];
$this->assertArraySubset($expected, $result);
@ -385,32 +388,31 @@ class AbstractPromiseTest extends TestCase
*/
public function testResolveTypeOnUnionYieldsUsefulError() : void
{
$HumanType = new ObjectType([
'name' => 'Human',
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'fields' => [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'fields' => [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) {
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
if ($obj instanceof Dog) {
@ -422,28 +424,29 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
});
},
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -462,20 +465,20 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
$expected = [
'data' => [
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
null
]
null,
],
],
'errors' => [
[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2]
]
]
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2],
],
],
];
$this->assertArraySubset($expected, $result);
@ -487,7 +490,7 @@ class AbstractPromiseTest extends TestCase
public function testResolveTypeAllowsResolvingWithTypeName() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) {
return new Deferred(function () use ($obj) {
if ($obj instanceof Dog) {
@ -496,49 +499,49 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Cat) {
return 'Cat';
}
return null;
});
},
'fields' => [
'name' => ['type' => Type::string()]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -560,8 +563,8 @@ class AbstractPromiseTest extends TestCase
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
]
]
],
],
];
$this->assertEquals($expected, $result);
}
@ -571,53 +574,52 @@ class AbstractPromiseTest extends TestCase
*/
public function testResolveTypeCanBeCaught() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function () {
return new Deferred(function () {
throw new UserError('We are testing this error');
});
},
'fields' => [
'name' => ['type' => Type::string()]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [$CatType, $DogType]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -635,21 +637,21 @@ class AbstractPromiseTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray();
$expected = [
'data' => [
'pets' => [null, null]
'data' => [
'pets' => [null, null],
],
'errors' => [
[
'message' => 'We are testing this error',
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 0]
'path' => ['pets', 0],
],
[
'message' => 'We are testing this error',
'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 1]
]
]
'path' => ['pets', 1],
],
],
];
$this->assertArraySubset($expected, $result);

View File

@ -1,23 +1,28 @@
<?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\GraphQL;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Human;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
/**
* Execute: Handles execution of abstract types
*/
class AbstractTest extends TestCase
{
// Execute: Handles execution of abstract types
/**
* @see it('isTypeOf used to resolve runtime type for Interface')
*/
@ -25,48 +30,50 @@ class AbstractTest extends TestCase
{
// isTypeOf used to resolve runtime type for Interface
$petType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
// Added to interface type when defined
$dogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
]
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]);
$catType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$petType],
'isTypeOf' => function ($obj) {
'isTypeOf' => function ($obj) {
return $obj instanceof Cat;
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($petType),
'type' => Type::listOf($petType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
}
]
]
},
],
],
]),
'types' => [$catType, $dogType]
'types' => [$catType, $dogType],
]);
$query = '{
@ -84,8 +91,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
['name' => 'Garfield', 'meows' => false],
],
]);
$result = Executor::execute($schema, Parser::parse($query));
@ -98,42 +105,44 @@ class AbstractTest extends TestCase
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
{
$dogType = new ObjectType([
'name' => 'Dog',
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
]
'name' => 'Dog',
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]);
$catType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'isTypeOf' => function ($obj) {
return $obj instanceof Cat;
},
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$petType = new UnionType([
'name' => 'Pet',
'types' => [$dogType, $catType]
'name' => 'Pet',
'types' => [$dogType, $catType],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($petType),
'resolve' => function() {
return [ new Dog('Odie', true), new Cat('Garfield', false) ];
}
]
]
])
'type' => Type::listOf($petType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
},
],
],
]),
]);
$query = '{
@ -151,8 +160,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
['name' => 'Garfield', 'meows' => false],
],
]);
$this->assertEquals($expected, Executor::execute($schema, Parser::parse($query)));
@ -161,14 +170,14 @@ class AbstractTest extends TestCase
/**
* @see it('resolveType on Interface yields useful error')
*/
function testResolveTypeOnInterfaceYieldsUsefulError()
public function testResolveTypeOnInterfaceYieldsUsefulError() : void
{
$DogType = null;
$CatType = null;
$DogType = null;
$CatType = null;
$HumanType = null;
$PetType = new InterfaceType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
if ($obj instanceof Dog) {
return $DogType;
@ -179,58 +188,58 @@ class AbstractTest extends TestCase
if ($obj instanceof Human) {
return $HumanType;
}
return null;
},
'fields' => [
'name' => ['type' => Type::string()]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$HumanType = new ObjectType([
'name' => 'Human',
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
},
],
],
]),
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$query = '{
pets {
name
@ -244,20 +253,21 @@ class AbstractTest extends TestCase
}';
$expected = [
'data' => [
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
null
]
null,
],
],
'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2]
]]
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2],
],
],
];
$actual = GraphQL::executeQuery($schema, $query)->toArray(true);
$actual = GraphQL::executeQuery($schema, $query)->toArray(true);
$this->assertArraySubset($expected, $actual);
}
@ -268,30 +278,30 @@ class AbstractTest extends TestCase
public function testResolveTypeOnUnionYieldsUsefulError() : void
{
$HumanType = new ObjectType([
'name' => 'Human',
'name' => 'Human',
'fields' => [
'name' => ['type' => Type::string()],
]
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'fields' => [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
]
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'fields' => [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
]
],
]);
$PetType = new UnionType([
'name' => 'Pet',
'name' => 'Pet',
'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) {
if ($obj instanceof Dog) {
return $DogType;
@ -303,25 +313,25 @@ class AbstractTest extends TestCase
return $HumanType;
}
},
'types' => [$DogType, $CatType]
'types' => [$DogType, $CatType],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false),
new Human('Jon')
new Human('Jon'),
];
}
]
]
])
},
],
],
]),
]);
$query = '{
@ -337,22 +347,27 @@ class AbstractTest extends TestCase
}
}';
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
$expected = [
'data' => [
'data' => [
'pets' => [
['name' => 'Odie',
'woofs' => true],
['name' => 'Garfield',
'meows' => false],
null
]
[
'name' => 'Odie',
'woofs' => true,
],
[
'name' => 'Garfield',
'meows' => false,
],
null,
],
],
'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2]
]]
'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2],
],
],
];
$this->assertArraySubset($expected, $result);
}
@ -363,25 +378,25 @@ class AbstractTest extends TestCase
public function testReturningInvalidValueFromResolveTypeYieldsUsefulError() : void
{
$fooInterface = new InterfaceType([
'name' => 'FooInterface',
'fields' => ['bar' => ['type' => Type::string()]],
'name' => 'FooInterface',
'fields' => ['bar' => ['type' => Type::string()]],
'resolveType' => function () {
return [];
},
]);
$fooObject = new ObjectType([
'name' => 'FooObject',
'fields' => ['bar' => ['type' => Type::string()]],
'name' => 'FooObject',
'fields' => ['bar' => ['type' => Type::string()]],
'interfaces' => [$fooInterface],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'foo' => [
'type' => $fooInterface,
'type' => $fooInterface,
'resolve' => function () {
return 'dummy';
},
@ -394,18 +409,18 @@ class AbstractTest extends TestCase
$result = GraphQL::executeQuery($schema, '{ foo { bar } }');
$expected = [
'data' => ['foo' => null],
'data' => ['foo' => null],
'errors' => [
[
'message' => 'Internal server error',
'message' => 'Internal server error',
'debugMessage' =>
'Abstract type FooInterface must resolve to an Object type at ' .
'runtime for field Query.foo with value "dummy", received "[]". ' .
'Either the FooInterface type should provide a "resolveType" ' .
'function or each possible type should provide an "isTypeOf" function.',
'locations' => [['line' => 1, 'column' => 3]],
'path' => ['foo'],
'category' => 'internal',
'locations' => [['line' => 1, 'column' => 3]],
'path' => ['foo'],
'category' => 'internal',
],
],
];
@ -418,51 +433,56 @@ class AbstractTest extends TestCase
public function testResolveTypeAllowsResolvingWithTypeName() : void
{
$PetType = new InterfaceType([
'name' => 'Pet',
'resolveType' => function($obj) {
if ($obj instanceof Dog) return 'Dog';
if ($obj instanceof Cat) return 'Cat';
'name' => 'Pet',
'resolveType' => function ($obj) {
if ($obj instanceof Dog) {
return 'Dog';
}
if ($obj instanceof Cat) {
return 'Cat';
}
return null;
},
'fields' => [
'name' => [ 'type' => Type::string() ]
]
'fields' => [
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'interfaces' => [ $PetType ],
'fields' => [
'name' => [ 'type' => Type::string() ],
'woofs' => [ 'type' => Type::boolean() ],
]
'name' => 'Dog',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
]);
$CatType = new ObjectType([
'name' => 'Cat',
'interfaces' => [ $PetType ],
'fields' => [
'name' => [ 'type' => Type::string() ],
'meows' => [ 'type' => Type::boolean() ],
]
'name' => 'Cat',
'interfaces' => [$PetType],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
],
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($PetType),
'resolve' => function() {
'type' => Type::listOf($PetType),
'resolve' => function () {
return [
new Dog('Odie', true),
new Cat('Garfield', false)
new Cat('Garfield', false),
];
}
]
]
},
],
],
]),
'types' => [ $CatType, $DogType ]
'types' => [$CatType, $DogType],
]);
$query = '{
@ -479,51 +499,52 @@ class AbstractTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray();
$this->assertEquals([
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
]
]
], $result);
$this->assertEquals(
[
'data' => [
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false],
],
],
],
$result
);
}
public function testHintsOnConflictingTypeInstancesInResolveType() : void
{
$createTest = function() use (&$iface) {
$createTest = function () use (&$iface) {
return new ObjectType([
'name' => 'Test',
'fields' => [
'a' => Type::string()
'name' => 'Test',
'fields' => [
'a' => Type::string(),
],
'interfaces' => function() use ($iface) {
'interfaces' => function () use ($iface) {
return [$iface];
}
},
]);
};
$iface = new InterfaceType([
'name' => 'Node',
'fields' => [
'a' => Type::string()
'name' => 'Node',
'fields' => [
'a' => Type::string(),
],
'resolveType' => function() use (&$createTest) {
'resolveType' => function () use (&$createTest) {
return $createTest();
}
},
]);
$query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'node' => $iface,
'test' => $createTest()
]
'test' => $createTest(),
],
]);
$schema = new Schema([
'query' => $query,
]);
$schema = new Schema(['query' => $query]);
$schema->assertValid();
$query = '
@ -537,9 +558,9 @@ class AbstractTest extends TestCase
$result = Executor::execute($schema, Parser::parse($query), ['node' => ['a' => 'value']]);
$this->assertEquals(
'Schema must contain unique named types but contains multiple types named "Test". '.
'Make sure that `resolveType` function of abstract type "Node" returns the same type instance '.
'as referenced anywhere else within the schema '.
'Schema must contain unique named types but contains multiple types named "Test". ' .
'Make sure that `resolveType` function of abstract type "Node" returns the same type instance ' .
'as referenced anywhere else within the schema ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$result->errors[0]->getMessage()
);

View File

@ -1,33 +1,44 @@
<?php
namespace GraphQL\Tests\Executor;
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase;
use function in_array;
class DeferredFieldsTest extends TestCase
{
/** @var ObjectType */
private $userType;
/** @var ObjectType */
private $storyType;
/** @var ObjectType */
private $categoryType;
/** @var */
private $path;
/** @var mixed[][] */
private $storyDataSource;
/** @var mixed[][] */
private $userDataSource;
/** @var mixed[][] */
private $categoryDataSource;
/** @var ObjectType */
private $queryType;
public function setUp()
@ -54,127 +65,152 @@ class DeferredFieldsTest extends TestCase
$this->categoryDataSource = [
['id' => 1, 'name' => 'Category #1', 'topStoryId' => 8],
['id' => 2, 'name' => 'Category #2', 'topStoryId' => 3],
['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9]
['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9],
];
$this->path = [];
$this->path = [];
$this->userType = new ObjectType([
'name' => 'User',
'fields' => function() {
'name' => 'User',
'fields' => function () {
return [
'name' => [
'type' => Type::string(),
'name' => [
'type' => Type::string(),
'resolve' => function ($user, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $user['name'];
}
},
],
'bestFriend' => [
'type' => $this->userType,
'resolve' => function($user, $args, $context, ResolveInfo $info) {
'type' => $this->userType,
'resolve' => function ($user, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function() use ($user) {
return new Deferred(function () use ($user) {
$this->path[] = 'deferred-for-best-friend-of-' . $user['id'];
return Utils::find($this->userDataSource, function($entry) use ($user) {
return $entry['id'] === $user['bestFriendId'];
});
return Utils::find(
$this->userDataSource,
function ($entry) use ($user) {
return $entry['id'] === $user['bestFriendId'];
}
);
});
}
]
},
],
];
}
},
]);
$this->storyType = new ObjectType([
'name' => 'Story',
'name' => 'Story',
'fields' => [
'title' => [
'type' => Type::string(),
'resolve' => function($entry, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $entry['title'];
}
],
'author' => [
'type' => $this->userType,
'resolve' => function($story, $args, $context, ResolveInfo $info) {
'title' => [
'type' => Type::string(),
'resolve' => function ($entry, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function() use ($story) {
return $entry['title'];
},
],
'author' => [
'type' => $this->userType,
'resolve' => function ($story, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function () use ($story) {
$this->path[] = 'deferred-for-story-' . $story['id'] . '-author';
return Utils::find($this->userDataSource, function($entry) use ($story) {
return $entry['id'] === $story['authorId'];
});
return Utils::find(
$this->userDataSource,
function ($entry) use ($story) {
return $entry['id'] === $story['authorId'];
}
);
});
}
]
]
},
],
],
]);
$this->categoryType = new ObjectType([
'name' => 'Category',
'name' => 'Category',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function($category, $args, $context, ResolveInfo $info) {
'type' => Type::string(),
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $category['name'];
}
},
],
'stories' => [
'type' => Type::listOf($this->storyType),
'resolve' => function($category, $args, $context, ResolveInfo $info) {
'stories' => [
'type' => Type::listOf($this->storyType),
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) use ($category) {
return in_array($category['id'], $story['categoryIds']);
});
}
return Utils::filter(
$this->storyDataSource,
function ($story) use ($category) {
return in_array($category['id'], $story['categoryIds']);
}
);
},
],
'topStory' => [
'type' => $this->storyType,
'resolve' => function($category, $args, $context, ResolveInfo $info) {
'type' => $this->storyType,
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function () use ($category) {
$this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory';
return Utils::find($this->storyDataSource, function($story) use ($category) {
return $story['id'] === $category['topStoryId'];
});
return Utils::find(
$this->storyDataSource,
function ($story) use ($category) {
return $story['id'] === $category['topStoryId'];
}
);
});
}
]
]
},
],
],
]);
$this->queryType = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'topStories' => [
'type' => Type::listOf($this->storyType),
'resolve' => function($val, $args, $context, ResolveInfo $info) {
'topStories' => [
'type' => Type::listOf($this->storyType),
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) {
return $story['id'] % 2 === 1;
});
}
return Utils::filter(
$this->storyDataSource,
function ($story) {
return $story['id'] % 2 === 1;
}
);
},
],
'featuredCategory' => [
'type' => $this->categoryType,
'resolve' => function($val, $args, $context, ResolveInfo $info) {
'type' => $this->categoryType,
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $this->categoryDataSource[0];
}
},
],
'categories' => [
'type' => Type::listOf($this->categoryType),
'resolve' => function($val, $args, $context, ResolveInfo $info) {
'categories' => [
'type' => Type::listOf($this->categoryType),
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path;
return $this->categoryDataSource;
}
]
]
},
],
],
]);
parent::setUp();
@ -202,12 +238,12 @@ class DeferredFieldsTest extends TestCase
');
$schema = new Schema([
'query' => $this->queryType
'query' => $this->queryType,
]);
$expected = [
'data' => [
'topStories' => [
'topStories' => [
['title' => 'Story #1', 'author' => ['name' => 'John']],
['title' => 'Story #3', 'author' => ['name' => 'Joe']],
['title' => 'Story #5', 'author' => ['name' => 'John']],
@ -220,9 +256,9 @@ class DeferredFieldsTest extends TestCase
['title' => 'Story #4', 'author' => ['name' => 'Joe']],
['title' => 'Story #6', 'author' => ['name' => 'Jane']],
['title' => 'Story #8', 'author' => ['name' => 'John']],
]
]
]
],
],
],
];
$result = Executor::execute($schema, $query);
@ -292,12 +328,12 @@ class DeferredFieldsTest extends TestCase
');
$schema = new Schema([
'query' => $this->queryType
'query' => $this->queryType,
]);
$author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']];
$author2 = ['name' => 'Jane', 'bestFriend' => ['name' => 'Joe']];
$author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']];
$author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']];
$author4 = ['name' => 'Dirk', 'bestFriend' => ['name' => 'John']];
$expected = [
@ -306,8 +342,8 @@ class DeferredFieldsTest extends TestCase
['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]],
['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]],
['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]],
]
]
],
],
];
$result = Executor::execute($schema, $query);
@ -352,54 +388,56 @@ class DeferredFieldsTest extends TestCase
public function testComplexRecursiveDeferredFields() : void
{
$complexType = new ObjectType([
'name' => 'ComplexType',
'fields' => function() use (&$complexType) {
'name' => 'ComplexType',
'fields' => function () use (&$complexType) {
return [
'sync' => [
'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return 'sync';
}
],
'deferred' => [
'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'sync' => [
'type' => Type::string(),
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function() use ($info) {
return 'sync';
},
],
'deferred' => [
'type' => Type::string(),
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function () use ($info) {
$this->path[] = ['!dfd for: ', $info->path];
return 'deferred';
});
}
},
],
'nest' => [
'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'nest' => [
'type' => $complexType,
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return [];
}
},
],
'deferredNest' => [
'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) {
'type' => $complexType,
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path;
return new Deferred(function() use ($info) {
return new Deferred(function () use ($info) {
$this->path[] = ['!dfd nest for: ', $info->path];
return [];
});
}
]
},
],
];
}
},
]);
$schema = new Schema([
'query' => $complexType
]);
$schema = new Schema(['query' => $complexType]);
$query = Parser::parse('
$query = Parser::parse('
{
nest {
sync
@ -427,34 +465,34 @@ class DeferredFieldsTest extends TestCase
}
}
');
$result = Executor::execute($schema, $query);
$result = Executor::execute($schema, $query);
$expected = [
'data' => [
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred'
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred',
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred'
]
'sync' => 'sync',
'deferred' => 'deferred',
],
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred'
'sync' => 'sync',
'deferred' => 'deferred',
'nest' => [
'sync' => 'sync',
'deferred' => 'deferred',
],
'deferredNest' => [
'sync' => 'sync',
'deferred' => 'deferred'
]
]
]
'sync' => 'sync',
'deferred' => 'deferred',
],
],
],
];
$this->assertEquals($expected, $result->toArray());

View File

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

View File

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

View File

@ -1,99 +1,119 @@
<?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase;
use function count;
class ExecutorLazySchemaTest extends TestCase
{
public $SomeScalarType;
/** @var ScalarType */
public $someScalarType;
public $SomeObjectType;
/** @var ObjectType */
public $someObjectType;
public $OtherObjectType;
/** @var ObjectType */
public $otherObjectType;
public $DeeperObjectType;
/** @var ObjectType */
public $deeperObjectType;
public $SomeUnionType;
/** @var UnionType */
public $someUnionType;
public $SomeInterfaceType;
/** @var InterfaceType */
public $someInterfaceType;
public $SomeEnumType;
/** @var EnumType */
public $someEnumType;
public $SomeInputObjectType;
/** @var InputObjectType */
public $someInputObjectType;
public $QueryType;
/** @var ObjectType */
public $queryType;
/** @var string[] */
public $calls = [];
/** @var bool[] */
public $loadedTypes = [];
public function testWarnsAboutSlowIsTypeOfForLazySchema() : void
{
// isTypeOf used to resolve runtime type for Interface
$petType = new InterfaceType([
'name' => 'Pet',
'fields' => function() {
'name' => 'Pet',
'fields' => function () {
return [
'name' => ['type' => Type::string()]
'name' => ['type' => Type::string()],
];
}
},
]);
// Added to interface type when defined
$dogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
'fields' => function() {
'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => function () {
return [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
];
}
},
]);
$catType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$petType],
'isTypeOf' => function ($obj) {
'isTypeOf' => function ($obj) {
return $obj instanceof Cat;
},
'fields' => function() {
'fields' => function () {
return [
'name' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
];
}
},
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'pets' => [
'type' => Type::listOf($petType),
'type' => Type::listOf($petType),
'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)];
}
]
]
},
],
],
]),
'types' => [$catType, $dogType],
'typeLoader' => function($name) use ($dogType, $petType, $catType) {
'types' => [$catType, $dogType],
'typeLoader' => function ($name) use ($dogType, $petType, $catType) {
switch ($name) {
case 'Dog':
return $dogType;
@ -102,7 +122,7 @@ class ExecutorLazySchemaTest extends TestCase
case 'Cat':
return $catType;
}
}
},
]);
$query = '{
@ -120,7 +140,7 @@ class ExecutorLazySchemaTest extends TestCase
$expected = new ExecutionResult([
'pets' => [
['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false]
['name' => 'Garfield', 'meows' => false],
],
]);
@ -134,43 +154,46 @@ class ExecutorLazySchemaTest extends TestCase
$this->assertInstanceOf(Error::class, $result->errors[0]->getPrevious());
$this->assertEquals(
'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of '.
'GraphQL\Tests\Executor\Dog. Switching to slow resolution method using `isTypeOf` of all possible '.
'implementations. It requires full schema scan and degrades query performance significantly. '.
'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of ' .
'GraphQL\Tests\Executor\TestClasses\Dog. Switching to slow resolution method using `isTypeOf` of all possible ' .
'implementations. It requires full schema scan and degrades query performance significantly. ' .
'Make sure your `resolveType` always returns valid implementation or throws.',
$result->errors[0]->getMessage());
$result->errors[0]->getMessage()
);
}
public function testHintsOnConflictingTypeInstancesInDefinitions() : void
{
$calls = [];
$typeLoader = function($name) use (&$calls) {
$calls = [];
$typeLoader = function ($name) use (&$calls) {
$calls[] = $name;
switch ($name) {
case 'Test':
return new ObjectType([
'name' => 'Test',
'fields' => function() {
'name' => 'Test',
'fields' => function () {
return [
'test' => Type::string(),
];
}
},
]);
default:
return null;
}
};
$query = new ObjectType([
'name' => 'Query',
'fields' => function() use ($typeLoader) {
'name' => 'Query',
'fields' => function () use ($typeLoader) {
return [
'test' => $typeLoader('Test')
'test' => $typeLoader('Test'),
];
}
},
]);
$schema = new Schema([
'query' => $query,
'typeLoader' => $typeLoader
'query' => $query,
'typeLoader' => $typeLoader,
]);
$query = '
@ -186,8 +209,8 @@ class ExecutorLazySchemaTest extends TestCase
$this->assertEquals(['Test', 'Test'], $calls);
$this->assertEquals(
'Schema must contain unique named types but contains multiple types named "Test". '.
'Make sure that type loader returns the same instance as defined in Query.test '.
'Schema must contain unique named types but contains multiple types named "Test". ' .
'Make sure that type loader returns the same instance as defined in Query.test ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$result->errors[0]->getMessage()
);
@ -200,54 +223,161 @@ class ExecutorLazySchemaTest extends TestCase
public function testSimpleQuery() : void
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
'query' => $this->loadType('Query'),
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '{ object { string } }';
$query = '{ object { string } }';
$result = Executor::execute(
$schema,
Parser::parse($query),
['object' => ['string' => 'test']]
);
$expected = [
$expected = [
'data' => ['object' => ['string' => 'test']],
];
$expectedExecutorCalls = [
'Query.fields',
'SomeObject',
'SomeObject.fields'
'SomeObject.fields',
];
$this->assertEquals($expected, $result->toArray(true));
$this->assertEquals($expectedExecutorCalls, $this->calls);
}
public function loadType($name, $isExecutorCall = false)
{
if ($isExecutorCall) {
$this->calls[] = $name;
}
$this->loadedTypes[$name] = true;
switch ($name) {
case 'Query':
return $this->queryType ?: $this->queryType = new ObjectType([
'name' => 'Query',
'fields' => function () {
$this->calls[] = 'Query.fields';
return [
'object' => ['type' => $this->loadType('SomeObject')],
'other' => ['type' => $this->loadType('OtherObject')],
];
},
]);
case 'SomeObject':
return $this->someObjectType ?: $this->someObjectType = new ObjectType([
'name' => 'SomeObject',
'fields' => function () {
$this->calls[] = 'SomeObject.fields';
return [
'string' => ['type' => Type::string()],
'object' => ['type' => $this->someObjectType],
];
},
'interfaces' => function () {
$this->calls[] = 'SomeObject.interfaces';
return [
$this->loadType('SomeInterface'),
];
},
]);
case 'OtherObject':
return $this->otherObjectType ?: $this->otherObjectType = new ObjectType([
'name' => 'OtherObject',
'fields' => function () {
$this->calls[] = 'OtherObject.fields';
return [
'union' => ['type' => $this->loadType('SomeUnion')],
'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))],
];
},
]);
case 'DeeperObject':
return $this->deeperObjectType ?: $this->deeperObjectType = new ObjectType([
'name' => 'DeeperObject',
'fields' => function () {
return [
'scalar' => ['type' => $this->loadType('SomeScalar')],
];
},
]);
case 'SomeScalar':
return $this->someScalarType ?: $this->someScalarType = new CustomScalarType([
'name' => 'SomeScalar',
'serialize' => function ($value) {
return $value;
},
'parseValue' => function ($value) {
return $value;
},
'parseLiteral' => function () {
},
]);
case 'SomeUnion':
return $this->someUnionType ?: $this->someUnionType = new UnionType([
'name' => 'SomeUnion',
'resolveType' => function () {
$this->calls[] = 'SomeUnion.resolveType';
return $this->loadType('DeeperObject');
},
'types' => function () {
$this->calls[] = 'SomeUnion.types';
return [$this->loadType('DeeperObject')];
},
]);
case 'SomeInterface':
return $this->someInterfaceType ?: $this->someInterfaceType = new InterfaceType([
'name' => 'SomeInterface',
'resolveType' => function () {
$this->calls[] = 'SomeInterface.resolveType';
return $this->loadType('SomeObject');
},
'fields' => function () {
$this->calls[] = 'SomeInterface.fields';
return [
'string' => ['type' => Type::string()],
];
},
]);
default:
return null;
}
}
public function testDeepQuery() : void
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
'query' => $this->loadType('Query'),
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '{ object { object { object { string } } } }';
$query = '{ object { object { object { string } } } }';
$result = Executor::execute(
$schema,
Parser::parse($query),
['object' => ['object' => ['object' => ['string' => 'test']]]]
);
$expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]]
$expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]],
];
$expectedLoadedTypes = [
'Query' => true,
'SomeObject' => true,
'OtherObject' => true
'Query' => true,
'SomeObject' => true,
'OtherObject' => true,
];
$this->assertEquals($expected, $result->toArray(true));
@ -256,7 +386,7 @@ class ExecutorLazySchemaTest extends TestCase
$expectedExecutorCalls = [
'Query.fields',
'SomeObject',
'SomeObject.fields'
'SomeObject.fields',
];
$this->assertEquals($expectedExecutorCalls, $this->calls);
}
@ -264,13 +394,13 @@ class ExecutorLazySchemaTest extends TestCase
public function testResolveUnion() : void
{
$schema = new Schema([
'query' => $this->loadType('Query'),
'typeLoader' => function($name) {
'query' => $this->loadType('Query'),
'typeLoader' => function ($name) {
return $this->loadType($name, true);
}
},
]);
$query = '
$query = '
{
other {
union {
@ -285,17 +415,17 @@ class ExecutorLazySchemaTest extends TestCase
['other' => ['union' => ['scalar' => 'test']]]
);
$expected = [
$expected = [
'data' => ['other' => ['union' => ['scalar' => 'test']]],
];
$expectedLoadedTypes = [
'Query' => true,
'SomeObject' => true,
'OtherObject' => true,
'SomeUnion' => true,
'Query' => true,
'SomeObject' => true,
'OtherObject' => true,
'SomeUnion' => true,
'SomeInterface' => true,
'DeeperObject' => true,
'SomeScalar' => true,
'DeeperObject' => true,
'SomeScalar' => true,
];
$this->assertEquals($expected, $result->toArray(true));
@ -313,98 +443,4 @@ class ExecutorLazySchemaTest extends TestCase
];
$this->assertEquals($expectedCalls, $this->calls);
}
public function loadType($name, $isExecutorCall = false)
{
if ($isExecutorCall) {
$this->calls[] = $name;
}
$this->loadedTypes[$name] = true;
switch ($name) {
case 'Query':
return $this->QueryType ?: $this->QueryType = new ObjectType([
'name' => 'Query',
'fields' => function() {
$this->calls[] = 'Query.fields';
return [
'object' => ['type' => $this->loadType('SomeObject')],
'other' => ['type' => $this->loadType('OtherObject')],
];
}
]);
case 'SomeObject':
return $this->SomeObjectType ?: $this->SomeObjectType = new ObjectType([
'name' => 'SomeObject',
'fields' => function() {
$this->calls[] = 'SomeObject.fields';
return [
'string' => ['type' => Type::string()],
'object' => ['type' => $this->SomeObjectType]
];
},
'interfaces' => function() {
$this->calls[] = 'SomeObject.interfaces';
return [
$this->loadType('SomeInterface')
];
}
]);
case 'OtherObject':
return $this->OtherObjectType ?: $this->OtherObjectType = new ObjectType([
'name' => 'OtherObject',
'fields' => function() {
$this->calls[] = 'OtherObject.fields';
return [
'union' => ['type' => $this->loadType('SomeUnion')],
'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))],
];
}
]);
case 'DeeperObject':
return $this->DeeperObjectType ?: $this->DeeperObjectType = new ObjectType([
'name' => 'DeeperObject',
'fields' => function() {
return [
'scalar' => ['type' => $this->loadType('SomeScalar')],
];
}
]);
case 'SomeScalar';
return $this->SomeScalarType ?: $this->SomeScalarType = new CustomScalarType([
'name' => 'SomeScalar',
'serialize' => function($value) {return $value;},
'parseValue' => function($value) {return $value;},
'parseLiteral' => function() {}
]);
case 'SomeUnion':
return $this->SomeUnionType ?: $this->SomeUnionType = new UnionType([
'name' => 'SomeUnion',
'resolveType' => function() {
$this->calls[] = 'SomeUnion.resolveType';
return $this->loadType('DeeperObject');
},
'types' => function() {
$this->calls[] = 'SomeUnion.types';
return [ $this->loadType('DeeperObject') ];
}
]);
case 'SomeInterface':
return $this->SomeInterfaceType ?: $this->SomeInterfaceType = new InterfaceType([
'name' => 'SomeInterface',
'resolveType' => function() {
$this->calls[] = 'SomeInterface.resolveType';
return $this->loadType('SomeObject');
},
'fields' => function() {
$this->calls[] = 'SomeInterface.fields';
return [
'string' => ['type' => Type::string() ]
];
}
]);
default:
return null;
}
}
}

View File

@ -1,74 +1,77 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function sprintf;
class ExecutorSchemaTest extends TestCase
{
// Execute: Handles execution with a complex schema
/**
* @see it('executes using a schema')
*/
public function testExecutesUsingASchema() : void
{
$BlogArticle = null;
$BlogImage = new ObjectType([
'name' => 'Image',
$BlogImage = new ObjectType([
'name' => 'Image',
'fields' => [
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()],
]
],
]);
$BlogAuthor = new ObjectType([
'name' => 'Author',
'fields' => function() use (&$BlogArticle, &$BlogImage) {
'name' => 'Author',
'fields' => function () use (&$BlogArticle, &$BlogImage) {
return [
'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'pic' => [
'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]],
'type' => $BlogImage,
'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'pic' => [
'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]],
'type' => $BlogImage,
'resolve' => function ($obj, $args) {
return $obj['pic']($args['width'], $args['height']);
}
},
],
'recentArticle' => $BlogArticle
'recentArticle' => $BlogArticle,
];
}
},
]);
$BlogArticle = new ObjectType([
'name' => 'Article',
'name' => 'Article',
'fields' => [
'id' => ['type' => Type::nonNull(Type::string())],
'id' => ['type' => Type::nonNull(Type::string())],
'isPublished' => ['type' => Type::boolean()],
'author' => ['type' => $BlogAuthor],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'keywords' => ['type' => Type::listOf(Type::string())]
]
'author' => ['type' => $BlogAuthor],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'keywords' => ['type' => Type::listOf(Type::string())],
],
]);
$BlogQuery = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'article' => [
'type' => $BlogArticle,
'args' => ['id' => ['type' => Type::id()]],
'type' => $BlogArticle,
'args' => ['id' => ['type' => Type::id()]],
'resolve' => function ($_, $args) {
return $this->article($args['id']);
}
},
],
'feed' => [
'type' => Type::listOf($BlogArticle),
'feed' => [
'type' => Type::listOf($BlogArticle),
'resolve' => function () {
return [
$this->article(1),
@ -80,16 +83,15 @@ class ExecutorSchemaTest extends TestCase
$this->article(7),
$this->article(8),
$this->article(9),
$this->article(10)
$this->article(10),
];
}
]
]
},
],
],
]);
$BlogSchema = new Schema(['query' => $BlogQuery]);
$request = '
{
feed {
@ -126,51 +128,71 @@ class ExecutorSchemaTest extends TestCase
$expected = [
'data' => [
'feed' => [
['id' => '1',
'title' => 'My Article 1'],
['id' => '2',
'title' => 'My Article 2'],
['id' => '3',
'title' => 'My Article 3'],
['id' => '4',
'title' => 'My Article 4'],
['id' => '5',
'title' => 'My Article 5'],
['id' => '6',
'title' => 'My Article 6'],
['id' => '7',
'title' => 'My Article 7'],
['id' => '8',
'title' => 'My Article 8'],
['id' => '9',
'title' => 'My Article 9'],
['id' => '10',
'title' => 'My Article 10']
'feed' => [
[
'id' => '1',
'title' => 'My Article 1',
],
[
'id' => '2',
'title' => 'My Article 2',
],
[
'id' => '3',
'title' => 'My Article 3',
],
[
'id' => '4',
'title' => 'My Article 4',
],
[
'id' => '5',
'title' => 'My Article 5',
],
[
'id' => '6',
'title' => 'My Article 6',
],
[
'id' => '7',
'title' => 'My Article 7',
],
[
'id' => '8',
'title' => 'My Article 8',
],
[
'id' => '9',
'title' => 'My Article 9',
],
[
'id' => '10',
'title' => 'My Article 10',
],
],
'article' => [
'id' => '1',
'id' => '1',
'isPublished' => true,
'title' => 'My Article 1',
'body' => 'This is a post',
'author' => [
'id' => '123',
'name' => 'John Smith',
'pic' => [
'url' => 'cdn://123',
'width' => 640,
'height' => 480
'title' => 'My Article 1',
'body' => 'This is a post',
'author' => [
'id' => '123',
'name' => 'John Smith',
'pic' => [
'url' => 'cdn://123',
'width' => 640,
'height' => 480,
],
'recentArticle' => [
'id' => '1',
'id' => '1',
'isPublished' => true,
'title' => 'My Article 1',
'body' => 'This is a post',
'keywords' => ['foo', 'bar', '1', 'true', null]
]
]
]
]
'title' => 'My Article 1',
'body' => 'This is a post',
'keywords' => ['foo', 'bar', '1', 'true', null],
],
],
],
],
];
$this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray());
@ -179,30 +201,32 @@ class ExecutorSchemaTest extends TestCase
private function article($id)
{
$johnSmith = null;
$article = function($id) use (&$johnSmith) {
$article = function ($id) use (&$johnSmith) {
return [
'id' => $id,
'id' => $id,
'isPublished' => 'true',
'author' => $johnSmith,
'title' => 'My Article ' . $id,
'body' => 'This is a post',
'hidden' => 'This data is not exposed in the schema',
'keywords' => ['foo', 'bar', 1, true, null]
'author' => $johnSmith,
'title' => 'My Article ' . $id,
'body' => 'This is a post',
'hidden' => 'This data is not exposed in the schema',
'keywords' => ['foo', 'bar', 1, true, null],
];
};
$getPic = function($uid, $width, $height) {
$getPic = function ($uid, $width, $height) {
return [
'url' => "cdn://$uid",
'width' => $width,
'height' => $height
'url' => sprintf('cdn://%s', $uid),
'width' => $width,
'height' => $height,
];
};
$johnSmith = [
'id' => 123,
'name' => 'John Smith',
'pic' => function($width, $height) use ($getPic) {return $getPic(123, $width, $height);},
'id' => 123,
'name' => 'John Smith',
'pic' => function ($width, $height) use ($getPic) {
return $getPic(123, $width, $height);
},
'recentArticle' => $article(1),
];

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +1,34 @@
<?php
declare(strict_types=1);
/**
* @author: Ivo Meißner
* Date: 03.05.16
* Time: 13:14
*/
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class LazyInterfaceTest extends TestCase
{
/**
* @var Schema
*/
/** @var Schema */
protected $schema;
/**
* @var InterfaceType
*/
/** @var InterfaceType */
protected $lazyInterface;
/**
* @var ObjectType
*/
/** @var ObjectType */
protected $testObject;
/**
* Setup schema
*/
protected function setUp()
{
$query = new ObjectType([
'name' => 'query',
'fields' => function () {
return [
'lazyInterface' => [
'type' => $this->getLazyInterfaceType(),
'resolve' => function() {
return [];
}
]
];
}
]);
$this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]);
}
/**
* Returns the LazyInterface
*
* @return InterfaceType
*/
protected function getLazyInterfaceType()
{
if (!$this->lazyInterface) {
$this->lazyInterface = new InterfaceType([
'name' => 'LazyInterface',
'fields' => [
'a' => Type::string()
],
'resolveType' => function() {
return $this->getTestObjectType();
},
]);
}
return $this->lazyInterface;
}
/**
* Returns the test ObjectType
* @return ObjectType
*/
protected function getTestObjectType()
{
if (!$this->testObject) {
$this->testObject = new ObjectType([
'name' => 'TestObject',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function() {
return 'testname';
}
]
],
'interfaces' => [$this->getLazyInterfaceType()]
]);
}
return $this->testObject;
}
/**
* Handles execution of a lazily created interface
*/
@ -116,12 +46,78 @@ class LazyInterfaceTest extends TestCase
$expected = [
'data' => [
'lazyInterface' => [
'name' => 'testname'
]
]
'lazyInterface' => ['name' => 'testname'],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray());
}
/**
* Setup schema
*/
protected function setUp()
{
$query = new ObjectType([
'name' => 'query',
'fields' => function () {
return [
'lazyInterface' => [
'type' => $this->getLazyInterfaceType(),
'resolve' => function () {
return [];
},
],
];
},
]);
$this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]);
}
/**
* Returns the LazyInterface
*
* @return InterfaceType
*/
protected function getLazyInterfaceType()
{
if (! $this->lazyInterface) {
$this->lazyInterface = new InterfaceType([
'name' => 'LazyInterface',
'fields' => [
'a' => Type::string(),
],
'resolveType' => function () {
return $this->getTestObjectType();
},
]);
}
return $this->lazyInterface;
}
/**
* Returns the test ObjectType
* @return ObjectType
*/
protected function getTestObjectType()
{
if (! $this->testObject) {
$this->testObject = new ObjectType([
'name' => 'TestObject',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function () {
return 'testname';
},
],
],
'interfaces' => [$this->getLazyInterfaceType()],
]);
}
return $this->testObject;
}
}

View File

@ -1,22 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Error\UserError;
use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class ListsTest extends TestCase
{
// Describe: Execute: Handles list nullability
/**
* [T]
*/
@ -24,23 +23,57 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNullableLists(
[ 1, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
[1, 2],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNullableLists(
[ 1, null, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
[1, null, 2],
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNullableLists(
null,
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
}
private function checkHandlesNullableLists($testData, $expected)
{
$testType = Type::listOf(Type::int());
$this->check($testType, $testData, $expected);
}
private function check($testType, $testData, $expected, $debug = false)
{
$data = ['test' => $testData];
$dataType = null;
$dataType = new ObjectType([
'name' => 'DataType',
'fields' => function () use (&$testType, &$dataType, $data) {
return [
'test' => ['type' => $testType],
'nest' => [
'type' => $dataType,
'resolve' => function () use ($data) {
return $data;
},
],
];
},
]);
$schema = new Schema(['query' => $dataType]);
$ast = Parser::parse('{ nest { test } }');
$result = Executor::execute($schema, $ast, $data);
$this->assertArraySubset($expected, $result->toArray($debug));
}
/**
* [T]
*/
@ -48,26 +81,26 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNullableLists(
new Deferred(function() {
return [1,2];
new Deferred(function () {
return [1, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNullableLists(
new Deferred(function() {
new Deferred(function () {
return [1, null, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNullableLists(
new Deferred(function() {
new Deferred(function () {
return null;
}),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
// Rejected
@ -78,14 +111,14 @@ class ListsTest extends TestCase
});
},
[
'data' => ['nest' => ['test' => null]],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -98,57 +131,64 @@ class ListsTest extends TestCase
// Contains values
$this->checkHandlesNullableLists(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
}),
],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNullableLists(
[
new Deferred(function() {return 1;}),
new Deferred(function() {return null;}),
new Deferred(function() {return 2;})
new Deferred(function () {
return 1;
}),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNullableLists(
new Deferred(function() {
new Deferred(function () {
return null;
}),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
// Contains reject
$this->checkHandlesNullableLists(
function () {
return [
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
throw new UserError('bad');
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
];
},
[
'data' => ['nest' => ['test' => [1, null, 2]]],
'data' => ['nest' => ['test' => [1, null, 2]]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1]
]
]
'path' => ['nest', 'test', 1],
],
],
]
);
}
@ -160,32 +200,38 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNonNullableLists(
[ 1, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
[1, 2],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullableLists(
[ 1, null, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
[1, null, 2],
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNonNullableLists(
null,
[
'data' => [ 'nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
}
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/**
* [T]!
*/
@ -193,31 +239,31 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNonNullableLists(
new Deferred(function() {
return [1,2];
new Deferred(function () {
return [1, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullableLists(
new Deferred(function() {
new Deferred(function () {
return [1, null, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Returns null
$this->checkHandlesNonNullableLists(
null,
[
'data' => [ 'nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -225,19 +271,19 @@ class ListsTest extends TestCase
// Rejected
$this->checkHandlesNonNullableLists(
function () {
return new Deferred(function() {
return new Deferred(function () {
throw new UserError('bad');
});
},
[
'data' => ['nest' => null],
'data' => ['nest' => null],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -250,56 +296,56 @@ class ListsTest extends TestCase
// Contains values
$this->checkHandlesNonNullableLists(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullableLists(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return null;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, null, 2]]]]
);
// Contains reject
$this->checkHandlesNonNullableLists(
function () {
return [
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
throw new UserError('bad');
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
];
},
[
'data' => ['nest' => ['test' => [1, null, 2]]],
'data' => ['nest' => ['test' => [1, null, 2]]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1]
]
]
'path' => ['nest', 'test', 1],
],
],
]
);
}
@ -311,21 +357,21 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesListOfNonNulls(
[ 1, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
[1, 2],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesListOfNonNulls(
[ 1, null, 2 ],
[1, null, 2],
[
'data' => [ 'nest' => [ 'test' => null ] ],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -333,10 +379,16 @@ class ListsTest extends TestCase
// Returns null
$this->checkHandlesListOfNonNulls(
null,
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
}
private function checkHandlesListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::listOf(Type::nonNull(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/**
* [T!]
*/
@ -344,53 +396,53 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return [1, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return [1, null, 2];
}),
[
'data' => [ 'nest' => [ 'test' => null ] ],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
// Returns null
$this->checkHandlesListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return null;
}),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
// Rejected
$this->checkHandlesListOfNonNulls(
function () {
return new Deferred(function() {
return new Deferred(function () {
throw new UserError('bad');
});
},
[
'data' => ['nest' => ['test' => null]],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -403,50 +455,56 @@ class ListsTest extends TestCase
// Contains values
$this->checkHandlesListOfNonNulls(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesListOfNonNulls(
[
new Deferred(function() {return 1;}),
new Deferred(function() {return null;}),
new Deferred(function() {return 2;})
new Deferred(function () {
return 1;
}),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
],
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
['data' => ['nest' => ['test' => null]]]
);
// Contains reject
$this->checkHandlesListOfNonNulls(
function () {
return [
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
throw new UserError('bad');
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
];
},
[
'data' => ['nest' => ['test' => null]],
'data' => ['nest' => ['test' => null]],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1]
]
]
'path' => ['nest', 'test', 1],
],
],
]
);
}
@ -458,22 +516,21 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNonNullListOfNonNulls(
[ 1, 2 ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
[1, 2],
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullListOfNonNulls(
[ 1, null, 2 ],
[1, null, 2],
[
'data' => [ 'nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10 ]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -482,18 +539,24 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls(
null,
[
'data' => [ 'nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
}
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$this->check($testType, $testData, $expected, $debug);
}
/**
* [T!]!
*/
@ -501,42 +564,42 @@ class ListsTest extends TestCase
{
// Contains values
$this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return [1, 2];
}),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return [1, null, 2];
}),
[
'data' => [ 'nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
// Returns null
$this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() {
new Deferred(function () {
return null;
}),
[
'data' => [ 'nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -544,19 +607,19 @@ class ListsTest extends TestCase
// Rejected
$this->checkHandlesNonNullListOfNonNulls(
function () {
return new Deferred(function() {
return new Deferred(function () {
throw new UserError('bad');
});
},
[
'data' => ['nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
@ -569,38 +632,38 @@ class ListsTest extends TestCase
// Contains values
$this->checkHandlesNonNullListOfNonNulls(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
['data' => ['nest' => ['test' => [1, 2]]]]
);
// Contains null
$this->checkHandlesNonNullListOfNonNulls(
[
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
return null;
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
],
[
'data' => [ 'nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]]
]
]
'locations' => [['line' => 1, 'column' => 10]],
],
],
],
true
);
@ -609,83 +672,27 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls(
function () {
return [
new Deferred(function() {
new Deferred(function () {
return 1;
}),
new Deferred(function() {
new Deferred(function () {
throw new UserError('bad');
}),
new Deferred(function() {
new Deferred(function () {
return 2;
})
}),
];
},
[
'data' => ['nest' => null ],
'data' => ['nest' => null],
'errors' => [
[
'message' => 'bad',
'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test']
]
]
'path' => ['nest', 'test'],
],
],
]
);
}
private function checkHandlesNullableLists($testData, $expected)
{
$testType = Type::listOf(Type::int());
$this->check($testType, $testData, $expected);
}
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
private function checkHandlesListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::listOf(Type::nonNull(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$this->check($testType, $testData, $expected, $debug);
}
private function check($testType, $testData, $expected, $debug = false)
{
$data = ['test' => $testData];
$dataType = null;
$dataType = new ObjectType([
'name' => 'DataType',
'fields' => function () use (&$testType, &$dataType, $data) {
return [
'test' => [
'type' => $testType
],
'nest' => [
'type' => $dataType,
'resolve' => function () use ($data) {
return $data;
}
]
];
}
]);
$schema = new Schema([
'query' => $dataType
]);
$ast = Parser::parse('{ nest { test } }');
$result = Executor::execute($schema, $ast, $data);
$this->assertArraySubset($expected, $result->toArray($debug));
}
}

View File

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

View File

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

View File

@ -1,17 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use PHPUnit\Framework\TestCase;
use React\Promise\Deferred;
use React\Promise\FulfilledPromise;
use React\Promise\LazyPromise;
use React\Promise\Promise as ReactPromise;
use React\Promise\RejectedPromise;
use function class_exists;
/**
* @group ReactPromise
@ -20,27 +20,35 @@ 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 FulfilledPromise()));
$this->assertSame(true, $reactAdapter->isThenable(new RejectedPromise()));
$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));
$this->assertSame(false, $reactAdapter->isThenable(0));
$this->assertSame(false, $reactAdapter->isThenable('test'));
$this->assertSame(false, $reactAdapter->isThenable(''));
$this->assertSame(false, $reactAdapter->isThenable([]));
$this->assertSame(false, $reactAdapter->isThenable(new \stdClass()));
$this->assertTrue(
$reactAdapter->isThenable(new ReactPromise(function () {
}))
);
$this->assertTrue($reactAdapter->isThenable(new FulfilledPromise()));
$this->assertTrue($reactAdapter->isThenable(new RejectedPromise()));
$this->assertTrue(
$reactAdapter->isThenable(new LazyPromise(function () {
}))
);
$this->assertFalse($reactAdapter->isThenable(false));
$this->assertFalse($reactAdapter->isThenable(true));
$this->assertFalse($reactAdapter->isThenable(1));
$this->assertFalse($reactAdapter->isThenable(0));
$this->assertFalse($reactAdapter->isThenable('test'));
$this->assertFalse($reactAdapter->isThenable(''));
$this->assertFalse($reactAdapter->isThenable([]));
$this->assertFalse($reactAdapter->isThenable(new \stdClass()));
}
public function testConvertsReactPromisesToGraphQlOnes() : void
@ -58,13 +66,16 @@ class ReactPromiseAdapterTest extends TestCase
{
$reactAdapter = new ReactPromiseAdapter();
$reactPromise = new FulfilledPromise(1);
$promise = $reactAdapter->convertThenable($reactPromise);
$promise = $reactAdapter->convertThenable($reactPromise);
$result = null;
$resultPromise = $reactAdapter->then($promise, function ($value) use (&$result) {
$result = $value;
});
$resultPromise = $reactAdapter->then(
$promise,
function ($value) use (&$result) {
$result = $value;
}
);
$this->assertSame(1, $result);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise);
@ -73,9 +84,9 @@ class ReactPromiseAdapterTest extends TestCase
public function testCreate() : void
{
$reactAdapter = new ReactPromiseAdapter();
$reactAdapter = new ReactPromiseAdapter();
$resolvedPromise = $reactAdapter->create(function ($resolve) {
$resolve(1);
$resolve(1);
});
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resolvedPromise);
@ -84,7 +95,7 @@ class ReactPromiseAdapterTest extends TestCase
$result = null;
$resolvedPromise->then(function ($value) use (&$result) {
$result = $value;
$result = $value;
});
$this->assertSame(1, $result);
@ -92,7 +103,7 @@ class ReactPromiseAdapterTest extends TestCase
public function testCreateFulfilled() : void
{
$reactAdapter = new ReactPromiseAdapter();
$reactAdapter = new ReactPromiseAdapter();
$fulfilledPromise = $reactAdapter->createFulfilled(1);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $fulfilledPromise);
@ -109,7 +120,7 @@ class ReactPromiseAdapterTest extends TestCase
public function testCreateRejected() : void
{
$reactAdapter = new ReactPromiseAdapter();
$reactAdapter = new ReactPromiseAdapter();
$rejectedPromise = $reactAdapter->createRejected(new \Exception('I am a bad promise'));
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $rejectedPromise);
@ -117,9 +128,12 @@ class ReactPromiseAdapterTest extends TestCase
$exception = null;
$rejectedPromise->then(null, function ($error) use (&$exception) {
$exception = $error;
});
$rejectedPromise->then(
null,
function ($error) use (&$exception) {
$exception = $error;
}
);
$this->assertInstanceOf('\Exception', $exception);
$this->assertEquals('I am a bad promise', $exception->getMessage());
@ -128,7 +142,7 @@ class ReactPromiseAdapterTest extends TestCase
public function testAll() : void
{
$reactAdapter = new ReactPromiseAdapter();
$promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)];
$promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)];
$allPromise = $reactAdapter->all($promises);
@ -138,7 +152,7 @@ class ReactPromiseAdapterTest extends TestCase
$result = null;
$allPromise->then(function ($values) use (&$result) {
$result = $values;
$result = $values;
});
$this->assertSame([1, 2, 3], $result);
@ -147,9 +161,9 @@ class ReactPromiseAdapterTest extends TestCase
public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromises() : void
{
$reactAdapter = new ReactPromiseAdapter();
$deferred = new Deferred();
$promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)];
$result = null;
$deferred = new Deferred();
$promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)];
$result = null;
$reactAdapter->all($promises)->then(function ($values) use (&$result) {
$result = $values;

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Deferred;
@ -10,9 +13,7 @@ use PHPUnit\Framework\TestCase;
class SyncPromiseAdapterTest extends TestCase
{
/**
* @var SyncPromiseAdapter
*/
/** @var SyncPromiseAdapter */
private $promises;
public function setUp()
@ -22,7 +23,11 @@ class SyncPromiseAdapterTest extends TestCase
public function testIsThenable() : void
{
$this->assertEquals(true, $this->promises->isThenable(new Deferred(function() {})));
$this->assertEquals(
true,
$this->promises->isThenable(new Deferred(function () {
}))
);
$this->assertEquals(false, $this->promises->isThenable(false));
$this->assertEquals(false, $this->promises->isThenable(true));
$this->assertEquals(false, $this->promises->isThenable(1));
@ -35,7 +40,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testConvert() : void
{
$dfd = new Deferred(function() {});
$dfd = new Deferred(function () {
});
$result = $this->promises->convertThenable($dfd);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $result);
@ -48,7 +54,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testThen() : void
{
$dfd = new Deferred(function() {});
$dfd = new Deferred(function () {
});
$promise = $this->promises->convertThenable($dfd);
$result = $this->promises->then($promise);
@ -59,18 +66,55 @@ class SyncPromiseAdapterTest extends TestCase
public function testCreatePromise() : void
{
$promise = $this->promises->create(function($resolve, $reject) {});
$promise = $this->promises->create(function ($resolve, $reject) {
});
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise);
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise);
$promise = $this->promises->create(function($resolve, $reject) {
$promise = $this->promises->create(function ($resolve, $reject) {
$resolve('A');
});
$this->assertValidPromise($promise, null, 'A', SyncPromise::FULFILLED);
}
private function assertValidPromise($promise, $expectedNextReason, $expectedNextValue, $expectedNextState)
{
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise);
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise);
$actualNextValue = null;
$actualNextReason = null;
$onFulfilledCalled = false;
$onRejectedCalled = false;
$promise->then(
function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
$onFulfilledCalled = true;
$actualNextValue = $nextValue;
},
function (\Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) {
$onRejectedCalled = true;
$actualNextReason = $reason->getMessage();
}
);
$this->assertSame($onFulfilledCalled, false);
$this->assertSame($onRejectedCalled, false);
SyncPromise::runQueue();
if ($expectedNextState !== SyncPromise::PENDING) {
$this->assertSame(! $expectedNextReason, $onFulfilledCalled);
$this->assertSame(! ! $expectedNextReason, $onRejectedCalled);
}
$this->assertSame($expectedNextValue, $actualNextValue);
$this->assertSame($expectedNextReason, $actualNextReason);
$this->assertSame($expectedNextState, $promise->adoptedPromise->state);
}
public function testCreateFulfilledPromise() : void
{
$promise = $this->promises->createFulfilled('test');
@ -94,8 +138,8 @@ class SyncPromiseAdapterTest extends TestCase
$promise1 = new SyncPromise();
$promise2 = new SyncPromise();
$promise3 = $promise2->then(
function($value) {
return $value .'-value3';
function ($value) {
return $value . '-value3';
}
);
@ -105,7 +149,7 @@ class SyncPromiseAdapterTest extends TestCase
new Promise($promise2, $this->promises),
3,
new Promise($promise3, $this->promises),
[]
[],
];
$promise = $this->promises->all($data);
@ -114,36 +158,46 @@ class SyncPromiseAdapterTest extends TestCase
$promise1->resolve('value1');
$this->assertValidPromise($promise, null, null, SyncPromise::PENDING);
$promise2->resolve('value2');
$this->assertValidPromise($promise, null, ['1', 'value1', 'value2', 3, 'value2-value3', []], SyncPromise::FULFILLED);
$this->assertValidPromise(
$promise,
null,
['1', 'value1', 'value2', 3, 'value2-value3', []],
SyncPromise::FULFILLED
);
}
public function testWait() : void
{
$called = [];
$deferred1 = new Deferred(function() use (&$called) {
$deferred1 = new Deferred(function () use (&$called) {
$called[] = 1;
return 1;
});
$deferred2 = new Deferred(function() use (&$called) {
$deferred2 = new Deferred(function () use (&$called) {
$called[] = 2;
return 2;
});
$p1 = $this->promises->convertThenable($deferred1);
$p2 = $this->promises->convertThenable($deferred2);
$p3 = $p2->then(function() use (&$called) {
$dfd = new Deferred(function() use (&$called) {
$p3 = $p2->then(function () use (&$called) {
$dfd = new Deferred(function () use (&$called) {
$called[] = 3;
return 3;
});
return $this->promises->convertThenable($dfd);
});
$p4 = $p3->then(function() use (&$called) {
return new Deferred(function() use (&$called) {
$p4 = $p3->then(function () use (&$called) {
return new Deferred(function () use (&$called) {
$called[] = 4;
return 4;
});
});
@ -156,46 +210,10 @@ class SyncPromiseAdapterTest extends TestCase
$this->assertEquals(SyncPromise::PENDING, $all->adoptedPromise->state);
$this->assertEquals([1, 2], $called);
$expectedResult = [0,1,2,3,4];
$result = $this->promises->wait($all);
$expectedResult = [0, 1, 2, 3, 4];
$result = $this->promises->wait($all);
$this->assertEquals($expectedResult, $result);
$this->assertEquals([1, 2, 3, 4], $called);
$this->assertValidPromise($all, null, [0,1,2,3,4], SyncPromise::FULFILLED);
}
private function assertValidPromise($promise, $expectedNextReason, $expectedNextValue, $expectedNextState)
{
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise);
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise);
$actualNextValue = null;
$actualNextReason = null;
$onFulfilledCalled = false;
$onRejectedCalled = false;
$promise->then(
function($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
$onFulfilledCalled = true;
$actualNextValue = $nextValue;
},
function(\Exception $reason) use (&$actualNextReason, &$onRejectedCalled) {
$onRejectedCalled = true;
$actualNextReason = $reason->getMessage();
}
);
$this->assertSame($onFulfilledCalled, false);
$this->assertSame($onRejectedCalled, false);
SyncPromise::runQueue();
if ($expectedNextState !== SyncPromise::PENDING) {
$this->assertSame(!$expectedNextReason, $onFulfilledCalled);
$this->assertSame(!!$expectedNextReason, $onRejectedCalled);
}
$this->assertSame($expectedNextValue, $actualNextValue);
$this->assertSame($expectedNextReason, $actualNextReason);
$this->assertSame($expectedNextState, $promise->adoptedPromise->state);
$this->assertValidPromise($all, null, [0, 1, 2, 3, 4], SyncPromise::FULFILLED);
}
}

View File

@ -1,25 +1,32 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase;
use function uniqid;
class SyncPromiseTest extends TestCase
{
public function getFulfilledPromiseResolveData()
{
$onFulfilledReturnsNull = function() {
$onFulfilledReturnsNull = function () {
return null;
};
$onFulfilledReturnsSameValue = function($value) {
$onFulfilledReturnsSameValue = function ($value) {
return $value;
};
$onFulfilledReturnsOtherValue = function($value) {
$onFulfilledReturnsOtherValue = function ($value) {
return 'other-' . $value;
};
$onFulfilledThrows = function($value) {
throw new \Exception("onFulfilled throws this!");
$onFulfilledThrows = function ($value) {
throw new \Exception('onFulfilled throws this!');
};
return [
@ -28,7 +35,7 @@ class SyncPromiseTest extends TestCase
[uniqid(), $onFulfilledReturnsNull, null, null, SyncPromise::FULFILLED],
['test-value', $onFulfilledReturnsSameValue, 'test-value', null, SyncPromise::FULFILLED],
['test-value-2', $onFulfilledReturnsOtherValue, 'other-test-value-2', null, SyncPromise::FULFILLED],
['test-value-3', $onFulfilledThrows, null, "onFulfilled throws this!", SyncPromise::REJECTED],
['test-value-3', $onFulfilledThrows, null, 'onFulfilled throws this!', SyncPromise::REJECTED],
];
}
@ -41,8 +48,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -63,8 +69,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -85,21 +90,27 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
$promise->resolve($resolvedValue);
$this->assertEquals(SyncPromise::FULFILLED, $promise->state);
$nextPromise = $promise->then(null, function() {});
$nextPromise = $promise->then(
null,
function () {
}
);
$this->assertSame($promise, $nextPromise);
$onRejectedCalled = false;
$nextPromise = $promise->then($onFulfilled, function () use (&$onRejectedCalled) {
$onRejectedCalled = true;
});
$nextPromise = $promise->then(
$onFulfilled,
function () use (&$onRejectedCalled) {
$onRejectedCalled = true;
}
);
if ($onFulfilled) {
$this->assertNotSame($promise, $nextPromise);
@ -124,19 +135,57 @@ class SyncPromiseTest extends TestCase
$this->assertValidPromise($nextPromise3, $expectedNextReason, $expectedNextValue, $expectedNextState);
}
private function assertValidPromise(
SyncPromise $promise,
$expectedNextReason,
$expectedNextValue,
$expectedNextState
) {
$actualNextValue = null;
$actualNextReason = null;
$onFulfilledCalled = false;
$onRejectedCalled = false;
$promise->then(
function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
$onFulfilledCalled = true;
$actualNextValue = $nextValue;
},
function (\Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) {
$onRejectedCalled = true;
$actualNextReason = $reason->getMessage();
}
);
$this->assertEquals($onFulfilledCalled, false);
$this->assertEquals($onRejectedCalled, false);
SyncPromise::runQueue();
$this->assertEquals(! $expectedNextReason, $onFulfilledCalled);
$this->assertEquals(! ! $expectedNextReason, $onRejectedCalled);
$this->assertEquals($expectedNextValue, $actualNextValue);
$this->assertEquals($expectedNextReason, $actualNextReason);
$this->assertEquals($expectedNextState, $promise->state);
}
public function getRejectedPromiseData()
{
$onRejectedReturnsNull = function() {
$onRejectedReturnsNull = function () {
return null;
};
$onRejectedReturnsSomeValue = function($reason) {
$onRejectedReturnsSomeValue = function ($reason) {
return 'some-value';
};
$onRejectedThrowsSameReason = function($reason) {
$onRejectedThrowsSameReason = function ($reason) {
throw $reason;
};
$onRejectedThrowsOtherReason = function($value) {
throw new \Exception("onRejected throws other!");
$onRejectedThrowsOtherReason = function ($value) {
throw new \Exception('onRejected throws other!');
};
return [
@ -158,8 +207,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -169,7 +217,6 @@ class SyncPromiseTest extends TestCase
$this->expectException(\Throwable::class);
$this->expectExceptionMessage('Cannot change rejection reason');
$promise->reject(new \Exception('other-reason'));
}
/**
@ -181,8 +228,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -203,8 +249,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue,
$expectedNextReason,
$expectedNextState
)
{
) {
$promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -214,22 +259,26 @@ class SyncPromiseTest extends TestCase
try {
$promise->reject(new \Exception('other-reason'));
$this->fail('Expected exception not thrown');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertEquals('Cannot change rejection reason', $e->getMessage());
}
try {
$promise->resolve('anything');
$this->fail('Expected exception not thrown');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertEquals('Cannot resolve rejected promise', $e->getMessage());
}
$nextPromise = $promise->then(function() {}, null);
$nextPromise = $promise->then(
function () {
},
null
);
$this->assertSame($promise, $nextPromise);
$onFulfilledCalled = false;
$nextPromise = $promise->then(
$nextPromise = $promise->then(
function () use (&$onFulfilledCalled) {
$onFulfilledCalled = true;
},
@ -266,7 +315,7 @@ class SyncPromiseTest extends TestCase
try {
$promise->resolve($promise);
$this->fail('Expected exception not thrown');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertEquals('Cannot resolve promise with self', $e->getMessage());
$this->assertEquals(SyncPromise::PENDING, $promise->state);
}
@ -299,24 +348,25 @@ class SyncPromiseTest extends TestCase
throw $e;
} catch (\Throwable $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state);
} catch (\Exception $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state);
}
$promise->reject(new \Exception("Rejected Reason"));
$this->assertValidPromise($promise, "Rejected Reason", null, SyncPromise::REJECTED);
$promise->reject(new \Exception('Rejected Reason'));
$this->assertValidPromise($promise, 'Rejected Reason', null, SyncPromise::REJECTED);
$promise = new SyncPromise();
$promise2 = $promise->then(null, function() {
return 'value';
});
$promise->reject(new \Exception("Rejected Again"));
$promise = new SyncPromise();
$promise2 = $promise->then(
null,
function () {
return 'value';
}
);
$promise->reject(new \Exception('Rejected Again'));
$this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED);
$promise = new SyncPromise();
$promise = new SyncPromise();
$promise2 = $promise->then();
$promise->reject(new \Exception("Rejected Once Again"));
$this->assertValidPromise($promise2, "Rejected Once Again", null, SyncPromise::REJECTED);
$promise->reject(new \Exception('Rejected Once Again'));
$this->assertValidPromise($promise2, 'Rejected Once Again', null, SyncPromise::REJECTED);
}
public function testPendingPromiseThen() : void
@ -331,12 +381,14 @@ class SyncPromiseTest extends TestCase
// Make sure that it queues derivative promises until resolution:
$onFulfilledCount = 0;
$onRejectedCount = 0;
$onFulfilled = function($value) use (&$onFulfilledCount) {
$onRejectedCount = 0;
$onFulfilled = function ($value) use (&$onFulfilledCount) {
$onFulfilledCount++;
return $onFulfilledCount;
};
$onRejected = function($reason) use (&$onRejectedCount) {
$onRejected = function ($reason) use (&$onRejectedCount) {
$onRejectedCount++;
throw $reason;
};
@ -367,35 +419,4 @@ class SyncPromiseTest extends TestCase
$this->assertValidPromise($nextPromise3, null, 2, SyncPromise::FULFILLED);
$this->assertValidPromise($nextPromise4, null, 3, SyncPromise::FULFILLED);
}
private function assertValidPromise(SyncPromise $promise, $expectedNextReason, $expectedNextValue, $expectedNextState)
{
$actualNextValue = null;
$actualNextReason = null;
$onFulfilledCalled = false;
$onRejectedCalled = false;
$promise->then(
function($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
$onFulfilledCalled = true;
$actualNextValue = $nextValue;
},
function(\Exception $reason) use (&$actualNextReason, &$onRejectedCalled) {
$onRejectedCalled = true;
$actualNextReason = $reason->getMessage();
}
);
$this->assertEquals($onFulfilledCalled, false);
$this->assertEquals($onRejectedCalled, false);
SyncPromise::runQueue();
$this->assertEquals(!$expectedNextReason, $onFulfilledCalled);
$this->assertEquals(!!$expectedNextReason, $onRejectedCalled);
$this->assertEquals($expectedNextValue, $actualNextValue);
$this->assertEquals($expectedNextReason, $actualNextReason);
$this->assertEquals($expectedNextState, $promise->state);
}
}

View File

@ -1,30 +1,22 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\Adder;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function json_encode;
use function uniqid;
class ResolveTest extends TestCase
{
// Execute: resolve function
private function buildSchema($testField)
{
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'test' => $testField
]
])
]);
}
/**
* @see it('default function accesses properties')
*/
@ -32,9 +24,7 @@ class ResolveTest extends TestCase
{
$schema = $this->buildSchema(['type' => Type::string()]);
$source = [
'test' => 'testValue'
];
$source = ['test' => 'testValue'];
$this->assertEquals(
['data' => ['test' => 'testValue']],
@ -42,18 +32,28 @@ class ResolveTest extends TestCase
);
}
private function buildSchema($testField)
{
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['test' => $testField],
]),
]);
}
/**
* @see it('default function calls methods')
*/
public function testDefaultFunctionCallsClosures() : void
{
$schema = $this->buildSchema(['type' => Type::string()]);
$schema = $this->buildSchema(['type' => Type::string()]);
$_secret = 'secretValue' . uniqid();
$source = [
'test' => function() use ($_secret) {
'test' => function () use ($_secret) {
return $_secret;
}
},
];
$this->assertEquals(
['data' => ['test' => $_secret]],
@ -69,7 +69,7 @@ class ResolveTest extends TestCase
$schema = $this->buildSchema([
'type' => Type::int(),
'args' => [
'addend1' => [ 'type' => Type::int() ],
'addend1' => ['type' => Type::int()],
],
]);
@ -85,14 +85,14 @@ class ResolveTest extends TestCase
public function testUsesProvidedResolveFunction() : void
{
$schema = $this->buildSchema([
'type' => Type::string(),
'args' => [
'type' => Type::string(),
'args' => [
'aStr' => ['type' => Type::string()],
'aInt' => ['type' => Type::int()],
],
'resolve' => function ($source, $args) {
return json_encode([$source, $args]);
}
},
]);
$this->assertEquals(

View File

@ -1,8 +1,10 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
@ -29,37 +31,38 @@ class SyncTest extends TestCase
public function setUp()
{
$this->schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'syncField' => [
'type' => Type::string(),
'syncField' => [
'type' => Type::string(),
'resolve' => function ($rootValue) {
return $rootValue;
}
},
],
'asyncField' => [
'type' => Type::string(),
'type' => Type::string(),
'resolve' => function ($rootValue) {
return new Deferred(function () use ($rootValue) {
return $rootValue;
});
}
]
]
},
],
],
]),
'mutation' => new ObjectType([
'name' => 'Mutation',
'name' => 'Mutation',
'fields' => [
'syncMutationField' => [
'type' => Type::string(),
'type' => Type::string(),
'resolve' => function ($rootValue) {
return $rootValue;
}
]
]
])
},
],
],
]),
]);
$this->promiseAdapter = new SyncPromiseAdapter();
}
@ -70,7 +73,7 @@ class SyncTest extends TestCase
*/
public function testDoesNotReturnAPromiseForInitialErrors() : void
{
$doc = 'fragment Example on Query { syncField }';
$doc = 'fragment Example on Query { syncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
@ -79,106 +82,6 @@ class SyncTest extends TestCase
$this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result);
}
/**
* @see it('does not return a Promise if fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void
{
$doc = 'query Example { syncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
/**
* @see it('does not return a Promise if mutation fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void
{
$doc = 'mutation Example { syncMutationField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result);
}
/**
* @see it('returns a Promise if any field is asynchronous')
*/
public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void
{
$doc = 'query Example { syncField, asyncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
}
// Describe: graphqlSync
/**
* @see it('does not return a Promise for syntax errors')
*/
public function testDoesNotReturnAPromiseForSyntaxErrors() : void
{
$doc = 'fragment Example on Query { { { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc
);
$this->assertSync([
'errors' => [
['message' => 'Syntax Error: Expected Name, found {',
'locations' => [['line' => 1, 'column' => 29]]]
]
], $result);
}
/**
* @see it('does not return a Promise for validation errors')
*/
public function testDoesNotReturnAPromiseForValidationErrors() : void
{
$doc = 'fragment Example on Query { unknownField }';
$validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc));
$result = $this->graphqlSync(
$this->schema,
$doc
);
$expected = [
'errors' => Utils::map($validationErrors, function ($e) {
return FormattedError::createFromException($e);
})
];
$this->assertSync($expected, $result);
}
/**
* @see it('does not return a Promise for sync execution')
*/
public function testDoesNotReturnAPromiseForSyncExecution() : void
{
$doc = 'query Example { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc,
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
private function graphqlSync($schema, $doc, $rootValue = null)
{
return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
}
private function execute($schema, $doc, $rootValue = null)
{
return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
@ -191,7 +94,56 @@ class SyncTest extends TestCase
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
$this->assertEquals(SyncPromise::FULFILLED, $actualResult->adoptedPromise->state, $message);
$this->assertInstanceOf(ExecutionResult::class, $actualResult->adoptedPromise->result, $message);
$this->assertArraySubset($expectedFinalArray, $actualResult->adoptedPromise->result->toArray(), $message);
$this->assertArraySubset(
$expectedFinalArray,
$actualResult->adoptedPromise->result->toArray(),
false,
$message
);
}
/**
* @see it('does not return a Promise if fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void
{
$doc = 'query Example { syncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
// Describe: graphqlSync
/**
* @see it('does not return a Promise if mutation fields are all synchronous')
*/
public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void
{
$doc = 'mutation Example { syncMutationField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result);
}
/**
* @see it('returns a Promise if any field is asynchronous')
*/
public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void
{
$doc = 'query Example { syncField, asyncField }';
$result = $this->execute(
$this->schema,
Parser::parse($doc),
'rootValue'
);
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
}
private function assertAsync($expectedFinalArray, $actualResult)
@ -202,6 +154,70 @@ class SyncTest extends TestCase
$this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message);
$resolvedResult = $this->promiseAdapter->wait($actualResult);
$this->assertInstanceOf(ExecutionResult::class, $resolvedResult, $message);
$this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), $message);
$this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), false, $message);
}
/**
* @see it('does not return a Promise for syntax errors')
*/
public function testDoesNotReturnAPromiseForSyntaxErrors() : void
{
$doc = 'fragment Example on Query { { { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc
);
$this->assertSync(
[
'errors' => [
[
'message' => 'Syntax Error: Expected Name, found {',
'locations' => [['line' => 1, 'column' => 29]],
],
],
],
$result
);
}
private function graphqlSync($schema, $doc, $rootValue = null)
{
return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
}
/**
* @see it('does not return a Promise for validation errors')
*/
public function testDoesNotReturnAPromiseForValidationErrors() : void
{
$doc = 'fragment Example on Query { unknownField }';
$validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc));
$result = $this->graphqlSync(
$this->schema,
$doc
);
$expected = [
'errors' => Utils::map(
$validationErrors,
function ($e) {
return FormattedError::createFromException($e);
}
),
];
$this->assertSync($expected, $result);
}
/**
* @see it('does not return a Promise for sync execution')
*/
public function testDoesNotReturnAPromiseForSyncExecution() : void
{
$doc = 'query Example { syncField }';
$result = $this->graphqlSync(
$this->schema,
$doc,
'rootValue'
);
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,64 +1,76 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Error\Warning;
use GraphQL\Executor\Executor;
use GraphQL\GraphQL;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Person;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class UnionInterfaceTest extends TestCase
{
/** @var */
public $schema;
/** @var Cat */
public $garfield;
/** @var Dog */
public $odie;
/** @var Person */
public $liz;
/** @var Person */
public $john;
public function setUp()
{
$NamedType = new InterfaceType([
'name' => 'Named',
'name' => 'Named',
'fields' => [
'name' => ['type' => Type::string()]
]
'name' => ['type' => Type::string()],
],
]);
$DogType = new ObjectType([
'name' => 'Dog',
'name' => 'Dog',
'interfaces' => [$NamedType],
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()]
'fields' => [
'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()],
],
'isTypeOf' => function ($value) {
'isTypeOf' => function ($value) {
return $value instanceof Dog;
}
},
]);
$CatType = new ObjectType([
'name' => 'Cat',
'name' => 'Cat',
'interfaces' => [$NamedType],
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()]
'fields' => [
'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()],
],
'isTypeOf' => function ($value) {
'isTypeOf' => function ($value) {
return $value instanceof Cat;
}
},
]);
$PetType = new UnionType([
'name' => 'Pet',
'types' => [$DogType, $CatType],
'name' => 'Pet',
'types' => [$DogType, $CatType],
'resolveType' => function ($value) use ($DogType, $CatType) {
if ($value instanceof Dog) {
return $DogType;
@ -66,32 +78,31 @@ class UnionInterfaceTest extends TestCase
if ($value instanceof Cat) {
return $CatType;
}
}
},
]);
$PersonType = new ObjectType([
'name' => 'Person',
'name' => 'Person',
'interfaces' => [$NamedType],
'fields' => [
'name' => ['type' => Type::string()],
'pets' => ['type' => Type::listOf($PetType)],
'friends' => ['type' => Type::listOf($NamedType)]
'fields' => [
'name' => ['type' => Type::string()],
'pets' => ['type' => Type::listOf($PetType)],
'friends' => ['type' => Type::listOf($NamedType)],
],
'isTypeOf' => function ($value) {
'isTypeOf' => function ($value) {
return $value instanceof Person;
}
},
]);
$this->schema = new Schema([
'query' => $PersonType,
'types' => [ $PetType ]
'types' => [$PetType],
]);
$this->garfield = new Cat('Garfield', false);
$this->odie = new Dog('Odie', true);
$this->liz = new Person('Liz');
$this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]);
$this->odie = new Dog('Odie', true);
$this->liz = new Person('Liz');
$this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]);
}
// Execute: Union and intersection types
@ -101,7 +112,6 @@ class UnionInterfaceTest extends TestCase
*/
public function testCanIntrospectOnUnionAndIntersectionTypes() : void
{
$ast = Parser::parse('
{
Named: __type(name: "Named") {
@ -128,33 +138,33 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'Named' => [
'kind' => 'INTERFACE',
'name' => 'Named',
'fields' => [
['name' => 'name']
'kind' => 'INTERFACE',
'name' => 'Named',
'fields' => [
['name' => 'name'],
],
'interfaces' => null,
'interfaces' => null,
'possibleTypes' => [
['name' => 'Person'],
['name' => 'Dog'],
['name' => 'Cat']
['name' => 'Cat'],
],
'enumValues' => null,
'inputFields' => null
'enumValues' => null,
'inputFields' => null,
],
'Pet' => [
'kind' => 'UNION',
'name' => 'Pet',
'fields' => null,
'interfaces' => null,
'Pet' => [
'kind' => 'UNION',
'name' => 'Pet',
'fields' => null,
'interfaces' => null,
'possibleTypes' => [
['name' => 'Dog'],
['name' => 'Cat']
['name' => 'Cat'],
],
'enumValues' => null,
'inputFields' => null
]
]
'enumValues' => null,
'inputFields' => null,
],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray());
}
@ -165,7 +175,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUsingUnionTypes() : void
{
// NOTE: This is an *invalid* query, but it should be an *executable* query.
$ast = Parser::parse('
$ast = Parser::parse('
{
__typename
name
@ -180,12 +190,12 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'pets' => [
'name' => 'John',
'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
]
]
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -197,7 +207,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUnionTypesWithInlineFragments() : void
{
// This is the valid version of the query in the above test.
$ast = Parser::parse('
$ast = Parser::parse('
{
__typename
name
@ -217,13 +227,13 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'pets' => [
'name' => 'John',
'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
]
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
],
]
],
];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
}
@ -234,7 +244,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUsingInterfaceTypes() : void
{
// NOTE: This is an *invalid* query, but it should be an *executable* query.
$ast = Parser::parse('
$ast = Parser::parse('
{
__typename
name
@ -249,12 +259,12 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'friends' => [
'name' => 'John',
'friends' => [
['__typename' => 'Person', 'name' => 'Liz'],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
]
]
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -266,7 +276,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesInterfaceTypesWithInlineFragments() : void
{
// This is the valid version of the query in the above test.
$ast = Parser::parse('
$ast = Parser::parse('
{
__typename
name
@ -285,12 +295,12 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'friends' => [
'name' => 'John',
'friends' => [
['__typename' => 'Person', 'name' => 'Liz'],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
]
]
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true));
@ -336,16 +346,16 @@ class UnionInterfaceTest extends TestCase
$expected = [
'data' => [
'__typename' => 'Person',
'name' => 'John',
'pets' => [
'name' => 'John',
'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
],
'friends' => [
'friends' => [
['__typename' => 'Person', 'name' => 'Liz'],
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
]
]
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
],
],
];
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -356,36 +366,45 @@ class UnionInterfaceTest extends TestCase
*/
public function testGetsExecutionInfoInResolver() : void
{
$encounteredContext = null;
$encounteredSchema = null;
$encounteredContext = null;
$encounteredSchema = null;
$encounteredRootValue = null;
$PersonType2 = null;
$PersonType2 = null;
$NamedType2 = new InterfaceType([
'name' => 'Named',
'fields' => [
'name' => ['type' => Type::string()]
'name' => 'Named',
'fields' => [
'name' => ['type' => Type::string()],
],
'resolveType' => function ($obj, $context, ResolveInfo $info) use (&$encounteredContext, &$encounteredSchema, &$encounteredRootValue, &$PersonType2) {
$encounteredContext = $context;
$encounteredSchema = $info->schema;
'resolveType' => function (
$obj,
$context,
ResolveInfo $info
) use (
&$encounteredContext,
&
$encounteredSchema,
&$encounteredRootValue,
&$PersonType2
) {
$encounteredContext = $context;
$encounteredSchema = $info->schema;
$encounteredRootValue = $info->rootValue;
return $PersonType2;
}
},
]);
$PersonType2 = new ObjectType([
'name' => 'Person',
'name' => 'Person',
'interfaces' => [$NamedType2],
'fields' => [
'name' => ['type' => Type::string()],
'fields' => [
'name' => ['type' => Type::string()],
'friends' => ['type' => Type::listOf($NamedType2)],
],
]);
$schema2 = new Schema([
'query' => $PersonType2
]);
$schema2 = new Schema(['query' => $PersonType2]);
$john2 = new Person('John', [], [$this->liz]);

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Values;
@ -10,19 +13,97 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function count;
use function var_export;
use const PHP_EOL;
class ValuesTest extends TestCase
{
/** @var Schema */
private static $schema;
public function testGetIDVariableValues() : void
{
$this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']);
$this->assertEquals(
['errors'=> [], 'coerced' => ['idInput' => '123456789']],
self::runTestCase(['idInput' => 123456789]),
['errors' => [], 'coerced' => ['idInput' => '123456789']],
$this->runTestCase(['idInput' => 123456789]),
'Integer ID was not converted to string'
);
}
private function expectInputVariablesMatchOutputVariables($variables) : void
{
$this->assertEquals(
$variables,
$this->runTestCase($variables)['coerced'],
'Output variables did not match input variables' . PHP_EOL . var_export($variables, true) . PHP_EOL
);
}
/**
* @param mixed[] $variables
* @return mixed[]
*/
private function runTestCase($variables) : array
{
return Values::getVariableValues(self::getSchema(), self::getVariableDefinitionNodes(), $variables);
}
private static function getSchema() : Schema
{
if (! self::$schema) {
self::$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'test' => [
'type' => Type::boolean(),
'args' => [
'idInput' => Type::id(),
'boolInput' => Type::boolean(),
'intInput' => Type::int(),
'stringInput' => Type::string(),
'floatInput' => Type::float(),
],
],
],
]),
]);
}
return self::$schema;
}
/**
* @return VariableDefinitionNode[]
*/
private static function getVariableDefinitionNodes() : array
{
$idInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'idInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'ID'])]),
]);
$boolInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'boolInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Boolean'])]),
]);
$intInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'intInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Int'])]),
]);
$stringInputDefintion = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'stringInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'String'])]),
]);
$floatInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'floatInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Float'])]),
]);
return [$idInputDefinition, $boolInputDefinition, $intInputDefinition, $stringInputDefintion, $floatInputDefinition];
}
public function testGetBooleanVariableValues() : void
{
$this->expectInputVariablesMatchOutputVariables(['boolInput' => true]);
@ -64,11 +145,20 @@ class ValuesTest extends TestCase
$this->expectGraphQLError(['idInput' => true]);
}
private function expectGraphQLError($variables) : void
{
$result = $this->runTestCase($variables);
$this->assertGreaterThan(0, count($result['errors']));
}
public function testFloatForIDVariableThrowsError() : void
{
$this->expectGraphQLError(['idInput' => 1.0]);
}
/**
* Helpers for running test cases and making assertions
*/
public function testStringForBooleanVariableThrowsError() : void
{
$this->expectGraphQLError(['boolInput' => 'true']);
@ -98,77 +188,4 @@ class ValuesTest extends TestCase
{
$this->expectGraphQLError(['intInput' => -2147483649]);
}
// Helpers for running test cases and making assertions
private function expectInputVariablesMatchOutputVariables($variables)
{
$this->assertEquals(
$variables,
self::runTestCase($variables)['coerced'],
'Output variables did not match input variables' . PHP_EOL . var_export($variables, true) . PHP_EOL
);
}
private function expectGraphQLError($variables)
{
$result = self::runTestCase($variables);
$this->assertGreaterThan(0, count($result['errors']));
}
private static $schema;
private static function getSchema()
{
if (!self::$schema) {
self::$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'test' => [
'type' => Type::boolean(),
'args' => [
'idInput' => Type::id(),
'boolInput' => Type::boolean(),
'intInput' => Type::int(),
'stringInput' => Type::string(),
'floatInput' => Type::float()
]
],
]
])
]);
}
return self::$schema;
}
private static function getVariableDefinitionNodes()
{
$idInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'idInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'ID'])])
]);
$boolInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'boolInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Boolean'])])
]);
$intInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'intInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Int'])])
]);
$stringInputDefintion = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'stringInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'String'])])
]);
$floatInputDefinition = new VariableDefinitionNode([
'variable' => new VariableNode(['name' => new NameNode(['value' => 'floatInput'])]),
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Float'])])
]);
return [$idInputDefinition, $boolInputDefinition, $intInputDefinition, $stringInputDefintion, $floatInputDefinition];
}
private function runTestCase($variables)
{
return Values::getVariableValues(self::getSchema(), self::getVariableDefinitionNodes(), $variables);
}
}

View File

@ -1,16 +1,19 @@
<?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Error\Error;
use GraphQL\Executor\Executor;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Tests\Executor\TestClasses\ComplexScalar;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function json_encode;
/**
* Execute: Handles inputs
@ -18,7 +21,6 @@ use PHPUnit\Framework\TestCase;
*/
class VariablesTest extends TestCase
{
public function testUsingInlineStructs() : void
{
// executes with complex input:
@ -29,14 +31,12 @@ class VariablesTest extends TestCase
');
$expected = [
'data' => [
'fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'
]
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
];
$this->assertEquals($expected, $result->toArray());
// properly parses single value to list:
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"})
}
@ -46,7 +46,7 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
// properly parses null value to null
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null})
}
@ -56,7 +56,7 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
// properly parses null value in list
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"})
}
@ -73,12 +73,13 @@ class VariablesTest extends TestCase
');
$expected = [
'data' => ['fieldWithObjectInput' => null],
'data' => ['fieldWithObjectInput' => null],
'errors' => [[
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
'path' => ['fieldWithObjectInput'],
'locations' => [['line' => 3, 'column' => 39]]
]]
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
'path' => ['fieldWithObjectInput'],
'locations' => [['line' => 3, 'column' => 39]],
],
],
];
$this->assertArraySubset($expected, $result->toArray());
@ -94,6 +95,77 @@ class VariablesTest extends TestCase
);
}
private function executeQuery($query, $variableValues = null)
{
$document = Parser::parse($query);
return Executor::execute($this->schema(), $document, null, null, $variableValues);
}
/**
* Describe: Handles nullable scalars
*/
public function schema() : Schema
{
$ComplexScalarType = ComplexScalar::create();
$TestInputObject = new InputObjectType([
'name' => 'TestInputObject',
'fields' => [
'a' => ['type' => Type::string()],
'b' => ['type' => Type::listOf(Type::string())],
'c' => ['type' => Type::nonNull(Type::string())],
'd' => ['type' => $ComplexScalarType],
],
]);
$TestNestedInputObject = new InputObjectType([
'name' => 'TestNestedInputObject',
'fields' => [
'na' => ['type' => Type::nonNull($TestInputObject)],
'nb' => ['type' => Type::nonNull(Type::string())],
],
]);
$TestType = new ObjectType([
'name' => 'TestType',
'fields' => [
'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]),
'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]),
'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]),
'fieldWithDefaultArgumentValue' => $this->fieldWithInputArg([
'type' => Type::string(),
'defaultValue' => 'Hello World',
]),
'fieldWithNestedInputObject' => $this->fieldWithInputArg([
'type' => $TestNestedInputObject,
'defaultValue' => 'Hello World',
]),
'list' => $this->fieldWithInputArg(['type' => Type::listOf(Type::string())]),
'nnList' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::string()))]),
'listNN' => $this->fieldWithInputArg(['type' => Type::listOf(Type::nonNull(Type::string()))]),
'nnListNN' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]),
],
]);
return new Schema(['query' => $TestType]);
}
private function fieldWithInputArg($inputArg)
{
return [
'type' => Type::string(),
'args' => ['input' => $inputArg],
'resolve' => function ($_, $args) {
if (isset($args['input'])) {
return json_encode($args['input']);
}
return null;
},
];
}
public function testUsingVariables() : void
{
$doc = '
@ -119,7 +191,7 @@ class VariablesTest extends TestCase
');
$expected = [
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}']
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
];
$this->assertEquals($expected, $result->toArray());
@ -132,63 +204,61 @@ class VariablesTest extends TestCase
);
// executes with complex scalar input:
$params = [ 'input' => [ 'c' => 'foo', 'd' => 'SerializedValue' ] ];
$result = $this->executeQuery($doc, $params);
$params = ['input' => ['c' => 'foo', 'd' => 'SerializedValue']];
$result = $this->executeQuery($doc, $params);
$expected = [
'data' => [
'fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'
]
'data' => ['fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'],
];
$this->assertEquals($expected, $result->toArray());
// errors on null for nested non-null:
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]];
$result = $this->executeQuery($doc, $params);
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]];
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value ' .
'{"a":"foo","b":"bar","c":null}; ' .
'Expected non-nullable type String! not to be null at value.c.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql'
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
// errors on incorrect type:
$params = [ 'input' => 'foo bar' ];
$result = $this->executeQuery($doc, $params);
$params = ['input' => 'foo bar'];
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value "foo bar"; ' .
'Expected type TestInputObject to be an object.',
'locations' => [ [ 'line' => 2, 'column' => 21 ] ],
'category' => 'graphql',
]
]
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
// errors on omission of nested non-null:
$params = ['input' => ['a' => 'foo', 'b' => 'bar']];
$result = $this->executeQuery($doc, $params);
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
[
'message' =>
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; '.
'message' =>
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; ' .
'Field value.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
@ -198,62 +268,59 @@ class VariablesTest extends TestCase
fieldWithNestedObjectInput(input: $input)
}
';
$params = [ 'input' => [ 'na' => [ 'a' => 'foo' ] ] ];
$params = ['input' => ['na' => ['a' => 'foo']]];
$result = $this->executeQuery($nestedDoc, $params);
$result = $this->executeQuery($nestedDoc, $params);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
'Field value.na.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql',
'category' => 'graphql',
],
[
'message' =>
'message' =>
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
'Field value.nb of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
// errors on addition of unknown input field
$params = ['input' => [ 'a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog' ]];
$result = $this->executeQuery($doc, $params);
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog']];
$result = $this->executeQuery($doc, $params);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value ' .
'{"a":"foo","b":"bar","c":"baz","extra":"dog"}; ' .
'Field "extra" is not defined by type TestInputObject.',
'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles nullable scalars
/**
* @see it('allows nullable inputs to be omitted')
*/
public function testAllowsNullableInputsToBeOmitted() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNullableStringInput
}
');
$expected = [
'data' => ['fieldWithNullableStringInput' => null]
'data' => ['fieldWithNullableStringInput' => null],
];
$this->assertEquals($expected, $result->toArray());
@ -264,7 +331,7 @@ class VariablesTest extends TestCase
*/
public function testAllowsNullableInputsToBeOmittedInAVariable() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value)
}
@ -279,7 +346,7 @@ class VariablesTest extends TestCase
*/
public function testAllowsNullableInputsToBeOmittedInAnUnlistedVariable() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
query SetsNullable {
fieldWithNullableStringInput(input: $value)
}
@ -288,12 +355,15 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles non-nullable scalars
/**
* @see it('allows nullable inputs to be set to null in a variable')
*/
public function testAllowsNullableInputsToBeSetToNullInAVariable() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value)
}
@ -308,12 +378,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsNullableInputsToBeSetToAValueInAVariable() : void
{
$doc = '
$doc = '
query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value)
}
';
$result = $this->executeQuery($doc, ['value' => 'a']);
$result = $this->executeQuery($doc, ['value' => 'a']);
$expected = ['data' => ['fieldWithNullableStringInput' => '"a"']];
$this->assertEquals($expected, $result->toArray());
}
@ -323,7 +393,7 @@ class VariablesTest extends TestCase
*/
public function testAllowsNullableInputsToBeSetToAValueDirectly() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNullableStringInput(input: "a")
}
@ -332,21 +402,18 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles non-nullable scalars
/**
* @see it('allows non-nullable inputs to be omitted given a default')
*/
public function testAllowsNonNullableInputsToBeOmittedGivenADefault() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
query SetsNonNullable($value: String = "default") {
fieldWithNonNullableStringInput(input: $value)
}
');
$expected = [
'data' => ['fieldWithNonNullableStringInput' => '"default"']
'data' => ['fieldWithNonNullableStringInput' => '"default"'],
];
$this->assertEquals($expected, $result->toArray());
}
@ -365,11 +432,11 @@ class VariablesTest extends TestCase
$expected = [
'errors' => [
[
'message' => 'Variable "$value" of required type "String!" was not provided.',
'message' => 'Variable "$value" of required type "String!" was not provided.',
'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql'
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -379,22 +446,22 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable() : void
{
$doc = '
$doc = '
query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value)
}
';
$result = $this->executeQuery($doc, ['value' => null]);
$result = $this->executeQuery($doc, ['value' => null]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$value" got invalid value null; ' .
'Expected non-nullable type String! not to be null.',
'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -404,12 +471,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullableInputsToBeSetToAValueInAVariable() : void
{
$doc = '
$doc = '
query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value)
}
';
$result = $this->executeQuery($doc, ['value' => 'a']);
$result = $this->executeQuery($doc, ['value' => 'a']);
$expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']];
$this->assertEquals($expected, $result->toArray());
}
@ -419,7 +486,7 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullableInputsToBeSetToAValueDirectly() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNonNullableStringInput(input: "a")
}
@ -433,47 +500,50 @@ class VariablesTest extends TestCase
*/
public function testReportsErrorForMissingNonNullableInputs() : void
{
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNonNullableStringInput
}
');
$expected = [
'data' => ['fieldWithNonNullableStringInput' => null],
'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[
'message' => 'Argument "input" of required type "String!" was not provided.',
'locations' => [ [ 'line' => 3, 'column' => 9 ] ],
'path' => [ 'fieldWithNonNullableStringInput' ],
'category' => 'graphql',
]]
'message' => 'Argument "input" of required type "String!" was not provided.',
'locations' => [['line' => 3, 'column' => 9]],
'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles lists and nullability
/**
* @see it('reports error for array passed into string input')
*/
public function testReportsErrorForArrayPassedIntoStringInput() : void
{
$doc = '
$doc = '
query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value)
}
';
$variables = ['value' => [1, 2, 3]];
$result = $this->executeQuery($doc, $variables);
$result = $this->executeQuery($doc, $variables);
$expected = [
'errors' => [[
'message' =>
'message' =>
'Variable "$value" got invalid value [1,2,3]; Expected type ' .
'String; String cannot represent an array value: [1,2,3]',
'category' => 'graphql',
'category' => 'graphql',
'locations' => [
['line' => 2, 'column' => 31]
]
]]
['line' => 2, 'column' => 31],
],
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -498,38 +568,37 @@ class VariablesTest extends TestCase
// and are being run against a new schema which have introduced a breaking
// change to make a formerly non-required argument required, this asserts
// failure before allowing the underlying code to receive a non-null value.
$result = $this->executeQuery('
$result = $this->executeQuery('
{
fieldWithNonNullableStringInput(input: $foo)
}
');
$expected = [
'data' => ['fieldWithNonNullableStringInput' => null],
'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[
'message' =>
'message' =>
'Argument "input" of required type "String!" was provided the ' .
'variable "$foo" which was not provided a runtime value.',
'locations' => [['line' => 3, 'column' => 48]],
'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql',
]]
'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Handles lists and nullability
/**
* @see it('allows lists to be null')
*/
public function testAllowsListsToBeNull() : void
{
$doc = '
$doc = '
query q($input:[String]) {
list(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => null]);
$result = $this->executeQuery($doc, ['input' => null]);
$expected = ['data' => ['list' => null]];
$this->assertEquals($expected, $result->toArray());
@ -540,12 +609,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsListsToContainValues() : void
{
$doc = '
$doc = '
query q($input:[String]) {
list(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A']]);
$result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['list' => '["A"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -555,12 +624,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsListsToContainNull() : void
{
$doc = '
$doc = '
query q($input:[String]) {
list(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A',null,'B']]);
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = ['data' => ['list' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -570,22 +639,22 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowNonNullListsToBeNull() : void
{
$doc = '
$doc = '
query q($input:[String]!) {
nnList(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => null]);
$result = $this->executeQuery($doc, ['input' => null]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String]! not to be null.',
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -595,12 +664,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullListsToContainValues() : void
{
$doc = '
$doc = '
query q($input:[String]!) {
nnList(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A']]);
$result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['nnList' => '["A"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -610,12 +679,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullListsToContainNull() : void
{
$doc = '
$doc = '
query q($input:[String]!) {
nnList(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A',null,'B']]);
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = ['data' => ['nnList' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -625,12 +694,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsListsOfNonNullsToBeNull() : void
{
$doc = '
$doc = '
query q($input:[String!]) {
listNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => null]);
$result = $this->executeQuery($doc, ['input' => null]);
$expected = ['data' => ['listNN' => null]];
$this->assertEquals($expected, $result->toArray());
}
@ -640,12 +709,12 @@ class VariablesTest extends TestCase
*/
public function testAllowsListsOfNonNullsToContainValues() : void
{
$doc = '
$doc = '
query q($input:[String!]) {
listNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A']]);
$result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['listNN' => '["A"]']];
$this->assertEquals($expected, $result->toArray());
}
@ -655,22 +724,22 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowListsOfNonNullsToContainNull() : void
{
$doc = '
$doc = '
query q($input:[String!]) {
listNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ],
'category' => 'graphql',
]
]
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -680,22 +749,22 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowNonNullListsOfNonNullsToBeNull() : void
{
$doc = '
$doc = '
query q($input:[String!]!) {
nnListNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => null]);
$result = $this->executeQuery($doc, ['input' => null]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String!]! not to be null.',
'locations' => [ ['line' => 2, 'column' => 17] ],
'category' => 'graphql',
]
]
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -705,37 +774,39 @@ class VariablesTest extends TestCase
*/
public function testAllowsNonNullListsOfNonNullsToContainValues() : void
{
$doc = '
$doc = '
query q($input:[String!]!) {
nnListNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A']]);
$result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['nnListNN' => '["A"]']];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Execute: Uses argument default values
/**
* @see it('does not allow non-null lists of non-nulls to contain null')
*/
public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() : void
{
$doc = '
$doc = '
query q($input:[String!]!) {
nnListNN(input: $input)
}
';
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ],
'category' => 'graphql',
]
]
'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -745,23 +816,23 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowInvalidTypesToBeUsedAsValues() : void
{
$doc = '
$doc = '
query q($input: TestType!) {
fieldWithObjectInput(input: $input)
}
';
$vars = [ 'input' => [ 'list' => [ 'A', 'B' ] ] ];
$result = $this->executeQuery($doc, $vars);
$vars = ['input' => ['list' => ['A', 'B']]];
$result = $this->executeQuery($doc, $vars);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" expected value of type "TestType!" which cannot ' .
'be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -771,29 +842,28 @@ class VariablesTest extends TestCase
*/
public function testDoesNotAllowUnknownTypesToBeUsedAsValues() : void
{
$doc = '
$doc = '
query q($input: UnknownType!) {
fieldWithObjectInput(input: $input)
}
';
$vars = ['input' => 'whoknows'];
$result = $this->executeQuery($doc, $vars);
$result = $this->executeQuery($doc, $vars);
$expected = [
'errors' => [
[
'message' =>
'message' =>
'Variable "$input" expected value of type "UnknownType!" which ' .
'cannot be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql',
]
]
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
// Describe: Execute: Uses argument default values
/**
* @see it('when no argument provided')
*/
@ -834,84 +904,17 @@ class VariablesTest extends TestCase
}');
$expected = [
'data' => ['fieldWithDefaultArgumentValue' => null],
'data' => ['fieldWithDefaultArgumentValue' => null],
'errors' => [[
'message' =>
'message' =>
'Argument "input" has invalid value WRONG_TYPE.',
'locations' => [ [ 'line' => 2, 'column' => 50 ] ],
'path' => [ 'fieldWithDefaultArgumentValue' ],
'category' => 'graphql',
]]
'locations' => [['line' => 2, 'column' => 50]],
'path' => ['fieldWithDefaultArgumentValue'],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
public function schema()
{
$ComplexScalarType = ComplexScalar::create();
$TestInputObject = new InputObjectType([
'name' => 'TestInputObject',
'fields' => [
'a' => ['type' => Type::string()],
'b' => ['type' => Type::listOf(Type::string())],
'c' => ['type' => Type::nonNull(Type::string())],
'd' => ['type' => $ComplexScalarType],
]
]);
$TestNestedInputObject = new InputObjectType([
'name' => 'TestNestedInputObject',
'fields' => [
'na' => [ 'type' => Type::nonNull($TestInputObject) ],
'nb' => [ 'type' => Type::nonNull(Type::string()) ],
],
]);
$TestType = new ObjectType([
'name' => 'TestType',
'fields' => [
'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]),
'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]),
'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]),
'fieldWithDefaultArgumentValue' => $this->fieldWithInputArg([
'type' => Type::string(),
'defaultValue' => 'Hello World',
]),
'fieldWithNestedInputObject' => $this->fieldWithInputArg([
'type' => $TestNestedInputObject,
'defaultValue' => 'Hello World'
]),
'list' => $this->fieldWithInputArg(['type' => Type::listOf(Type::string())]),
'nnList' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::string()))]),
'listNN' => $this->fieldWithInputArg(['type' => Type::listOf(Type::nonNull(Type::string()))]),
'nnListNN' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]),
]
]);
$schema = new Schema(['query' => $TestType]);
return $schema;
}
private function fieldWithInputArg($inputArg)
{
return [
'type' => Type::string(),
'args' => ['input' => $inputArg],
'resolve' => function ($_, $args) {
if (isset($args['input'])) {
return json_encode($args['input']);
}
return null;
},
];
}
private function executeQuery($query, $variableValues = null)
{
$document = Parser::parse($query);
return Executor::execute($this->schema(), $document, null, null, $variableValues);
}
}

View File

@ -1,13 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\Lexer;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use GraphQL\Language\Token;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase;
use function count;
use function json_decode;
class LexerTest extends TestCase
{
@ -23,17 +28,45 @@ class LexerTest extends TestCase
);
}
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
$this->lexOne($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
}
}
/**
* @param string $body
* @return Token
*/
private function lexOne($body)
{
$lexer = new Lexer(new Source($body));
return $lexer->advance();
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
/**
* @see it('accepts BOM header')
*/
public function testAcceptsBomHeader() : void
{
$bom = Utils::chr(0xFEFF);
$bom = Utils::chr(0xFEFF);
$expected = [
'kind' => Token::NAME,
'kind' => Token::NAME,
'start' => 2,
'end' => 5,
'value' => 'foo'
'end' => 5,
'value' => 'foo',
];
$this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo'));
@ -45,12 +78,12 @@ class LexerTest extends TestCase
public function testRecordsLineAndColumn() : void
{
$expected = [
'kind' => Token::NAME,
'start' => 8,
'end' => 11,
'line' => 4,
'kind' => Token::NAME,
'start' => 8,
'end' => 11,
'line' => 4,
'column' => 3,
'value' => 'foo'
'value' => 'foo',
];
$this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n"));
}
@ -67,10 +100,10 @@ class LexerTest extends TestCase
';
$expected = [
'kind' => Token::NAME,
'kind' => Token::NAME,
'start' => 6,
'end' => 9,
'value' => 'foo'
'end' => 9,
'value' => 'foo',
];
$this->assertArraySubset($expected, (array) $this->lexOne($example1));
@ -80,18 +113,18 @@ class LexerTest extends TestCase
';
$expected = [
'kind' => Token::NAME,
'kind' => Token::NAME,
'start' => 18,
'end' => 21,
'value' => 'foo'
'end' => 21,
'value' => 'foo',
];
$this->assertArraySubset($expected, (array) $this->lexOne($example2));
$expected = [
'kind' => Token::NAME,
'kind' => Token::NAME,
'start' => 3,
'end' => 6,
'value' => 'foo'
'end' => 6,
'value' => 'foo',
];
$example3 = ',,,foo,,,';
@ -131,7 +164,7 @@ class LexerTest extends TestCase
*/
public function testUpdatesLineNumbersInErrorForFileContext() : void
{
$str = '' .
$str = '' .
"\n" .
"\n" .
" ?\n" .
@ -181,63 +214,86 @@ class LexerTest extends TestCase
*/
public function testLexesStrings() : void
{
$this->assertArraySubset([
'kind' => Token::STRING,
'start' => 0,
'end' => 8,
'value' => 'simple'
], (array) $this->lexOne('"simple"'));
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 8,
'value' => 'simple',
],
(array) $this->lexOne('"simple"')
);
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 15,
'value' => ' white space ',
],
(array) $this->lexOne('" white space "')
);
$this->assertArraySubset([
'kind' => Token::STRING,
'start' => 0,
'end' => 15,
'value' => ' white space '
], (array) $this->lexOne('" white space "'));
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 10,
'value' => 'quote "',
],
(array) $this->lexOne('"quote \\""')
);
$this->assertArraySubset([
'kind' => Token::STRING,
'start' => 0,
'end' => 10,
'value' => 'quote "'
], (array) $this->lexOne('"quote \\""'));
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 25,
'value' => 'escaped \n\r\b\t\f',
],
(array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')
);
$this->assertArraySubset([
'kind' => Token::STRING,
'start' => 0,
'end' => 25,
'value' => 'escaped \n\r\b\t\f'
], (array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"'));
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 16,
'value' => 'slashes \\ \/',
],
(array) $this->lexOne('"slashes \\\\ \\\\/"')
);
$this->assertArraySubset([
'kind' => Token::STRING,
'start' => 0,
'end' => 16,
'value' => 'slashes \\ \/'
], (array) $this->lexOne('"slashes \\\\ \\\\/"'));
$this->assertArraySubset([
'kind' => Token::STRING,
'start' => 0,
'end' => 13,
'value' => 'unicode яуц'
], (array) $this->lexOne('"unicode яуц"'));
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 13,
'value' => 'unicode яуц',
],
(array) $this->lexOne('"unicode яуц"')
);
$unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"');
$this->assertArraySubset([
'kind' => Token::STRING,
'start' => 0,
'end' => 34,
'value' => 'unicode ' . $unicode
], (array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"'));
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 34,
'value' => 'unicode ' . $unicode,
],
(array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')
);
$this->assertArraySubset([
'kind' => Token::STRING,
'start' => 0,
'end' => 26,
'value' => $unicode
], (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"'));
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 26,
'value' => $unicode,
],
(array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')
);
}
/**
@ -245,86 +301,128 @@ class LexerTest extends TestCase
*/
public function testLexesBlockString() : void
{
$this->assertArraySubset([
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 12,
'value' => 'simple'
], (array) $this->lexOne('"""simple"""'));
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 12,
'value' => 'simple',
],
(array) $this->lexOne('"""simple"""')
);
$this->assertArraySubset([
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 19,
'value' => ' white space '
], (array) $this->lexOne('""" white space """'));
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 19,
'value' => ' white space ',
],
(array) $this->lexOne('""" white space """')
);
$this->assertArraySubset([
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 22,
'value' => 'contains " quote'
], (array) $this->lexOne('"""contains " quote"""'));
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 22,
'value' => 'contains " quote',
],
(array) $this->lexOne('"""contains " quote"""')
);
$this->assertArraySubset([
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 31,
'value' => 'contains """ triplequote'
], (array) $this->lexOne('"""contains \\""" triplequote"""'));
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 31,
'value' => 'contains """ triplequote',
],
(array) $this->lexOne('"""contains \\""" triplequote"""')
);
$this->assertArraySubset([
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 16,
'value' => "multi\nline"
], (array) $this->lexOne("\"\"\"multi\nline\"\"\""));
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 16,
'value' => "multi\nline",
],
(array) $this->lexOne("\"\"\"multi\nline\"\"\"")
);
$this->assertArraySubset([
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 28,
'value' => "multi\nline\nnormalized"
], (array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\""));
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 28,
'value' => "multi\nline\nnormalized",
],
(array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")
);
$this->assertArraySubset([
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 32,
'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234'
], (array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""'));
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 32,
'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234',
],
(array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""')
);
$this->assertArraySubset([
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 19,
'value' => 'slashes \\\\ \\/'
], (array) $this->lexOne('"""slashes \\\\ \\/"""'));
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 19,
'value' => 'slashes \\\\ \\/',
],
(array) $this->lexOne('"""slashes \\\\ \\/"""')
);
$this->assertArraySubset([
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 68,
'value' => "spans\n multiple\n lines"
], (array) $this->lexOne("\"\"\"
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 68,
'value' => "spans\n multiple\n lines",
],
(array) $this->lexOne('"""
spans
multiple
lines
\"\"\""));
"""')
);
}
public function reportsUsefulStringErrors() {
public function reportsUsefulStringErrors()
{
return [
['"', "Unterminated string.", $this->loc(1, 2)],
['"no end quote', "Unterminated string.", $this->loc(1, 14)],
["'single quotes'", "Unexpected single quote character ('), did you mean to use a double quote (\")?", $this->loc(1, 1)],
['"contains unescaped \u0007 control char"', "Invalid character within String: \"\\u0007\"", $this->loc(1, 21)],
['"', 'Unterminated string.', $this->loc(1, 2)],
['"no end quote', 'Unterminated string.', $this->loc(1, 14)],
[
"'single quotes'",
"Unexpected single quote character ('), did you mean to use a double quote (\")?",
$this->loc(
1,
1
),
],
[
'"contains unescaped \u0007 control char"',
"Invalid character within String: \"\\u0007\"",
$this->loc(
1,
21
),
],
['"null-byte is not \u0000 end of file"', 'Invalid character within String: "\\u0000"', $this->loc(1, 19)],
['"multi' . "\n" . 'line"', "Unterminated string.", $this->loc(1, 7)],
['"multi' . "\r" . 'line"', "Unterminated string.", $this->loc(1, 7)],
['"bad \\z esc"', "Invalid character escape sequence: \\z", $this->loc(1, 7)],
['"multi' . "\n" . 'line"', 'Unterminated string.', $this->loc(1, 7)],
['"multi' . "\r" . 'line"', 'Unterminated string.', $this->loc(1, 7)],
['"bad \\z esc"', 'Invalid character escape sequence: \\z', $this->loc(1, 7)],
['"bad \\x esc"', "Invalid character escape sequence: \\x", $this->loc(1, 7)],
['"bad \\u1 esc"', "Invalid character escape sequence: \\u1 es", $this->loc(1, 7)],
['"bad \\u0XX1 esc"', "Invalid character escape sequence: \\u0XX1", $this->loc(1, 7)],
@ -336,25 +434,40 @@ class LexerTest extends TestCase
/**
* @dataProvider reportsUsefulStringErrors
* @see it('lex reports useful string errors')
* @see it('lex reports useful string errors')
*/
public function testLexReportsUsefulStringErrors($str, $expectedMessage, $location) : void
{
$this->expectSyntaxError($str, $expectedMessage, $location);
}
public function reportsUsefulBlockStringErrors() {
public function reportsUsefulBlockStringErrors()
{
return [
['"""', "Unterminated string.", $this->loc(1, 4)],
['"""no end quote', "Unterminated string.", $this->loc(1, 16)],
['"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""', "Invalid character within String: \"\\u0007\"", $this->loc(1, 23)],
['"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""', "Invalid character within String: \"\\u0000\"", $this->loc(1, 21)],
['"""', 'Unterminated string.', $this->loc(1, 4)],
['"""no end quote', 'Unterminated string.', $this->loc(1, 16)],
[
'"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""',
"Invalid character within String: \"\\u0007\"",
$this->loc(
1,
23
),
],
[
'"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""',
"Invalid character within String: \"\\u0000\"",
$this->loc(
1,
21
),
],
];
}
/**
* @dataProvider reportsUsefulBlockStringErrors
* @see it('lex reports useful block string errors')
* @see it('lex reports useful block string errors')
*/
public function testReportsUsefulBlockStringErrors($str, $expectedMessage, $location) : void
{
@ -435,21 +548,21 @@ class LexerTest extends TestCase
public function reportsUsefulNumberErrors()
{
return [
[ '00', "Invalid number, unexpected digit after 0: \"0\"", $this->loc(1, 2)],
[ '+1', "Cannot parse the unexpected character \"+\".", $this->loc(1, 1)],
[ '1.', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 3)],
[ '1.e1', "Invalid number, expected digit but got: \"e\"", $this->loc(1, 3)],
[ '.123', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)],
[ '1.A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 3)],
[ '-A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 2)],
[ '1.0e', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 5)],
[ '1.0eA', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 5)],
['00', 'Invalid number, unexpected digit after 0: "0"', $this->loc(1, 2)],
['+1', 'Cannot parse the unexpected character "+".', $this->loc(1, 1)],
['1.', 'Invalid number, expected digit but got: <EOF>', $this->loc(1, 3)],
['1.e1', 'Invalid number, expected digit but got: "e"', $this->loc(1, 3)],
['.123', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)],
['1.A', 'Invalid number, expected digit but got: "A"', $this->loc(1, 3)],
['-A', 'Invalid number, expected digit but got: "A"', $this->loc(1, 2)],
['1.0e', 'Invalid number, expected digit but got: <EOF>', $this->loc(1, 5)],
['1.0eA', 'Invalid number, expected digit but got: "A"', $this->loc(1, 5)],
];
}
/**
* @dataProvider reportsUsefulNumberErrors
* @see it('lex reports useful number errors')
* @see it('lex reports useful number errors')
*/
public function testReportsUsefulNumberErrors($str, $expectedMessage, $location) : void
{
@ -521,8 +634,8 @@ class LexerTest extends TestCase
$unicode2 = json_decode('"\u200b"');
return [
['..', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)],
['?', "Cannot parse the unexpected character \"?\".", $this->loc(1, 1)],
['..', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)],
['?', 'Cannot parse the unexpected character "?".', $this->loc(1, 1)],
[$unicode1, "Cannot parse the unexpected character \"\\u203b\".", $this->loc(1, 1)],
[$unicode2, "Cannot parse the unexpected character \"\\u200b\".", $this->loc(1, 1)],
];
@ -530,7 +643,7 @@ class LexerTest extends TestCase
/**
* @dataProvider reportsUsefulUnknownCharErrors
* @see it('lex reports useful unknown character error')
* @see it('lex reports useful unknown character error')
*/
public function testReportsUsefulUnknownCharErrors($str, $expectedMessage, $location) : void
{
@ -542,17 +655,20 @@ class LexerTest extends TestCase
*/
public function testReportsUsefulDashesInfo() : void
{
$q = 'a-b';
$q = 'a-b';
$lexer = new Lexer(new Source($q));
$this->assertArraySubset(['kind' => Token::NAME, 'start' => 0, 'end' => 1, 'value' => 'a'], (array) $lexer->advance());
$this->assertArraySubset(
['kind' => Token::NAME, 'start' => 0, 'end' => 1, 'value' => 'a'],
(array) $lexer->advance()
);
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"');
try {
$lexer->advance();
$this->fail('Expected exception not thrown');
} catch(SyntaxError $error) {
$this->assertEquals([$this->loc(1,3)], $error->getLocations());
} catch (SyntaxError $error) {
$this->assertEquals([$this->loc(1, 3)], $error->getLocations());
throw $error;
}
}
@ -580,49 +696,28 @@ class LexerTest extends TestCase
$tokens = [];
for ($tok = $startToken; $tok; $tok = $tok->next) {
if (!empty($tokens)) {
if (! empty($tokens)) {
// Tokens are double-linked, prev should point to last seen token.
$this->assertSame($tokens[count($tokens) - 1], $tok->prev);
}
$tokens[] = $tok;
}
$this->assertEquals([
'<SOF>',
'{',
'Comment',
'Name',
'}',
'<EOF>'
], Utils::map($tokens, function ($tok) {
return $tok->kind;
}));
}
/**
* @param string $body
* @return Token
*/
private function lexOne($body)
{
$lexer = new Lexer(new Source($body));
return $lexer->advance();
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
$this->lexOne($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
}
$this->assertEquals(
[
'<SOF>',
'{',
'Comment',
'Name',
'}',
'<EOF>',
],
Utils::map(
$tokens,
function ($tok) {
return $tok->kind;
}
)
);
}
}

View File

@ -1,7 +1,11 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Error\InvariantViolation;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NameNode;
@ -13,9 +17,10 @@ use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase;
use function file_get_contents;
use function sprintf;
class ParserTest extends TestCase
{
@ -43,22 +48,40 @@ class ParserTest extends TestCase
public function parseProvidesUsefulErrors()
{
return [
['{', "Syntax Error: Expected Name, found <EOF>", "Syntax Error: Expected Name, found <EOF>\n\nGraphQL request (1:2)\n1: {\n ^\n", [1], [new SourceLocation(1, 2)]],
['{ ...MissingOn }
[
'{',
'Syntax Error: Expected Name, found <EOF>',
"Syntax Error: Expected Name, found <EOF>\n\nGraphQL request (1:2)\n1: {\n ^\n",
[1],
[new SourceLocation(
1,
2
),
],
],
[
'{ ...MissingOn }
fragment MissingOn Type
', "Syntax Error: Expected \"on\", found Name \"Type\"", "Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n",],
['{ field: {} }', "Syntax Error: Expected Name, found {", "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"],
['notanoperation Foo { field }', "Syntax Error: Unexpected Name \"notanoperation\"", "Syntax Error: Unexpected Name \"notanoperation\"\n\nGraphQL request (1:1)\n1: notanoperation Foo { field }\n ^\n"],
['...', "Syntax Error: Unexpected ...", "Syntax Error: Unexpected ...\n\nGraphQL request (1:1)\n1: ...\n ^\n"],
', 'Syntax Error: Expected "on", found Name "Type"',
"Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n",
],
['{ field: {} }', 'Syntax Error: Expected Name, found {', "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"],
['notanoperation Foo { field }', 'Syntax Error: Unexpected Name "notanoperation"', "Syntax Error: Unexpected Name \"notanoperation\"\n\nGraphQL request (1:1)\n1: notanoperation Foo { field }\n ^\n"],
['...', 'Syntax Error: Unexpected ...', "Syntax Error: Unexpected ...\n\nGraphQL request (1:1)\n1: ...\n ^\n"],
];
}
/**
* @dataProvider parseProvidesUsefulErrors
* @see it('parse provides useful errors')
* @see it('parse provides useful errors')
*/
public function testParseProvidesUsefulErrors($str, $expectedMessage, $stringRepresentation, $expectedPositions = null, $expectedLocations = null) : void
{
public function testParseProvidesUsefulErrors(
$str,
$expectedMessage,
$stringRepresentation,
$expectedPositions = null,
$expectedLocations = null
) : void {
try {
Parser::parse($str);
$this->fail('Expected exception not thrown');
@ -110,10 +133,27 @@ fragment MissingOn Type
$this->expectSyntaxError(
'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }',
'Unexpected $',
$this->loc(1,37)
$this->loc(1, 37)
);
}
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
Parser::parse($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
}
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
/**
* @see it('does not accept fragments spread of "on"')
*/
@ -122,7 +162,7 @@ fragment MissingOn Type
$this->expectSyntaxError(
'fragment on on on { on }',
'Unexpected Name "on"',
$this->loc(1,10)
$this->loc(1, 10)
);
}
@ -134,7 +174,7 @@ fragment MissingOn Type
$this->expectSyntaxError(
'{ ...on }',
'Expected Name, found }',
$this->loc(1,9)
$this->loc(1, 9)
);
}
@ -145,7 +185,7 @@ fragment MissingOn Type
{
// Note: \u0A0A could be naively interpretted as two line-feed chars.
$char = Utils::chr(0x0A0A);
$char = Utils::chr(0x0A0A);
$query = <<<HEREDOC
# This comment has a $char multi-byte character.
{ field(arg: "Has a $char multi-byte character.") }
@ -156,18 +196,18 @@ HEREDOC;
$expected = new SelectionSetNode([
'selections' => new NodeList([
new FieldNode([
'name' => new NameNode(['value' => 'field']),
'arguments' => new NodeList([
'name' => new NameNode(['value' => 'field']),
'arguments' => new NodeList([
new ArgumentNode([
'name' => new NameNode(['value' => 'arg']),
'value' => new StringValueNode([
'value' => "Has a $char multi-byte character."
])
])
'name' => new NameNode(['value' => 'arg']),
'value' => new StringValueNode(
['value' => sprintf('Has a %s multi-byte character.', $char)]
),
]),
]),
'directives' => new NodeList([])
])
])
'directives' => new NodeList([]),
]),
]),
]);
$this->assertEquals($expected, $result->definitions[0]->selectionSet);
@ -180,7 +220,7 @@ HEREDOC;
{
// Following should not throw:
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$result = Parser::parse($kitchenSink);
$result = Parser::parse($kitchenSink);
$this->assertNotEmpty($result);
}
@ -196,7 +236,7 @@ HEREDOC;
'mutation',
'subscription',
'true',
'false'
'false',
];
foreach ($nonKeywords as $keyword) {
$fragmentName = $keyword;
@ -205,14 +245,19 @@ HEREDOC;
}
// Expected not to throw:
$result = Parser::parse("query $keyword {
... $fragmentName
... on $keyword { field }
$result = Parser::parse(<<<GRAPHQL
query $keyword {
... $fragmentName
... on $keyword { field }
}
fragment $fragmentName on Type {
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
}
");
fragment $fragmentName on Type {
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
}
GRAPHQL
);
$this->assertNotEmpty($result);
}
}
@ -286,94 +331,102 @@ fragment $fragmentName on Type {
');
$result = Parser::parse($source);
$loc = function($start, $end) use ($source) {
$loc = function ($start, $end) {
return [
'start' => $start,
'end' => $end
'end' => $end,
];
};
$expected = [
'kind' => NodeKind::DOCUMENT,
'loc' => $loc(0, 41),
'kind' => NodeKind::DOCUMENT,
'loc' => $loc(0, 41),
'definitions' => [
[
'kind' => NodeKind::OPERATION_DEFINITION,
'loc' => $loc(0, 40),
'operation' => 'query',
'name' => null,
'kind' => NodeKind::OPERATION_DEFINITION,
'loc' => $loc(0, 40),
'operation' => 'query',
'name' => null,
'variableDefinitions' => [],
'directives' => [],
'selectionSet' => [
'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(0, 40),
'directives' => [],
'selectionSet' => [
'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(0, 40),
'selections' => [
[
'kind' => NodeKind::FIELD,
'loc' => $loc(4, 38),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(4, 8),
'value' => 'node'
'kind' => NodeKind::FIELD,
'loc' => $loc(4, 38),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(4, 8),
'value' => 'node',
],
'arguments' => [
'arguments' => [
[
'kind' => NodeKind::ARGUMENT,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(9, 11),
'value' => 'id'
'kind' => NodeKind::ARGUMENT,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(9, 11),
'value' => 'id',
],
'value' => [
'kind' => NodeKind::INT,
'loc' => $loc(13, 14),
'value' => '4'
'kind' => NodeKind::INT,
'loc' => $loc(13, 14),
'value' => '4',
],
'loc' => $loc(9, 14, $source)
]
'loc' => $loc(9, 14, $source),
],
],
'directives' => [],
'directives' => [],
'selectionSet' => [
'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(16, 38),
'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(16, 38),
'selections' => [
[
'kind' => NodeKind::FIELD,
'loc' => $loc(22, 24),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(22, 24),
'value' => 'id'
'kind' => NodeKind::FIELD,
'loc' => $loc(22, 24),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(22, 24),
'value' => 'id',
],
'arguments' => [],
'directives' => [],
'selectionSet' => null
'arguments' => [],
'directives' => [],
'selectionSet' => null,
],
[
'kind' => NodeKind::FIELD,
'loc' => $loc(30, 34),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(30, 34),
'value' => 'name'
'kind' => NodeKind::FIELD,
'loc' => $loc(30, 34),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(30, 34),
'value' => 'name',
],
'arguments' => [],
'directives' => [],
'selectionSet' => null
]
]
]
]
]
]
]
]
'arguments' => [],
'directives' => [],
'selectionSet' => null,
],
],
],
],
],
],
],
],
];
$this->assertEquals($expected, $this->nodeToArray($result));
$this->assertEquals($expected, self::nodeToArray($result));
}
/**
* @return mixed[]
*/
public static function nodeToArray(Node $node) : array
{
return TestUtils::nodeToArray($node);
}
/**
@ -389,63 +442,63 @@ fragment $fragmentName on Type {
');
$result = Parser::parse($source);
$loc = function($start, $end) use ($source) {
$loc = function ($start, $end) {
return [
'start' => $start,
'end' => $end
'end' => $end,
];
};
$expected = [
'kind' => NodeKind::DOCUMENT,
'loc' => $loc(0, 30),
'kind' => NodeKind::DOCUMENT,
'loc' => $loc(0, 30),
'definitions' => [
[
'kind' => NodeKind::OPERATION_DEFINITION,
'loc' => $loc(0, 29),
'operation' => 'query',
'name' => null,
'kind' => NodeKind::OPERATION_DEFINITION,
'loc' => $loc(0, 29),
'operation' => 'query',
'name' => null,
'variableDefinitions' => [],
'directives' => [],
'selectionSet' => [
'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(6, 29),
'directives' => [],
'selectionSet' => [
'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(6, 29),
'selections' => [
[
'kind' => NodeKind::FIELD,
'loc' => $loc(10, 27),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(10, 14),
'value' => 'node'
'kind' => NodeKind::FIELD,
'loc' => $loc(10, 27),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(10, 14),
'value' => 'node',
],
'arguments' => [],
'directives' => [],
'arguments' => [],
'directives' => [],
'selectionSet' => [
'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(15, 27),
'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(15, 27),
'selections' => [
[
'kind' => NodeKind::FIELD,
'loc' => $loc(21, 23),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(21, 23),
'value' => 'id'
'kind' => NodeKind::FIELD,
'loc' => $loc(21, 23),
'alias' => null,
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(21, 23),
'value' => 'id',
],
'arguments' => [],
'directives' => [],
'selectionSet' => null
]
]
]
]
]
]
]
]
'arguments' => [],
'directives' => [],
'selectionSet' => null,
],
],
],
],
],
],
],
],
];
$this->assertEquals($expected, $this->nodeToArray($result));
@ -475,6 +528,8 @@ fragment $fragmentName on Type {
Parser::parse($source);
}
// Describe: parseValue
/**
* @see it('contains location information that only stringifys start/end')
*/
@ -495,6 +550,8 @@ fragment $fragmentName on Type {
$this->assertEquals($source, $result->loc->source);
}
// Describe: parseType
/**
* @see it('contains references to start and end tokens')
*/
@ -506,17 +563,18 @@ fragment $fragmentName on Type {
$this->assertEquals('<EOF>', $result->loc->endToken->kind);
}
// Describe: parseValue
/**
* @see it('parses null value')
*/
public function testParsesNullValues() : void
{
$this->assertEquals([
'kind' => NodeKind::NULL,
'loc' => ['start' => 0, 'end' => 4]
], $this->nodeToArray(Parser::parseValue('null')));
$this->assertEquals(
[
'kind' => NodeKind::NULL,
'loc' => ['start' => 0, 'end' => 4],
],
$this->nodeToArray(Parser::parseValue('null'))
);
}
/**
@ -524,41 +582,45 @@ fragment $fragmentName on Type {
*/
public function testParsesListValues() : void
{
$this->assertEquals([
'kind' => NodeKind::LST,
'loc' => ['start' => 0, 'end' => 11],
'values' => [
[
'kind' => NodeKind::INT,
'loc' => ['start' => 1, 'end' => 4],
'value' => '123'
$this->assertEquals(
[
'kind' => NodeKind::LST,
'loc' => ['start' => 0, 'end' => 11],
'values' => [
[
'kind' => NodeKind::INT,
'loc' => ['start' => 1, 'end' => 4],
'value' => '123',
],
[
'kind' => NodeKind::STRING,
'loc' => ['start' => 5, 'end' => 10],
'value' => 'abc',
'block' => false,
],
],
[
'kind' => NodeKind::STRING,
'loc' => ['start' => 5, 'end' => 10],
'value' => 'abc',
'block' => false
]
]
], $this->nodeToArray(Parser::parseValue('[123 "abc"]')));
],
$this->nodeToArray(Parser::parseValue('[123 "abc"]'))
);
}
// Describe: parseType
/**
* @see it('parses well known types')
*/
public function testParsesWellKnownTypes() : void
{
$this->assertEquals([
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6],
'value' => 'String'
]
], $this->nodeToArray(Parser::parseType('String')));
$this->assertEquals(
[
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6],
'value' => 'String',
],
],
$this->nodeToArray(Parser::parseType('String'))
);
}
/**
@ -566,15 +628,18 @@ fragment $fragmentName on Type {
*/
public function testParsesCustomTypes() : void
{
$this->assertEquals([
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6],
'value' => 'MyType'
]
], $this->nodeToArray(Parser::parseType('MyType')));
$this->assertEquals(
[
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6],
'value' => 'MyType',
],
],
$this->nodeToArray(Parser::parseType('MyType'))
);
}
/**
@ -582,19 +647,22 @@ fragment $fragmentName on Type {
*/
public function testParsesListTypes() : void
{
$this->assertEquals([
'kind' => NodeKind::LIST_TYPE,
'loc' => ['start' => 0, 'end' => 8],
'type' => [
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 1, 'end' => 7],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 1, 'end' => 7],
'value' => 'MyType'
]
]
], $this->nodeToArray(Parser::parseType('[MyType]')));
$this->assertEquals(
[
'kind' => NodeKind::LIST_TYPE,
'loc' => ['start' => 0, 'end' => 8],
'type' => [
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 1, 'end' => 7],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 1, 'end' => 7],
'value' => 'MyType',
],
],
],
$this->nodeToArray(Parser::parseType('[MyType]'))
);
}
/**
@ -602,19 +670,22 @@ fragment $fragmentName on Type {
*/
public function testParsesNonNullTypes() : void
{
$this->assertEquals([
'kind' => NodeKind::NON_NULL_TYPE,
'loc' => ['start' => 0, 'end' => 7],
'type' => [
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6],
'value' => 'MyType'
]
]
], $this->nodeToArray(Parser::parseType('MyType!')));
$this->assertEquals(
[
'kind' => NodeKind::NON_NULL_TYPE,
'loc' => ['start' => 0, 'end' => 7],
'type' => [
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6],
'value' => 'MyType',
],
],
],
$this->nodeToArray(Parser::parseType('MyType!'))
);
}
/**
@ -622,48 +693,25 @@ fragment $fragmentName on Type {
*/
public function testParsesNestedTypes() : void
{
$this->assertEquals([
'kind' => NodeKind::LIST_TYPE,
'loc' => ['start' => 0, 'end' => 9],
'type' => [
'kind' => NodeKind::NON_NULL_TYPE,
'loc' => ['start' => 1, 'end' => 8],
$this->assertEquals(
[
'kind' => NodeKind::LIST_TYPE,
'loc' => ['start' => 0, 'end' => 9],
'type' => [
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 1, 'end' => 7],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 1, 'end' => 7],
'value' => 'MyType'
]
]
]
], $this->nodeToArray(Parser::parseType('[MyType!]')));
}
/**
* @param Node $node
* @return array
*/
public static function nodeToArray(Node $node)
{
return TestUtils::nodeToArray($node);
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
Parser::parse($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
}
'kind' => NodeKind::NON_NULL_TYPE,
'loc' => ['start' => 1, 'end' => 8],
'type' => [
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 1, 'end' => 7],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 1, 'end' => 7],
'value' => 'MyType',
],
],
],
],
$this->nodeToArray(Parser::parseType('[MyType!]'))
);
}
}

View File

@ -1,18 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Printer;
use PHPUnit\Framework\TestCase;
use function file_get_contents;
class PrinterTest extends TestCase
{
@ -22,7 +19,7 @@ class PrinterTest extends TestCase
public function testDoesntAlterAST() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink);
$ast = Parser::parse($kitchenSink);
$astCopy = $ast->cloneDeep();
$this->assertEquals($astCopy, $ast);
@ -66,7 +63,7 @@ class PrinterTest extends TestCase
$this->assertEquals($expected, Printer::doPrint($queryAstShorthanded));
$mutationAst = Parser::parse('mutation { id, name }');
$expected = 'mutation {
$expected = 'mutation {
id
name
}
@ -76,7 +73,7 @@ class PrinterTest extends TestCase
$queryAstWithArtifacts = Parser::parse(
'query ($foo: TestType) @testDirective { id, name }'
);
$expected = 'query ($foo: TestType) @testDirective {
$expected = 'query ($foo: TestType) @testDirective {
id
name
}
@ -86,7 +83,7 @@ class PrinterTest extends TestCase
$mutationAstWithArtifacts = Parser::parse(
'mutation ($foo: TestType) @testDirective { id, name }'
);
$expected = 'mutation ($foo: TestType) @testDirective {
$expected = 'mutation ($foo: TestType) @testDirective {
id
name
}
@ -100,13 +97,13 @@ class PrinterTest extends TestCase
public function testCorrectlyPrintsSingleLineBlockStringsWithLeadingSpace() : void
{
$mutationAstWithArtifacts = Parser::parse(
'{ field(arg: """ space-led value""") }'
'{ field(arg: """ space-led value""") }'
);
$expected = '{
$expected = '{
field(arg: """ space-led value""")
}
';
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
}
/**
@ -122,8 +119,8 @@ class PrinterTest extends TestCase
indentation
""")
}'
);
$expected = '{
);
$expected = '{
field(arg: """
first
line
@ -131,7 +128,7 @@ class PrinterTest extends TestCase
""")
}
';
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
}
/**
@ -145,7 +142,7 @@ class PrinterTest extends TestCase
""")
}
');
$expected = <<<END
$expected = <<<END
{
field(arg: """ space-led value "quoted string"
""")
@ -160,7 +157,8 @@ END;
*/
public function testExperimentalCorrectlyPrintsFragmentDefinedVariables() : void
{
$fragmentWithVariable = Parser::parse('
$fragmentWithVariable = Parser::parse(
'
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
id
}
@ -187,13 +185,13 @@ END;
field(arg: """ space-led value "quoted string"
""")
}'
);
$expected = '{
);
$expected = '{
field(arg: """ space-led value "quoted string"
""")
}
';
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
}
/**
@ -202,7 +200,7 @@ END;
public function testPrintsKitchenSink() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink);
$ast = Parser::parse($kitchenSink);
$printed = Printer::doPrint($ast);

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
<?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
@ -7,6 +10,7 @@ use GraphQL\Language\Parser;
use GraphQL\Language\Printer;
use PHPUnit\Framework\TestCase;
use Throwable;
use function file_get_contents;
class SchemaPrinterTest extends TestCase
{
@ -16,7 +20,7 @@ class SchemaPrinterTest extends TestCase
public function testPrintsMinimalAst() : void
{
$ast = new ScalarTypeDefinitionNode([
'name' => new NameNode(['value' => 'foo'])
'name' => new NameNode(['value' => 'foo']),
]);
$this->assertEquals('scalar foo', Printer::doPrint($ast));
}
@ -41,7 +45,7 @@ class SchemaPrinterTest extends TestCase
{
$kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink);
$ast = Parser::parse($kitchenSink);
$astCopy = $ast->cloneDeep();
Printer::doPrint($ast);
@ -52,7 +56,7 @@ class SchemaPrinterTest extends TestCase
{
$kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink);
$ast = Parser::parse($kitchenSink);
$printed = Printer::doPrint($ast);
$expected = 'schema {

View File

@ -1,5 +1,8 @@
<?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Node;
@ -7,80 +10,68 @@ use GraphQL\Language\AST\NodeList;
use GraphQL\Language\Parser;
use GraphQL\Utils\AST;
use PHPUnit\Framework\TestCase;
use function array_keys;
use function count;
use function file_get_contents;
use function get_class;
use function get_object_vars;
use function implode;
use function json_decode;
class SerializationTest extends TestCase
{
public function testSerializesAst() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink);
$ast = Parser::parse($kitchenSink);
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
$this->assertEquals($expectedAst, $ast->toArray(true));
}
public function testUnserializesAst() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
$actualAst = AST::fromArray($serializedAst);
$parsedAst = Parser::parse($kitchenSink);
$this->assertNodesAreEqual($parsedAst, $actualAst);
}
public function testSerializeSupportsNoLocationOption() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink, ['noLocation' => true]);
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
$this->assertEquals($expectedAst, $ast->toArray(true));
}
public function testUnserializeSupportsNoLocationOption() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
$actualAst = AST::fromArray($serializedAst);
$parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]);
$actualAst = AST::fromArray($serializedAst);
$parsedAst = Parser::parse($kitchenSink);
$this->assertNodesAreEqual($parsedAst, $actualAst);
}
/**
* Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc
*
* @param $expected
* @param $actual
* @param array $path
* @param string[] $path
*/
private function assertNodesAreEqual($expected, $actual, $path = [])
private function assertNodesAreEqual(Node $expected, Node $actual, array $path = []) : void
{
$err = "Mismatch at AST path: " . implode(', ', $path);
$err = 'Mismatch at AST path: ' . implode(', ', $path);
$this->assertInstanceOf(Node::class, $actual, $err);
$this->assertEquals(get_class($expected), get_class($actual), $err);
$expectedVars = get_object_vars($expected);
$actualVars = get_object_vars($actual);
$this->assertSame(count($expectedVars), count($actualVars), $err);
$actualVars = get_object_vars($actual);
$this->assertCount(count($expectedVars), $actualVars, $err);
$this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err);
foreach ($expectedVars as $name => $expectedValue) {
$actualValue = $actualVars[$name];
$tmpPath = $path;
$tmpPath[] = $name;
$err = "Mismatch at AST path: " . implode(', ', $tmpPath);
$tmpPath = $path;
$tmpPath[] = $name;
$err = 'Mismatch at AST path: ' . implode(', ', $tmpPath);
if ($expectedValue instanceof Node) {
$this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath);
} else if ($expectedValue instanceof NodeList) {
} elseif ($expectedValue instanceof NodeList) {
$this->assertEquals(count($expectedValue), count($actualValue), $err);
$this->assertInstanceOf(NodeList::class, $actualValue, $err);
foreach ($expectedValue as $index => $listNode) {
$tmpPath2 = $tmpPath;
$tmpPath2 [] = $index;
$tmpPath2 = $tmpPath;
$tmpPath2[] = $index;
$this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2);
}
} else if ($expectedValue instanceof Location) {
} elseif ($expectedValue instanceof Location) {
$this->assertInstanceOf(Location::class, $actualValue, $err);
$this->assertSame($expectedValue->start, $actualValue->start, $err);
$this->assertSame($expectedValue->end, $actualValue->end, $err);
@ -89,4 +80,21 @@ class SerializationTest extends TestCase
}
}
}
public function testSerializeSupportsNoLocationOption() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink, ['noLocation' => true]);
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
$this->assertEquals($expectedAst, $ast->toArray(true));
}
public function testUnserializeSupportsNoLocationOption() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
$actualAst = AST::fromArray($serializedAst);
$parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]);
$this->assertNodesAreEqual($parsedAst, $actualAst);
}
}

View File

@ -1,36 +1,41 @@
<?php
namespace GraphQL\Tests\Language;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList;
use function get_object_vars;
use function is_array;
use function is_scalar;
class TestUtils
{
/**
* @param Node $node
* @return array
* @return mixed[]
*/
public static function nodeToArray(Node $node)
public static function nodeToArray(Node $node) : array
{
$result = [
'kind' => $node->kind,
'loc' => self::locationToArray($node->loc)
'loc' => self::locationToArray($node->loc),
];
foreach (get_object_vars($node) as $prop => $propValue) {
if (isset($result[$prop]))
if (isset($result[$prop])) {
continue;
}
if (is_array($propValue) || $propValue instanceof NodeList) {
$tmp = [];
foreach ($propValue as $tmp1) {
$tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1;
}
} else if ($propValue instanceof Node) {
} elseif ($propValue instanceof Node) {
$tmp = self::nodeToArray($propValue);
} else if (is_scalar($propValue) || null === $propValue) {
} elseif (is_scalar($propValue) || $propValue === null) {
$tmp = $propValue;
} else {
$tmp = null;
@ -38,27 +43,25 @@ class TestUtils
$result[$prop] = $tmp;
}
return $result;
}
/**
* @param Location $loc
* @return array
* @return int[]
*/
public static function locationToArray(Location $loc)
public static function locationToArray(Location $loc) : array
{
return [
'start' => $loc->start,
'end' => $loc->end
'end' => $loc->end,
];
}
/**
* @param $start
* @param $end
* @return array
* @return int[]
*/
public static function locArray($start, $end)
public static function locArray(int $start, int $end) : array
{
return ['start' => $start, 'end' => $end];
}

View File

@ -1,5 +1,8 @@
<?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\Token;
use PHPUnit\Framework\TestCase;
@ -8,12 +11,12 @@ class TokenTest extends TestCase
{
public function testReturnTokenOnArray() : void
{
$token = new Token('Kind', 1, 10, 3, 5);
$token = new Token('Kind', 1, 10, 3, 5);
$expected = [
'kind' => 'Kind',
'value' => null,
'line' => 3,
'column' => 5
'kind' => 'Kind',
'value' => null,
'line' => 3,
'column' => 5,
];
$this->assertEquals($expected, $token->toArray());

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Server\Psr7;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use function strtolower;
/**
* phpcs:ignoreFile -- this is not a core file
*/
class PsrRequestStub implements ServerRequestInterface
{
public $queryParams = [];
@ -13,14 +20,10 @@ class PsrRequestStub implements ServerRequestInterface
public $method;
/**
* @var PsrStreamStub
*/
/** @var PsrStreamStub */
public $body;
/**
* @var array
*/
/** @var mixed[] */
public $parsedBody;
/**
@ -32,7 +35,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function getProtocolVersion()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -50,7 +53,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withProtocolVersion($version)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -80,7 +83,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function getHeaders()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -93,7 +96,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function hasHeader($name)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -113,7 +116,8 @@ class PsrRequestStub implements ServerRequestInterface
public function getHeader($name)
{
$name = strtolower($name);
return isset($this->headers[$name]) ? $this->headers[$name] : [];
return $this->headers[$name] ?? [];
}
/**
@ -137,7 +141,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function getHeaderLine($name)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -150,14 +154,14 @@ class PsrRequestStub implements ServerRequestInterface
* immutability of the message, and MUST return an instance that has the
* new and/or updated header and value.
*
* @param string $name Case-insensitive header field name.
* @param string $name Case-insensitive header field name.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withHeader($name, $value)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -171,14 +175,14 @@ class PsrRequestStub implements ServerRequestInterface
* immutability of the message, and MUST return an instance that has the
* new header and/or value.
*
* @param string $name Case-insensitive header field name to add.
* @param string $name Case-insensitive header field name to add.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withAddedHeader($name, $value)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -195,7 +199,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withoutHeader($name)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -223,7 +227,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withBody(StreamInterface $body)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -244,7 +248,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function getRequestTarget()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -266,7 +270,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withRequestTarget($requestTarget)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -296,7 +300,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withMethod($method)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -339,13 +343,13 @@ class PsrRequestStub implements ServerRequestInterface
* new UriInterface instance.
*
* @link http://tools.ietf.org/html/rfc3986#section-4.3
* @param UriInterface $uri New request URI to use.
* @param bool $preserveHost Preserve the original state of the Host header.
* @param UriInterface $uri New request URI to use.
* @param bool $preserveHost Preserve the original state of the Host header.
* @return static
*/
public function withUri(UriInterface $uri, $preserveHost = false)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -359,7 +363,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function getServerParams()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -374,7 +378,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function getCookieParams()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -396,7 +400,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withCookieParams(array $cookies)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -440,7 +444,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withQueryParams(array $query)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -457,7 +461,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function getUploadedFiles()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -473,7 +477,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withUploadedFiles(array $uploadedFiles)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -526,7 +530,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withParsedBody($data)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -542,7 +546,7 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function getAttributes()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -556,13 +560,13 @@ class PsrRequestStub implements ServerRequestInterface
* specifying a default value to return if the attribute is not found.
*
* @see getAttributes()
* @param string $name The attribute name.
* @param mixed $default Default value to return if the attribute does not exist.
* @param string $name The attribute name.
* @param mixed $default Default value to return if the attribute does not exist.
* @return mixed
*/
public function getAttribute($name, $default = null)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -576,13 +580,13 @@ class PsrRequestStub implements ServerRequestInterface
* updated attribute.
*
* @see getAttributes()
* @param string $name The attribute name.
* @param mixed $value The value of the attribute.
* @param string $name The attribute name.
* @param mixed $value The value of the attribute.
* @return static
*/
public function withAttribute($name, $value)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -601,6 +605,6 @@ class PsrRequestStub implements ServerRequestInterface
*/
public function withoutAttribute($name)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
}

View File

@ -1,10 +1,15 @@
<?php
namespace GraphQL\Tests\Server\Psr7;
declare(strict_types=1);
namespace GraphQL\Tests\Server\Psr7;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
/**
* phpcs:ignoreFile -- this is not a core file
*/
class PsrResponseStub implements ResponseInterface
{
public $headers = [];
@ -22,7 +27,7 @@ class PsrResponseStub implements ResponseInterface
*/
public function getProtocolVersion()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -40,7 +45,7 @@ class PsrResponseStub implements ResponseInterface
*/
public function withProtocolVersion($version)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -70,7 +75,7 @@ class PsrResponseStub implements ResponseInterface
*/
public function getHeaders()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -83,7 +88,7 @@ class PsrResponseStub implements ResponseInterface
*/
public function hasHeader($name)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -102,7 +107,7 @@ class PsrResponseStub implements ResponseInterface
*/
public function getHeader($name)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -126,7 +131,7 @@ class PsrResponseStub implements ResponseInterface
*/
public function getHeaderLine($name)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -139,15 +144,16 @@ class PsrResponseStub implements ResponseInterface
* immutability of the message, and MUST return an instance that has the
* new and/or updated header and value.
*
* @param string $name Case-insensitive header field name.
* @param string $name Case-insensitive header field name.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withHeader($name, $value)
{
$tmp = clone $this;
$tmp = clone $this;
$tmp->headers[$name][] = $value;
return $tmp;
}
@ -162,14 +168,14 @@ class PsrResponseStub implements ResponseInterface
* immutability of the message, and MUST return an instance that has the
* new header and/or value.
*
* @param string $name Case-insensitive header field name to add.
* @param string $name Case-insensitive header field name to add.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withAddedHeader($name, $value)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -186,7 +192,7 @@ class PsrResponseStub implements ResponseInterface
*/
public function withoutHeader($name)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -196,7 +202,7 @@ class PsrResponseStub implements ResponseInterface
*/
public function getBody()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -214,8 +220,9 @@ class PsrResponseStub implements ResponseInterface
*/
public function withBody(StreamInterface $body)
{
$tmp = clone $this;
$tmp = clone $this;
$tmp->body = $body;
return $tmp;
}
@ -229,7 +236,7 @@ class PsrResponseStub implements ResponseInterface
*/
public function getStatusCode()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -245,7 +252,7 @@ class PsrResponseStub implements ResponseInterface
*
* @link http://tools.ietf.org/html/rfc7231#section-6
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
* @param int $code The 3-digit integer result code to set.
* @param int $code The 3-digit integer result code to set.
* @param string $reasonPhrase The reason phrase to use with the
* provided status code; if none is provided, implementations MAY
* use the defaults as suggested in the HTTP specification.
@ -254,8 +261,9 @@ class PsrResponseStub implements ResponseInterface
*/
public function withStatus($code, $reasonPhrase = '')
{
$tmp = clone $this;
$tmp = clone $this;
$tmp->statusCode = $code;
return $tmp;
}
@ -274,6 +282,6 @@ class PsrResponseStub implements ResponseInterface
*/
public function getReasonPhrase()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
}
}

View File

@ -1,8 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Server\Psr7;
use Psr\Http\Message\StreamInterface;
use function strlen;
use const SEEK_SET;
/**
* phpcs:ignoreFile -- this is not a core file
*/
class PsrStreamStub implements StreamInterface
{
public $content;
@ -33,7 +41,7 @@ class PsrStreamStub implements StreamInterface
*/
public function close()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -45,7 +53,7 @@ class PsrStreamStub implements StreamInterface
*/
public function detach()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -55,7 +63,7 @@ class PsrStreamStub implements StreamInterface
*/
public function getSize()
{
return strlen($this->content?:'');
return strlen($this->content ?: '');
}
/**
@ -66,7 +74,7 @@ class PsrStreamStub implements StreamInterface
*/
public function tell()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -76,7 +84,7 @@ class PsrStreamStub implements StreamInterface
*/
public function eof()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -86,7 +94,7 @@ class PsrStreamStub implements StreamInterface
*/
public function isSeekable()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -103,7 +111,7 @@ class PsrStreamStub implements StreamInterface
*/
public function seek($offset, $whence = SEEK_SET)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -118,7 +126,7 @@ class PsrStreamStub implements StreamInterface
*/
public function rewind()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -141,6 +149,7 @@ class PsrStreamStub implements StreamInterface
public function write($string)
{
$this->content = $string;
return strlen($string);
}
@ -151,7 +160,7 @@ class PsrStreamStub implements StreamInterface
*/
public function isReadable()
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -166,7 +175,7 @@ class PsrStreamStub implements StreamInterface
*/
public function read($length)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
/**
@ -195,6 +204,6 @@ class PsrStreamStub implements StreamInterface
*/
public function getMetadata($key = null)
{
throw new \Exception("Not implemented");
throw new \Exception('Not implemented');
}
}

View File

@ -1,18 +1,22 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Server;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Server\Helper;
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
use GraphQL\Tests\Server\Psr7\PsrResponseStub;
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
use PHPUnit\Framework\TestCase;
use function json_encode;
class PsrResponseTest extends TestCase
{
public function testConvertsResultToPsrResponse() : void
{
$result = new ExecutionResult(['key' => 'value']);
$stream = new PsrStreamStub();
$result = new ExecutionResult(['key' => 'value']);
$stream = new PsrStreamStub();
$psrResponse = new PsrResponseStub();
$helper = new Helper();

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Server;
use GraphQL\Error\Debug;
@ -14,17 +17,17 @@ use GraphQL\Server\ServerConfig;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\CustomValidationRule;
use GraphQL\Validator\ValidationContext;
use function count;
use function sprintf;
class QueryExecutionTest extends ServerTestCase
{
/**
* @var ServerConfig
*/
/** @var ServerConfig */
private $config;
public function setUp()
{
$schema = $this->buildSchema();
$schema = $this->buildSchema();
$this->config = ServerConfig::create()
->setSchema($schema);
}
@ -34,20 +37,36 @@ class QueryExecutionTest extends ServerTestCase
$query = '{f1}';
$expected = [
'data' => [
'f1' => 'f1'
]
'data' => ['f1' => 'f1'],
];
$this->assertQueryResultEquals($expected, $query);
}
private function assertQueryResultEquals($expected, $query, $variables = null)
{
$result = $this->executeQuery($query, $variables);
$this->assertArraySubset($expected, $result->toArray(true));
return $result;
}
private function executeQuery($query, $variables = null, $readonly = false)
{
$op = OperationParams::create(['query' => $query, 'variables' => $variables], $readonly);
$helper = new Helper();
$result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
public function testReturnsSyntaxErrors() : void
{
$query = '{f1';
$result = $this->executeQuery($query);
$this->assertSame(null, $result->data);
$this->assertNull($result->data);
$this->assertCount(1, $result->errors);
$this->assertContains(
'Syntax Error: Expected Name, found <EOF>',
@ -70,11 +89,12 @@ class QueryExecutionTest extends ServerTestCase
$expected = [
'data' => [
'fieldWithSafeException' => null,
'f1' => 'f1'
'f1' => 'f1',
],
'errors' => [
[
'message' => 'This is the exception we want',
'path' => ['fieldWithSafeException'],
'trace' => []
]
@ -100,7 +120,7 @@ class QueryExecutionTest extends ServerTestCase
public function testPassesRootValueAndContext() : void
{
$rootValue = 'myRootValue';
$context = new \stdClass();
$context = new \stdClass();
$this->config
->setContext($context)
@ -112,7 +132,7 @@ class QueryExecutionTest extends ServerTestCase
}
';
$this->assertTrue(!isset($context->testedRootValue));
$this->assertTrue(! isset($context->testedRootValue));
$this->executeQuery($query);
$this->assertSame($rootValue, $context->testedRootValue);
}
@ -120,30 +140,30 @@ class QueryExecutionTest extends ServerTestCase
public function testPassesVariables() : void
{
$variables = ['a' => 'a', 'b' => 'b'];
$query = '
$query = '
query ($a: String!, $b: String!) {
a: fieldWithArg(arg: $a)
b: fieldWithArg(arg: $b)
}
';
$expected = [
$expected = [
'data' => [
'a' => 'a',
'b' => 'b'
]
'b' => 'b',
],
];
$this->assertQueryResultEquals($expected, $query, $variables);
}
public function testPassesCustomValidationRules() : void
{
$query = '
$query = '
{nonExistentField}
';
$expected = [
'errors' => [
['message' => 'Cannot query field "nonExistentField" on type "Query".']
]
['message' => 'Cannot query field "nonExistentField" on type "Query".'],
],
];
$this->assertQueryResultEquals($expected, $query);
@ -151,15 +171,16 @@ class QueryExecutionTest extends ServerTestCase
$called = false;
$rules = [
new CustomValidationRule('SomeRule', function() use (&$called) {
new CustomValidationRule('SomeRule', function () use (&$called) {
$called = true;
return [];
})
}),
];
$this->config->setValidationRules($rules);
$expected = [
'data' => []
'data' => [],
];
$this->assertQueryResultEquals($expected, $query);
$this->assertTrue($called);
@ -170,11 +191,12 @@ class QueryExecutionTest extends ServerTestCase
$called = false;
$params = $doc = $operationType = null;
$this->config->setValidationRules(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true;
$params = $p;
$doc = $d;
$this->config->setValidationRules(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true;
$params = $p;
$doc = $d;
$operationType = $o;
return [];
});
@ -188,23 +210,25 @@ class QueryExecutionTest extends ServerTestCase
public function testAllowsDifferentValidationRulesDependingOnOperation() : void
{
$q1 = '{f1}';
$q2 = '{invalid}';
$q1 = '{f1}';
$q2 = '{invalid}';
$called1 = false;
$called2 = false;
$this->config->setValidationRules(function(OperationParams $params) use ($q1, $q2, &$called1, &$called2) {
$this->config->setValidationRules(function (OperationParams $params) use ($q1, &$called1, &$called2) {
if ($params->query === $q1) {
$called1 = true;
return DocumentValidator::allRules();
} else {
$called2 = true;
return [
new CustomValidationRule('MyRule', function(ValidationContext $context) {
$context->reportError(new Error("This is the error we are looking for!"));
})
];
}
$called2 = true;
return [
new CustomValidationRule('MyRule', function (ValidationContext $context) {
$context->reportError(new Error('This is the error we are looking for!'));
}),
];
});
$expected = ['data' => ['f1' => 'f1']];
@ -212,8 +236,8 @@ class QueryExecutionTest extends ServerTestCase
$this->assertTrue($called1);
$this->assertFalse($called2);
$called1 = false;
$called2 = false;
$called1 = false;
$called2 = false;
$expected = ['errors' => [['message' => 'This is the error we are looking for!']]];
$this->assertQueryResultEquals($expected, $q2);
$this->assertFalse($called1);
@ -223,7 +247,7 @@ class QueryExecutionTest extends ServerTestCase
public function testAllowsSkippingValidation() : void
{
$this->config->setValidationRules([]);
$query = '{nonExistentField}';
$query = '{nonExistentField}';
$expected = ['data' => []];
$this->assertQueryResultEquals($expected, $query);
}
@ -235,23 +259,29 @@ class QueryExecutionTest extends ServerTestCase
$expected = [
'errors' => [
[
'message' => 'Persisted queries are not supported by this server',
'category' => 'request'
]
]
'message' => 'Persisted queries are not supported by this server',
'category' => 'request',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
private function executePersistedQuery($queryId, $variables = null)
{
$op = OperationParams::create(['queryId' => $queryId, 'variables' => $variables]);
$helper = new Helper();
$result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
public function testBatchedQueriesAreDisabledByDefault() : void
{
$batch = [
[
'query' => '{invalid}'
],
[
'query' => '{f1,fieldWithSafeException}'
]
['query' => '{invalid}'],
['query' => '{f1,fieldWithSafeException}'],
];
$result = $this->executeBatchedQuery($batch);
@ -260,18 +290,18 @@ class QueryExecutionTest extends ServerTestCase
[
'errors' => [
[
'message' => 'Batched queries are not supported by this server',
'category' => 'request'
]
]
'message' => 'Batched queries are not supported by this server',
'category' => 'request',
],
],
],
[
'errors' => [
[
'message' => 'Batched queries are not supported by this server',
'category' => 'request'
]
]
'message' => 'Batched queries are not supported by this server',
'category' => 'request',
],
],
],
];
@ -279,6 +309,31 @@ class QueryExecutionTest extends ServerTestCase
$this->assertEquals($expected[1], $result[1]->toArray());
}
/**
* @param mixed[][] $qs
*/
private function executeBatchedQuery(array $qs)
{
$batch = [];
foreach ($qs as $params) {
$batch[] = OperationParams::create($params);
}
$helper = new Helper();
$result = $helper->executeBatch($this->config, $batch);
$this->assertInternalType('array', $result);
$this->assertCount(count($qs), $result);
foreach ($result as $index => $entry) {
$this->assertInstanceOf(
ExecutionResult::class,
$entry,
sprintf('Result at %s is not an instance of %s', $index, ExecutionResult::class)
);
}
return $result;
}
public function testMutationsAreNotAllowedInReadonlyMode() : void
{
$mutation = 'mutation { a }';
@ -286,10 +341,10 @@ class QueryExecutionTest extends ServerTestCase
$expected = [
'errors' => [
[
'message' => 'GET supports only query operation',
'category' => 'request'
]
]
'message' => 'GET supports only query operation',
'category' => 'request',
],
],
];
$result = $this->executeQuery($mutation, null, true);
@ -299,9 +354,10 @@ class QueryExecutionTest extends ServerTestCase
public function testAllowsPersistentQueries() : void
{
$called = false;
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) {
$this->config->setPersistentQueryLoader(function ($queryId, OperationParams $params) use (&$called) {
$called = true;
$this->assertEquals('some-id', $queryId);
return '{f1}';
});
@ -309,17 +365,16 @@ class QueryExecutionTest extends ServerTestCase
$this->assertTrue($called);
$expected = [
'data' => [
'f1' => 'f1'
]
'data' => ['f1' => 'f1'],
];
$this->assertEquals($expected, $result->toArray());
// Make sure it allows returning document node:
$called = false;
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) {
$this->config->setPersistentQueryLoader(function ($queryId, OperationParams $params) use (&$called) {
$called = true;
$this->assertEquals('some-id', $queryId);
return Parser::parse('{f1}');
});
$result = $this->executePersistedQuery('some-id');
@ -334,7 +389,7 @@ class QueryExecutionTest extends ServerTestCase
'Persistent query loader must return query string or instance of GraphQL\Language\AST\DocumentNode ' .
'but got: {"err":"err"}'
);
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) {
$this->config->setPersistentQueryLoader(function () {
return ['err' => 'err'];
});
$this->executePersistedQuery('some-id');
@ -342,56 +397,55 @@ class QueryExecutionTest extends ServerTestCase
public function testPersistedQueriesAreStillValidatedByDefault() : void
{
$this->config->setPersistentQueryLoader(function() {
$this->config->setPersistentQueryLoader(function () {
return '{invalid}';
});
$result = $this->executePersistedQuery('some-id');
$result = $this->executePersistedQuery('some-id');
$expected = [
'errors' => [
[
'message' => 'Cannot query field "invalid" on type "Query".',
'locations' => [ ['line' => 1, 'column' => 2] ],
'category' => 'graphql'
]
]
'message' => 'Cannot query field "invalid" on type "Query".',
'locations' => [['line' => 1, 'column' => 2]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
public function testAllowSkippingValidationForPersistedQueries() : void
{
$this->config
->setPersistentQueryLoader(function($queryId) {
->setPersistentQueryLoader(function ($queryId) {
if ($queryId === 'some-id') {
return '{invalid}';
} else {
return '{invalid2}';
}
return '{invalid2}';
})
->setValidationRules(function(OperationParams $params) {
->setValidationRules(function (OperationParams $params) {
if ($params->queryId === 'some-id') {
return [];
} else {
return DocumentValidator::allRules();
}
return DocumentValidator::allRules();
});
$result = $this->executePersistedQuery('some-id');
$result = $this->executePersistedQuery('some-id');
$expected = [
'data' => []
'data' => [],
];
$this->assertEquals($expected, $result->toArray());
$result = $this->executePersistedQuery('some-other-id');
$result = $this->executePersistedQuery('some-other-id');
$expected = [
'errors' => [
[
'message' => 'Cannot query field "invalid2" on type "Query".',
'locations' => [ ['line' => 1, 'column' => 2] ],
'category' => 'graphql'
]
]
'message' => 'Cannot query field "invalid2" on type "Query".',
'locations' => [['line' => 1, 'column' => 2]],
'category' => 'graphql',
],
],
];
$this->assertEquals($expected, $result->toArray());
}
@ -400,7 +454,7 @@ class QueryExecutionTest extends ServerTestCase
{
$this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Expecting validation rules to be array or callable returning array, but got: instance of stdClass');
$this->config->setValidationRules(function(OperationParams $params) {
$this->config->setValidationRules(function (OperationParams $params) {
return new \stdClass();
});
$this->executeQuery('{f1}');
@ -411,28 +465,24 @@ class QueryExecutionTest extends ServerTestCase
$this->config->setQueryBatching(true);
$batch = [
['query' => '{invalid}'],
['query' => '{f1,fieldWithSafeException}'],
[
'query' => '{invalid}'
],
[
'query' => '{f1,fieldWithSafeException}'
],
[
'query' => '
'query' => '
query ($a: String!, $b: String!) {
a: fieldWithArg(arg: $a)
b: fieldWithArg(arg: $b)
}
',
'variables' => ['a' => 'a', 'b' => 'b'],
]
],
];
$result = $this->executeBatchedQuery($batch);
$expected = [
[
'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']]
'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']],
],
[
'data' => [
@ -440,15 +490,15 @@ class QueryExecutionTest extends ServerTestCase
'fieldWithSafeException' => null
],
'errors' => [
['message' => 'This is the exception we want']
]
['message' => 'This is the exception we want'],
],
],
[
'data' => [
'a' => 'a',
'b' => 'b'
]
]
'b' => 'b',
],
],
];
$this->assertArraySubset($expected[0], $result[0]->toArray());
@ -459,15 +509,9 @@ class QueryExecutionTest extends ServerTestCase
public function testDeferredsAreSharedAmongAllBatchedQueries() : void
{
$batch = [
[
'query' => '{dfd(num: 1)}'
],
[
'query' => '{dfd(num: 2)}'
],
[
'query' => '{dfd(num: 3)}',
]
['query' => '{dfd(num: 1)}'],
['query' => '{dfd(num: 2)}'],
['query' => '{dfd(num: 3)}'],
];
$calls = [];
@ -476,13 +520,14 @@ class QueryExecutionTest extends ServerTestCase
->setQueryBatching(true)
->setRootValue('1')
->setContext([
'buffer' => function($num) use (&$calls) {
$calls[] = "buffer: $num";
'buffer' => function ($num) use (&$calls) {
$calls[] = sprintf('buffer: %d', $num);
},
'load' => function ($num) use (&$calls) {
$calls[] = sprintf('load: %d', $num);
return sprintf('loaded: %d', $num);
},
'load' => function($num) use (&$calls) {
$calls[] = "load: $num";
return "loaded: $num";
}
]);
$result = $this->executeBatchedQuery($batch);
@ -499,19 +544,13 @@ class QueryExecutionTest extends ServerTestCase
$expected = [
[
'data' => [
'dfd' => 'loaded: 1'
]
'data' => ['dfd' => 'loaded: 1'],
],
[
'data' => [
'dfd' => 'loaded: 2'
]
'data' => ['dfd' => 'loaded: 2'],
],
[
'data' => [
'dfd' => 'loaded: 3'
]
'data' => ['dfd' => 'loaded: 3'],
],
];
@ -522,7 +561,7 @@ class QueryExecutionTest extends ServerTestCase
public function testValidatesParamsBeforeExecution() : void
{
$op = OperationParams::create(['queryBad' => '{f1}']);
$op = OperationParams::create(['queryBad' => '{f1}']);
$helper = new Helper();
$result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result);
@ -546,10 +585,10 @@ class QueryExecutionTest extends ServerTestCase
$called = false;
$params = $doc = $operationType = null;
$this->config->setContext(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true;
$params = $p;
$doc = $d;
$this->config->setContext(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true;
$params = $p;
$doc = $d;
$operationType = $o;
});
@ -566,10 +605,10 @@ class QueryExecutionTest extends ServerTestCase
$called = false;
$params = $doc = $operationType = null;
$this->config->setRootValue(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true;
$params = $p;
$doc = $d;
$this->config->setRootValue(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true;
$params = $p;
$doc = $d;
$operationType = $o;
});
@ -584,20 +623,21 @@ class QueryExecutionTest extends ServerTestCase
public function testAppliesErrorFormatter() : void
{
$called = false;
$error = null;
$this->config->setErrorFormatter(function($e) use (&$called, &$error) {
$error = null;
$this->config->setErrorFormatter(function ($e) use (&$called, &$error) {
$called = true;
$error = $e;
$error = $e;
return ['test' => 'formatted'];
});
$result = $this->executeQuery('{fieldWithSafeException}');
$this->assertFalse($called);
$formatted = $result->toArray();
$expected = [
$expected = [
'errors' => [
['test' => 'formatted']
]
['test' => 'formatted'],
],
];
$this->assertTrue($called);
$this->assertArraySubset($expected, $formatted);
@ -605,28 +645,29 @@ class QueryExecutionTest extends ServerTestCase
// Assert debugging still works even with custom formatter
$formatted = $result->toArray(Debug::INCLUDE_TRACE);
$expected = [
$expected = [
'errors' => [
[
'test' => 'formatted',
'trace' => []
]
]
'test' => 'formatted',
'trace' => [],
],
],
];
$this->assertArraySubset($expected, $formatted);
}
public function testAppliesErrorsHandler() : void
{
$called = false;
$errors = null;
$called = false;
$errors = null;
$formatter = null;
$this->config->setErrorsHandler(function($e, $f) use (&$called, &$errors, &$formatter) {
$called = true;
$errors = $e;
$this->config->setErrorsHandler(function ($e, $f) use (&$called, &$errors, &$formatter) {
$called = true;
$errors = $e;
$formatter = $f;
return [
['test' => 'handled']
['test' => 'handled'],
];
});
@ -634,10 +675,10 @@ class QueryExecutionTest extends ServerTestCase
$this->assertFalse($called);
$formatted = $result->toArray();
$expected = [
$expected = [
'errors' => [
['test' => 'handled']
]
['test' => 'handled'],
],
];
$this->assertTrue($called);
$this->assertArraySubset($expected, $formatted);
@ -646,46 +687,4 @@ class QueryExecutionTest extends ServerTestCase
$this->assertInternalType('callable', $formatter);
$this->assertArraySubset($expected, $formatted);
}
private function executePersistedQuery($queryId, $variables = null)
{
$op = OperationParams::create(['queryId' => $queryId, 'variables' => $variables]);
$helper = new Helper();
$result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
private function executeQuery($query, $variables = null, $readonly = false)
{
$op = OperationParams::create(['query' => $query, 'variables' => $variables], $readonly);
$helper = new Helper();
$result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
private function executeBatchedQuery(array $qs)
{
$batch = [];
foreach ($qs as $params) {
$batch[] = OperationParams::create($params);
}
$helper = new Helper();
$result = $helper->executeBatch($this->config, $batch);
$this->assertInternalType('array', $result);
$this->assertCount(count($qs), $result);
foreach ($result as $index => $entry) {
$this->assertInstanceOf(ExecutionResult::class, $entry, "Result at $index is not an instance of " . ExecutionResult::class);
}
return $result;
}
private function assertQueryResultEquals($expected, $query, $variables = null)
{
$result = $this->executeQuery($query, $variables);
$this->assertArraySubset($expected, $result->toArray(true));
return $result;
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Server;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Server\Helper;
use GraphQL\Server\OperationParams;
@ -9,15 +11,17 @@ use GraphQL\Server\RequestError;
use GraphQL\Tests\Server\Psr7\PsrRequestStub;
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
use PHPUnit\Framework\TestCase;
use function json_decode;
use function json_encode;
class RequestParsingTest extends TestCase
{
public function testParsesGraphqlRequest() : void
{
$query = '{my query}';
$query = '{my query}';
$parsed = [
'raw' => $this->parseRawRequest('application/graphql', $query),
'psr' => $this->parsePsrRequest('application/graphql', $query)
'psr' => $this->parsePsrRequest('application/graphql', $query),
];
foreach ($parsed as $source => $parsedBody) {
@ -26,20 +30,91 @@ class RequestParsingTest extends TestCase
}
}
/**
* @param string $contentType
* @param string $content
*
* @return OperationParams|OperationParams[]
*/
private function parseRawRequest($contentType, $content, string $method = 'POST')
{
$_SERVER['CONTENT_TYPE'] = $contentType;
$_SERVER['REQUEST_METHOD'] = $method;
$helper = new Helper();
return $helper->parseHttpRequest(function () use ($content) {
return $content;
});
}
/**
* @param string $contentType
* @param string $content
*
* @return OperationParams|OperationParams[]
*/
private function parsePsrRequest($contentType, $content, string $method = 'POST')
{
$psrRequestBody = new PsrStreamStub();
$psrRequestBody->content = $content;
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = [$contentType];
$psrRequest->method = $method;
$psrRequest->body = $psrRequestBody;
if ($contentType === 'application/json') {
$parsedBody = json_decode($content, true);
$parsedBody = $parsedBody === false ? null : $parsedBody;
} else {
$parsedBody = null;
}
$psrRequest->parsedBody = $parsedBody;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param OperationParams $params
* @param string $query
* @param string $queryId
* @param mixed|null $variables
* @param string $operation
*/
private function assertValidOperationParams(
$params,
$query,
$queryId = null,
$variables = null,
$operation = null,
$message = ''
) {
$this->assertInstanceOf(OperationParams::class, $params, $message);
$this->assertSame($query, $params->query, $message);
$this->assertSame($queryId, $params->queryId, $message);
$this->assertSame($variables, $params->variables, $message);
$this->assertSame($operation, $params->operation, $message);
}
public function testParsesUrlencodedRequest() : void
{
$query = '{my query}';
$query = '{my query}';
$variables = ['test' => 1, 'test2' => 2];
$operation = 'op';
$post = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation
$post = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation,
];
$parsed = [
'raw' => $this->parseRawFormUrlencodedRequest($post),
'psr' => $this->parsePsrFormUrlEncodedRequest($post)
'psr' => $this->parsePsrFormUrlEncodedRequest($post),
];
foreach ($parsed as $method => $parsedBody) {
@ -48,20 +123,53 @@ class RequestParsingTest extends TestCase
}
}
/**
* @param mixed[] $postValue
* @return OperationParams|OperationParams[]
*/
private function parseRawFormUrlencodedRequest($postValue)
{
$_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $postValue;
$helper = new Helper();
return $helper->parseHttpRequest(function () {
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
});
}
/**
* @param mixed[] $postValue
* @return OperationParams[]|OperationParams
*/
private function parsePsrFormUrlEncodedRequest($postValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded'];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $postValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
public function testParsesGetRequest() : void
{
$query = '{my query}';
$query = '{my query}';
$variables = ['test' => 1, 'test2' => 2];
$operation = 'op';
$get = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation
$get = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation,
];
$parsed = [
'raw' => $this->parseRawGetRequest($get),
'psr' => $this->parsePsrGetRequest($get)
'psr' => $this->parsePsrGetRequest($get),
];
foreach ($parsed as $method => $parsedBody) {
@ -70,20 +178,51 @@ class RequestParsingTest extends TestCase
}
}
/**
* @param mixed[] $getValue
* @return OperationParams
*/
private function parseRawGetRequest($getValue)
{
$_SERVER['REQUEST_METHOD'] = 'GET';
$_GET = $getValue;
$helper = new Helper();
return $helper->parseHttpRequest(function () {
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
});
}
/**
* @param mixed[] $getValue
* @return OperationParams[]|OperationParams
*/
private function parsePsrGetRequest($getValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->method = 'GET';
$psrRequest->queryParams = $getValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
public function testParsesMultipartFormdataRequest() : void
{
$query = '{my query}';
$query = '{my query}';
$variables = ['test' => 1, 'test2' => 2];
$operation = 'op';
$post = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation
$post = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation,
];
$parsed = [
'raw' => $this->parseRawMultipartFormdataRequest($post),
'psr' => $this->parsePsrMultipartFormdataRequest($post)
'psr' => $this->parsePsrMultipartFormdataRequest($post),
];
foreach ($parsed as $method => $parsedBody) {
@ -92,20 +231,53 @@ class RequestParsingTest extends TestCase
}
}
/**
* @param mixed[] $postValue
* @return OperationParams|OperationParams[]
*/
private function parseRawMultipartFormDataRequest($postValue)
{
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data; boundary=----FormBoundary';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $postValue;
$helper = new Helper();
return $helper->parseHttpRequest(function () {
throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request");
});
}
/**
* @param mixed[] $postValue
* @return OperationParams|OperationParams[]
*/
private function parsePsrMultipartFormDataRequest($postValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary'];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $postValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
public function testParsesJSONRequest() : void
{
$query = '{my query}';
$query = '{my query}';
$variables = ['test' => 1, 'test2' => 2];
$operation = 'op';
$body = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation
$body = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation,
];
$parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)),
'psr' => $this->parsePsrRequest('application/json', json_encode($body))
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
];
foreach ($parsed as $method => $parsedBody) {
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
@ -115,18 +287,18 @@ class RequestParsingTest extends TestCase
public function testParsesVariablesAsJSON() : void
{
$query = '{my query}';
$query = '{my query}';
$variables = ['test' => 1, 'test2' => 2];
$operation = 'op';
$body = [
'query' => $query,
'variables' => json_encode($variables),
'operationName' => $operation
$body = [
'query' => $query,
'variables' => json_encode($variables),
'operationName' => $operation,
];
$parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)),
'psr' => $this->parsePsrRequest('application/json', json_encode($body))
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
];
foreach ($parsed as $method => $parsedBody) {
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
@ -136,14 +308,14 @@ class RequestParsingTest extends TestCase
public function testIgnoresInvalidVariablesJson() : void
{
$query = '{my query}';
$query = '{my query}';
$variables = '"some invalid json';
$operation = 'op';
$body = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation
$body = [
'query' => $query,
'variables' => $variables,
'operationName' => $operation,
];
$parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)),
@ -157,27 +329,41 @@ class RequestParsingTest extends TestCase
public function testParsesBatchJSONRequest() : void
{
$body = [
$body = [
[
'query' => '{my query}',
'variables' => ['test' => 1, 'test2' => 2],
'operationName' => 'op'
'query' => '{my query}',
'variables' => ['test' => 1, 'test2' => 2],
'operationName' => 'op',
],
[
'queryId' => 'my-query-id',
'variables' => ['test' => 1, 'test2' => 2],
'operationName' => 'op2'
'queryId' => 'my-query-id',
'variables' => ['test' => 1, 'test2' => 2],
'operationName' => 'op2',
],
];
$parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)),
'psr' => $this->parsePsrRequest('application/json', json_encode($body))
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
];
foreach ($parsed as $method => $parsedBody) {
$this->assertInternalType('array', $parsedBody, $method);
$this->assertCount(2, $parsedBody, $method);
$this->assertValidOperationParams($parsedBody[0], $body[0]['query'], null, $body[0]['variables'], $body[0]['operationName'], $method);
$this->assertValidOperationParams($parsedBody[1], null, $body[1]['queryId'], $body[1]['variables'], $body[1]['operationName'], $method);
$this->assertValidOperationParams(
$parsedBody[0],
$body[0]['query'],
null,
$body[0]['variables'],
$body[0]['operationName'],
$method
);
$this->assertValidOperationParams(
$parsedBody[1],
null,
$body[1]['queryId'],
$body[1]['variables'],
$body[1]['operationName'],
$method
);
}
}
@ -187,8 +373,8 @@ class RequestParsingTest extends TestCase
$this->expectException(RequestError::class);
$this->expectExceptionMessage('Could not parse JSON: Syntax error');
$this->parseRawRequest('application/json', $body);
}
$this->parseRawRequest('application/json', $body);
}
public function testFailsParsingInvalidRawJsonRequestPsr() : void
{
@ -196,7 +382,7 @@ class RequestParsingTest extends TestCase
$this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "application/json" requests but got null');
$this->parsePsrRequest('application/json', $body);
$this->parsePsrRequest('application/json', $body);
}
public function testFailsParsingNonPreParsedPsrRequest() : void
@ -222,8 +408,8 @@ class RequestParsingTest extends TestCase
$this->expectException(RequestError::class);
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
$this->parseRawRequest('application/json', $body);
}
$this->parseRawRequest('application/json', $body);
}
public function testFailsParsingNonArrayOrObjectJsonRequestPsr() : void
{
@ -231,13 +417,13 @@ class RequestParsingTest extends TestCase
$this->expectException(RequestError::class);
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
$this->parsePsrRequest('application/json', $body);
}
$this->parsePsrRequest('application/json', $body);
}
public function testFailsParsingInvalidContentTypeRaw() : void
{
$contentType = 'not-supported-content-type';
$body = 'test';
$body = 'test';
$this->expectException(RequestError::class);
$this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
@ -247,194 +433,38 @@ class RequestParsingTest extends TestCase
public function testFailsParsingInvalidContentTypePsr() : void
{
$contentType = 'not-supported-content-type';
$body = 'test';
$body = 'test';
$this->expectException(RequestError::class);
$this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
$this->parseRawRequest($contentType, $body);
}
$this->parseRawRequest($contentType, $body);
}
public function testFailsWithMissingContentTypeRaw() : void
{
$this->expectException(RequestError::class);
$this->expectExceptionMessage('Missing "Content-Type" header');
$this->parseRawRequest(null, 'test');
}
$this->parseRawRequest(null, 'test');
}
public function testFailsWithMissingContentTypePsr() : void
{
$this->expectException(RequestError::class);
$this->expectExceptionMessage('Missing "Content-Type" header');
$this->parsePsrRequest(null, 'test');
$this->parsePsrRequest(null, 'test');
}
public function testFailsOnMethodsOtherThanPostOrGetRaw() : void
{
$this->expectException(RequestError::class);
$this->expectExceptionMessage('HTTP Method "PUT" is not supported');
$this->parseRawRequest('application/json', json_encode([]), "PUT");
$this->parseRawRequest('application/json', json_encode([]), 'PUT');
}
public function testFailsOnMethodsOtherThanPostOrGetPsr() : void
{
$this->expectException(RequestError::class);
$this->expectExceptionMessage('HTTP Method "PUT" is not supported');
$this->parsePsrRequest('application/json', json_encode([]), "PUT");
}
/**
* @param string $contentType
* @param string $content
* @param $method
*
* @return OperationParams|OperationParams[]
*/
private function parseRawRequest($contentType, $content, $method = 'POST')
{
$_SERVER['CONTENT_TYPE'] = $contentType;
$_SERVER['REQUEST_METHOD'] = $method;
$helper = new Helper();
return $helper->parseHttpRequest(function() use ($content) {
return $content;
});
}
/**
* @param string $contentType
* @param string $content
* @param $method
*
* @return OperationParams|OperationParams[]
*/
private function parsePsrRequest($contentType, $content, $method = 'POST')
{
$psrRequestBody = new PsrStreamStub();
$psrRequestBody->content = $content;
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = [$contentType];
$psrRequest->method = $method;
$psrRequest->body = $psrRequestBody;
if ($contentType === 'application/json') {
$parsedBody = json_decode($content, true);
$parsedBody = $parsedBody === false ? null : $parsedBody;
} else {
$parsedBody = null;
}
$psrRequest->parsedBody = $parsedBody;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param array $postValue
* @return OperationParams|OperationParams[]
*/
private function parseRawFormUrlencodedRequest($postValue)
{
$_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $postValue;
$helper = new Helper();
return $helper->parseHttpRequest(function() {
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
});
}
/**
* @param $postValue
* @return array|Helper
*/
private function parsePsrFormUrlEncodedRequest($postValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded'];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $postValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param array $postValue
* @return OperationParams|OperationParams[]
*/
private function parseRawMultipartFormDataRequest($postValue)
{
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data; boundary=----FormBoundary';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $postValue;
$helper = new Helper();
return $helper->parseHttpRequest(function() {
throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request");
});
}
/**
* @param $postValue
* @return array|Helper
*/
private function parsePsrMultipartFormDataRequest($postValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary'];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $postValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param $getValue
* @return OperationParams
*/
private function parseRawGetRequest($getValue)
{
$_SERVER['REQUEST_METHOD'] = 'GET';
$_GET = $getValue;
$helper = new Helper();
return $helper->parseHttpRequest(function() {
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
});
}
/**
* @param $getValue
* @return array|Helper
*/
private function parsePsrGetRequest($getValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->method = 'GET';
$psrRequest->queryParams = $getValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param OperationParams $params
* @param string $query
* @param string $queryId
* @param array $variables
* @param string $operation
*/
private function assertValidOperationParams($params, $query, $queryId = null, $variables = null, $operation = null, $message = '')
{
$this->assertInstanceOf(OperationParams::class, $params, $message);
$this->assertSame($query, $params->query, $message);
$this->assertSame($queryId, $params->queryId, $message);
$this->assertSame($variables, $params->variables, $message);
$this->assertSame($operation, $params->operation, $message);
$this->parsePsrRequest('application/json', json_encode([]), 'PUT');
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Server;
use GraphQL\Server\Helper;
@ -9,28 +12,35 @@ class RequestValidationTest extends TestCase
{
public function testSimpleRequestShouldValidate() : void
{
$query = '{my q}';
$query = '{my q}';
$variables = ['a' => 'b', 'c' => 'd'];
$operation = 'op';
$parsedBody = OperationParams::create([
'query' => $query,
'variables' => $variables,
'query' => $query,
'variables' => $variables,
'operationName' => $operation,
]);
$this->assertValid($parsedBody);
}
private function assertValid($parsedRequest)
{
$helper = new Helper();
$errors = $helper->validateOperationParams($parsedRequest);
$this->assertEmpty($errors, isset($errors[0]) ? $errors[0]->getMessage() : '');
}
public function testRequestWithQueryIdShouldValidate() : void
{
$queryId = 'some-query-id';
$queryId = 'some-query-id';
$variables = ['a' => 'b', 'c' => 'd'];
$operation = 'op';
$parsedBody = OperationParams::create([
'queryId' => $queryId,
'variables' => $variables,
'queryId' => $queryId,
'variables' => $variables,
'operationName' => $operation,
]);
@ -40,7 +50,7 @@ class RequestValidationTest extends TestCase
public function testRequiresQueryOrQueryId() : void
{
$parsedBody = OperationParams::create([
'variables' => ['foo' => 'bar'],
'variables' => ['foo' => 'bar'],
'operationName' => 'op',
]);
@ -50,10 +60,21 @@ class RequestValidationTest extends TestCase
);
}
private function assertInputError($parsedRequest, $expectedMessage)
{
$helper = new Helper();
$errors = $helper->validateOperationParams($parsedRequest);
if (! empty($errors[0])) {
$this->assertEquals($expectedMessage, $errors[0]->getMessage());
} else {
$this->fail('Expected error not returned');
}
}
public function testFailsWhenBothQueryAndQueryIdArePresent() : void
{
$parsedBody = OperationParams::create([
'query' => '{my query}',
'query' => '{my query}',
'queryId' => 'my-query-id',
]);
@ -66,7 +87,7 @@ class RequestValidationTest extends TestCase
public function testFailsWhenQueryParameterIsNotString() : void
{
$parsedBody = OperationParams::create([
'query' => ['t' => '{my query}']
'query' => ['t' => '{my query}'],
]);
$this->assertInputError(
@ -78,7 +99,7 @@ class RequestValidationTest extends TestCase
public function testFailsWhenQueryIdParameterIsNotString() : void
{
$parsedBody = OperationParams::create([
'queryId' => ['t' => '{my query}']
'queryId' => ['t' => '{my query}'],
]);
$this->assertInputError(
@ -90,8 +111,8 @@ class RequestValidationTest extends TestCase
public function testFailsWhenOperationParameterIsNotString() : void
{
$parsedBody = OperationParams::create([
'query' => '{my query}',
'operationName' => []
'query' => '{my query}',
'operationName' => [],
]);
$this->assertInputError(
@ -105,17 +126,17 @@ class RequestValidationTest extends TestCase
*/
public function testIgnoresNullAndEmptyStringVariables() : void
{
$query = '{my q}';
$query = '{my q}';
$parsedBody = OperationParams::create([
'query' => $query,
'variables' => null
'query' => $query,
'variables' => null,
]);
$this->assertValid($parsedBody);
$variables = "";
$variables = '';
$parsedBody = OperationParams::create([
'query' => $query,
'variables' => $variables
'query' => $query,
'variables' => $variables,
]);
$this->assertValid($parsedBody);
}
@ -123,8 +144,8 @@ class RequestValidationTest extends TestCase
public function testFailsWhenVariablesParameterIsNotObject() : void
{
$parsedBody = OperationParams::create([
'query' => '{my query}',
'variables' => 0
'query' => '{my query}',
'variables' => 0,
]);
$this->assertInputError(
@ -132,22 +153,4 @@ class RequestValidationTest extends TestCase
'GraphQL Request parameter "variables" must be object or JSON string parsed to object, but got 0'
);
}
private function assertValid($parsedRequest)
{
$helper = new Helper();
$errors = $helper->validateOperationParams($parsedRequest);
$this->assertEmpty($errors, isset($errors[0]) ? $errors[0]->getMessage() : '');
}
private function assertInputError($parsedRequest, $expectedMessage)
{
$helper = new Helper();
$errors = $helper->validateOperationParams($parsedRequest);
if (!empty($errors[0])) {
$this->assertEquals($expectedMessage, $errors[0]->getMessage());
} else {
$this->fail('Expected error not returned');
}
}
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Server;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Type\Schema;
use GraphQL\Server\ServerConfig;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class ServerConfigTest extends TestCase
@ -14,17 +17,17 @@ class ServerConfigTest extends TestCase
public function testDefaults() : void
{
$config = ServerConfig::create();
$this->assertEquals(null, $config->getSchema());
$this->assertEquals(null, $config->getContext());
$this->assertEquals(null, $config->getRootValue());
$this->assertEquals(null, $config->getErrorFormatter());
$this->assertEquals(null, $config->getErrorsHandler());
$this->assertEquals(null, $config->getPromiseAdapter());
$this->assertEquals(null, $config->getValidationRules());
$this->assertEquals(null, $config->getFieldResolver());
$this->assertEquals(null, $config->getPersistentQueryLoader());
$this->assertEquals(false, $config->getDebug());
$this->assertEquals(false, $config->getQueryBatching());
$this->assertNull($config->getSchema());
$this->assertNull($config->getContext());
$this->assertNull($config->getRootValue());
$this->assertNull($config->getErrorFormatter());
$this->assertNull($config->getErrorsHandler());
$this->assertNull($config->getPromiseAdapter());
$this->assertNull($config->getValidationRules());
$this->assertNull($config->getFieldResolver());
$this->assertNull($config->getPersistentQueryLoader());
$this->assertFalse($config->getDebug());
$this->assertFalse($config->getQueryBatching());
}
public function testAllowsSettingSchema() : void
@ -70,7 +73,8 @@ class ServerConfigTest extends TestCase
{
$config = ServerConfig::create();
$formatter = function() {};
$formatter = function () {
};
$config->setErrorFormatter($formatter);
$this->assertSame($formatter, $config->getErrorFormatter());
@ -83,7 +87,8 @@ class ServerConfigTest extends TestCase
{
$config = ServerConfig::create();
$handler = function() {};
$handler = function () {
};
$config->setErrorsHandler($handler);
$this->assertSame($handler, $config->getErrorsHandler());
@ -113,11 +118,17 @@ class ServerConfigTest extends TestCase
$config->setValidationRules($rules);
$this->assertSame($rules, $config->getValidationRules());
$rules = [function() {}];
$rules = [function () {
},
];
$config->setValidationRules($rules);
$this->assertSame($rules, $config->getValidationRules());
$rules = function() {return [function() {}];};
$rules = function () {
return [function () {
},
];
};
$config->setValidationRules($rules);
$this->assertSame($rules, $config->getValidationRules());
}
@ -126,7 +137,8 @@ class ServerConfigTest extends TestCase
{
$config = ServerConfig::create();
$resolver = function() {};
$resolver = function () {
};
$config->setFieldResolver($resolver);
$this->assertSame($resolver, $config->getFieldResolver());
@ -139,7 +151,8 @@ class ServerConfigTest extends TestCase
{
$config = ServerConfig::create();
$loader = function() {};
$loader = function () {
};
$config->setPersistentQueryLoader($loader);
$this->assertSame($loader, $config->getPersistentQueryLoader());
@ -153,27 +166,32 @@ class ServerConfigTest extends TestCase
$config = ServerConfig::create();
$config->setDebug(true);
$this->assertSame(true, $config->getDebug());
$this->assertTrue($config->getDebug());
$config->setDebug(false);
$this->assertSame(false, $config->getDebug());
$this->assertFalse($config->getDebug());
}
public function testAcceptsArray() : void
{
$arr = [
'schema' => new \GraphQL\Type\Schema([
'query' => new ObjectType(['name' => 't', 'fields' => ['a' => Type::string()]])
'schema' => new Schema([
'query' => new ObjectType(['name' => 't', 'fields' => ['a' => Type::string()]]),
]),
'context' => new \stdClass(),
'rootValue' => new \stdClass(),
'errorFormatter' => function() {},
'promiseAdapter' => new SyncPromiseAdapter(),
'validationRules' => [function() {}],
'fieldResolver' => function() {},
'persistentQueryLoader' => function() {},
'debug' => true,
'queryBatching' => true,
'context' => new \stdClass(),
'rootValue' => new \stdClass(),
'errorFormatter' => function () {
},
'promiseAdapter' => new SyncPromiseAdapter(),
'validationRules' => [function () {
},
],
'fieldResolver' => function () {
},
'persistentQueryLoader' => function () {
},
'debug' => true,
'queryBatching' => true,
];
$config = ServerConfig::create($arr);
@ -186,15 +204,13 @@ class ServerConfigTest extends TestCase
$this->assertSame($arr['validationRules'], $config->getValidationRules());
$this->assertSame($arr['fieldResolver'], $config->getFieldResolver());
$this->assertSame($arr['persistentQueryLoader'], $config->getPersistentQueryLoader());
$this->assertSame(true, $config->getDebug());
$this->assertSame(true, $config->getQueryBatching());
$this->assertTrue($config->getDebug());
$this->assertTrue($config->getQueryBatching());
}
public function testThrowsOnInvalidArrayKey() : void
{
$arr = [
'missingKey' => 'value'
];
$arr = ['missingKey' => 'value'];
$this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Unknown server config option "missingKey"');
@ -204,7 +220,7 @@ class ServerConfigTest extends TestCase
public function testInvalidValidationRules() : void
{
$rules = new \stdClass();
$rules = new \stdClass();
$config = ServerConfig::create();
$this->expectException(InvariantViolation::class);

View File

@ -1,6 +1,8 @@
<?php
namespace GraphQL\Tests\Server;
declare(strict_types=1);
namespace GraphQL\Tests\Server;
use GraphQL\Deferred;
use GraphQL\Error\ClientAware;
@ -9,31 +11,36 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function trigger_error;
use const E_USER_DEPRECATED;
use const E_USER_NOTICE;
use const E_USER_WARNING;
abstract class ServerTestCase extends TestCase
{
protected function buildSchema()
{
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'f1' => [
'type' => Type::string(),
'resolve' => function($root, $args, $context, $info) {
'f1' => [
'type' => Type::string(),
'resolve' => function ($root, $args, $context, $info) {
return $info->fieldName;
}
},
],
'fieldWithPhpError' => [
'type' => Type::string(),
'resolve' => function($root, $args, $context, $info) {
'fieldWithPhpError' => [
'type' => Type::string(),
'resolve' => function ($root, $args, $context, $info) {
trigger_error('deprecated', E_USER_DEPRECATED);
trigger_error('notice', E_USER_NOTICE);
trigger_error('warning', E_USER_WARNING);
$a = [];
$a['test']; // should produce PHP notice
return $info->fieldName;
}
},
],
'fieldWithSafeException' => [
'type' => Type::string(),
@ -48,54 +55,56 @@ abstract class ServerTestCase extends TestCase
}
],
'testContextAndRootValue' => [
'type' => Type::string(),
'resolve' => function($root, $args, $context, $info) {
'type' => Type::string(),
'resolve' => function ($root, $args, $context, $info) {
$context->testedRootValue = $root;
return $info->fieldName;
}
},
],
'fieldWithArg' => [
'type' => Type::string(),
'args' => [
'fieldWithArg' => [
'type' => Type::string(),
'args' => [
'arg' => [
'type' => Type::nonNull(Type::string())
'type' => Type::nonNull(Type::string()),
],
],
'resolve' => function($root, $args) {
'resolve' => function ($root, $args) {
return $args['arg'];
}
},
],
'dfd' => [
'type' => Type::string(),
'args' => [
'dfd' => [
'type' => Type::string(),
'args' => [
'num' => [
'type' => Type::nonNull(Type::int())
'type' => Type::nonNull(Type::int()),
],
],
'resolve' => function($root, $args, $context) {
'resolve' => function ($root, $args, $context) {
$context['buffer']($args['num']);
return new Deferred(function() use ($args, $context) {
return new Deferred(function () use ($args, $context) {
return $context['load']($args['num']);
});
}
]
]
},
],
],
]),
'mutation' => new ObjectType([
'name' => 'Mutation',
'name' => 'Mutation',
'fields' => [
'm1' => [
'type' => new ObjectType([
'name' => 'TestMutation',
'name' => 'TestMutation',
'fields' => [
'result' => Type::string()
]
])
]
]
])
'result' => Type::string(),
],
]),
],
],
]),
]);
return $schema;
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Server;
use GraphQL\Executor\ExecutionResult;
@ -6,72 +9,75 @@ use GraphQL\Server\Helper;
use GraphQL\Server\ServerConfig;
use GraphQL\Server\StandardServer;
use GraphQL\Tests\Server\Psr7\PsrRequestStub;
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
use function json_encode;
class StandardServerTest extends ServerTestCase
{
/**
* @var ServerConfig
*/
/** @var ServerConfig */
private $config;
public function setUp()
{
$schema = $this->buildSchema();
$schema = $this->buildSchema();
$this->config = ServerConfig::create()
->setSchema($schema);
}
public function testSimpleRequestExecutionWithOutsideParsing() : void
{
$body = json_encode([
'query' => '{f1}'
]);
$body = json_encode(['query' => '{f1}']);
$parsedBody = $this->parseRawRequest('application/json', $body);
$server = new StandardServer($this->config);
$server = new StandardServer($this->config);
$result = $server->executeRequest($parsedBody);
$result = $server->executeRequest($parsedBody);
$expected = [
'data' => [
'f1' => 'f1',
]
'data' => ['f1' => 'f1'],
];
$this->assertEquals($expected, $result->toArray(true));
}
private function parseRawRequest($contentType, $content, $method = 'POST')
{
$_SERVER['CONTENT_TYPE'] = $contentType;
$_SERVER['REQUEST_METHOD'] = $method;
$helper = new Helper();
return $helper->parseHttpRequest(function () use ($content) {
return $content;
});
}
public function testSimplePsrRequestExecution() : void
{
$body = [
'query' => '{f1}'
];
$body = ['query' => '{f1}'];
$expected = [
'data' => [
'f1' => 'f1'
]
'data' => ['f1' => 'f1'],
];
$request = $this->preparePsrRequest('application/json', $body);
$this->assertPsrRequestEquals($expected, $request);
}
public function testMultipleOperationPsrRequestExecution() : void
private function preparePsrRequest($contentType, $parsedBody)
{
$body = [
'query' => 'query firstOp {fieldWithPhpError} query secondOp {f1}',
'operationName' => 'secondOp'
];
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = [$contentType];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $parsedBody;
$expected = [
'data' => [
'f1' => 'f1'
]
];
return $psrRequest;
}
$request = $this->preparePsrRequest('application/json', $body);
$this->assertPsrRequestEquals($expected, $request);
private function assertPsrRequestEquals($expected, $request)
{
$result = $this->executePsrRequest($request);
$this->assertArraySubset($expected, $result->toArray(true));
return $result;
}
private function executePsrRequest($psrRequest)
@ -79,33 +85,22 @@ class StandardServerTest extends ServerTestCase
$server = new StandardServer($this->config);
$result = $server->executePsrRequest($psrRequest);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
private function assertPsrRequestEquals($expected, $request)
public function testMultipleOperationPsrRequestExecution() : void
{
$result = $this->executePsrRequest($request);
$this->assertArraySubset($expected, $result->toArray(true));
return $result;
}
$body = [
'query' => 'query firstOp {fieldWithPhpError} query secondOp {f1}',
'operationName' => 'secondOp',
];
private function preparePsrRequest($contentType, $parsedBody)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = [$contentType];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $parsedBody;
return $psrRequest;
}
$expected = [
'data' => ['f1' => 'f1'],
];
private function parseRawRequest($contentType, $content, $method = 'POST')
{
$_SERVER['CONTENT_TYPE'] = $contentType;
$_SERVER['REQUEST_METHOD'] = $method;
$helper = new Helper();
return $helper->parseHttpRequest(function() use ($content) {
return $content;
});
$request = $this->preparePsrRequest('application/json', $body);
$this->assertPsrRequestEquals($expected, $request);
}
}

View File

@ -1,62 +1,31 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests;
use function array_map;
class StarWarsData
{
private static function luke()
/**
* Helper function to get a character by ID.
*/
public static function getCharacter($id)
{
return [
'id' => '1000',
'name' => 'Luke Skywalker',
'friends' => ['1002', '1003', '2000', '2001'],
'appearsIn' => [4, 5, 6],
'homePlanet' => 'Tatooine',
];
$humans = self::humans();
$droids = self::droids();
if (isset($humans[$id])) {
return $humans[$id];
}
if (isset($droids[$id])) {
return $droids[$id];
}
return null;
}
private static function vader()
{
return [
'id' => '1001',
'name' => 'Darth Vader',
'friends' => ['1004'],
'appearsIn' => [4, 5, 6],
'homePlanet' => 'Tatooine',
];
}
private static function han()
{
return [
'id' => '1002',
'name' => 'Han Solo',
'friends' => ['1000', '1003', '2001'],
'appearsIn' => [4, 5, 6],
];
}
private static function leia()
{
return [
'id' => '1003',
'name' => 'Leia Organa',
'friends' => ['1000', '1002', '2000', '2001'],
'appearsIn' => [4, 5, 6],
'homePlanet' => 'Alderaan',
];
}
private static function tarkin()
{
return [
'id' => '1004',
'name' => 'Wilhuff Tarkin',
'friends' => ['1001'],
'appearsIn' => [4],
];
}
static function humans()
public static function humans()
{
return [
'1000' => self::luke(),
@ -67,13 +36,74 @@ class StarWarsData
];
}
private static function luke()
{
return [
'id' => '1000',
'name' => 'Luke Skywalker',
'friends' => ['1002', '1003', '2000', '2001'],
'appearsIn' => [4, 5, 6],
'homePlanet' => 'Tatooine',
];
}
private static function vader()
{
return [
'id' => '1001',
'name' => 'Darth Vader',
'friends' => ['1004'],
'appearsIn' => [4, 5, 6],
'homePlanet' => 'Tatooine',
];
}
private static function han()
{
return [
'id' => '1002',
'name' => 'Han Solo',
'friends' => ['1000', '1003', '2001'],
'appearsIn' => [4, 5, 6],
];
}
private static function leia()
{
return [
'id' => '1003',
'name' => 'Leia Organa',
'friends' => ['1000', '1002', '2000', '2001'],
'appearsIn' => [4, 5, 6],
'homePlanet' => 'Alderaan',
];
}
private static function tarkin()
{
return [
'id' => '1004',
'name' => 'Wilhuff Tarkin',
'friends' => ['1001'],
'appearsIn' => [4],
];
}
public static function droids()
{
return [
'2000' => self::threepio(),
'2001' => self::artoo(),
];
}
private static function threepio()
{
return [
'id' => '2000',
'name' => 'C-3PO',
'friends' => ['1000', '1002', '1003', '2001'],
'appearsIn' => [4, 5, 6],
'id' => '2000',
'name' => 'C-3PO',
'friends' => ['1000', '1002', '1003', '2001'],
'appearsIn' => [4, 5, 6],
'primaryFunction' => 'Protocol',
];
}
@ -82,81 +112,60 @@ class StarWarsData
* We export artoo directly because the schema returns him
* from a root field, and hence needs to reference him.
*/
static function artoo()
private static function artoo()
{
return [
'id' => '2001',
'name' => 'R2-D2',
'friends' => ['1000', '1002', '1003'],
'appearsIn' => [4, 5, 6],
'id' => '2001',
'name' => 'R2-D2',
'friends' => ['1000', '1002', '1003'],
'appearsIn' => [4, 5, 6],
'primaryFunction' => 'Astromech',
];
}
static function droids()
{
return [
'2000' => self::threepio(),
'2001' => self::artoo(),
];
}
/**
* Helper function to get a character by ID.
*/
static function getCharacter($id)
{
$humans = self::humans();
$droids = self::droids();
if (isset($humans[$id])) {
return $humans[$id];
}
if (isset($droids[$id])) {
return $droids[$id];
}
return null;
}
/**
* Allows us to query for a character's friends.
*/
static function getFriends($character)
public static function getFriends($character)
{
return array_map([__CLASS__, 'getCharacter'], $character['friends']);
}
/**
* @param $episode
* @return array
* @param int $episode
* @return mixed[]
*/
static function getHero($episode)
public static function getHero($episode)
{
if ($episode === 5) {
// Luke is the hero of Episode V.
return self::luke();
}
// Artoo is the hero otherwise.
return self::artoo();
}
/**
* @param $id
* @param string $id
* @return mixed|null
*/
static function getHuman($id)
public static function getHuman($id)
{
$humans = self::humans();
return isset($humans[$id]) ? $humans[$id] : null;
return $humans[$id] ?? null;
}
/**
* @param $id
* @param string $id
* @return mixed|null
*/
static function getDroid($id)
public static function getDroid($id)
{
$droids = self::droids();
return isset($droids[$id]) ? $droids[$id] : null;
return $droids[$id] ?? null;
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests;
use GraphQL\GraphQL;
@ -14,7 +17,7 @@ class StarWarsIntrospectionTest extends TestCase
*/
public function testAllowsQueryingTheSchemaForTypes() : void
{
$query = '
$query = '
query IntrospectionTypeQuery {
__schema {
types {
@ -44,18 +47,26 @@ class StarWarsIntrospectionTest extends TestCase
['name' => '__EnumValue'],
['name' => '__Directive'],
['name' => '__DirectiveLocation'],
]
]
],
],
];
$this->assertValidQuery($query, $expected);
}
/**
* Helper function to test a query and the expected response.
*/
private function assertValidQuery($query, $expected) : void
{
$this->assertEquals(['data' => $expected], GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray());
}
/**
* @see it('Allows querying the schema for query type')
*/
public function testAllowsQueryingTheSchemaForQueryType() : void
{
$query = '
$query = '
query IntrospectionQueryTypeQuery {
__schema {
queryType {
@ -66,10 +77,8 @@ class StarWarsIntrospectionTest extends TestCase
';
$expected = [
'__schema' => [
'queryType' => [
'name' => 'Query'
],
]
'queryType' => ['name' => 'Query'],
],
];
$this->assertValidQuery($query, $expected);
}
@ -79,7 +88,7 @@ class StarWarsIntrospectionTest extends TestCase
*/
public function testAllowsQueryingTheSchemaForASpecificType() : void
{
$query = '
$query = '
query IntrospectionDroidTypeQuery {
__type(name: "Droid") {
name
@ -87,9 +96,7 @@ class StarWarsIntrospectionTest extends TestCase
}
';
$expected = [
'__type' => [
'name' => 'Droid'
]
'__type' => ['name' => 'Droid'],
];
$this->assertValidQuery($query, $expected);
}
@ -99,7 +106,7 @@ class StarWarsIntrospectionTest extends TestCase
*/
public function testAllowsQueryingForAnObjectKind() : void
{
$query = '
$query = '
query IntrospectionDroidKindQuery {
__type(name: "Droid") {
name
@ -110,8 +117,8 @@ class StarWarsIntrospectionTest extends TestCase
$expected = [
'__type' => [
'name' => 'Droid',
'kind' => 'OBJECT'
]
'kind' => 'OBJECT',
],
];
$this->assertValidQuery($query, $expected);
}
@ -121,7 +128,7 @@ class StarWarsIntrospectionTest extends TestCase
*/
public function testAllowsQueryingForInterfaceKind() : void
{
$query = '
$query = '
query IntrospectionCharacterKindQuery {
__type(name: "Character") {
name
@ -132,8 +139,8 @@ class StarWarsIntrospectionTest extends TestCase
$expected = [
'__type' => [
'name' => 'Character',
'kind' => 'INTERFACE'
]
'kind' => 'INTERFACE',
],
];
$this->assertValidQuery($query, $expected);
}
@ -143,7 +150,7 @@ class StarWarsIntrospectionTest extends TestCase
*/
public function testAllowsQueryingForObjectFields() : void
{
$query = '
$query = '
query IntrospectionDroidFieldsQuery {
__type(name: "Droid") {
name
@ -159,52 +166,52 @@ class StarWarsIntrospectionTest extends TestCase
';
$expected = [
'__type' => [
'name' => 'Droid',
'name' => 'Droid',
'fields' => [
[
'name' => 'id',
'type' => [
'name' => null,
'kind' => 'NON_NULL'
]
'kind' => 'NON_NULL',
],
],
[
'name' => 'name',
'type' => [
'name' => 'String',
'kind' => 'SCALAR'
]
'kind' => 'SCALAR',
],
],
[
'name' => 'friends',
'type' => [
'name' => null,
'kind' => 'LIST'
]
'kind' => 'LIST',
],
],
[
'name' => 'appearsIn',
'type' => [
'name' => null,
'kind' => 'LIST'
]
'kind' => 'LIST',
],
],
[
'name' => 'secretBackstory',
'type' => [
'name' => 'String',
'kind' => 'SCALAR'
]
'kind' => 'SCALAR',
],
],
[
'name' => 'primaryFunction',
'type' => [
'name' => 'String',
'kind' => 'SCALAR'
]
]
]
]
'kind' => 'SCALAR',
],
],
],
],
];
$this->assertValidQuery($query, $expected);
}
@ -214,7 +221,7 @@ class StarWarsIntrospectionTest extends TestCase
*/
public function testAllowsQueryingTheSchemaForNestedObjectFields() : void
{
$query = '
$query = '
query IntrospectionDroidNestedFieldsQuery {
__type(name: "Droid") {
name
@ -234,67 +241,67 @@ class StarWarsIntrospectionTest extends TestCase
';
$expected = [
'__type' => [
'name' => 'Droid',
'name' => 'Droid',
'fields' => [
[
'name' => 'id',
'type' => [
'name' => null,
'kind' => 'NON_NULL',
'name' => null,
'kind' => 'NON_NULL',
'ofType' => [
'name' => 'String',
'kind' => 'SCALAR'
]
]
'kind' => 'SCALAR',
],
],
],
[
'name' => 'name',
'type' => [
'name' => 'String',
'kind' => 'SCALAR',
'ofType' => null
]
'name' => 'String',
'kind' => 'SCALAR',
'ofType' => null,
],
],
[
'name' => 'friends',
'type' => [
'name' => null,
'kind' => 'LIST',
'name' => null,
'kind' => 'LIST',
'ofType' => [
'name' => 'Character',
'kind' => 'INTERFACE'
]
]
'kind' => 'INTERFACE',
],
],
],
[
'name' => 'appearsIn',
'type' => [
'name' => null,
'kind' => 'LIST',
'name' => null,
'kind' => 'LIST',
'ofType' => [
'name' => 'Episode',
'kind' => 'ENUM'
]
]
'kind' => 'ENUM',
],
],
],
[
'name' => 'secretBackstory',
'type' => [
'name' => 'String',
'kind' => 'SCALAR',
'ofType' => null
]
'name' => 'String',
'kind' => 'SCALAR',
'ofType' => null,
],
],
[
'name' => 'primaryFunction',
'type' => [
'name' => 'String',
'kind' => 'SCALAR',
'ofType' => null
]
]
]
]
'name' => 'String',
'kind' => 'SCALAR',
'ofType' => null,
],
],
],
],
];
$this->assertValidQuery($query, $expected);
}
@ -329,7 +336,7 @@ class StarWarsIntrospectionTest extends TestCase
}
';
$expected = array(
$expected = [
'__schema' => [
'queryType' => [
'fields' => [
@ -337,13 +344,13 @@ class StarWarsIntrospectionTest extends TestCase
'name' => 'hero',
'args' => [
[
'defaultValue' => NULL,
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
'name' => 'episode',
'type' => [
'kind' => 'ENUM',
'name' => 'Episode',
'ofType' => NULL,
'defaultValue' => null,
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
'name' => 'episode',
'type' => [
'kind' => 'ENUM',
'name' => 'Episode',
'ofType' => null,
],
],
],
@ -352,17 +359,17 @@ class StarWarsIntrospectionTest extends TestCase
'name' => 'human',
'args' => [
[
'name' => 'id',
'description' => 'id of the human',
'type' => [
'kind' => 'NON_NULL',
'name' => NULL,
'name' => 'id',
'description' => 'id of the human',
'type' => [
'kind' => 'NON_NULL',
'name' => null,
'ofType' => [
'kind' => 'SCALAR',
'name' => 'String',
],
],
'defaultValue' => NULL,
'defaultValue' => null,
],
],
],
@ -370,25 +377,25 @@ class StarWarsIntrospectionTest extends TestCase
'name' => 'droid',
'args' => [
[
'name' => 'id',
'description' => 'id of the droid',
'type' => [
'kind' => 'NON_NULL',
'name' => NULL,
'name' => 'id',
'description' => 'id of the droid',
'type' => [
'kind' => 'NON_NULL',
'name' => null,
'ofType' =>
[
'kind' => 'SCALAR',
'name' => 'String',
],
],
'defaultValue' => NULL,
'defaultValue' => null,
],
],
],
],
],
],
);
];
$this->assertValidQuery($query, $expected);
}
@ -398,7 +405,7 @@ class StarWarsIntrospectionTest extends TestCase
*/
public function testAllowsQueryingTheSchemaForDocumentation() : void
{
$query = '
$query = '
query IntrospectionDroidDescriptionQuery {
__type(name: "Droid") {
name
@ -408,18 +415,10 @@ class StarWarsIntrospectionTest extends TestCase
';
$expected = [
'__type' => [
'name' => 'Droid',
'description' => 'A mechanical creature in the Star Wars universe.'
]
'name' => 'Droid',
'description' => 'A mechanical creature in the Star Wars universe.',
],
];
$this->assertValidQuery($query, $expected);
}
/**
* Helper function to test a query and the expected response.
*/
private function assertValidQuery($query, $expected)
{
$this->assertEquals(['data' => $expected], GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray());
}
}

View File

@ -1,21 +1,20 @@
<?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests;
use GraphQL\GraphQL;
use PHPUnit\Framework\TestCase;
class StarWarsQueryTest extends TestCase
{
// Star Wars Query Tests
// Basic Queries
/**
* @see it('Correctly identifies R2-D2 as the hero of the Star Wars Saga')
*/
public function testCorrectlyIdentifiesR2D2AsTheHeroOfTheStarWarsSaga() : void
{
$query = '
$query = '
query HeroNameQuery {
hero {
name
@ -23,19 +22,30 @@ class StarWarsQueryTest extends TestCase
}
';
$expected = [
'hero' => [
'name' => 'R2-D2'
]
'hero' => ['name' => 'R2-D2'],
];
$this->assertValidQuery($query, $expected);
}
/**
* Helper function to test a query and the expected response.
*/
private function assertValidQuery($query, $expected) : void
{
$this->assertEquals(
['data' => $expected],
GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray()
);
}
// Describe: Nested Queries
/**
* @see it('Allows us to query for the ID and friends of R2-D2')
*/
public function testAllowsUsToQueryForTheIDAndFriendsOfR2D2() : void
{
$query = '
$query = '
query HeroNameAndFriendsQuery {
hero {
id
@ -48,32 +58,26 @@ class StarWarsQueryTest extends TestCase
';
$expected = [
'hero' => [
'id' => '2001',
'name' => 'R2-D2',
'id' => '2001',
'name' => 'R2-D2',
'friends' => [
[
'name' => 'Luke Skywalker',
],
[
'name' => 'Han Solo',
],
[
'name' => 'Leia Organa',
],
]
]
['name' => 'Luke Skywalker'],
['name' => 'Han Solo'],
['name' => 'Leia Organa'],
],
],
];
$this->assertValidQuery($query, $expected);
}
// Describe: Nested Queries
// Describe: Using IDs and query parameters to refetch objects
/**
* @see it('Allows us to query for the friends of friends of R2-D2')
*/
public function testAllowsUsToQueryForTheFriendsOfFriendsOfR2D2() : void
{
$query = '
$query = '
query NestedQuery {
hero {
name
@ -89,52 +93,50 @@ class StarWarsQueryTest extends TestCase
';
$expected = [
'hero' => [
'name' => 'R2-D2',
'name' => 'R2-D2',
'friends' => [
[
'name' => 'Luke Skywalker',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI',],
'friends' => [
['name' => 'Han Solo',],
['name' => 'Leia Organa',],
['name' => 'C-3PO',],
['name' => 'R2-D2',],
'name' => 'Luke Skywalker',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'friends' => [
['name' => 'Han Solo'],
['name' => 'Leia Organa'],
['name' => 'C-3PO'],
['name' => 'R2-D2'],
],
],
[
'name' => 'Han Solo',
'name' => 'Han Solo',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'friends' => [
['name' => 'Luke Skywalker',],
'friends' => [
['name' => 'Luke Skywalker'],
['name' => 'Leia Organa'],
['name' => 'R2-D2',],
]
['name' => 'R2-D2'],
],
],
[
'name' => 'Leia Organa',
'name' => 'Leia Organa',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'friends' =>
'friends' =>
[
['name' => 'Luke Skywalker',],
['name' => 'Han Solo',],
['name' => 'C-3PO',],
['name' => 'R2-D2',],
['name' => 'Luke Skywalker'],
['name' => 'Han Solo'],
['name' => 'C-3PO'],
['name' => 'R2-D2'],
],
],
],
]
],
];
$this->assertValidQuery($query, $expected);
}
// Describe: Using IDs and query parameters to refetch objects
/**
* @see it('Using IDs and query parameters to refetch objects')
*/
public function testAllowsUsToQueryForLukeSkywalkerDirectlyUsingHisID() : void
{
$query = '
$query = '
query FetchLukeQuery {
human(id: "1000") {
name
@ -142,9 +144,7 @@ class StarWarsQueryTest extends TestCase
}
';
$expected = [
'human' => [
'name' => 'Luke Skywalker'
]
'human' => ['name' => 'Luke Skywalker'],
];
$this->assertValidQuery($query, $expected);
@ -155,44 +155,49 @@ class StarWarsQueryTest extends TestCase
*/
public function testGenericQueryToGetLukeSkywalkerById() : void
{
$query = '
$query = '
query FetchSomeIDQuery($someId: String!) {
human(id: $someId) {
name
}
}
';
$params = [
'someId' => '1000'
];
$params = ['someId' => '1000'];
$expected = [
'human' => [
'name' => 'Luke Skywalker'
]
'human' => ['name' => 'Luke Skywalker'],
];
$this->assertValidQueryWithParams($query, $params, $expected);
}
/**
* Helper function to test a query with params and the expected response.
*/
private function assertValidQueryWithParams($query, $params, $expected)
{
$this->assertEquals(
['data' => $expected],
GraphQL::executeQuery(StarWarsSchema::build(), $query, null, null, $params)->toArray()
);
}
// Using aliases to change the key in the response
/**
* @see it('Allows us to create a generic query, then use it to fetch Han Solo using his ID')
*/
public function testGenericQueryToGetHanSoloById() : void
{
$query = '
$query = '
query FetchSomeIDQuery($someId: String!) {
human(id: $someId) {
name
}
}
';
$params = [
'someId' => '1002'
];
$params = ['someId' => '1002'];
$expected = [
'human' => [
'name' => 'Han Solo'
]
'human' => ['name' => 'Han Solo'],
];
$this->assertValidQueryWithParams($query, $params, $expected);
}
@ -202,30 +207,26 @@ class StarWarsQueryTest extends TestCase
*/
public function testGenericQueryWithInvalidId() : void
{
$query = '
$query = '
query humanQuery($id: String!) {
human(id: $id) {
name
}
}
';
$params = [
'id' => 'not a valid id'
];
$expected = [
'human' => null
];
$params = ['id' => 'not a valid id'];
$expected = ['human' => null];
$this->assertValidQueryWithParams($query, $params, $expected);
}
// Using aliases to change the key in the response
// Uses fragments to express more complex queries
/**
* @see it('Allows us to query for Luke, changing his key with an alias')
*/
function testLukeKeyAlias()
public function testLukeKeyAlias() : void
{
$query = '
$query = '
query FetchLukeAliased {
luke: human(id: "1000") {
name
@ -233,9 +234,7 @@ class StarWarsQueryTest extends TestCase
}
';
$expected = [
'luke' => [
'name' => 'Luke Skywalker'
],
'luke' => ['name' => 'Luke Skywalker'],
];
$this->assertValidQuery($query, $expected);
}
@ -243,9 +242,9 @@ class StarWarsQueryTest extends TestCase
/**
* @see it('Allows us to query for both Luke and Leia, using two root fields and an alias')
*/
function testTwoRootKeysAsAnAlias()
public function testTwoRootKeysAsAnAlias() : void
{
$query = '
$query = '
query FetchLukeAndLeiaAliased {
luke: human(id: "1000") {
name
@ -256,24 +255,18 @@ class StarWarsQueryTest extends TestCase
}
';
$expected = [
'luke' => [
'name' => 'Luke Skywalker'
],
'leia' => [
'name' => 'Leia Organa'
]
'luke' => ['name' => 'Luke Skywalker'],
'leia' => ['name' => 'Leia Organa'],
];
$this->assertValidQuery($query, $expected);
}
// Uses fragments to express more complex queries
/**
* @see it('Allows us to query using duplicated content')
*/
function testQueryUsingDuplicatedContent()
public function testQueryUsingDuplicatedContent() : void
{
$query = '
$query = '
query DuplicateFields {
luke: human(id: "1000") {
name
@ -287,13 +280,13 @@ class StarWarsQueryTest extends TestCase
';
$expected = [
'luke' => [
'name' => 'Luke Skywalker',
'homePlanet' => 'Tatooine'
'name' => 'Luke Skywalker',
'homePlanet' => 'Tatooine',
],
'leia' => [
'name' => 'Leia Organa',
'homePlanet' => 'Alderaan'
]
'name' => 'Leia Organa',
'homePlanet' => 'Alderaan',
],
];
$this->assertValidQuery($query, $expected);
}
@ -301,7 +294,7 @@ class StarWarsQueryTest extends TestCase
/**
* @see it('Allows us to use a fragment to avoid duplicating content')
*/
function testUsingFragment()
public function testUsingFragment() : void
{
$query = '
query UseFragment {
@ -321,13 +314,13 @@ class StarWarsQueryTest extends TestCase
$expected = [
'luke' => [
'name' => 'Luke Skywalker',
'homePlanet' => 'Tatooine'
'name' => 'Luke Skywalker',
'homePlanet' => 'Tatooine',
],
'leia' => [
'name' => 'Leia Organa',
'homePlanet' => 'Alderaan'
]
'name' => 'Leia Organa',
'homePlanet' => 'Alderaan',
],
];
$this->assertValidQuery($query, $expected);
}
@ -337,7 +330,7 @@ class StarWarsQueryTest extends TestCase
*/
public function testVerifyThatR2D2IsADroid() : void
{
$query = '
$query = '
query CheckTypeOfR2 {
hero {
__typename
@ -348,7 +341,7 @@ class StarWarsQueryTest extends TestCase
$expected = [
'hero' => [
'__typename' => 'Droid',
'name' => 'R2-D2'
'name' => 'R2-D2',
],
];
$this->assertValidQuery($query, $expected);
@ -371,32 +364,10 @@ class StarWarsQueryTest extends TestCase
$expected = [
'hero' => [
'__typename' => 'Human',
'name' => 'Luke Skywalker'
'name' => 'Luke Skywalker',
],
];
$this->assertValidQuery($query, $expected);
}
/**
* Helper function to test a query and the expected response.
*/
private function assertValidQuery($query, $expected)
{
$this->assertEquals(
['data' => $expected],
GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray()
);
}
/**
* Helper function to test a query with params and the expected response.
*/
private function assertValidQueryWithParams($query, $params, $expected)
{
$this->assertEquals(
['data' => $expected],
GraphQL::executeQuery(StarWarsSchema::build(), $query, null, null, $params)->toArray()
);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests;
/**
@ -11,13 +14,16 @@ namespace GraphQL\Tests;
* NOTE: This may contain spoilers for the original Star
* Wars trilogy.
*/
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use function array_intersect_key;
use function array_map;
/**
* Using our shorthand to describe type systems, the type system for our
@ -56,10 +62,9 @@ use GraphQL\Type\Definition\Type;
*
* We begin by setting up our schema.
*/
class StarWarsSchema
{
public static function build()
public static function build() : Schema
{
/**
* The original trilogy consists of three movies.
@ -68,22 +73,22 @@ class StarWarsSchema
* enum Episode { NEWHOPE, EMPIRE, JEDI }
*/
$episodeEnum = new EnumType([
'name' => 'Episode',
'name' => 'Episode',
'description' => 'One of the films in the Star Wars Trilogy',
'values' => [
'values' => [
'NEWHOPE' => [
'value' => 4,
'description' => 'Released in 1977.'
'value' => 4,
'description' => 'Released in 1977.',
],
'EMPIRE' => [
'value' => 5,
'description' => 'Released in 1980.'
'EMPIRE' => [
'value' => 5,
'description' => 'Released in 1980.',
],
'JEDI' => [
'value' => 6,
'description' => 'Released in 1983.'
'JEDI' => [
'value' => 6,
'description' => 'Released in 1983.',
],
]
],
]);
$humanType = null;
@ -101,28 +106,28 @@ class StarWarsSchema
* }
*/
$characterInterface = new InterfaceType([
'name' => 'Character',
'name' => 'Character',
'description' => 'A character in the Star Wars Trilogy',
'fields' => function() use (&$characterInterface, $episodeEnum) {
'fields' => function () use (&$characterInterface, $episodeEnum) {
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the character.',
],
'name' => [
'type' => Type::string(),
'description' => 'The name of the character.'
'name' => [
'type' => Type::string(),
'description' => 'The name of the character.',
],
'friends' => [
'type' => Type::listOf($characterInterface),
'friends' => [
'type' => Type::listOf($characterInterface),
'description' => 'The friends of the character, or an empty list if they have none.',
],
'appearsIn' => [
'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.'
'appearsIn' => [
'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.',
],
'secretBackstory' => [
'type' => Type::string(),
'type' => Type::string(),
'description' => 'All secrets about their past.',
],
];
@ -145,26 +150,26 @@ class StarWarsSchema
* }
*/
$humanType = new ObjectType([
'name' => 'Human',
'name' => 'Human',
'description' => 'A humanoid creature in the Star Wars universe.',
'fields' => [
'id' => [
'type' => new NonNull(Type::string()),
'fields' => [
'id' => [
'type' => new NonNull(Type::string()),
'description' => 'The id of the human.',
],
'name' => [
'type' => Type::string(),
'name' => [
'type' => Type::string(),
'description' => 'The name of the human.',
],
'friends' => [
'type' => Type::listOf($characterInterface),
'friends' => [
'type' => Type::listOf($characterInterface),
'description' => 'The friends of the human, or an empty list if they have none.',
'resolve' => function ($human, $args, $context, ResolveInfo $info) {
$fieldSelection = $info->getFieldSelection();
'resolve' => function ($human, $args, $context, ResolveInfo $info) {
$fieldSelection = $info->getFieldSelection();
$fieldSelection['id'] = true;
$friends = array_map(
function($friend) use ($fieldSelection) {
function ($friend) use ($fieldSelection) {
return array_intersect_key($friend, $fieldSelection);
},
StarWarsData::getFriends($human)
@ -173,24 +178,24 @@ class StarWarsSchema
return $friends;
},
],
'appearsIn' => [
'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.'
'appearsIn' => [
'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.',
],
'homePlanet' => [
'type' => Type::string(),
'description' => 'The home planet of the human, or null if unknown.'
'homePlanet' => [
'type' => Type::string(),
'description' => 'The home planet of the human, or null if unknown.',
],
'secretBackstory' => [
'type' => Type::string(),
'type' => Type::string(),
'description' => 'Where are they from and how they came to be who they are.',
'resolve' => function() {
'resolve' => function () {
// This is to demonstrate error reporting
throw new \Exception('secretBackstory is secret.');
},
],
],
'interfaces' => [$characterInterface]
'interfaces' => [$characterInterface],
]);
/**
@ -207,42 +212,42 @@ class StarWarsSchema
* }
*/
$droidType = new ObjectType([
'name' => 'Droid',
'name' => 'Droid',
'description' => 'A mechanical creature in the Star Wars universe.',
'fields' => [
'id' => [
'type' => Type::nonNull(Type::string()),
'fields' => [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the droid.',
],
'name' => [
'type' => Type::string(),
'description' => 'The name of the droid.'
'name' => [
'type' => Type::string(),
'description' => 'The name of the droid.',
],
'friends' => [
'type' => Type::listOf($characterInterface),
'friends' => [
'type' => Type::listOf($characterInterface),
'description' => 'The friends of the droid, or an empty list if they have none.',
'resolve' => function ($droid) {
'resolve' => function ($droid) {
return StarWarsData::getFriends($droid);
},
],
'appearsIn' => [
'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.'
'appearsIn' => [
'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.',
],
'secretBackstory' => [
'type' => Type::string(),
'type' => Type::string(),
'description' => 'Construction date and the name of the designer.',
'resolve' => function() {
'resolve' => function () {
// This is to demonstrate error reporting
throw new \Exception('secretBackstory is secret.');
},
],
'primaryFunction' => [
'type' => Type::string(),
'description' => 'The primary function of the droid.'
]
'type' => Type::string(),
'description' => 'The primary function of the droid.',
],
],
'interfaces' => [$characterInterface]
'interfaces' => [$characterInterface],
]);
/**
@ -260,49 +265,51 @@ class StarWarsSchema
*
*/
$queryType = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'hero' => [
'type' => $characterInterface,
'args' => [
'hero' => [
'type' => $characterInterface,
'args' => [
'episode' => [
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
'type' => $episodeEnum
]
'type' => $episodeEnum,
],
],
'resolve' => function ($root, $args) {
return StarWarsData::getHero(isset($args['episode']) ? $args['episode'] : null);
return StarWarsData::getHero($args['episode'] ?? null);
},
],
'human' => [
'type' => $humanType,
'args' => [
'type' => $humanType,
'args' => [
'id' => [
'name' => 'id',
'name' => 'id',
'description' => 'id of the human',
'type' => Type::nonNull(Type::string())
]
'type' => Type::nonNull(Type::string()),
],
],
'resolve' => function ($root, $args) {
$humans = StarWarsData::humans();
return isset($humans[$args['id']]) ? $humans[$args['id']] : null;
}
return $humans[$args['id']] ?? null;
},
],
'droid' => [
'type' => $droidType,
'args' => [
'type' => $droidType,
'args' => [
'id' => [
'name' => 'id',
'name' => 'id',
'description' => 'id of the droid',
'type' => Type::nonNull(Type::string())
]
'type' => Type::nonNull(Type::string()),
],
],
'resolve' => function ($root, $args) {
$droids = StarWarsData::droids();
return isset($droids[$args['id']]) ? $droids[$args['id']] : null;
}
]
]
return $droids[$args['id']] ?? null;
},
],
],
]);
return new Schema(['query' => $queryType]);

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests;
use GraphQL\Language\Parser;
@ -15,7 +18,7 @@ class StarWarsValidationTest extends TestCase
*/
public function testValidatesAComplexButValidQuery() : void
{
$query = '
$query = '
query NestedQueryWithFragment {
hero {
...NameAndAppearances
@ -37,12 +40,22 @@ class StarWarsValidationTest extends TestCase
$this->assertEquals(true, empty($errors));
}
/**
* Helper function to test a query and the expected response.
*/
private function validationErrors($query)
{
$ast = Parser::parse($query);
return DocumentValidator::validate(StarWarsSchema::build(), $ast);
}
/**
* @see it('Notes that non-existent fields are invalid')
*/
public function testThatNonExistentFieldsAreInvalid() : void
{
$query = '
$query = '
query HeroSpaceshipQuery {
hero {
favoriteSpaceship
@ -73,7 +86,7 @@ class StarWarsValidationTest extends TestCase
*/
public function testDisallowsFieldsOnScalars() : void
{
$query = '
$query = '
query HeroFieldsOnScalarQuery {
hero {
name {
@ -91,7 +104,7 @@ class StarWarsValidationTest extends TestCase
*/
public function testDisallowsObjectFieldsOnInterfaces() : void
{
$query = '
$query = '
query DroidFieldOnCharacter {
hero {
name
@ -108,7 +121,7 @@ class StarWarsValidationTest extends TestCase
*/
public function testAllowsObjectFieldsInFragments() : void
{
$query = '
$query = '
query DroidFieldInFragment {
hero {
name
@ -129,7 +142,7 @@ class StarWarsValidationTest extends TestCase
*/
public function testAllowsObjectFieldsInInlineFragments() : void
{
$query = '
$query = '
query DroidFieldInFragment {
hero {
name
@ -142,13 +155,4 @@ class StarWarsValidationTest extends TestCase
$errors = $this->validationErrors($query);
$this->assertEquals(true, empty($errors));
}
/**
* Helper function to test a query and the expected response.
*/
private function validationErrors($query)
{
$ast = Parser::parse($query);
return DocumentValidator::validate(StarWarsSchema::build(), $ast);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,68 +1,77 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type;
use ArrayObject;
use GraphQL\GraphQL;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function count;
use function is_array;
class EnumTypeTest extends TestCase
{
/**
* @var Schema
*/
/** @var Schema */
private $schema;
/**
* @var EnumType
*/
/** @var EnumType */
private $ComplexEnum;
/** @var mixed[] */
private $Complex1;
/** @var ArrayObject */
private $Complex2;
public function setUp()
{
$ColorType = new EnumType([
'name' => 'Color',
'name' => 'Color',
'values' => [
'RED' => ['value' => 0],
'RED' => ['value' => 0],
'GREEN' => ['value' => 1],
'BLUE' => ['value' => 2],
]
'BLUE' => ['value' => 2],
],
]);
$simpleEnum = new EnumType([
'name' => 'SimpleEnum',
'name' => 'SimpleEnum',
'values' => [
'ONE', 'TWO', 'THREE'
]
'ONE',
'TWO',
'THREE',
],
]);
$Complex1 = ['someRandomFunction' => function() {}];
$Complex1 = [
'someRandomFunction' => function () {
},
];
$Complex2 = new \ArrayObject(['someRandomValue' => 123]);
$ComplexEnum = new EnumType([
'name' => 'Complex',
'name' => 'Complex',
'values' => [
'ONE' => ['value' => $Complex1],
'TWO' => ['value' => $Complex2]
]
'TWO' => ['value' => $Complex2],
],
]);
$QueryType = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'colorEnum' => [
'type' => $ColorType,
'args' => [
'fromEnum' => ['type' => $ColorType],
'fromInt' => ['type' => Type::int()],
'colorEnum' => [
'type' => $ColorType,
'args' => [
'fromEnum' => ['type' => $ColorType],
'fromInt' => ['type' => Type::int()],
'fromString' => ['type' => Type::string()],
],
'resolve' => function ($value, $args) {
@ -75,28 +84,28 @@ class EnumTypeTest extends TestCase
if (isset($args['fromEnum'])) {
return $args['fromEnum'];
}
}
},
],
'simpleEnum' => [
'type' => $simpleEnum,
'args' => [
'fromName' => ['type' => Type::string()],
'fromValue' => ['type' => Type::string()]
'simpleEnum' => [
'type' => $simpleEnum,
'args' => [
'fromName' => ['type' => Type::string()],
'fromValue' => ['type' => Type::string()],
],
'resolve' => function($value, $args) {
'resolve' => function ($value, $args) {
if (isset($args['fromName'])) {
return $args['fromName'];
}
if (isset($args['fromValue'])) {
return $args['fromValue'];
}
}
},
],
'colorInt' => [
'type' => Type::int(),
'args' => [
'colorInt' => [
'type' => Type::int(),
'args' => [
'fromEnum' => ['type' => $ColorType],
'fromInt' => ['type' => Type::int()],
'fromInt' => ['type' => Type::int()],
],
'resolve' => function ($value, $args) {
if (isset($args['fromInt'])) {
@ -105,75 +114,76 @@ class EnumTypeTest extends TestCase
if (isset($args['fromEnum'])) {
return $args['fromEnum'];
}
}
},
],
'complexEnum' => [
'type' => $ComplexEnum,
'args' => [
'fromEnum' => [
'type' => $ComplexEnum,
'type' => $ComplexEnum,
'args' => [
'fromEnum' => [
'type' => $ComplexEnum,
// Note: defaultValue is provided an *internal* representation for
// Enums, rather than the string name.
'defaultValue' => $Complex1
'defaultValue' => $Complex1,
],
'provideGoodValue' => [
'type' => Type::boolean(),
],
'provideBadValue' => [
'type' => Type::boolean()
]
'provideBadValue' => [
'type' => Type::boolean(),
],
],
'resolve' => function($value, $args) use ($Complex1, $Complex2) {
if (!empty($args['provideGoodValue'])) {
'resolve' => function ($value, $args) use ($Complex2) {
if (! empty($args['provideGoodValue'])) {
// Note: this is one of the references of the internal values which
// ComplexEnum allows.
return $Complex2;
}
if (!empty($args['provideBadValue'])) {
if (! empty($args['provideBadValue'])) {
// Note: similar shape, but not the same *reference*
// as Complex2 above. Enum internal values require === equality.
return new \ArrayObject(['someRandomValue' => 123]);
}
return $args['fromEnum'];
}
]
]
},
],
],
]);
$MutationType = new ObjectType([
'name' => 'Mutation',
'name' => 'Mutation',
'fields' => [
'favoriteEnum' => [
'type' => $ColorType,
'args' => ['color' => ['type' => $ColorType]],
'type' => $ColorType,
'args' => ['color' => ['type' => $ColorType]],
'resolve' => function ($value, $args) {
return isset($args['color']) ? $args['color'] : null;
}
]
]
return $args['color'] ?? null;
},
],
],
]);
$SubscriptionType = new ObjectType([
'name' => 'Subscription',
'name' => 'Subscription',
'fields' => [
'subscribeToEnum' => [
'type' => $ColorType,
'args' => ['color' => ['type' => $ColorType]],
'type' => $ColorType,
'args' => ['color' => ['type' => $ColorType]],
'resolve' => function ($value, $args) {
return isset($args['color']) ? $args['color'] : null;
}
]
]
return $args['color'] ?? null;
},
],
],
]);
$this->Complex1 = $Complex1;
$this->Complex2 = $Complex2;
$this->Complex1 = $Complex1;
$this->Complex2 = $Complex2;
$this->ComplexEnum = $ComplexEnum;
$this->schema = new Schema([
'query' => $QueryType,
'mutation' => $MutationType,
'subscription' => $SubscriptionType
'query' => $QueryType,
'mutation' => $MutationType,
'subscription' => $SubscriptionType,
]);
}
@ -221,12 +231,34 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromEnum: "GREEN") }',
null,
[
'message' => "Expected type Color, found \"GREEN\"; Did you mean the enum value GREEN?",
'locations' => [new SourceLocation(1, 23)]
'message' => 'Expected type Color, found "GREEN"; Did you mean the enum value GREEN?',
'locations' => [new SourceLocation(1, 23)],
]
);
}
private function expectFailure($query, $vars, $err)
{
$result = GraphQL::executeQuery($this->schema, $query, null, null, $vars);
$this->assertEquals(1, count($result->errors));
if (is_array($err)) {
$this->assertEquals(
$err['message'],
$result->errors[0]->getMessage()
);
$this->assertEquals(
$err['locations'],
$result->errors[0]->getLocations()
);
} else {
$this->assertEquals(
$err,
$result->errors[0]->getMessage()
);
}
}
/**
* @see it('does not accept valuesNotInTheEnum')
*/
@ -236,8 +268,8 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromEnum: GREENISH) }',
null,
[
'message' => "Expected type Color, found GREENISH; Did you mean the enum value GREEN?",
'locations' => [new SourceLocation(1, 23)]
'message' => 'Expected type Color, found GREENISH; Did you mean the enum value GREEN?',
'locations' => [new SourceLocation(1, 23)],
]
);
}
@ -251,8 +283,8 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromEnum: green) }',
null,
[
'message' => "Expected type Color, found green; Did you mean the enum value GREEN?",
'locations' => [new SourceLocation(1, 23)]
'message' => 'Expected type Color, found green; Did you mean the enum value GREEN?',
'locations' => [new SourceLocation(1, 23)],
]
);
}
@ -266,9 +298,9 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromString: "GREEN") }',
null,
[
'message' => 'Expected a value of type "Color" but received: GREEN',
'message' => 'Expected a value of type "Color" but received: GREEN',
'locations' => [new SourceLocation(1, 3)],
'path' => ['colorEnum'],
'path' => ['colorEnum'],
]
);
}
@ -281,7 +313,7 @@ class EnumTypeTest extends TestCase
$this->expectFailure(
'{ colorEnum(fromEnum: 1) }',
null,
"Expected type Color, found 1."
'Expected type Color, found 1.'
);
}
@ -293,7 +325,7 @@ class EnumTypeTest extends TestCase
$this->expectFailure(
'{ colorEnum(fromInt: GREEN) }',
null,
"Expected type Int, found GREEN."
'Expected type Int, found GREEN.'
);
}
@ -381,7 +413,7 @@ class EnumTypeTest extends TestCase
$this->expectFailure(
'query test($color: Int!) { colorEnum(fromEnum: $color) }',
['color' => 2],
'Variable "$color" of type "Int!" used in position ' . 'expecting type "Color".'
'Variable "$color" of type "Int!" used in position expecting type "Color".'
);
}
@ -392,10 +424,13 @@ class EnumTypeTest extends TestCase
{
$this->assertEquals(
['data' => ['colorEnum' => 'RED', 'colorInt' => 0]],
GraphQL::executeQuery($this->schema, "{
GraphQL::executeQuery(
$this->schema,
'{
colorEnum(fromEnum: RED)
colorInt(fromEnum: RED)
}")->toArray()
}'
)->toArray()
);
}
@ -406,10 +441,13 @@ class EnumTypeTest extends TestCase
{
$this->assertEquals(
['data' => ['colorEnum' => null, 'colorInt' => null]],
GraphQL::executeQuery($this->schema, "{
GraphQL::executeQuery(
$this->schema,
'{
colorEnum
colorInt
}")->toArray()
}'
)->toArray()
);
}
@ -419,7 +457,7 @@ class EnumTypeTest extends TestCase
public function testPresentsGetValuesAPIForComplexEnums() : void
{
$ComplexEnum = $this->ComplexEnum;
$values = $ComplexEnum->getValues();
$values = $ComplexEnum->getValues();
$this->assertEquals(2, count($values));
$this->assertEquals('ONE', $values[0]->name);
@ -446,25 +484,29 @@ class EnumTypeTest extends TestCase
*/
public function testMayBeInternallyRepresentedWithComplexValues() : void
{
$result = GraphQL::executeQuery($this->schema, '{
$result = GraphQL::executeQuery(
$this->schema,
'{
first: complexEnum
second: complexEnum(fromEnum: TWO)
good: complexEnum(provideGoodValue: true)
bad: complexEnum(provideBadValue: true)
}')->toArray(true);
}'
)->toArray(true);
$expected = [
'data' => [
'first' => 'ONE',
'data' => [
'first' => 'ONE',
'second' => 'TWO',
'good' => 'TWO',
'bad' => null
'good' => 'TWO',
'bad' => null,
],
'errors' => [[
'debugMessage' =>
'Expected a value of type "Complex" but received: instance of ArrayObject',
'locations' => [['line' => 5, 'column' => 9]]
]]
'locations' => [['line' => 5, 'column' => 9]],
],
],
];
$this->assertArraySubset($expected, $result);
@ -489,35 +531,14 @@ class EnumTypeTest extends TestCase
$this->assertArraySubset(
[
'data' => ['first' => 'ONE', 'second' => 'TWO', 'third' => null],
'data' => ['first' => 'ONE', 'second' => 'TWO', 'third' => null],
'errors' => [[
'debugMessage' => 'Expected a value of type "SimpleEnum" but received: WRONG',
'locations' => [['line' => 4, 'column' => 13]]
]]
'locations' => [['line' => 4, 'column' => 13]],
],
],
],
GraphQL::executeQuery($this->schema, $q)->toArray(true)
);
}
private function expectFailure($query, $vars, $err)
{
$result = GraphQL::executeQuery($this->schema, $query, null, null, $vars);
$this->assertEquals(1, count($result->errors));
if (is_array($err)) {
$this->assertEquals(
$err['message'],
$result->errors[0]->getMessage()
);
$this->assertEquals(
$err['locations'],
$result->errors[0]->getLocations()
);
} else {
$this->assertEquals(
$err,
$result->errors[0]->getMessage()
);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,12 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type;
class ObjectIdStub
{
/**
* @var int
*/
/** @var int */
private $id;
/**

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type;
use GraphQL\Error\InvariantViolation;
@ -10,124 +13,84 @@ use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\EagerResolution;
use GraphQL\Type\LazyResolution;
use PHPUnit\Framework\TestCase;
use function lcfirst;
class ResolutionTest extends TestCase
{
/**
* @var ObjectType
*/
/** @var ObjectType */
private $query;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $mutation;
/**
* @var InterfaceType
*/
/** @var InterfaceType */
private $node;
/**
* @var InterfaceType
*/
/** @var InterfaceType */
private $content;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $blogStory;
/**
* @var ObjectType
*/
private $link;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $video;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $videoMetadata;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $comment;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $user;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $category;
/**
* @var UnionType
*/
/** @var UnionType */
private $mention;
/** @var ObjectType */
private $postStoryMutation;
/** @var InputObjectType */
private $postStoryMutationInput;
/** @var ObjectType */
private $postCommentMutation;
/** @var InputObjectType */
private $postCommentMutationInput;
public function setUp()
{
$this->node = new InterfaceType([
'name' => 'Node',
'name' => 'Node',
'fields' => [
'id' => Type::string()
]
'id' => Type::string(),
],
]);
$this->content = new InterfaceType([
'name' => 'Content',
'fields' => function() {
'name' => 'Content',
'fields' => function () {
return [
'title' => Type::string(),
'body' => Type::string(),
'author' => $this->user,
'comments' => Type::listOf($this->comment),
'categories' => Type::listOf($this->category)
'title' => Type::string(),
'body' => Type::string(),
'author' => $this->user,
'comments' => Type::listOf($this->comment),
'categories' => Type::listOf($this->category),
];
}
},
]);
$this->blogStory = new ObjectType([
'name' => 'BlogStory',
'name' => 'BlogStory',
'interfaces' => [
$this->node,
$this->content
$this->content,
],
'fields' => function() {
return [
$this->node->getField('id'),
$this->content->getField('title'),
$this->content->getField('body'),
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories')
];
},
]);
$this->link = new ObjectType([
'name' => 'Link',
'interfaces' => [
$this->node,
$this->content
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
$this->content->getField('title'),
@ -135,143 +98,166 @@ class ResolutionTest extends TestCase
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories'),
'url' => Type::string()
];
},
]);
new ObjectType([
'name' => 'Link',
'interfaces' => [
$this->node,
$this->content,
],
'fields' => function () {
return [
'id' => $this->node->getField('id'),
'title' => $this->content->getField('title'),
'body' => $this->content->getField('body'),
'author' => $this->content->getField('author'),
'comments' => $this->content->getField('comments'),
'categories' => $this->content->getField('categories'),
'url' => Type::string(),
];
},
]);
$this->videoMetadata = new ObjectType([
'name' => 'VideoMetadata',
'fields' => [
'lat' => Type::float(),
'lng' => Type::float(),
],
]);
$this->video = new ObjectType([
'name' => 'Video',
'name' => 'Video',
'interfaces' => [
$this->node,
$this->content
$this->content,
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
$this->content->getField('title'),
$this->content->getField('body'),
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories'),
'streamUrl' => Type::string(),
'id' => $this->node->getField('id'),
'title' => $this->content->getField('title'),
'body' => $this->content->getField('body'),
'author' => $this->content->getField('author'),
'comments' => $this->content->getField('comments'),
'categories' => $this->content->getField('categories'),
'streamUrl' => Type::string(),
'downloadUrl' => Type::string(),
'metadata' => $this->videoMetadata = new ObjectType([
'name' => 'VideoMetadata',
'fields' => [
'lat' => Type::float(),
'lng' => Type::float()
]
])
'metadata' => $this->videoMetadata,
];
}
},
]);
$this->comment = new ObjectType([
'name' => 'Comment',
'name' => 'Comment',
'interfaces' => [
$this->node
$this->node,
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
'author' => $this->user,
'text' => Type::string(),
'id' => $this->node->getField('id'),
'author' => $this->user,
'text' => Type::string(),
'replies' => Type::listOf($this->comment),
'parent' => $this->comment,
'content' => $this->content
'parent' => $this->comment,
'content' => $this->content,
];
}
},
]);
$this->user = new ObjectType([
'name' => 'User',
'name' => 'User',
'interfaces' => [
$this->node
$this->node,
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
'id' => $this->node->getField('id'),
'name' => Type::string(),
];
}
},
]);
$this->category = new ObjectType([
'name' => 'Category',
'name' => 'Category',
'interfaces' => [
$this->node
$this->node,
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
'name' => Type::string()
'id' => $this->node->getField('id'),
'name' => Type::string(),
];
}
},
]);
$this->mention = new UnionType([
'name' => 'Mention',
'name' => 'Mention',
'types' => [
$this->user,
$this->category
]
$this->category,
],
]);
$this->query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'viewer' => $this->user,
'viewer' => $this->user,
'latestContent' => $this->content,
'node' => $this->node,
'mentions' => Type::listOf($this->mention)
]
'node' => $this->node,
'mentions' => Type::listOf($this->mention),
],
]);
$this->postStoryMutationInput = new InputObjectType([
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id(),
],
]);
$this->mutation = new ObjectType([
'name' => 'Mutation',
'name' => 'Mutation',
'fields' => [
'postStory' => [
'postStory' => [
'type' => $this->postStoryMutation = new ObjectType([
'name' => 'PostStoryMutation',
'name' => 'PostStoryMutation',
'fields' => [
'story' => $this->blogStory
]
'story' => $this->blogStory,
],
]),
'args' => [
'input' => Type::nonNull($this->postStoryMutationInput = new InputObjectType([
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id()
]
])),
'clientRequestId' => Type::string()
]
'input' => Type::nonNull($this->postStoryMutationInput),
'clientRequestId' => Type::string(),
],
],
'postComment' => [
'type' => $this->postCommentMutation = new ObjectType([
'name' => 'PostCommentMutation',
'name' => 'PostCommentMutation',
'fields' => [
'comment' => $this->comment
]
'comment' => $this->comment,
],
]),
'args' => [
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
'name' => 'PostCommentMutationInput',
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
'name' => 'PostCommentMutationInput',
'fields' => [
'text' => Type::nonNull(Type::string()),
'author' => Type::nonNull(Type::id()),
'text' => Type::nonNull(Type::string()),
'author' => Type::nonNull(Type::id()),
'content' => Type::id(),
'parent' => Type::id()
]
'parent' => Type::id(),
],
])),
'clientRequestId' => Type::string()
]
]
]
'clientRequestId' => Type::string(),
],
],
],
]);
}
@ -279,29 +265,29 @@ class ResolutionTest extends TestCase
{
// Has internal types by default:
$eagerTypeResolution = new EagerResolution([]);
$expectedTypeMap = [
'ID' => Type::id(),
'String' => Type::string(),
'Float' => Type::float(),
'Int' => Type::int(),
'Boolean' => Type::boolean()
$expectedTypeMap = [
'ID' => Type::id(),
'String' => Type::string(),
'Float' => Type::float(),
'Int' => Type::int(),
'Boolean' => Type::boolean(),
];
$this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap());
$expectedDescriptor = [
'version' => '1.0',
'typeMap' => [
'ID' => 1,
'String' => 1,
'Float' => 1,
'Int' => 1,
'version' => '1.0',
'typeMap' => [
'ID' => 1,
'String' => 1,
'Float' => 1,
'Int' => 1,
'Boolean' => 1,
],
'possibleTypeMap' => []
'possibleTypeMap' => [],
];
$this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor());
$this->assertSame(null, $eagerTypeResolution->resolveType('User'));
$this->assertNull($eagerTypeResolution->resolveType('User'));
$this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->node));
$this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->content));
$this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->mention));
@ -321,72 +307,76 @@ class ResolutionTest extends TestCase
$this->assertSame($this->postStoryMutation, $eagerTypeResolution->resolveType('PostStoryMutation'));
$this->assertSame($this->postStoryMutationInput, $eagerTypeResolution->resolveType('PostStoryMutationInput'));
$this->assertSame($this->postCommentMutation, $eagerTypeResolution->resolveType('PostCommentMutation'));
$this->assertSame($this->postCommentMutationInput, $eagerTypeResolution->resolveType('PostCommentMutationInput'));
$this->assertSame(
$this->postCommentMutationInput,
$eagerTypeResolution->resolveType('PostCommentMutationInput')
);
$this->assertEquals([$this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->content));
$this->assertEquals([$this->user, $this->comment, $this->category, $this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->node));
$this->assertEquals(
[$this->user, $this->comment, $this->category, $this->blogStory],
$eagerTypeResolution->resolvePossibleTypes($this->node)
);
$this->assertEquals([$this->user, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->mention));
$expectedTypeMap = [
'Query' => $this->query,
'Mutation' => $this->mutation,
'User' => $this->user,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'Comment' => $this->comment,
'Mention' => $this->mention,
'BlogStory' => $this->blogStory,
'Category' => $this->category,
'PostStoryMutationInput' => $this->postStoryMutationInput,
'ID' => Type::id(),
'PostStoryMutation' => $this->postStoryMutation,
'Query' => $this->query,
'Mutation' => $this->mutation,
'User' => $this->user,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'Comment' => $this->comment,
'Mention' => $this->mention,
'BlogStory' => $this->blogStory,
'Category' => $this->category,
'PostStoryMutationInput' => $this->postStoryMutationInput,
'ID' => Type::id(),
'PostStoryMutation' => $this->postStoryMutation,
'PostCommentMutationInput' => $this->postCommentMutationInput,
'PostCommentMutation' => $this->postCommentMutation,
'Float' => Type::float(),
'Int' => Type::int(),
'Boolean' => Type::boolean()
'PostCommentMutation' => $this->postCommentMutation,
'Float' => Type::float(),
'Int' => Type::int(),
'Boolean' => Type::boolean(),
];
$this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap());
$expectedDescriptor = [
'version' => '1.0',
'typeMap' => [
'Query' => 1,
'Mutation' => 1,
'User' => 1,
'Node' => 1,
'String' => 1,
'Content' => 1,
'Comment' => 1,
'Mention' => 1,
'BlogStory' => 1,
'Category' => 1,
'PostStoryMutationInput' => 1,
'ID' => 1,
'PostStoryMutation' => 1,
'version' => '1.0',
'typeMap' => [
'Query' => 1,
'Mutation' => 1,
'User' => 1,
'Node' => 1,
'String' => 1,
'Content' => 1,
'Comment' => 1,
'Mention' => 1,
'BlogStory' => 1,
'Category' => 1,
'PostStoryMutationInput' => 1,
'ID' => 1,
'PostStoryMutation' => 1,
'PostCommentMutationInput' => 1,
'PostCommentMutation' => 1,
'Float' => 1,
'Int' => 1,
'Boolean' => 1
'PostCommentMutation' => 1,
'Float' => 1,
'Int' => 1,
'Boolean' => 1,
],
'possibleTypeMap' => [
'Node' => [
'User' => 1,
'Comment' => 1,
'Category' => 1,
'BlogStory' => 1
],
'Content' => [
'BlogStory' => 1
'Node' => [
'User' => 1,
'Comment' => 1,
'Category' => 1,
'BlogStory' => 1,
],
'Content' => ['BlogStory' => 1],
'Mention' => [
'User' => 1,
'Category' => 1
]
]
'User' => 1,
'Category' => 1,
],
],
];
$this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor());
@ -402,7 +392,10 @@ class ResolutionTest extends TestCase
$this->assertEquals(null, $eagerTypeResolution->resolveType('VideoMetadata'));
$this->assertEquals([$this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->content));
$this->assertEquals([$this->user, $this->comment, $this->category, $this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->node));
$this->assertEquals(
[$this->user, $this->comment, $this->category, $this->blogStory],
$eagerTypeResolution->resolvePossibleTypes($this->node)
);
$this->assertEquals([$this->user, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->mention));
$eagerTypeResolution = new EagerResolution([null, $this->video, null]);
@ -410,52 +403,53 @@ class ResolutionTest extends TestCase
$this->assertEquals($this->video, $eagerTypeResolution->resolveType('Video'));
$this->assertEquals([$this->video], $eagerTypeResolution->resolvePossibleTypes($this->content));
$this->assertEquals([$this->video, $this->user, $this->comment, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->node));
$this->assertEquals(
[$this->video, $this->user, $this->comment, $this->category],
$eagerTypeResolution->resolvePossibleTypes($this->node)
);
$this->assertEquals([], $eagerTypeResolution->resolvePossibleTypes($this->mention));
$expectedTypeMap = [
'Video' => $this->video,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'User' => $this->user,
'Comment' => $this->comment,
'Category' => $this->category,
'Video' => $this->video,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'User' => $this->user,
'Comment' => $this->comment,
'Category' => $this->category,
'VideoMetadata' => $this->videoMetadata,
'Float' => Type::float(),
'ID' => Type::id(),
'Int' => Type::int(),
'Boolean' => Type::boolean()
'Float' => Type::float(),
'ID' => Type::id(),
'Int' => Type::int(),
'Boolean' => Type::boolean(),
];
$this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap());
$expectedDescriptor = [
'version' => '1.0',
'typeMap' => [
'Video' => 1,
'Node' => 1,
'String' => 1,
'Content' => 1,
'User' => 1,
'Comment' => 1,
'Category' => 1,
'version' => '1.0',
'typeMap' => [
'Video' => 1,
'Node' => 1,
'String' => 1,
'Content' => 1,
'User' => 1,
'Comment' => 1,
'Category' => 1,
'VideoMetadata' => 1,
'Float' => 1,
'ID' => 1,
'Int' => 1,
'Boolean' => 1
'Float' => 1,
'ID' => 1,
'Int' => 1,
'Boolean' => 1,
],
'possibleTypeMap' => [
'Node' => [
'Video' => 1,
'User' => 1,
'Comment' => 1,
'Category' => 1
'Node' => [
'Video' => 1,
'User' => 1,
'Comment' => 1,
'Category' => 1,
],
'Content' => [
'Video' => 1
]
]
'Content' => ['Video' => 1],
],
];
$this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor());
}
@ -463,11 +457,11 @@ class ResolutionTest extends TestCase
public function testLazyResolutionFollowsEagerResolution() : void
{
// Lazy resolution should work the same way as eager resolution works, except that it should load types on demand
$eager = new EagerResolution([]);
$eager = new EagerResolution([]);
$emptyDescriptor = $eager->getDescriptor();
$typeLoader = function($name) {
throw new \Exception("This should be never called for empty descriptor");
$typeLoader = function ($name) {
throw new \Exception('This should be never called for empty descriptor');
};
$lazy = new LazyResolution($emptyDescriptor, $typeLoader);
@ -478,11 +472,12 @@ class ResolutionTest extends TestCase
$eager = new EagerResolution([$this->query, $this->mutation]);
$called = 0;
$called = 0;
$descriptor = $eager->getDescriptor();
$typeLoader = function($name) use (&$called) {
$typeLoader = function ($name) use (&$called) {
$called++;
$prop = lcfirst($name);
return $this->{$prop};
};
@ -507,7 +502,10 @@ class ResolutionTest extends TestCase
$this->assertSame($eager->resolveType('PostStoryMutation'), $lazy->resolveType('PostStoryMutation'));
$this->assertSame($eager->resolveType('PostStoryMutationInput'), $lazy->resolveType('PostStoryMutationInput'));
$this->assertSame($eager->resolveType('PostCommentMutation'), $lazy->resolveType('PostCommentMutation'));
$this->assertSame($eager->resolveType('PostCommentMutationInput'), $lazy->resolveType('PostCommentMutationInput'));
$this->assertSame(
$eager->resolveType('PostCommentMutationInput'),
$lazy->resolveType('PostCommentMutationInput')
);
$this->assertSame(13, $called);
$this->assertEquals($eager->resolvePossibleTypes($this->content), $lazy->resolvePossibleTypes($this->content));
@ -515,8 +513,8 @@ class ResolutionTest extends TestCase
$this->assertEquals($eager->resolvePossibleTypes($this->mention), $lazy->resolvePossibleTypes($this->mention));
$called = 0;
$eager = new EagerResolution([$this->video]);
$lazy = new LazyResolution($eager->getDescriptor(), $typeLoader);
$eager = new EagerResolution([$this->video]);
$lazy = new LazyResolution($eager->getDescriptor(), $typeLoader);
$this->assertEquals($eager->resolveType('VideoMetadata'), $lazy->resolveType('VideoMetadata'));
$this->assertEquals($eager->resolveType('Video'), $lazy->resolveType('Video'));
@ -527,40 +525,6 @@ class ResolutionTest extends TestCase
$this->assertEquals($eager->resolvePossibleTypes($this->mention), $lazy->resolvePossibleTypes($this->mention));
}
private function createLazy(){
$descriptor = [
'version' => '1.0',
'typeMap' => [
'null' => 1,
'int' => 1
],
'possibleTypeMap' => [
'a' => [
'null' => 1,
],
'b' => [
'int' => 1
]
]
];
$invalidTypeLoader = function($name) {
switch ($name) {
case 'null':
return null;
case 'int':
return 7;
}
};
$lazy = new LazyResolution($descriptor, $invalidTypeLoader);
$value = $lazy->resolveType('null');
$this->assertEquals(null, $value);
return $lazy;
}
public function testLazyThrowsOnInvalidLoadedType() : void
{
$lazy = $this->createLazy();
@ -569,9 +533,39 @@ class ResolutionTest extends TestCase
$lazy->resolveType('int');
}
private function createLazy()
{
$descriptor = [
'version' => '1.0',
'typeMap' => [
'null' => 1,
'int' => 1,
],
'possibleTypeMap' => [
'a' => ['null' => 1],
'b' => ['int' => 1],
],
];
$invalidTypeLoader = function ($name) {
switch ($name) {
case 'null':
return null;
case 'int':
return 7;
}
};
$lazy = new LazyResolution($descriptor, $invalidTypeLoader);
$value = $lazy->resolveType('null');
$this->assertEquals(null, $value);
return $lazy;
}
public function testLazyThrowsOnInvalidLoadedPossibleType() : void
{
$tmp = new InterfaceType(['name' => 'a', 'fields' => []]);
$tmp = new InterfaceType(['name' => 'a', 'fields' => []]);
$lazy = $this->createLazy();
$this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Lazy Type Resolution Error: Implementation null of interface a is expected to be instance of ObjectType, but got NULL');
@ -580,7 +574,7 @@ class ResolutionTest extends TestCase
public function testLazyThrowsOnInvalidLoadedPossibleTypeWithInteger() : void
{
$tmp = new InterfaceType(['name' => 'b', 'fields' => []]);
$tmp = new InterfaceType(['name' => 'b', 'fields' => []]);
$lazy = $this->createLazy();
$this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Lazy Type Resolution Error: Expecting GraphQL Type instance, but got integer');

View File

@ -1,11 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type;
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
class ResolveInfoTest extends TestCase
@ -13,53 +16,56 @@ class ResolveInfoTest extends TestCase
public function testFieldSelection() : void
{
$image = new ObjectType([
'name' => 'Image',
'name' => 'Image',
'fields' => [
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()]
]
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()],
],
]);
$article = null;
$author = new ObjectType([
'name' => 'Author',
'fields' => function() use ($image, &$article) {
'name' => 'Author',
'fields' => function () use ($image, &$article) {
return [
'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'pic' => [ 'type' => $image, 'args' => [
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()]
]],
'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'pic' => [
'type' => $image,
'args' => [
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()],
],
],
'recentArticle' => ['type' => $article],
];
},
]);
$reply = new ObjectType([
'name' => 'Reply',
'name' => 'Reply',
'fields' => [
'author' => ['type' => $author],
'body' => ['type' => Type::string()]
]
'body' => ['type' => Type::string()],
],
]);
$article = new ObjectType([
'name' => 'Article',
'name' => 'Article',
'fields' => [
'id' => ['type' => Type::string()],
'id' => ['type' => Type::string()],
'isPublished' => ['type' => Type::boolean()],
'author' => ['type' => $author],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'image' => ['type' => $image],
'replies' => ['type' => Type::listOf($reply)]
]
'author' => ['type' => $author],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'image' => ['type' => $image],
'replies' => ['type' => Type::listOf($reply)],
],
]);
$doc = '
$doc = '
query Test {
article {
author {
@ -100,59 +106,70 @@ class ResolveInfoTest extends TestCase
}
';
$expectedDefaultSelection = [
'author' => true,
'image' => true,
'replies' => true
'author' => true,
'image' => true,
'replies' => true,
];
$expectedDeepSelection = [
'author' => [
$expectedDeepSelection = [
'author' => [
'name' => true,
'pic' => [
'url' => true,
'width' => true
]
'pic' => [
'url' => true,
'width' => true,
],
],
'image' => [
'width' => true,
'image' => [
'width' => true,
'height' => true,
'url' => true
'url' => true,
],
'replies' => [
'body' => true,
'body' => true,
'author' => [
'id' => true,
'name' => true,
'pic' => [
'url' => true,
'width' => true,
'height' => true
'id' => true,
'name' => true,
'pic' => [
'url' => true,
'width' => true,
'height' => true,
],
'recentArticle' => [
'id' => true,
'id' => true,
'title' => true,
'body' => true
]
]
]
'body' => true,
],
],
],
];
$hasCalled = false;
$hasCalled = false;
$actualDefaultSelection = null;
$actualDeepSelection = null;
$actualDeepSelection = null;
$blogQuery = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'article' => [
'type' => $article,
'resolve' => function($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$actualDefaultSelection, &$actualDeepSelection) {
$hasCalled = true;
'type' => $article,
'resolve' => function (
$value,
$args,
$context,
ResolveInfo $info
) use (
&$hasCalled,
&
$actualDefaultSelection,
&$actualDeepSelection
) {
$hasCalled = true;
$actualDefaultSelection = $info->getFieldSelection();
$actualDeepSelection = $info->getFieldSelection(5);
$actualDeepSelection = $info->getFieldSelection(5);
return null;
}
]
]
},
],
],
]);
$schema = new Schema(['query' => $blogQuery]);
@ -167,50 +184,53 @@ class ResolveInfoTest extends TestCase
public function testMergedFragmentsFieldSelection() : void
{
$image = new ObjectType([
'name' => 'Image',
'name' => 'Image',
'fields' => [
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()]
]
'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()],
],
]);
$article = null;
$author = new ObjectType([
'name' => 'Author',
'fields' => function() use ($image, &$article) {
'name' => 'Author',
'fields' => function () use ($image, &$article) {
return [
'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'pic' => [ 'type' => $image, 'args' => [
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()]
]],
'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()],
'pic' => [
'type' => $image,
'args' => [
'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()],
],
],
'recentArticle' => ['type' => $article],
];
},
]);
$reply = new ObjectType([
'name' => 'Reply',
'name' => 'Reply',
'fields' => [
'author' => ['type' => $author],
'body' => ['type' => Type::string()]
]
'body' => ['type' => Type::string()],
],
]);
$article = new ObjectType([
'name' => 'Article',
'name' => 'Article',
'fields' => [
'id' => ['type' => Type::string()],
'id' => ['type' => Type::string()],
'isPublished' => ['type' => Type::boolean()],
'author' => ['type' => $author],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'image' => ['type' => $image],
'replies' => ['type' => Type::listOf($reply)]
]
'author' => ['type' => $author],
'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()],
'image' => ['type' => $image],
'replies' => ['type' => Type::listOf($reply)],
],
]);
$doc = '
@ -264,53 +284,63 @@ class ResolveInfoTest extends TestCase
';
$expectedDeepSelection = [
'author' => [
'author' => [
'name' => true,
'pic' => [
'url' => true,
'width' => true
]
'pic' => [
'url' => true,
'width' => true,
],
],
'image' => [
'width' => true,
'image' => [
'width' => true,
'height' => true,
'url' => true
'url' => true,
],
'replies' => [
'body' => true, //this would be missing if not for the fix https://github.com/webonyx/graphql-php/pull/98
'body' => true, //this would be missing if not for the fix https://github.com/webonyx/graphql-php/pull/98
'author' => [
'id' => true,
'name' => true,
'pic' => [
'url' => true,
'width' => true,
'height' => true
'id' => true,
'name' => true,
'pic' => [
'url' => true,
'width' => true,
'height' => true,
],
'recentArticle' => [
'id' => true,
'id' => true,
'title' => true,
'body' => true
]
]
]
'body' => true,
],
],
],
];
$hasCalled = false;
$hasCalled = false;
$actualDefaultSelection = null;
$actualDeepSelection = null;
$actualDeepSelection = null;
$blogQuery = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'article' => [
'type' => $article,
'resolve' => function($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$actualDeepSelection) {
$hasCalled = true;
'type' => $article,
'resolve' => function (
$value,
$args,
$context,
ResolveInfo $info
) use (
&$hasCalled,
&
$actualDeepSelection
) {
$hasCalled = true;
$actualDeepSelection = $info->getFieldSelection(5);
return null;
}
]
]
},
],
],
]);
$schema = new Schema(['query' => $blogQuery]);
@ -320,6 +350,4 @@ class ResolveInfoTest extends TestCase
$this->assertEquals(['data' => ['article' => null]], $result);
$this->assertEquals($expectedDeepSelection, $actualDeepSelection);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type;
use GraphQL\Error\Error;
@ -8,7 +11,6 @@ use PHPUnit\Framework\TestCase;
class ScalarSerializationTest extends TestCase
{
// Type System: Scalar coercion
/**
* @see it('serializes output int')
*/
@ -42,7 +44,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non-integer value: 1.1');
$intType->serialize(1.1);
}
public function testSerializesOutputIntCannotRepresentNegativeFloat() : void
@ -51,7 +52,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non-integer value: -1.1');
$intType->serialize(-1.1);
}
public function testSerializesOutputIntCannotRepresentNumericString() : void
@ -60,7 +60,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: Int cannot represent non-integer value: "-1.1"');
$intType->serialize('Int cannot represent non-integer value: "-1.1"');
}
public function testSerializesOutputIntCannotRepresentBiggerThan32Bits() : void
@ -71,7 +70,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 9876504321');
$intType->serialize(9876504321);
}
public function testSerializesOutputIntCannotRepresentLowerThan32Bits() : void
@ -104,7 +102,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: one');
$intType->serialize('one');
}
public function testSerializesOutputIntCannotRepresentEmptyString() : void
@ -189,14 +186,13 @@ class ScalarSerializationTest extends TestCase
{
$boolType = Type::boolean();
$this->assertSame(true, $boolType->serialize('string'));
$this->assertSame(false, $boolType->serialize(''));
$this->assertSame(true, $boolType->serialize('1'));
$this->assertSame(true, $boolType->serialize(1));
$this->assertSame(false, $boolType->serialize(0));
$this->assertSame(true, $boolType->serialize(true));
$this->assertSame(false, $boolType->serialize(false));
$this->assertTrue($boolType->serialize('string'));
$this->assertFalse($boolType->serialize(''));
$this->assertTrue($boolType->serialize('1'));
$this->assertTrue($boolType->serialize(1));
$this->assertFalse($boolType->serialize(0));
$this->assertTrue($boolType->serialize(true));
$this->assertFalse($boolType->serialize(false));
// TODO: how should it behave on '0'?
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type;
use GraphQL\Error\InvariantViolation;
@ -12,14 +15,19 @@ use PHPUnit\Framework\TestCase;
class SchemaTest extends TestCase
{
/** @var InterfaceType */
private $interfaceType;
/** @var ObjectType */
private $implementingType;
/** @var InputObjectType */
private $directiveInputType;
/** @var InputObjectType */
private $wrappedDirectiveInputType;
/** @var Directive */
private $directive;
/** @var Schema */
@ -28,20 +36,25 @@ class SchemaTest extends TestCase
public function setUp()
{
$this->interfaceType = new InterfaceType([
'name' => 'Interface',
'name' => 'Interface',
'fields' => ['fieldName' => ['type' => Type::string()]],
]);
$this->implementingType = new ObjectType([
'name' => 'Object',
'name' => 'Object',
'interfaces' => [$this->interfaceType],
'fields' => ['fieldName' => ['type' => Type::string(), 'resolve' => function () {
return '';
}]],
'fields' => [
'fieldName' => [
'type' => Type::string(),
'resolve' => function () {
return '';
},
],
],
]);
$this->directiveInputType = new InputObjectType([
'name' => 'DirInput',
'name' => 'DirInput',
'fields' => [
'field' => [
'type' => Type::string(),
@ -50,7 +63,7 @@ class SchemaTest extends TestCase
]);
$this->wrappedDirectiveInputType = new InputObjectType([
'name' => 'WrappedDirInput',
'name' => 'WrappedDirInput',
'fields' => [
'field' => [
'type' => Type::string(),
@ -59,10 +72,10 @@ class SchemaTest extends TestCase
]);
$this->directive = new Directive([
'name' => 'dir',
'name' => 'dir',
'locations' => ['OBJECT'],
'args' => [
'arg' => [
'args' => [
'arg' => [
'type' => $this->directiveInputType,
],
'argList' => [
@ -72,11 +85,11 @@ class SchemaTest extends TestCase
]);
$this->schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'getObject' => [
'type' => $this->interfaceType,
'type' => $this->interfaceType,
'resolve' => function () {
return [];
},

View File

@ -1,32 +0,0 @@
<?php
namespace GraphQL\Tests\Type;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
class MyCustomType extends ObjectType
{
public function __construct()
{
$config = [
'fields' => [
'a' => Type::string()
]
];
parent::__construct($config);
}
}
// Note: named OtherCustom vs OtherCustomType intentionally
class OtherCustom extends ObjectType
{
public function __construct()
{
$config = [
'fields' => [
'b' => Type::string()
]
];
parent::__construct($config);
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type\TestClasses;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
class MyCustomType extends ObjectType
{
public function __construct()
{
$config = [
'fields' => [
'a' => Type::string(),
],
];
parent::__construct($config);
}
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type\TestClasses;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
/**
* Note: named OtherCustom vs OtherCustomType intentionally
*/
class OtherCustom extends ObjectType
{
public function __construct()
{
$config = [
'fields' => [
'b' => Type::string(),
],
];
parent::__construct($config);
}
}

View File

@ -1,6 +1,8 @@
<?php
namespace GraphQL\Tests\Type;
declare(strict_types=1);
namespace GraphQL\Tests\Type;
use GraphQL\Error\InvariantViolation;
use GraphQL\Type\Definition\InputObjectType;
@ -9,52 +11,35 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
use function lcfirst;
class TypeLoaderTest extends TestCase
{
/**
* @var ObjectType
*/
/** @var ObjectType */
private $query;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $mutation;
/**
* @var InterfaceType
*/
/** @var InterfaceType */
private $node;
/**
* @var InterfaceType
*/
/** @var InterfaceType */
private $content;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $blogStory;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $postStoryMutation;
/**
* @var InputObjectType
*/
/** @var InputObjectType */
private $postStoryMutationInput;
/**
* @var callable
*/
/** @var callable */
private $typeLoader;
/**
* @var array
*/
/** @var string[] */
private $calls;
public function setUp()
@ -62,36 +47,41 @@ class TypeLoaderTest extends TestCase
$this->calls = [];
$this->node = new InterfaceType([
'name' => 'Node',
'fields' => function() {
'name' => 'Node',
'fields' => function () {
$this->calls[] = 'Node.fields';
return [
'id' => Type::string()
'id' => Type::string(),
];
},
'resolveType' => function() {}
'resolveType' => function () {
},
]);
$this->content = new InterfaceType([
'name' => 'Content',
'fields' => function() {
'name' => 'Content',
'fields' => function () {
$this->calls[] = 'Content.fields';
return [
'title' => Type::string(),
'body' => Type::string(),
'body' => Type::string(),
];
},
'resolveType' => function() {}
'resolveType' => function () {
},
]);
$this->blogStory = new ObjectType([
'name' => 'BlogStory',
'name' => 'BlogStory',
'interfaces' => [
$this->node,
$this->content
$this->content,
],
'fields' => function() {
'fields' => function () {
$this->calls[] = 'BlogStory.fields';
return [
$this->node->getField('id'),
$this->content->getField('title'),
@ -101,53 +91,56 @@ class TypeLoaderTest extends TestCase
]);
$this->query = new ObjectType([
'name' => 'Query',
'fields' => function() {
'name' => 'Query',
'fields' => function () {
$this->calls[] = 'Query.fields';
return [
'latestContent' => $this->content,
'node' => $this->node,
'node' => $this->node,
];
}
},
]);
$this->mutation = new ObjectType([
'name' => 'Mutation',
'fields' => function() {
'name' => 'Mutation',
'fields' => function () {
$this->calls[] = 'Mutation.fields';
return [
'postStory' => [
'type' => $this->postStoryMutation,
'args' => [
'input' => Type::nonNull($this->postStoryMutationInput),
'clientRequestId' => Type::string()
]
]
'input' => Type::nonNull($this->postStoryMutationInput),
'clientRequestId' => Type::string(),
],
],
];
}
},
]);
$this->postStoryMutation = new ObjectType([
'name' => 'PostStoryMutation',
'name' => 'PostStoryMutation',
'fields' => [
'story' => $this->blogStory
]
'story' => $this->blogStory,
],
]);
$this->postStoryMutationInput = new InputObjectType([
'name' => 'PostStoryMutationInput',
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id()
]
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id(),
],
]);
$this->typeLoader = function($name) {
$this->typeLoader = function ($name) {
$this->calls[] = $name;
$prop = lcfirst($name);
return isset($this->{$prop}) ? $this->{$prop} : null;
$prop = lcfirst($name);
return $this->{$prop} ?? null;
};
}
@ -155,11 +148,12 @@ class TypeLoaderTest extends TestCase
{
$this->expectNotToPerformAssertions();
new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['a' => Type::string()]
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['a' => Type::string()],
]),
'typeLoader' => function() {}
'typeLoader' => function () {
},
]);
}
@ -169,20 +163,20 @@ class TypeLoaderTest extends TestCase
$this->expectExceptionMessage('Schema type loader must be callable if provided but got: []');
new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['a' => Type::string()]
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['a' => Type::string()],
]),
'typeLoader' => []
'typeLoader' => [],
]);
}
public function testWorksWithoutTypeLoader() : void
{
$schema = new Schema([
'query' => $this->query,
'query' => $this->query,
'mutation' => $this->mutation,
'types' => [$this->blogStory]
'types' => [$this->blogStory],
]);
$expected = [
@ -203,12 +197,12 @@ class TypeLoaderTest extends TestCase
$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,
'Query' => $this->query,
'Mutation' => $this->mutation,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'BlogStory' => $this->blogStory,
'PostStoryMutationInput' => $this->postStoryMutationInput,
];
@ -218,9 +212,9 @@ class TypeLoaderTest extends TestCase
public function testWorksWithTypeLoader() : void
{
$schema = new Schema([
'query' => $this->query,
'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader
'query' => $this->query,
'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader,
]);
$this->assertEquals([], $this->calls);
@ -244,8 +238,8 @@ class TypeLoaderTest extends TestCase
public function testOnlyCallsLoaderOnce() : void
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => $this->typeLoader
'query' => $this->query,
'typeLoader' => $this->typeLoader,
]);
$schema->getType('Node');
@ -258,8 +252,9 @@ class TypeLoaderTest extends TestCase
public function testFailsOnNonExistentType() : void
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => function() {}
'query' => $this->query,
'typeLoader' => function () {
},
]);
$this->expectException(InvariantViolation::class);
@ -271,10 +266,10 @@ class TypeLoaderTest extends TestCase
public function testFailsOnNonType() : void
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => function() {
'query' => $this->query,
'typeLoader' => function () {
return new \stdClass();
}
},
]);
$this->expectException(InvariantViolation::class);
@ -286,10 +281,10 @@ class TypeLoaderTest extends TestCase
public function testFailsOnInvalidLoad() : void
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => function() {
'query' => $this->query,
'typeLoader' => function () {
return $this->content;
}
},
]);
$this->expectException(InvariantViolation::class);
@ -301,10 +296,10 @@ class TypeLoaderTest extends TestCase
public function testPassesThroughAnExceptionInLoader() : void
{
$schema = new Schema([
'query' => $this->query,
'typeLoader' => function() {
throw new \Exception("This is the exception we are looking for");
}
'query' => $this->query,
'typeLoader' => function () {
throw new \Exception('This is the exception we are looking for');
},
]);
$this->expectException(\Throwable::class);
@ -316,14 +311,14 @@ class TypeLoaderTest extends TestCase
public function testReturnsIdenticalResults() : void
{
$withoutLoader = new Schema([
'query' => $this->query,
'mutation' => $this->mutation
'query' => $this->query,
'mutation' => $this->mutation,
]);
$withLoader = new Schema([
'query' => $this->query,
'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader
'query' => $this->query,
'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader,
]);
$this->assertSame($withoutLoader->getQueryType(), $withLoader->getQueryType());
@ -335,9 +330,9 @@ class TypeLoaderTest extends TestCase
public function testSkipsLoaderForInternalTypes() : void
{
$schema = new Schema([
'query' => $this->query,
'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader
'query' => $this->query,
'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader,
]);
$type = $schema->getType('ID');

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Error\Error;
@ -9,7 +12,6 @@ use PHPUnit\Framework\TestCase;
class AssertValidNameTest extends TestCase
{
// Describe: assertValidName()
/**
* @see it('throws for use of leading double underscores')
*/

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Language\AST\BooleanValueNode;
@ -16,10 +19,12 @@ use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\AST;
use PHPUnit\Framework\TestCase;
use stdClass;
class AstFromValueTest extends TestCase
{
// Describe: astFromValue
/** @var stdClass */
private $complexValue;
/**
* @see it('converts boolean values to ASTs')
@ -31,8 +36,14 @@ class AstFromValueTest extends TestCase
$this->assertEquals(new NullValueNode([]), AST::astFromValue(null, Type::boolean()));
$this->assertEquals(new BooleanValueNode(['value' => false]), AST::astFromValue(0, Type::boolean()));
$this->assertEquals(new BooleanValueNode(['value' => true]), AST::astFromValue(1, Type::boolean()));
$this->assertEquals(new BooleanValueNode(['value' => false]), AST::astFromValue(0, Type::nonNull(Type::boolean())));
$this->assertEquals(null, AST::astFromValue(null, Type::nonNull(Type::boolean()))); // Note: null means that AST cannot
$this->assertEquals(
new BooleanValueNode(['value' => false]),
AST::astFromValue(0, Type::nonNull(Type::boolean()))
);
$this->assertEquals(
null,
AST::astFromValue(null, Type::nonNull(Type::boolean()))
); // Note: null means that AST cannot
}
/**
@ -59,7 +70,10 @@ class AstFromValueTest extends TestCase
{
$this->expectException(\Throwable::class);
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 1.0E+40');
AST::astFromValue(1e40, Type::int()); // Note: js version will produce 1e+40, both values are valid GraphQL floats
AST::astFromValue(
1e40,
Type::int()
); // Note: js version will produce 1e+40, both values are valid GraphQL floats
}
/**
@ -112,7 +126,7 @@ class AstFromValueTest extends TestCase
*/
public function testDoesNotConvertsNonNullValuestoNullValue() : void
{
$this->assertSame(null, AST::astFromValue(null, Type::nonNull(Type::boolean())));
$this->assertNull(AST::astFromValue(null, Type::nonNull(Type::boolean())));
}
/**
@ -121,13 +135,41 @@ class AstFromValueTest extends TestCase
public function testConvertsStringValuesToEnumASTsIfPossible() : void
{
$this->assertEquals(new EnumValueNode(['value' => 'HELLO']), AST::astFromValue('HELLO', $this->myEnum()));
$this->assertEquals(new EnumValueNode(['value' => 'COMPLEX']), AST::astFromValue($this->complexValue(), $this->myEnum()));
$this->assertEquals(
new EnumValueNode(['value' => 'COMPLEX']),
AST::astFromValue($this->complexValue(), $this->myEnum())
);
// Note: case sensitive
$this->assertEquals(null, AST::astFromValue('hello', $this->myEnum()));
$this->assertNull(AST::astFromValue('hello', $this->myEnum()));
// Note: Not a valid enum value
$this->assertEquals(null, AST::astFromValue('VALUE', $this->myEnum()));
$this->assertNull(AST::astFromValue('VALUE', $this->myEnum()));
}
/**
* @return EnumType
*/
private function myEnum()
{
return new EnumType([
'name' => 'MyEnum',
'values' => [
'HELLO' => [],
'GOODBYE' => [],
'COMPLEX' => ['value' => $this->complexValue()],
],
]);
}
private function complexValue()
{
if (! $this->complexValue) {
$this->complexValue = new \stdClass();
$this->complexValue->someArbitrary = 'complexValue';
}
return $this->complexValue;
}
/**
@ -138,8 +180,8 @@ class AstFromValueTest extends TestCase
$value1 = new ListValueNode([
'values' => [
new StringValueNode(['value' => 'FOO']),
new StringValueNode(['value' => 'BAR'])
]
new StringValueNode(['value' => 'BAR']),
],
]);
$this->assertEquals($value1, AST::astFromValue(['FOO', 'BAR'], Type::listOf(Type::string())));
@ -147,7 +189,7 @@ class AstFromValueTest extends TestCase
'values' => [
new EnumValueNode(['value' => 'HELLO']),
new EnumValueNode(['value' => 'GOODBYE']),
]
],
]);
$this->assertEquals($value2, AST::astFromValue(['HELLO', 'GOODBYE'], Type::listOf($this->myEnum())));
}
@ -157,7 +199,10 @@ class AstFromValueTest extends TestCase
*/
public function testConvertsListSingletons() : void
{
$this->assertEquals(new StringValueNode(['value' => 'FOO']), AST::astFromValue('FOO', Type::listOf(Type::string())));
$this->assertEquals(
new StringValueNode(['value' => 'FOO']),
AST::astFromValue('FOO', Type::listOf(Type::string()))
);
}
/**
@ -166,18 +211,18 @@ class AstFromValueTest extends TestCase
public function testConvertsInputObjects() : void
{
$inputObj = new InputObjectType([
'name' => 'MyInputObj',
'name' => 'MyInputObj',
'fields' => [
'foo' => Type::float(),
'bar' => $this->myEnum()
]
'bar' => $this->myEnum(),
],
]);
$expected = new ObjectValueNode([
'fields' => [
$this->objectField('foo', new IntValueNode(['value' => '3'])),
$this->objectField('bar', new EnumValueNode(['value' => 'HELLO']))
]
$this->objectField('bar', new EnumValueNode(['value' => 'HELLO'])),
],
]);
$data = ['foo' => 3, 'bar' => 'HELLO'];
@ -185,62 +230,38 @@ class AstFromValueTest extends TestCase
$this->assertEquals($expected, AST::astFromValue((object) $data, $inputObj));
}
/**
* @param mixed $value
* @return ObjectFieldNode
*/
private function objectField(string $name, $value)
{
return new ObjectFieldNode([
'name' => new NameNode(['value' => $name]),
'value' => $value,
]);
}
/**
* @see it('converts input objects with explicit nulls')
*/
public function testConvertsInputObjectsWithExplicitNulls() : void
{
$inputObj = new InputObjectType([
'name' => 'MyInputObj',
'name' => 'MyInputObj',
'fields' => [
'foo' => Type::float(),
'bar' => $this->myEnum()
]
'bar' => $this->myEnum(),
],
]);
$this->assertEquals(new ObjectValueNode([
$this->assertEquals(
new ObjectValueNode([
'fields' => [
$this->objectField('foo', new NullValueNode([]))
]
]), AST::astFromValue(['foo' => null], $inputObj));
}
private $complexValue;
private function complexValue()
{
if (!$this->complexValue) {
$this->complexValue = new \stdClass();
$this->complexValue->someArbitrary = 'complexValue';
}
return $this->complexValue;
}
/**
* @return EnumType
*/
private function myEnum()
{
return new EnumType([
'name' => 'MyEnum',
'values' => [
'HELLO' => [],
'GOODBYE' => [],
'COMPLEX' => ['value' => $this->complexValue()]
]
]);
}
/**
* @param $name
* @param $value
* @return ObjectFieldNode
*/
private function objectField($name, $value)
{
return new ObjectFieldNode([
'name' => new NameNode(['value' => $name]),
'value' => $value
]);
$this->objectField('foo', new NullValueNode([])),
],
]),
AST::astFromValue(['foo' => null], $inputObj)
);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Language\Parser;
@ -8,14 +11,6 @@ use PHPUnit\Framework\TestCase;
class AstFromValueUntypedTest extends TestCase
{
// Describe: valueFromASTUntyped
private function assertTestCase($valueText, $expected, array $variables = null) {
$this->assertEquals(
$expected,
AST::valueFromASTUntyped(Parser::parseValue($valueText), $variables)
);
}
/**
* @see it('parses simple values')
*/
@ -29,6 +24,17 @@ class AstFromValueUntypedTest extends TestCase
$this->assertTestCase('abc123', 'abc123');
}
/**
* @param mixed[]|null $variables
*/
private function assertTestCase($valueText, $expected, ?array $variables = null) : void
{
$this->assertEquals(
$expected,
AST::valueFromASTUntyped(Parser::parseValue($valueText), $variables)
);
}
/**
* @see it('parses lists of values')
*/

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Error\Error;
@ -8,24 +11,19 @@ use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Printer;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Utils\BuildSchema;
use GraphQL\Utils\SchemaPrinter;
use GraphQL\Type\Definition\Directive;
use PHPUnit\Framework\TestCase;
use function array_keys;
use function count;
class BuildSchemaTest extends TestCase
{
// Describe: Schema Builder
private function cycleOutput($body, $options = [])
{
$ast = Parser::parse($body);
$schema = BuildSchema::buildAST($ast, null, $options);
return "\n" . SchemaPrinter::doPrint($schema, $options);
}
/**
* @see it('can use built schema for limited execution')
*/
@ -46,16 +44,16 @@ class BuildSchemaTest extends TestCase
*/
public function testBuildSchemaDirectlyFromSource() : void
{
$schema = BuildSchema::build("
$schema = BuildSchema::build('
type Query {
add(x: Int, y: Int): Int
}
");
');
$root = [
'add' => function ($root, $args) {
return $args['x'] + $args['y'];
}
},
];
$result = GraphQL::executeQuery(
@ -71,7 +69,7 @@ class BuildSchemaTest extends TestCase
*/
public function testSimpleType() : void
{
$body = '
$body = '
type HelloScalars {
str: String
int: Int
@ -84,12 +82,20 @@ type HelloScalars {
$this->assertEquals($output, $body);
}
private function cycleOutput($body, $options = [])
{
$ast = Parser::parse($body);
$schema = BuildSchema::buildAST($ast, null, $options);
return "\n" . SchemaPrinter::doPrint($schema, $options);
}
/**
* @see it('With directives')
*/
public function testWithDirectives() : void
{
$body = '
$body = '
directive @foo(arg: Int) on FIELD
type Query {
@ -105,7 +111,7 @@ type Query {
*/
public function testSupportsDescriptions() : void
{
$body = '
$body = '
"""This is a directive"""
directive @foo(
"""It has an argument"""
@ -137,7 +143,7 @@ type Query {
*/
public function testSupportsOptionForCommentDescriptions() : void
{
$body = '
$body = '
# This is a directive
directive @foo(
# It has an argument
@ -159,7 +165,7 @@ type Query {
str: String
}
';
$output = $this->cycleOutput($body, [ 'commentDescriptions' => true ]);
$output = $this->cycleOutput($body, ['commentDescriptions' => true]);
$this->assertEquals($body, $output);
}
@ -168,7 +174,7 @@ type Query {
*/
public function testMaintainsSkipAndInclude() : void
{
$body = '
$body = '
type Query {
str: String
}
@ -185,7 +191,7 @@ type Query {
*/
public function testOverridingDirectivesExcludesSpecified() : void
{
$body = '
$body = '
directive @skip on FIELD
directive @include on FIELD
directive @deprecated on FIELD_DEFINITION
@ -206,7 +212,7 @@ type Query {
*/
public function testAddingDirectivesMaintainsSkipAndInclude() : void
{
$body = '
$body = '
directive @foo(arg: Int) on FIELD
type Query {
@ -225,7 +231,7 @@ type Query {
*/
public function testTypeModifiers() : void
{
$body = '
$body = '
type HelloScalars {
nonNullStr: String!
listOfStrs: [String]
@ -243,7 +249,7 @@ type HelloScalars {
*/
public function testRecursiveType() : void
{
$body = '
$body = '
type Query {
str: String
recurse: Query
@ -258,7 +264,7 @@ type Query {
*/
public function testTwoTypesCircular() : void
{
$body = '
$body = '
schema {
query: TypeOne
}
@ -282,7 +288,7 @@ type TypeTwo {
*/
public function testSingleArgumentField() : void
{
$body = '
$body = '
type Query {
str(int: Int): String
floatToStr(float: Float): String
@ -300,7 +306,7 @@ type Query {
*/
public function testSimpleTypeWithMultipleArguments() : void
{
$body = '
$body = '
type Query {
str(int: Int, bool: Boolean): String
}
@ -314,7 +320,7 @@ type Query {
*/
public function testSimpleTypeWithInterface() : void
{
$body = '
$body = '
type Query implements WorldInterface {
str: String
}
@ -332,7 +338,7 @@ interface WorldInterface {
*/
public function testSimpleOutputEnum() : void
{
$body = '
$body = '
enum Hello {
WORLD
}
@ -350,7 +356,7 @@ type Query {
*/
public function testSimpleInputEnum() : void
{
$body = '
$body = '
enum Hello {
WORLD
}
@ -368,7 +374,7 @@ type Query {
*/
public function testMultipleValueEnum() : void
{
$body = '
$body = '
enum Hello {
WO
RLD
@ -387,7 +393,7 @@ type Query {
*/
public function testSimpleUnion() : void
{
$body = '
$body = '
union Hello = World
type Query {
@ -407,7 +413,7 @@ type World {
*/
public function testMultipleUnion() : void
{
$body = '
$body = '
union Hello = WorldOne | WorldTwo
type Query {
@ -431,7 +437,7 @@ type WorldTwo {
*/
public function testSpecifyingUnionTypeUsingTypename() : void
{
$schema = BuildSchema::buildAST(Parser::parse('
$schema = BuildSchema::buildAST(Parser::parse('
type Query {
fruits: [Fruit]
}
@ -446,7 +452,7 @@ type WorldTwo {
length: Int
}
'));
$query = '
$query = '
{
fruits {
... on Apple {
@ -458,25 +464,25 @@ type WorldTwo {
}
}
';
$root = [
$root = [
'fruits' => [
[
'color' => 'green',
'color' => 'green',
'__typename' => 'Apple',
],
[
'length' => 5,
'length' => 5,
'__typename' => 'Banana',
]
]
],
],
];
$expected = [
'data' => [
'fruits' => [
['color' => 'green'],
['length' => 5],
]
]
],
],
];
$result = GraphQL::executeQuery($schema, $query, $root);
@ -488,7 +494,7 @@ type WorldTwo {
*/
public function testSpecifyingInterfaceUsingTypename() : void
{
$schema = BuildSchema::buildAST(Parser::parse('
$schema = BuildSchema::buildAST(Parser::parse('
type Query {
characters: [Character]
}
@ -507,7 +513,7 @@ type WorldTwo {
primaryFunction: String
}
'));
$query = '
$query = '
{
characters {
name
@ -520,27 +526,27 @@ type WorldTwo {
}
}
';
$root = [
$root = [
'characters' => [
[
'name' => 'Han Solo',
'name' => 'Han Solo',
'totalCredits' => 10,
'__typename' => 'Human',
'__typename' => 'Human',
],
[
'name' => 'R2-D2',
'name' => 'R2-D2',
'primaryFunction' => 'Astromech',
'__typename' => 'Droid',
]
]
'__typename' => 'Droid',
],
],
];
$expected = [
'data' => [
'characters' => [
['name' => 'Han Solo', 'totalCredits' => 10],
['name' => 'R2-D2', 'primaryFunction' => 'Astromech'],
]
]
],
],
];
$result = GraphQL::executeQuery($schema, $query, $root);
@ -552,7 +558,7 @@ type WorldTwo {
*/
public function testCustomScalar() : void
{
$body = '
$body = '
scalar CustomScalar
type Query {
@ -568,7 +574,7 @@ type Query {
*/
public function testInputObject() : void
{
$body = '
$body = '
input Input {
int: Int
}
@ -586,7 +592,7 @@ type Query {
*/
public function testSimpleArgumentFieldWithDefault() : void
{
$body = '
$body = '
type Query {
str(int: Int = 2): String
}
@ -600,7 +606,7 @@ type Query {
*/
public function testCustomScalarArgumentFieldWithDefault() : void
{
$body = '
$body = '
scalar CustomScalar
type Query {
@ -616,7 +622,7 @@ type Query {
*/
public function testSimpleTypeWithMutation() : void
{
$body = '
$body = '
schema {
query: HelloScalars
mutation: Mutation
@ -641,7 +647,7 @@ type Mutation {
*/
public function testSimpleTypeWithSubscription() : void
{
$body = '
$body = '
schema {
query: HelloScalars
subscription: Subscription
@ -666,7 +672,7 @@ type Subscription {
*/
public function testUnreferencedTypeImplementingReferencedInterface() : void
{
$body = '
$body = '
type Concrete implements Iface {
key: String
}
@ -688,7 +694,7 @@ type Query {
*/
public function testUnreferencedTypeImplementingReferencedUnion() : void
{
$body = '
$body = '
type Concrete {
key: String
}
@ -708,7 +714,7 @@ union Union = Concrete
*/
public function testSupportsDeprecated() : void
{
$body = '
$body = '
enum MyEnum {
VALUE
OLD_VALUE @deprecated
@ -724,7 +730,7 @@ type Query {
$output = $this->cycleOutput($body);
$this->assertEquals($output, $body);
$ast = Parser::parse($body);
$ast = Parser::parse($body);
$schema = BuildSchema::buildAST($ast);
/** @var EnumType $myEnum */
@ -785,15 +791,15 @@ type Query {
directive @test(arg: TestScalar) on FIELD
');
$schema = BuildSchema::buildAST($schemaAST);
$schema = BuildSchema::buildAST($schemaAST);
/** @var ObjectType $query */
$query = $schema->getType('Query');
$testInput = $schema->getType('TestInput');
$testEnum = $schema->getType('TestEnum');
$testUnion = $schema->getType('TestUnion');
$query = $schema->getType('Query');
$testInput = $schema->getType('TestInput');
$testEnum = $schema->getType('TestEnum');
$testUnion = $schema->getType('TestUnion');
$testInterface = $schema->getType('TestInterface');
$testType = $schema->getType('TestType');
$testScalar = $schema->getType('TestScalar');
$testType = $schema->getType('TestType');
$testScalar = $schema->getType('TestScalar');
$testDirective = $schema->getDirective('test');
$restoredIDL = SchemaPrinter::doPrint(BuildSchema::build(
@ -813,9 +819,15 @@ type Query {
$testField = $query->getField('testField');
$this->assertEquals('testField(testArg: TestInput): TestUnion', Printer::doPrint($testField->astNode));
$this->assertEquals('testArg: TestInput', Printer::doPrint($testField->args[0]->astNode));
$this->assertEquals('testInputField: TestEnum', Printer::doPrint($testInput->getField('testInputField')->astNode));
$this->assertEquals(
'testInputField: TestEnum',
Printer::doPrint($testInput->getField('testInputField')->astNode)
);
$this->assertEquals('TEST_VALUE', Printer::doPrint($testEnum->getValue('TEST_VALUE')->astNode));
$this->assertEquals('interfaceField: String', Printer::doPrint($testInterface->getField('interfaceField')->astNode));
$this->assertEquals(
'interfaceField: String',
Printer::doPrint($testInterface->getField('interfaceField')->astNode)
);
$this->assertEquals('interfaceField: String', Printer::doPrint($testType->getField('interfaceField')->astNode));
$this->assertEquals('arg: TestScalar', Printer::doPrint($testDirective->args[0]->astNode));
}
@ -893,7 +905,7 @@ type Hello {
bar: Bar
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
BuildSchema::buildAST($doc);
}
@ -918,7 +930,7 @@ type Yellow {
isColor: Boolean
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
BuildSchema::buildAST($doc);
}
@ -944,7 +956,7 @@ type Yellow {
isColor: Boolean
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
BuildSchema::buildAST($doc);
}
@ -970,7 +982,7 @@ type Yellow {
isColor: Boolean
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
BuildSchema::buildAST($doc);
}
@ -981,7 +993,7 @@ type Yellow {
{
$this->expectException(Error::class);
$this->expectExceptionMessage('Type "Bar" not found in document.');
$body = '
$body = '
schema {
query: Hello
}
@ -990,7 +1002,7 @@ type Hello {
bar: Bar
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
$schema = BuildSchema::buildAST($doc);
$schema->getTypeMap();
}
@ -1002,12 +1014,12 @@ type Hello {
{
$this->expectException(Error::class);
$this->expectExceptionMessage('Type "Bar" not found in document.');
$body = '
$body = '
type Query implements Bar {
field: String
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
$schema = BuildSchema::buildAST($doc);
$schema->getTypeMap();
}
@ -1019,11 +1031,11 @@ type Query implements Bar {
{
$this->expectException(Error::class);
$this->expectExceptionMessage('Type "Bar" not found in document.');
$body = '
$body = '
union TestUnion = Bar
type Query { testUnion: TestUnion }
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
$schema = BuildSchema::buildAST($doc);
$schema->getTypeMap();
}
@ -1044,7 +1056,7 @@ type Hello {
str: String
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
BuildSchema::buildAST($doc);
}
@ -1065,7 +1077,7 @@ type Hello {
str: String
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
BuildSchema::buildAST($doc);
}
@ -1091,7 +1103,7 @@ type Wat {
str: String
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
BuildSchema::buildAST($doc);
}
@ -1109,7 +1121,7 @@ schema {
query Foo { field }
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
BuildSchema::buildAST($doc);
}
@ -1127,7 +1139,7 @@ schema {
fragment Foo on Type { field }
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
BuildSchema::buildAST($doc);
}
@ -1149,7 +1161,7 @@ type Repeated {
id: String
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
$this->expectException(Error::class);
$this->expectExceptionMessage('Type "Repeated" was defined more than once.');
@ -1179,14 +1191,15 @@ interface Hello {
world: String
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
$decorated = [];
$calls = [];
$calls = [];
$typeConfigDecorator = function($defaultConfig, $node, $allNodesMap) use (&$decorated, &$calls) {
$typeConfigDecorator = function ($defaultConfig, $node, $allNodesMap) use (&$decorated, &$calls) {
$decorated[] = $defaultConfig['name'];
$calls[] = [$defaultConfig, $node, $allNodesMap];
$calls[] = [$defaultConfig, $node, $allNodesMap];
return ['description' => 'My description of ' . $node->name->value] + $defaultConfig;
};
@ -1204,19 +1217,21 @@ interface Hello {
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
$this->assertEquals('My description of Query', $schema->getType('Query')->description);
list($defaultConfig, $node, $allNodesMap) = $calls[1];
$this->assertInstanceOf(EnumTypeDefinitionNode::class, $node);
$this->assertEquals('Color', $defaultConfig['name']);
$enumValue = [
'description' => '',
'deprecationReason' => ''
'description' => '',
'deprecationReason' => '',
];
$this->assertArraySubset([
'RED' => $enumValue,
'GREEN' => $enumValue,
'BLUE' => $enumValue,
], $defaultConfig['values']);
$this->assertArraySubset(
[
'RED' => $enumValue,
'GREEN' => $enumValue,
'BLUE' => $enumValue,
],
$defaultConfig['values']
);
$this->assertCount(4, $defaultConfig); // 3 + astNode
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
$this->assertEquals('My description of Color', $schema->getType('Color')->description);
@ -1233,7 +1248,7 @@ interface Hello {
public function testCreatesTypesLazily() : void
{
$body = '
$body = '
schema {
query: Query
}
@ -1258,11 +1273,12 @@ type World implements Hello {
world: String
}
';
$doc = Parser::parse($body);
$doc = Parser::parse($body);
$created = [];
$typeConfigDecorator = function($config, $node) use (&$created) {
$typeConfigDecorator = function ($config, $node) use (&$created) {
$created[] = $node->name->value;
return $config;
};
@ -1282,5 +1298,4 @@ type World implements Hello {
$this->assertArrayHasKey('Hello', $types);
$this->assertArrayHasKey('World', $types);
}
}

View File

@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Error\Error;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type;
@ -12,13 +13,16 @@ use PHPUnit\Framework\TestCase;
class CoerceValueTest extends TestCase
{
/** @var EnumType */
private $testEnum;
/** @var InputObjectType */
private $testInputObject;
public function setUp()
{
$this->testEnum = new EnumType([
'name' => 'TestEnum',
'name' => 'TestEnum',
'values' => [
'FOO' => 'InternalFoo',
'BAR' => 123456789,
@ -26,7 +30,7 @@ class CoerceValueTest extends TestCase
]);
$this->testInputObject = new InputObjectType([
'name' => 'TestInputObject',
'name' => 'TestInputObject',
'fields' => [
'foo' => Type::nonNull(Type::int()),
'bar' => Type::int(),
@ -34,9 +38,9 @@ class CoerceValueTest extends TestCase
]);
}
// Describe: coerceValue
/**
* Describe: coerceValue
*
* @see it('coercing an array to GraphQLString produces an error')
*/
public function testCoercingAnArrayToGraphQLStringProducesAnError() : void
@ -53,7 +57,17 @@ class CoerceValueTest extends TestCase
);
}
// Describe: for GraphQLInt
/**
* Describe: for GraphQLInt
*/
private function expectError($result, $expected)
{
$this->assertInternalType('array', $result);
$this->assertInternalType('array', $result['errors']);
$this->assertCount(1, $result['errors']);
$this->assertEquals($expected, $result['errors'][0]->getMessage());
$this->assertEquals(Utils::undefined(), $result['value']);
}
/**
* @see it('returns no error for int input')
@ -64,6 +78,13 @@ class CoerceValueTest extends TestCase
$this->expectNoErrors($result);
}
private function expectNoErrors($result)
{
$this->assertInternalType('array', $result);
$this->assertNull($result['errors']);
$this->assertNotEquals(Utils::undefined(), $result['value']);
}
/**
* @see it('returns no error for negative int input')
*/
@ -115,6 +136,8 @@ class CoerceValueTest extends TestCase
);
}
// Describe: for GraphQLFloat
/**
* @see it('returns a single error for char input')
*/
@ -139,8 +162,6 @@ class CoerceValueTest extends TestCase
);
}
// Describe: for GraphQLFloat
/**
* @see it('returns no error for int input')
*/
@ -189,6 +210,8 @@ class CoerceValueTest extends TestCase
);
}
// DESCRIBE: for GraphQLEnum
/**
* @see it('returns a single error for char input')
*/
@ -213,8 +236,6 @@ class CoerceValueTest extends TestCase
);
}
// DESCRIBE: for GraphQLEnum
/**
* @see it('returns no error for a known enum name')
*/
@ -229,6 +250,8 @@ class CoerceValueTest extends TestCase
$this->assertEquals(123456789, $barResult['value']);
}
// DESCRIBE: for GraphQLInputObject
/**
* @see it('results error for misspelled enum value')
*/
@ -250,8 +273,6 @@ class CoerceValueTest extends TestCase
$this->expectError($result2, 'Expected type TestEnum.');
}
// DESCRIBE: for GraphQLInputObject
/**
* @see it('returns no error for a valid input')
*/
@ -277,7 +298,10 @@ class CoerceValueTest extends TestCase
public function testReturnErrorForAnInvalidField() : void
{
$result = Value::coerceValue(['foo' => 'abc'], $this->testInputObject);
$this->expectError($result, 'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc');
$this->expectError(
$result,
'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc'
);
}
/**
@ -286,10 +310,13 @@ class CoerceValueTest extends TestCase
public function testReturnsMultipleErrorsForMultipleInvalidFields() : void
{
$result = Value::coerceValue(['foo' => 'abc', 'bar' => 'def'], $this->testInputObject);
$this->assertEquals([
'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc',
'Expected type Int at value.bar; Int cannot represent non 32-bit signed integer value: def',
], $result['errors']);
$this->assertEquals(
[
'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc',
'Expected type Int at value.bar; Int cannot represent non 32-bit signed integer value: def',
],
$result['errors']
);
}
/**
@ -318,20 +345,4 @@ class CoerceValueTest extends TestCase
$result = Value::coerceValue(['foo' => 123, 'bart' => 123], $this->testInputObject);
$this->expectError($result, 'Field "bart" is not defined by type TestInputObject; did you mean bar?');
}
private function expectNoErrors($result)
{
$this->assertInternalType('array', $result);
$this->assertNull($result['errors']);
$this->assertNotEquals(Utils::undefined(), $result['value']);
}
private function expectError($result, $expected) {
$this->assertInternalType('array', $result);
$this->assertInternalType('array', $result['errors']);
$this->assertCount(1, $result['errors']);
$this->assertEquals($expected, $result['errors'][0]->getMessage());
$this->assertEquals(Utils::undefined(), $result['value']);
}
}

View File

@ -1,5 +1,8 @@
<?php
namespace Utils;
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Error\InvariantViolation;
use GraphQL\Type\Definition\InputObjectType;
@ -12,121 +15,74 @@ use PHPUnit\Framework\TestCase;
class ExtractTypesTest extends TestCase
{
/**
* @var ObjectType
*/
/** @var ObjectType */
private $query;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $mutation;
/**
* @var InterfaceType
*/
/** @var InterfaceType */
private $node;
/**
* @var InterfaceType
*/
/** @var InterfaceType */
private $content;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $blogStory;
/**
* @var ObjectType
*/
private $link;
/**
* @var ObjectType
*/
private $video;
/**
* @var ObjectType
*/
private $videoMetadata;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $comment;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $user;
/**
* @var ObjectType
*/
/** @var ObjectType */
private $category;
/**
* @var UnionType
*/
/** @var UnionType */
private $mention;
/** @var ObjectType */
private $postStoryMutation;
/** @var InputObjectType */
private $postStoryMutationInput;
/** @var ObjectType */
private $postCommentMutation;
/** @var InputObjectType */
private $postCommentMutationInput;
public function setUp()
{
$this->node = new InterfaceType([
'name' => 'Node',
'name' => 'Node',
'fields' => [
'id' => Type::string()
]
'id' => Type::string(),
],
]);
$this->content = new InterfaceType([
'name' => 'Content',
'fields' => function() {
'name' => 'Content',
'fields' => function () {
return [
'title' => Type::string(),
'body' => Type::string(),
'author' => $this->user,
'comments' => Type::listOf($this->comment),
'categories' => Type::listOf($this->category)
'title' => Type::string(),
'body' => Type::string(),
'author' => $this->user,
'comments' => Type::listOf($this->comment),
'categories' => Type::listOf($this->category),
];
}
},
]);
$this->blogStory = new ObjectType([
'name' => 'BlogStory',
'name' => 'BlogStory',
'interfaces' => [
$this->node,
$this->content
$this->content,
],
'fields' => function() {
return [
$this->node->getField('id'),
$this->content->getField('title'),
$this->content->getField('body'),
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories')
];
},
]);
$this->link = new ObjectType([
'name' => 'Link',
'interfaces' => [
$this->node,
$this->content
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
$this->content->getField('title'),
@ -134,156 +90,176 @@ class ExtractTypesTest extends TestCase
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories'),
'url' => Type::string()
];
},
]);
$this->video = new ObjectType([
'name' => 'Video',
new ObjectType([
'name' => 'Link',
'interfaces' => [
$this->node,
$this->content
$this->content,
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
$this->content->getField('title'),
$this->content->getField('body'),
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories'),
'streamUrl' => Type::string(),
'id' => $this->node->getField('id'),
'title' => $this->content->getField('title'),
'body' => $this->content->getField('body'),
'author' => $this->content->getField('author'),
'comments' => $this->content->getField('comments'),
'categories' => $this->content->getField('categories'),
'url' => Type::string(),
];
},
]);
new ObjectType([
'name' => 'Video',
'interfaces' => [
$this->node,
$this->content,
],
'fields' => function () {
return [
'id' => $this->node->getField('id'),
'title' => $this->content->getField('title'),
'body' => $this->content->getField('body'),
'author' => $this->content->getField('author'),
'comments' => $this->content->getField('comments'),
'categories' => $this->content->getField('categories'),
'streamUrl' => Type::string(),
'downloadUrl' => Type::string(),
'metadata' => $this->videoMetadata = new ObjectType([
'name' => 'VideoMetadata',
'metadata' => new ObjectType([
'name' => 'VideoMetadata',
'fields' => [
'lat' => Type::float(),
'lng' => Type::float()
]
])
'lng' => Type::float(),
],
]),
];
}
},
]);
$this->comment = new ObjectType([
'name' => 'Comment',
'name' => 'Comment',
'interfaces' => [
$this->node
$this->node,
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
'author' => $this->user,
'text' => Type::string(),
'id' => $this->node->getField('id'),
'author' => $this->user,
'text' => Type::string(),
'replies' => Type::listOf($this->comment),
'parent' => $this->comment,
'content' => $this->content
'parent' => $this->comment,
'content' => $this->content,
];
}
},
]);
$this->user = new ObjectType([
'name' => 'User',
'name' => 'User',
'interfaces' => [
$this->node
$this->node,
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
'id' => $this->node->getField('id'),
'name' => Type::string(),
];
}
},
]);
$this->category = new ObjectType([
'name' => 'Category',
'name' => 'Category',
'interfaces' => [
$this->node
$this->node,
],
'fields' => function() {
'fields' => function () {
return [
$this->node->getField('id'),
'name' => Type::string()
'id' => $this->node->getField('id'),
'name' => Type::string(),
];
}
},
]);
$this->mention = new UnionType([
'name' => 'Mention',
'name' => 'Mention',
'types' => [
$this->user,
$this->category
]
$this->category,
],
]);
$this->query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'viewer' => $this->user,
'viewer' => $this->user,
'latestContent' => $this->content,
'node' => $this->node,
'mentions' => Type::listOf($this->mention)
]
'node' => $this->node,
'mentions' => Type::listOf($this->mention),
],
]);
$this->postStoryMutationInput = new InputObjectType([
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id(),
],
]);
$this->mutation = new ObjectType([
'name' => 'Mutation',
'name' => 'Mutation',
'fields' => [
'postStory' => [
'postStory' => [
'type' => $this->postStoryMutation = new ObjectType([
'name' => 'PostStoryMutation',
'name' => 'PostStoryMutation',
'fields' => [
'story' => $this->blogStory
]
'story' => $this->blogStory,
],
]),
'args' => [
'input' => Type::nonNull($this->postStoryMutationInput = new InputObjectType([
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id()
]
])),
'clientRequestId' => Type::string()
]
'input' => Type::nonNull($this->postStoryMutationInput),
'clientRequestId' => Type::string(),
],
],
'postComment' => [
'type' => $this->postCommentMutation = new ObjectType([
'name' => 'PostCommentMutation',
'name' => 'PostCommentMutation',
'fields' => [
'comment' => $this->comment
]
'comment' => $this->comment,
],
]),
'args' => [
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
'name' => 'PostCommentMutationInput',
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
'name' => 'PostCommentMutationInput',
'fields' => [
'text' => Type::nonNull(Type::string()),
'author' => Type::nonNull(Type::id()),
'text' => Type::nonNull(Type::string()),
'author' => Type::nonNull(Type::id()),
'content' => Type::id(),
'parent' => Type::id()
]
'parent' => Type::id(),
],
])),
'clientRequestId' => Type::string()
]
]
]
'clientRequestId' => Type::string(),
],
],
],
]);
}
public function testExtractTypesFromQuery() : void
{
$expectedTypeMap = [
'Query' => $this->query,
'User' => $this->user,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'Comment' => $this->comment,
'Mention' => $this->mention,
'Query' => $this->query,
'User' => $this->user,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'Comment' => $this->comment,
'Mention' => $this->mention,
'Category' => $this->category,
];
@ -294,19 +270,19 @@ class ExtractTypesTest extends TestCase
public function testExtractTypesFromMutation() : void
{
$expectedTypeMap = [
'Mutation' => $this->mutation,
'User' => $this->user,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'Comment' => $this->comment,
'BlogStory' => $this->blogStory,
'Category' => $this->category,
'PostStoryMutationInput' => $this->postStoryMutationInput,
'ID' => Type::id(),
'PostStoryMutation' => $this->postStoryMutation,
'Mutation' => $this->mutation,
'User' => $this->user,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'Comment' => $this->comment,
'BlogStory' => $this->blogStory,
'Category' => $this->category,
'PostStoryMutationInput' => $this->postStoryMutationInput,
'ID' => Type::id(),
'PostStoryMutation' => $this->postStoryMutation,
'PostCommentMutationInput' => $this->postCommentMutationInput,
'PostCommentMutation' => $this->postCommentMutation,
'PostCommentMutation' => $this->postCommentMutation,
];
$actualTypeMap = TypeInfo::extractTypes($this->mutation);
@ -316,16 +292,16 @@ class ExtractTypesTest extends TestCase
public function testThrowsOnMultipleTypesWithSameName() : void
{
$otherUserType = new ObjectType([
'name' => 'User',
'fields' => ['a' => Type::string()]
'name' => 'User',
'fields' => ['a' => Type::string()],
]);
$queryType = new ObjectType([
'name' => 'Test',
'name' => 'Test',
'fields' => [
'otherUser' => $otherUserType,
'user' => $this->user
]
'user' => $this->user,
],
]);
$this->expectException(InvariantViolation::class);

View File

@ -1,17 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils;
use GraphQL\Validator\DocumentValidator;
use PHPUnit\Framework\TestCase;
class IsValidLiteralValueTest extends TestCase
{
// DESCRIBE: isValidLiteralValue
/**
* @see it('Returns no errors for a valid value')
*/

View File

@ -1,16 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Utils\Utils;
use GraphQL\Utils\MixedStore;
use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase;
class MixedStoreTest extends TestCase
{
/**
* @var MixedStore
*/
/** @var MixedStore */
private $mixedStore;
public function setUp()
@ -18,6 +18,13 @@ class MixedStoreTest extends TestCase
$this->mixedStore = new MixedStore();
}
public function testAcceptsNullKeys() : void
{
foreach ($this->getPossibleValues() as $value) {
$this->assertAcceptsKeyValue(null, $value);
}
}
public function getPossibleValues()
{
return [
@ -30,16 +37,38 @@ class MixedStoreTest extends TestCase
'a',
[],
new \stdClass(),
function() {},
new MixedStore()
function () {
},
new MixedStore(),
];
}
public function testAcceptsNullKeys() : void
private function assertAcceptsKeyValue($key, $value)
{
foreach ($this->getPossibleValues() as $value) {
$this->assertAcceptsKeyValue(null, $value);
}
$err = 'Failed assertion that MixedStore accepts key ' .
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
$this->mixedStore->offsetSet($key, $value);
$this->assertTrue($this->mixedStore->offsetExists($key), $err);
$this->assertSame($value, $this->mixedStore->offsetGet($key), $err);
$this->mixedStore->offsetUnset($key);
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
$this->assertProvidesArrayAccess($key, $value);
}
private function assertProvidesArrayAccess($key, $value)
{
$err = 'Failed assertion that MixedStore provides array access for key ' .
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
$this->assertFalse(isset($this->mixedStore[$key]), $err);
$this->mixedStore[$key] = $value;
$this->assertTrue(isset($this->mixedStore[$key]), $err);
$this->assertEquals(! empty($value), ! empty($this->mixedStore[$key]), $err);
$this->assertSame($value, $this->mixedStore[$key], $err);
unset($this->mixedStore[$key]);
$this->assertFalse(isset($this->mixedStore[$key]), $err);
}
public function testAcceptsBoolKeys() : void
@ -93,35 +122,11 @@ class MixedStoreTest extends TestCase
foreach ($this->getPossibleValues() as $value) {
$this->assertAcceptsKeyValue(new \stdClass(), $value);
$this->assertAcceptsKeyValue(new MixedStore(), $value);
$this->assertAcceptsKeyValue(function() {}, $value);
$this->assertAcceptsKeyValue(
function () {
},
$value
);
}
}
private function assertAcceptsKeyValue($key, $value)
{
$err = 'Failed assertion that MixedStore accepts key ' .
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
$this->mixedStore->offsetSet($key, $value);
$this->assertTrue($this->mixedStore->offsetExists($key), $err);
$this->assertSame($value, $this->mixedStore->offsetGet($key), $err);
$this->mixedStore->offsetUnset($key);
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
$this->assertProvidesArrayAccess($key, $value);
}
private function assertProvidesArrayAccess($key, $value)
{
$err = 'Failed assertion that MixedStore provides array access for key ' .
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
$this->assertFalse(isset($this->mixedStore[$key]), $err);
$this->mixedStore[$key] = $value;
$this->assertTrue(isset($this->mixedStore[$key]), $err);
$this->assertEquals(!empty($value), !empty($this->mixedStore[$key]), $err);
$this->assertSame($value, $this->mixedStore[$key], $err);
unset($this->mixedStore[$key]);
$this->assertFalse(isset($this->mixedStore[$key]), $err);
}
}

View File

@ -1,16 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils;
use GraphQL\Utils\Value;
use PHPUnit\Framework\TestCase;
class QuotedOrListTest extends TestCase
{
// DESCRIBE: quotedOrList
/**
* @see it('Does not accept an empty list')
*/

View File

@ -1,16 +1,19 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use GraphQL\Utils\BuildSchema;
use GraphQL\Utils\SchemaPrinter;
use PHPUnit\Framework\TestCase;
@ -19,37 +22,40 @@ class SchemaPrinterTest extends TestCase
{
// Describe: Type System Printer
private function printForTest($schema)
{
$schemaText = SchemaPrinter::doPrint($schema);
$this->assertEquals($schemaText, SchemaPrinter::doPrint(BuildSchema::build($schemaText)));
return "\n" . $schemaText;
}
private function printSingleFieldSchema($fieldConfig)
{
$query = new ObjectType([
'name' => 'Query',
'fields' => [
'singleField' => $fieldConfig
]
]);
return $this->printForTest(new Schema(['query' => $query]));
}
/**
* @see it('Prints String Field')
*/
public function testPrintsStringField() : void
{
$output = $this->printSingleFieldSchema([
'type' => Type::string()
'type' => Type::string(),
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField: String
}
', $output);
',
$output
);
}
private function printSingleFieldSchema($fieldConfig)
{
$query = new ObjectType([
'name' => 'Query',
'fields' => ['singleField' => $fieldConfig],
]);
return $this->printForTest(new Schema(['query' => $query]));
}
private function printForTest($schema)
{
$schemaText = SchemaPrinter::doPrint($schema);
$this->assertEquals($schemaText, SchemaPrinter::doPrint(BuildSchema::build($schemaText)));
return "\n" . $schemaText;
}
/**
@ -58,13 +64,16 @@ type Query {
public function testPrintArrayStringField() : void
{
$output = $this->printSingleFieldSchema([
'type' => Type::listOf(Type::string())
'type' => Type::listOf(Type::string()),
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField: [String]
}
', $output);
',
$output
);
}
/**
@ -73,13 +82,16 @@ type Query {
public function testPrintNonNullStringField() : void
{
$output = $this->printSingleFieldSchema([
'type' => Type::nonNull(Type::string())
'type' => Type::nonNull(Type::string()),
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField: String!
}
', $output);
',
$output
);
}
/**
@ -88,13 +100,16 @@ type Query {
public function testPrintNonNullArrayStringField() : void
{
$output = $this->printSingleFieldSchema([
'type' => Type::nonNull(Type::listOf(Type::string()))
'type' => Type::nonNull(Type::listOf(Type::string())),
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField: [String]!
}
', $output);
',
$output
);
}
/**
@ -103,13 +118,16 @@ type Query {
public function testPrintArrayNonNullStringField() : void
{
$output = $this->printSingleFieldSchema([
'type' => Type::listOf(Type::nonNull(Type::string()))
'type' => Type::listOf(Type::nonNull(Type::string())),
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField: [String!]
}
', $output);
',
$output
);
}
/**
@ -118,13 +136,16 @@ type Query {
public function testPrintNonNullArrayNonNullStringField() : void
{
$output = $this->printSingleFieldSchema([
'type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))
'type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string()))),
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField: [String!]!
}
', $output);
',
$output
);
}
/**
@ -133,18 +154,19 @@ type Query {
public function testPrintObjectField() : void
{
$fooType = new ObjectType([
'name' => 'Foo',
'fields' => ['str' => ['type' => Type::string()]]
'name' => 'Foo',
'fields' => ['str' => ['type' => Type::string()]],
]);
$root = new ObjectType([
'name' => 'Query',
'fields' => ['foo' => ['type' => $fooType]]
'name' => 'Query',
'fields' => ['foo' => ['type' => $fooType]],
]);
$schema = new Schema(['query' => $root]);
$output = $this->printForTest($schema);
$this->assertEquals('
$this->assertEquals(
'
type Foo {
str: String
}
@ -152,7 +174,9 @@ type Foo {
type Query {
foo: Foo
}
', $output);
',
$output
);
}
/**
@ -162,13 +186,16 @@ type Query {
{
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::int()]]
'args' => ['argOne' => ['type' => Type::int()]],
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField(argOne: Int): String
}
', $output);
',
$output
);
}
/**
@ -178,13 +205,16 @@ type Query {
{
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => 2]]
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => 2]],
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField(argOne: Int = 2): String
}
', $output);
',
$output
);
}
/**
@ -196,11 +226,14 @@ type Query {
'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::string(), 'defaultValue' => "tes\t de\fault"]],
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField(argOne: String = "tes\t de\fault"): String
}
', $output);
',
$output
);
}
/**
@ -210,13 +243,16 @@ type Query {
{
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => null]]
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => null]],
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField(argOne: Int = null): String
}
', $output);
',
$output
);
}
/**
@ -226,13 +262,16 @@ type Query {
{
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::nonNull(Type::int())]]
'args' => ['argOne' => ['type' => Type::nonNull(Type::int())]],
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField(argOne: Int!): String
}
', $output);
',
$output
);
}
/**
@ -244,14 +283,17 @@ type Query {
'type' => Type::string(),
'args' => [
'argOne' => ['type' => Type::int()],
'argTwo' => ['type' => Type::string()]
]
'argTwo' => ['type' => Type::string()],
],
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField(argOne: Int, argTwo: String): String
}
', $output);
',
$output
);
}
/**
@ -262,16 +304,19 @@ type Query {
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'args' => [
'argOne' => ['type' => Type::int(), 'defaultValue' => 1],
'argTwo' => ['type' => Type::string()],
'argThree' => ['type' => Type::boolean()]
]
'argOne' => ['type' => Type::int(), 'defaultValue' => 1],
'argTwo' => ['type' => Type::string()],
'argThree' => ['type' => Type::boolean()],
],
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField(argOne: Int = 1, argTwo: String, argThree: Boolean): String
}
', $output);
',
$output
);
}
/**
@ -282,16 +327,19 @@ type Query {
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'args' => [
'argOne' => ['type' => Type::int()],
'argTwo' => ['type' => Type::string(), 'defaultValue' => 'foo'],
'argThree' => ['type' => Type::boolean()]
]
'argOne' => ['type' => Type::int()],
'argTwo' => ['type' => Type::string(), 'defaultValue' => 'foo'],
'argThree' => ['type' => Type::boolean()],
],
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField(argOne: Int, argTwo: String = "foo", argThree: Boolean): String
}
', $output);
',
$output
);
}
/**
@ -302,16 +350,19 @@ type Query {
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'args' => [
'argOne' => ['type' => Type::int()],
'argTwo' => ['type' => Type::string()],
'argThree' => ['type' => Type::boolean(), 'defaultValue' => false]
]
'argOne' => ['type' => Type::int()],
'argTwo' => ['type' => Type::string()],
'argThree' => ['type' => Type::boolean(), 'defaultValue' => false],
],
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
singleField(argOne: Int, argTwo: String, argThree: Boolean = false): String
}
', $output);
',
$output
);
}
/**
@ -320,14 +371,12 @@ type Query {
public function testPrintsCustomQueryRootType() : void
{
$customQueryType = new ObjectType([
'name' => 'CustomQueryType',
'name' => 'CustomQueryType',
'fields' => ['bar' => ['type' => Type::string()]],
]);
$schema = new Schema([
'query' => $customQueryType,
]);
$output = $this->printForTest($schema);
$schema = new Schema(['query' => $customQueryType]);
$output = $this->printForTest($schema);
$expected = '
schema {
query: CustomQueryType
@ -346,27 +395,28 @@ type CustomQueryType {
public function testPrintInterface() : void
{
$fooType = new InterfaceType([
'name' => 'Foo',
'fields' => ['str' => ['type' => Type::string()]]
'name' => 'Foo',
'fields' => ['str' => ['type' => Type::string()]],
]);
$barType = new ObjectType([
'name' => 'Bar',
'fields' => ['str' => ['type' => Type::string()]],
'interfaces' => [$fooType]
'name' => 'Bar',
'fields' => ['str' => ['type' => Type::string()]],
'interfaces' => [$fooType],
]);
$query = new ObjectType([
'name' => 'Query',
'fields' => ['bar' => ['type' => $barType]]
'name' => 'Query',
'fields' => ['bar' => ['type' => $barType]],
]);
$schema = new Schema([
'query' => $query,
'types' => [$barType]
'types' => [$barType],
]);
$output = $this->printForTest($schema);
$this->assertEquals('
$this->assertEquals(
'
type Bar implements Foo {
str: String
}
@ -378,7 +428,9 @@ interface Foo {
type Query {
bar: Bar
}
', $output);
',
$output
);
}
/**
@ -387,35 +439,36 @@ type Query {
public function testPrintMultipleInterface() : void
{
$fooType = new InterfaceType([
'name' => 'Foo',
'fields' => ['str' => ['type' => Type::string()]]
'name' => 'Foo',
'fields' => ['str' => ['type' => Type::string()]],
]);
$baazType = new InterfaceType([
'name' => 'Baaz',
'fields' => ['int' => ['type' => Type::int()]]
'name' => 'Baaz',
'fields' => ['int' => ['type' => Type::int()]],
]);
$barType = new ObjectType([
'name' => 'Bar',
'fields' => [
'name' => 'Bar',
'fields' => [
'str' => ['type' => Type::string()],
'int' => ['type' => Type::int()]
'int' => ['type' => Type::int()],
],
'interfaces' => [$fooType, $baazType]
'interfaces' => [$fooType, $baazType],
]);
$query = new ObjectType([
'name' => 'Query',
'fields' => ['bar' => ['type' => $barType]]
'name' => 'Query',
'fields' => ['bar' => ['type' => $barType]],
]);
$schema = new Schema([
'query' => $query,
'types' => [$barType]
'types' => [$barType],
]);
$output = $this->printForTest($schema);
$this->assertEquals('
$this->assertEquals(
'
interface Baaz {
int: Int
}
@ -432,7 +485,9 @@ interface Foo {
type Query {
bar: Bar
}
', $output);
',
$output
);
}
/**
@ -441,36 +496,37 @@ type Query {
public function testPrintUnions() : void
{
$fooType = new ObjectType([
'name' => 'Foo',
'fields' => ['bool' => ['type' => Type::boolean()]]
'name' => 'Foo',
'fields' => ['bool' => ['type' => Type::boolean()]],
]);
$barType = new ObjectType([
'name' => 'Bar',
'fields' => ['str' => ['type' => Type::string()]]
'name' => 'Bar',
'fields' => ['str' => ['type' => Type::string()]],
]);
$singleUnion = new UnionType([
'name' => 'SingleUnion',
'types' => [$fooType]
'name' => 'SingleUnion',
'types' => [$fooType],
]);
$multipleUnion = new UnionType([
'name' => 'MultipleUnion',
'types' => [$fooType, $barType]
'name' => 'MultipleUnion',
'types' => [$fooType, $barType],
]);
$query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'single' => ['type' => $singleUnion],
'multiple' => ['type' => $multipleUnion]
]
'single' => ['type' => $singleUnion],
'multiple' => ['type' => $multipleUnion],
],
]);
$schema = new Schema(['query' => $query]);
$output = $this->printForTest($schema);
$this->assertEquals('
$this->assertEquals(
'
type Bar {
str: String
}
@ -487,7 +543,9 @@ type Query {
}
union SingleUnion = Foo
', $output);
',
$output
);
}
/**
@ -496,23 +554,24 @@ union SingleUnion = Foo
public function testInputType() : void
{
$inputType = new InputObjectType([
'name' => 'InputType',
'fields' => ['int' => ['type' => Type::int()]]
'name' => 'InputType',
'fields' => ['int' => ['type' => Type::int()]],
]);
$query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'str' => [
'type' => Type::string(),
'args' => ['argOne' => ['type' => $inputType]]
]
]
'args' => ['argOne' => ['type' => $inputType]],
],
],
]);
$schema = new Schema(['query' => $query]);
$output = $this->printForTest($schema);
$this->assertEquals('
$this->assertEquals(
'
input InputType {
int: Int
}
@ -520,7 +579,9 @@ input InputType {
type Query {
str(argOne: InputType): String
}
', $output);
',
$output
);
}
/**
@ -529,28 +590,31 @@ type Query {
public function testCustomScalar() : void
{
$oddType = new CustomScalarType([
'name' => 'Odd',
'serialize' => function($value) {
'name' => 'Odd',
'serialize' => function ($value) {
return $value % 2 === 1 ? $value : null;
}
},
]);
$query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'odd' => ['type' => $oddType]
]
'odd' => ['type' => $oddType],
],
]);
$schema = new Schema(['query' => $query]);
$output = $this->printForTest($schema);
$this->assertEquals('
$this->assertEquals(
'
scalar Odd
type Query {
odd: Odd
}
', $output);
',
$output
);
}
/**
@ -559,24 +623,25 @@ type Query {
public function testEnum() : void
{
$RGBType = new EnumType([
'name' => 'RGB',
'name' => 'RGB',
'values' => [
'RED' => ['value' => 0],
'RED' => ['value' => 0],
'GREEN' => ['value' => 1],
'BLUE' => ['value' => 2]
]
'BLUE' => ['value' => 2],
],
]);
$query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'rgb' => ['type' => $RGBType]
]
'rgb' => ['type' => $RGBType],
],
]);
$schema = new Schema(['query' => $query]);
$output = $this->printForTest($schema);
$this->assertEquals('
$this->assertEquals(
'
type Query {
rgb: RGB
}
@ -586,7 +651,9 @@ enum RGB {
GREEN
BLUE
}
', $output);
',
$output
);
}
/**
@ -595,32 +662,35 @@ enum RGB {
public function testPrintsCustomDirectives() : void
{
$query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'field' => ['type' => Type::string()],
]
],
]);
$customDirectives = new Directive([
'name' => 'customDirective',
'name' => 'customDirective',
'locations' => [
DirectiveLocation::FIELD
]
DirectiveLocation::FIELD,
],
]);
$schema = new Schema([
'query' => $query,
'query' => $query,
'directives' => [$customDirectives],
]);
$output = $this->printForTest($schema);
$this->assertEquals('
$this->assertEquals(
'
directive @customDirective on FIELD
type Query {
field: String
}
', $output);
',
$output
);
}
/**
@ -629,19 +699,22 @@ type Query {
public function testOneLinePrintsAShortDescription() : void
{
$description = 'This field is awesome';
$output = $this->printSingleFieldSchema([
"type" => Type::string(),
"description" => $description
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'description' => $description,
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
"""This field is awesome"""
singleField: String
}
', $output);
',
$output
);
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
$recreatedField = $recreatedRoot->getFields()['singleField'];
$this->assertEquals($description, $recreatedField->description);
}
@ -652,21 +725,24 @@ type Query {
public function testDoesNotOneLinePrintADescriptionThatEndsWithAQuote() : void
{
$description = 'This field is "awesome"';
$output = $this->printSingleFieldSchema([
"type" => Type::string(),
"description" => $description
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'description' => $description,
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
"""
This field is "awesome"
"""
singleField: String
}
', $output);
',
$output
);
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
$recreatedField = $recreatedRoot->getFields()['singleField'];
$this->assertEquals($description, $recreatedField->description);
}
@ -677,20 +753,23 @@ type Query {
public function testPReservesLeadingSpacesWhenPrintingADescription() : void
{
$description = ' This field is "awesome"';
$output = $this->printSingleFieldSchema([
"type" => Type::string(),
"description" => $description
$output = $this->printSingleFieldSchema([
'type' => Type::string(),
'description' => $description,
]);
$this->assertEquals('
$this->assertEquals(
'
type Query {
""" This field is "awesome"
"""
singleField: String
}
', $output);
',
$output
);
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
$recreatedField = $recreatedRoot->getFields()['singleField'];
$this->assertEquals($description, $recreatedField->description);
}
@ -701,14 +780,14 @@ type Query {
public function testPrintIntrospectionSchema() : void
{
$query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'onlyField' => ['type' => Type::string()]
]
'onlyField' => ['type' => Type::string()],
],
]);
$schema = new Schema(['query' => $query]);
$output = SchemaPrinter::printIntrosepctionSchema($schema);
$schema = new Schema(['query' => $query]);
$output = SchemaPrinter::printIntrosepctionSchema($schema);
$introspectionSchema = <<<'EOT'
"""
Directs the executor to include this field or fragment only when the `if` argument is true.
@ -946,16 +1025,17 @@ EOT;
public function testPrintIntrospectionSchemaWithCommentDescriptions() : void
{
$query = new ObjectType([
'name' => 'Query',
'name' => 'Query',
'fields' => [
'onlyField' => ['type' => Type::string()]
]
'onlyField' => ['type' => Type::string()],
],
]);
$schema = new Schema(['query' => $query]);
$output = SchemaPrinter::printIntrosepctionSchema($schema, [
'commentDescriptions' => true
]);
$schema = new Schema(['query' => $query]);
$output = SchemaPrinter::printIntrosepctionSchema(
$schema,
['commentDescriptions' => true]
);
$introspectionSchema = <<<'EOT'
# Directs the executor to include this field or fragment only when the `if` argument is true.
directive @include(

View File

@ -1,16 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils;
use GraphQL\Utils\Value;
use PHPUnit\Framework\TestCase;
class SuggestionListTest extends TestCase
{
// DESCRIBE: suggestionList
/**
* @see it('Returns results when input is empty')
*/

Some files were not shown because too many files have changed in this diff Show More