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.phar
composer.lock composer.lock
phpcs.xml phpcs.xml
phpstan.neon
vendor/ vendor/

View File

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

View File

@ -25,6 +25,8 @@ composer install
./vendor/bin/phpunit ./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
Coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). To run the inspection: 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": { "require-dev": {
"doctrine/coding-standard": "^4.0", "doctrine/coding-standard": "^4.0",
"phpstan/phpstan": "^0.10.3",
"phpstan/phpstan-phpunit": "^0.10.0",
"phpunit/phpunit": "^7.2", "phpunit/phpunit": "^7.2",
"psr/http-message": "^1.0" "psr/http-message": "^1.0",
"react/promise": "2.*"
}, },
"config": { "config": {
"preferred-install": "dist", "preferred-install": "dist",
@ -39,6 +42,7 @@
"psr/http-message": "To use standard GraphQL server" "psr/http-message": "To use standard GraphQL server"
}, },
"scripts": { "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 # Installation
Using [composer](https://getcomposer.org/doc/00-intro.md), simply run: Using [composer](https://getcomposer.org/doc/00-intro.md), run:
```sh ```sh
composer require webonyx/graphql-php 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 <?php
declare(strict_types=1);
namespace GraphQL; namespace GraphQL;
use GraphQL\Executor\Promise\Adapter\SyncPromise; use GraphQL\Executor\Promise\Adapter\SyncPromise;
class Deferred class Deferred
{ {
/** /** @var \SplQueue */
* @var \SplQueue
*/
private static $queue; private static $queue;
/** /** @var callable */
* @var callable
*/
private $callback; private $callback;
/** /** @var SyncPromise */
* @var SyncPromise
*/
public $promise; public $promise;
public static function getQueue() public static function getQueue()
@ -28,7 +25,7 @@ class Deferred
public static function runQueue() public static function runQueue()
{ {
$q = self::$queue; $q = self::$queue;
while ($q && !$q->isEmpty()) { while ($q && ! $q->isEmpty()) {
/** @var self $dfd */ /** @var self $dfd */
$dfd = $q->dequeue(); $dfd = $q->dequeue();
$dfd->run(); $dfd->run();
@ -38,7 +35,7 @@ class Deferred
public function __construct(callable $callback) public function __construct(callable $callback)
{ {
$this->callback = $callback; $this->callback = $callback;
$this->promise = new SyncPromise(); $this->promise = new SyncPromise();
self::getQueue()->enqueue($this); self::getQueue()->enqueue($this);
} }
@ -47,7 +44,7 @@ class Deferred
return $this->promise->then($onFulfilled, $onRejected); return $this->promise->then($onFulfilled, $onRejected);
} }
private function run() public function run() : void
{ {
try { try {
$cb = $this->callback; $cb = $this->callback;

View File

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

View File

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

View File

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

View File

@ -263,7 +263,8 @@ class Visitor
$visitFn = self::getVisitFn($visitor, $node->kind, $isLeaving); $visitFn = self::getVisitFn($visitor, $node->kind, $isLeaving);
if ($visitFn) { 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 !== null) {
if ($result instanceof VisitorOperation) { if ($result instanceof VisitorOperation) {

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,6 @@ use function is_array;
use function is_callable; use function is_callable;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
use function spritnf;
/** /**
* Class InputObjectType * Class InputObjectType
@ -70,7 +69,7 @@ class InputObjectType extends Type implements InputType, NamedType
if (! is_array($fields)) { if (! is_array($fields)) {
throw new InvariantViolation( 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 = intval($num);
// int cast with == used for performance reasons // int cast with == used for performance reasons
// @codingStandardsIgnoreLine // phpcs:ignore
if ($int != $num) { if ($int != $num) {
throw new Error( throw new Error(
'Int cannot represent non-integer value: ' . 'Int cannot represent non-integer value: ' .

View File

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

View File

@ -7,7 +7,9 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\DirectiveLocation; use GraphQL\Language\DirectiveLocation;
use GraphQL\Validator\ValidationContext; use GraphQL\Validator\ValidationContext;
use function count; 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) private function getDirectiveLocationForASTPath(array $ancestors)
{ {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,27 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Language\Source;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/**
* Describe: Execute: handles directives
*/
class DirectivesTest extends TestCase class DirectivesTest extends TestCase
{ {
// Describe: Execute: handles directives /** @var Schema */
private static $schema;
/** @var string[] */
private static $data;
/** /**
* @see it('basic query works') * @see it('basic query works')
@ -20,10 +31,50 @@ class DirectivesTest extends TestCase
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b }')); $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 public function testWorksOnScalars() : void
{ {
// if true includes scalar // 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 // if false omits on scalar
$this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @include(if: false) }')); $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) }') $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;

View File

@ -1,99 +1,119 @@
<?php <?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\InvariantViolation;
use GraphQL\Error\Warning; use GraphQL\Error\Warning;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use PHPUnit\Framework\Error\Error; use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
class ExecutorLazySchemaTest extends TestCase 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 = []; public $calls = [];
/** @var bool[] */
public $loadedTypes = []; public $loadedTypes = [];
public function testWarnsAboutSlowIsTypeOfForLazySchema() : void public function testWarnsAboutSlowIsTypeOfForLazySchema() : void
{ {
// isTypeOf used to resolve runtime type for Interface // isTypeOf used to resolve runtime type for Interface
$petType = new InterfaceType([ $petType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'fields' => function() { 'fields' => function () {
return [ return [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
]; ];
} },
]); ]);
// Added to interface type when defined // Added to interface type when defined
$dogType = new ObjectType([ $dogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$petType], 'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; }, 'isTypeOf' => function ($obj) {
'fields' => function() { return $obj instanceof Dog;
},
'fields' => function () {
return [ return [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()] 'woofs' => ['type' => Type::boolean()],
]; ];
} },
]); ]);
$catType = new ObjectType([ $catType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$petType], 'interfaces' => [$petType],
'isTypeOf' => function ($obj) { 'isTypeOf' => function ($obj) {
return $obj instanceof Cat; return $obj instanceof Cat;
}, },
'fields' => function() { 'fields' => function () {
return [ return [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
]; ];
} },
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($petType), 'type' => Type::listOf($petType),
'resolve' => function () { 'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)]; return [new Dog('Odie', true), new Cat('Garfield', false)];
} },
] ],
] ],
]), ]),
'types' => [$catType, $dogType], 'types' => [$catType, $dogType],
'typeLoader' => function($name) use ($dogType, $petType, $catType) { 'typeLoader' => function ($name) use ($dogType, $petType, $catType) {
switch ($name) { switch ($name) {
case 'Dog': case 'Dog':
return $dogType; return $dogType;
@ -102,7 +122,7 @@ class ExecutorLazySchemaTest extends TestCase
case 'Cat': case 'Cat':
return $catType; return $catType;
} }
} },
]); ]);
$query = '{ $query = '{
@ -120,7 +140,7 @@ class ExecutorLazySchemaTest extends TestCase
$expected = new ExecutionResult([ $expected = new ExecutionResult([
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['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->assertInstanceOf(Error::class, $result->errors[0]->getPrevious());
$this->assertEquals( $this->assertEquals(
'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of '. '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 '. '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. '. 'implementations. It requires full schema scan and degrades query performance significantly. ' .
'Make sure your `resolveType` always returns valid implementation or throws.', 'Make sure your `resolveType` always returns valid implementation or throws.',
$result->errors[0]->getMessage()); $result->errors[0]->getMessage()
);
} }
public function testHintsOnConflictingTypeInstancesInDefinitions() : void public function testHintsOnConflictingTypeInstancesInDefinitions() : void
{ {
$calls = []; $calls = [];
$typeLoader = function($name) use (&$calls) { $typeLoader = function ($name) use (&$calls) {
$calls[] = $name; $calls[] = $name;
switch ($name) { switch ($name) {
case 'Test': case 'Test':
return new ObjectType([ return new ObjectType([
'name' => 'Test', 'name' => 'Test',
'fields' => function() { 'fields' => function () {
return [ return [
'test' => Type::string(), 'test' => Type::string(),
]; ];
} },
]); ]);
default: default:
return null; return null;
} }
}; };
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => function() use ($typeLoader) { 'fields' => function () use ($typeLoader) {
return [ return [
'test' => $typeLoader('Test') 'test' => $typeLoader('Test'),
]; ];
} },
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => $query, 'query' => $query,
'typeLoader' => $typeLoader 'typeLoader' => $typeLoader,
]); ]);
$query = ' $query = '
@ -186,8 +209,8 @@ class ExecutorLazySchemaTest extends TestCase
$this->assertEquals(['Test', 'Test'], $calls); $this->assertEquals(['Test', 'Test'], $calls);
$this->assertEquals( $this->assertEquals(
'Schema must contain unique named types but contains multiple types named "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 '. '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).', '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$result->errors[0]->getMessage() $result->errors[0]->getMessage()
); );
@ -200,54 +223,161 @@ class ExecutorLazySchemaTest extends TestCase
public function testSimpleQuery() : void public function testSimpleQuery() : void
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->loadType('Query'), 'query' => $this->loadType('Query'),
'typeLoader' => function($name) { 'typeLoader' => function ($name) {
return $this->loadType($name, true); return $this->loadType($name, true);
} },
]); ]);
$query = '{ object { string } }'; $query = '{ object { string } }';
$result = Executor::execute( $result = Executor::execute(
$schema, $schema,
Parser::parse($query), Parser::parse($query),
['object' => ['string' => 'test']] ['object' => ['string' => 'test']]
); );
$expected = [ $expected = [
'data' => ['object' => ['string' => 'test']], 'data' => ['object' => ['string' => 'test']],
]; ];
$expectedExecutorCalls = [ $expectedExecutorCalls = [
'Query.fields', 'Query.fields',
'SomeObject', 'SomeObject',
'SomeObject.fields' 'SomeObject.fields',
]; ];
$this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expected, $result->toArray(true));
$this->assertEquals($expectedExecutorCalls, $this->calls); $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 public function testDeepQuery() : void
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->loadType('Query'), 'query' => $this->loadType('Query'),
'typeLoader' => function($name) { 'typeLoader' => function ($name) {
return $this->loadType($name, true); return $this->loadType($name, true);
} },
]); ]);
$query = '{ object { object { object { string } } } }'; $query = '{ object { object { object { string } } } }';
$result = Executor::execute( $result = Executor::execute(
$schema, $schema,
Parser::parse($query), Parser::parse($query),
['object' => ['object' => ['object' => ['string' => 'test']]]] ['object' => ['object' => ['object' => ['string' => 'test']]]]
); );
$expected = [ $expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]] 'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]],
]; ];
$expectedLoadedTypes = [ $expectedLoadedTypes = [
'Query' => true, 'Query' => true,
'SomeObject' => true, 'SomeObject' => true,
'OtherObject' => true 'OtherObject' => true,
]; ];
$this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expected, $result->toArray(true));
@ -256,7 +386,7 @@ class ExecutorLazySchemaTest extends TestCase
$expectedExecutorCalls = [ $expectedExecutorCalls = [
'Query.fields', 'Query.fields',
'SomeObject', 'SomeObject',
'SomeObject.fields' 'SomeObject.fields',
]; ];
$this->assertEquals($expectedExecutorCalls, $this->calls); $this->assertEquals($expectedExecutorCalls, $this->calls);
} }
@ -264,13 +394,13 @@ class ExecutorLazySchemaTest extends TestCase
public function testResolveUnion() : void public function testResolveUnion() : void
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->loadType('Query'), 'query' => $this->loadType('Query'),
'typeLoader' => function($name) { 'typeLoader' => function ($name) {
return $this->loadType($name, true); return $this->loadType($name, true);
} },
]); ]);
$query = ' $query = '
{ {
other { other {
union { union {
@ -285,17 +415,17 @@ class ExecutorLazySchemaTest extends TestCase
['other' => ['union' => ['scalar' => 'test']]] ['other' => ['union' => ['scalar' => 'test']]]
); );
$expected = [ $expected = [
'data' => ['other' => ['union' => ['scalar' => 'test']]], 'data' => ['other' => ['union' => ['scalar' => 'test']]],
]; ];
$expectedLoadedTypes = [ $expectedLoadedTypes = [
'Query' => true, 'Query' => true,
'SomeObject' => true, 'SomeObject' => true,
'OtherObject' => true, 'OtherObject' => true,
'SomeUnion' => true, 'SomeUnion' => true,
'SomeInterface' => true, 'SomeInterface' => true,
'DeeperObject' => true, 'DeeperObject' => true,
'SomeScalar' => true, 'SomeScalar' => true,
]; ];
$this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expected, $result->toArray(true));
@ -313,98 +443,4 @@ class ExecutorLazySchemaTest extends TestCase
]; ];
$this->assertEquals($expectedCalls, $this->calls); $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function sprintf;
class ExecutorSchemaTest extends TestCase class ExecutorSchemaTest extends TestCase
{ {
// Execute: Handles execution with a complex schema // Execute: Handles execution with a complex schema
/** /**
* @see it('executes using a schema') * @see it('executes using a schema')
*/ */
public function testExecutesUsingASchema() : void public function testExecutesUsingASchema() : void
{ {
$BlogArticle = null; $BlogArticle = null;
$BlogImage = new ObjectType([ $BlogImage = new ObjectType([
'name' => 'Image', 'name' => 'Image',
'fields' => [ 'fields' => [
'url' => ['type' => Type::string()], 'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()], 'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()], 'height' => ['type' => Type::int()],
] ],
]); ]);
$BlogAuthor = new ObjectType([ $BlogAuthor = new ObjectType([
'name' => 'Author', 'name' => 'Author',
'fields' => function() use (&$BlogArticle, &$BlogImage) { 'fields' => function () use (&$BlogArticle, &$BlogImage) {
return [ return [
'id' => ['type' => Type::string()], 'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'pic' => [ 'pic' => [
'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]], 'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]],
'type' => $BlogImage, 'type' => $BlogImage,
'resolve' => function ($obj, $args) { 'resolve' => function ($obj, $args) {
return $obj['pic']($args['width'], $args['height']); return $obj['pic']($args['width'], $args['height']);
} },
], ],
'recentArticle' => $BlogArticle 'recentArticle' => $BlogArticle,
]; ];
} },
]); ]);
$BlogArticle = new ObjectType([ $BlogArticle = new ObjectType([
'name' => 'Article', 'name' => 'Article',
'fields' => [ 'fields' => [
'id' => ['type' => Type::nonNull(Type::string())], 'id' => ['type' => Type::nonNull(Type::string())],
'isPublished' => ['type' => Type::boolean()], 'isPublished' => ['type' => Type::boolean()],
'author' => ['type' => $BlogAuthor], 'author' => ['type' => $BlogAuthor],
'title' => ['type' => Type::string()], 'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()], 'body' => ['type' => Type::string()],
'keywords' => ['type' => Type::listOf(Type::string())] 'keywords' => ['type' => Type::listOf(Type::string())],
] ],
]); ]);
$BlogQuery = new ObjectType([ $BlogQuery = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'article' => [ 'article' => [
'type' => $BlogArticle, 'type' => $BlogArticle,
'args' => ['id' => ['type' => Type::id()]], 'args' => ['id' => ['type' => Type::id()]],
'resolve' => function ($_, $args) { 'resolve' => function ($_, $args) {
return $this->article($args['id']); return $this->article($args['id']);
} },
], ],
'feed' => [ 'feed' => [
'type' => Type::listOf($BlogArticle), 'type' => Type::listOf($BlogArticle),
'resolve' => function () { 'resolve' => function () {
return [ return [
$this->article(1), $this->article(1),
@ -80,16 +83,15 @@ class ExecutorSchemaTest extends TestCase
$this->article(7), $this->article(7),
$this->article(8), $this->article(8),
$this->article(9), $this->article(9),
$this->article(10) $this->article(10),
]; ];
} },
] ],
] ],
]); ]);
$BlogSchema = new Schema(['query' => $BlogQuery]); $BlogSchema = new Schema(['query' => $BlogQuery]);
$request = ' $request = '
{ {
feed { feed {
@ -126,51 +128,71 @@ class ExecutorSchemaTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'feed' => [ 'feed' => [
['id' => '1', [
'title' => 'My Article 1'], 'id' => '1',
['id' => '2', 'title' => 'My Article 1',
'title' => 'My Article 2'], ],
['id' => '3', [
'title' => 'My Article 3'], 'id' => '2',
['id' => '4', 'title' => 'My Article 2',
'title' => 'My Article 4'], ],
['id' => '5', [
'title' => 'My Article 5'], 'id' => '3',
['id' => '6', 'title' => 'My Article 3',
'title' => 'My Article 6'], ],
['id' => '7', [
'title' => 'My Article 7'], 'id' => '4',
['id' => '8', 'title' => 'My Article 4',
'title' => 'My Article 8'], ],
['id' => '9', [
'title' => 'My Article 9'], 'id' => '5',
['id' => '10', 'title' => 'My Article 5',
'title' => 'My Article 10'] ],
[
'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' => [ 'article' => [
'id' => '1', 'id' => '1',
'isPublished' => true, 'isPublished' => true,
'title' => 'My Article 1', 'title' => 'My Article 1',
'body' => 'This is a post', 'body' => 'This is a post',
'author' => [ 'author' => [
'id' => '123', 'id' => '123',
'name' => 'John Smith', 'name' => 'John Smith',
'pic' => [ 'pic' => [
'url' => 'cdn://123', 'url' => 'cdn://123',
'width' => 640, 'width' => 640,
'height' => 480 'height' => 480,
], ],
'recentArticle' => [ 'recentArticle' => [
'id' => '1', 'id' => '1',
'isPublished' => true, 'isPublished' => true,
'title' => 'My Article 1', 'title' => 'My Article 1',
'body' => 'This is a post', 'body' => 'This is a post',
'keywords' => ['foo', 'bar', '1', 'true', null] 'keywords' => ['foo', 'bar', '1', 'true', null],
] ],
] ],
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray()); $this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray());
@ -179,30 +201,32 @@ class ExecutorSchemaTest extends TestCase
private function article($id) private function article($id)
{ {
$johnSmith = null; $johnSmith = null;
$article = function($id) use (&$johnSmith) { $article = function ($id) use (&$johnSmith) {
return [ return [
'id' => $id, 'id' => $id,
'isPublished' => 'true', 'isPublished' => 'true',
'author' => $johnSmith, 'author' => $johnSmith,
'title' => 'My Article ' . $id, 'title' => 'My Article ' . $id,
'body' => 'This is a post', 'body' => 'This is a post',
'hidden' => 'This data is not exposed in the schema', 'hidden' => 'This data is not exposed in the schema',
'keywords' => ['foo', 'bar', 1, true, null] 'keywords' => ['foo', 'bar', 1, true, null],
]; ];
}; };
$getPic = function($uid, $width, $height) { $getPic = function ($uid, $width, $height) {
return [ return [
'url' => "cdn://$uid", 'url' => sprintf('cdn://%s', $uid),
'width' => $width, 'width' => $width,
'height' => $height 'height' => $height,
]; ];
}; };
$johnSmith = [ $johnSmith = [
'id' => 123, 'id' => 123,
'name' => 'John Smith', 'name' => 'John Smith',
'pic' => function($width, $height) use ($getPic) {return $getPic(123, $width, $height);}, 'pic' => function ($width, $height) use ($getPic) {
return $getPic(123, $width, $height);
},
'recentArticle' => $article(1), 'recentArticle' => $article(1),
]; ];

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +1,34 @@
<?php <?php
declare(strict_types=1);
/** /**
* @author: Ivo Meißner * @author: Ivo Meißner
* Date: 03.05.16 * Date: 03.05.16
* Time: 13:14 * Time: 13:14
*/ */
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class LazyInterfaceTest extends TestCase class LazyInterfaceTest extends TestCase
{ {
/** /** @var Schema */
* @var Schema
*/
protected $schema; protected $schema;
/** /** @var InterfaceType */
* @var InterfaceType
*/
protected $lazyInterface; protected $lazyInterface;
/** /** @var ObjectType */
* @var ObjectType
*/
protected $testObject; 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 * Handles execution of a lazily created interface
*/ */
@ -116,12 +46,78 @@ class LazyInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'lazyInterface' => [ 'lazyInterface' => ['name' => 'testname'],
'name' => 'testname' ],
]
]
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray()); $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\UserError; use GraphQL\Error\UserError;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ListsTest extends TestCase class ListsTest extends TestCase
{ {
// Describe: Execute: Handles list nullability // Describe: Execute: Handles list nullability
/** /**
* [T] * [T]
*/ */
@ -24,23 +23,57 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ 1, null, 2 ], [1, null, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
null, 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] * [T]
*/ */
@ -48,26 +81,26 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return [1,2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Rejected // Rejected
@ -78,14 +111,14 @@ class ListsTest extends TestCase
}); });
}, },
[ [
'data' => ['nest' => ['test' => null]], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -98,57 +131,64 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
})], }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ],
['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ [
new Deferred(function() {return 1;}), new Deferred(function () {
new Deferred(function() {return null;}), return 1;
new Deferred(function() {return 2;}) }),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Contains reject // Contains reject
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
'data' => ['nest' => ['test' => [1, null, 2]]], 'data' => ['nest' => ['test' => [1, null, 2]]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1] 'path' => ['nest', 'test', 1],
] ],
] ],
] ]
); );
} }
@ -160,32 +200,38 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ 1, null, 2 ], [1, null, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
null, null,
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
} }
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/** /**
* [T]! * [T]!
*/ */
@ -193,31 +239,31 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
new Deferred(function() { new Deferred(function () {
return [1,2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
null, null,
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -225,19 +271,19 @@ class ListsTest extends TestCase
// Rejected // Rejected
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
function () { function () {
return new Deferred(function() { return new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}); });
}, },
[ [
'data' => ['nest' => null], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -250,56 +296,56 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Contains reject // Contains reject
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
'data' => ['nest' => ['test' => [1, null, 2]]], 'data' => ['nest' => ['test' => [1, null, 2]]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1] 'path' => ['nest', 'test', 1],
] ],
] ],
] ]
); );
} }
@ -311,21 +357,21 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ 1, null, 2 ], [1, null, 2],
[ [
'data' => [ 'nest' => [ 'test' => null ] ], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -333,10 +379,16 @@ class ListsTest extends TestCase
// Returns null // Returns null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
null, 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!] * [T!]
*/ */
@ -344,53 +396,53 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, 2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ [
'data' => [ 'nest' => [ 'test' => null ] ], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
// Returns null // Returns null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Rejected // Rejected
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
function () { function () {
return new Deferred(function() { return new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}); });
}, },
[ [
'data' => ['nest' => ['test' => null]], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -403,50 +455,56 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ [
new Deferred(function() {return 1;}), new Deferred(function () {
new Deferred(function() {return null;}), return 1;
new Deferred(function() {return 2;}) }),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
], ],
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Contains reject // Contains reject
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
'data' => ['nest' => ['test' => null]], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1] 'path' => ['nest', 'test', 1],
] ],
] ],
] ]
); );
} }
@ -458,22 +516,21 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ 1, null, 2 ], [1, null, 2],
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10 ]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -482,18 +539,24 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
null, null,
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
} }
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$this->check($testType, $testData, $expected, $debug);
}
/** /**
* [T!]! * [T!]!
*/ */
@ -501,42 +564,42 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, 2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
// Returns null // Returns null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -544,19 +607,19 @@ class ListsTest extends TestCase
// Rejected // Rejected
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
function () { function () {
return new Deferred(function() { return new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}); });
}, },
[ [
'data' => ['nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -569,38 +632,38 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -609,83 +672,27 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
'data' => ['nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], '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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation; use GraphQL\Tests\Executor\TestClasses\Root;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class MutationsTest extends TestCase class MutationsTest extends TestCase
{ {
// Execute: Handles mutation execution ordering // Execute: Handles mutation execution ordering
/** /**
* @see it('evaluates mutations serially') * @see it('evaluates mutations serially')
*/ */
public function testEvaluatesMutationsSerially() : void public function testEvaluatesMutationsSerially() : void
{ {
$doc = 'mutation M { $doc = 'mutation M {
first: immediatelyChangeTheNumber(newNumber: 1) { first: immediatelyChangeTheNumber(newNumber: 1) {
theNumber theNumber
}, },
@ -38,36 +37,79 @@ class MutationsTest extends TestCase
theNumber theNumber
} }
}'; }';
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6)); $mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [ $expected = [
'data' => [ 'data' => [
'first' => [ 'first' => ['theNumber' => 1],
'theNumber' => 1 'second' => ['theNumber' => 2],
], 'third' => ['theNumber' => 3],
'second' => [ 'fourth' => ['theNumber' => 4],
'theNumber' => 2 'fifth' => ['theNumber' => 5],
], ],
'third' => [
'theNumber' => 3
],
'fourth' => [
'theNumber' => 4
],
'fifth' => [
'theNumber' => 5
]
]
]; ];
$this->assertEquals($expected, $mutationResult->toArray()); $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') * @see it('evaluates mutations correctly in the presense of a failed mutation')
*/ */
public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() : void public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() : void
{ {
$doc = 'mutation M { $doc = 'mutation M {
first: immediatelyChangeTheNumber(newNumber: 1) { first: immediatelyChangeTheNumber(newNumber: 1) {
theNumber theNumber
}, },
@ -87,147 +129,28 @@ class MutationsTest extends TestCase
theNumber theNumber
} }
}'; }';
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6)); $mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [ $expected = [
'data' => [ 'data' => [
'first' => [ 'first' => ['theNumber' => 1],
'theNumber' => 1 'second' => ['theNumber' => 2],
], 'third' => null,
'second' => [ 'fourth' => ['theNumber' => 4],
'theNumber' => 2 'fifth' => ['theNumber' => 5],
], 'sixth' => null,
'third' => null,
'fourth' => [
'theNumber' => 4
],
'fifth' => [
'theNumber' => 5
],
'sixth' => null,
], ],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot change the number', 'debugMessage' => 'Cannot change the number',
'locations' => [['line' => 8, 'column' => 7]] 'locations' => [['line' => 8, 'column' => 7]],
], ],
[ [
'debugMessage' => 'Cannot change the number', 'debugMessage' => 'Cannot change the number',
'locations' => [['line' => 17, 'column' => 7]] 'locations' => [['line' => 17, 'column' => 7]],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $mutationResult->toArray(true)); $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\FormattedError;
use GraphQL\Error\UserError; use GraphQL\Error\UserError;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class NonNullTest extends TestCase class NonNullTest extends TestCase
@ -27,41 +29,46 @@ class NonNullTest extends TestCase
/** @var \Exception */ /** @var \Exception */
public $promiseNonNullError; public $promiseNonNullError;
/** @var callable[] */
public $throwingData; public $throwingData;
/** @var callable[] */
public $nullingData; public $nullingData;
/** @var Schema */
public $schema; public $schema;
public function setUp() public function setUp()
{ {
$this->syncError = new UserError('sync'); $this->syncError = new UserError('sync');
$this->syncNonNullError = new UserError('syncNonNull'); $this->syncNonNullError = new UserError('syncNonNull');
$this->promiseError = new UserError('promise'); $this->promiseError = new UserError('promise');
$this->promiseNonNullError = new UserError('promiseNonNull'); $this->promiseNonNullError = new UserError('promiseNonNull');
$this->throwingData = [ $this->throwingData = [
'sync' => function () { 'sync' => function () {
throw $this->syncError; throw $this->syncError;
}, },
'syncNonNull' => function () { 'syncNonNull' => function () {
throw $this->syncNonNullError; throw $this->syncNonNullError;
}, },
'promise' => function () { 'promise' => function () {
return new Deferred(function () { return new Deferred(function () {
throw $this->promiseError; throw $this->promiseError;
}); });
}, },
'promiseNonNull' => function () { 'promiseNonNull' => function () {
return new Deferred(function () { return new Deferred(function () {
throw $this->promiseNonNullError; throw $this->promiseNonNullError;
}); });
}, },
'syncNest' => function () { 'syncNest' => function () {
return $this->throwingData; return $this->throwingData;
}, },
'syncNonNullNest' => function () { 'syncNonNullNest' => function () {
return $this->throwingData; return $this->throwingData;
}, },
'promiseNest' => function () { 'promiseNest' => function () {
return new Deferred(function () { return new Deferred(function () {
return $this->throwingData; return $this->throwingData;
}); });
@ -74,29 +81,29 @@ class NonNullTest extends TestCase
]; ];
$this->nullingData = [ $this->nullingData = [
'sync' => function () { 'sync' => function () {
return null; return null;
}, },
'syncNonNull' => function () { 'syncNonNull' => function () {
return null; return null;
}, },
'promise' => function () { 'promise' => function () {
return new Deferred(function () { return new Deferred(function () {
return null; return null;
}); });
}, },
'promiseNonNull' => function () { 'promiseNonNull' => function () {
return new Deferred(function () { return new Deferred(function () {
return null; return null;
}); });
}, },
'syncNest' => function () { 'syncNest' => function () {
return $this->nullingData; return $this->nullingData;
}, },
'syncNonNullNest' => function () { 'syncNonNullNest' => function () {
return $this->nullingData; return $this->nullingData;
}, },
'promiseNest' => function () { 'promiseNest' => function () {
return new Deferred(function () { return new Deferred(function () {
return $this->nullingData; return $this->nullingData;
}); });
@ -109,19 +116,19 @@ class NonNullTest extends TestCase
]; ];
$dataType = new ObjectType([ $dataType = new ObjectType([
'name' => 'DataType', 'name' => 'DataType',
'fields' => function() use (&$dataType) { 'fields' => function () use (&$dataType) {
return [ return [
'sync' => ['type' => Type::string()], 'sync' => ['type' => Type::string()],
'syncNonNull' => ['type' => Type::nonNull(Type::string())], 'syncNonNull' => ['type' => Type::nonNull(Type::string())],
'promise' => Type::string(), 'promise' => Type::string(),
'promiseNonNull' => Type::nonNull(Type::string()), 'promiseNonNull' => Type::nonNull(Type::string()),
'syncNest' => $dataType, 'syncNest' => $dataType,
'syncNonNullNest' => Type::nonNull($dataType), 'syncNonNullNest' => Type::nonNull($dataType),
'promiseNest' => $dataType, 'promiseNest' => $dataType,
'promiseNonNullNest' => Type::nonNull($dataType), 'promiseNonNullNest' => Type::nonNull($dataType),
]; ];
} },
]); ]);
$this->schema = new Schema(['query' => $dataType]); $this->schema = new Schema(['query' => $dataType]);
@ -143,17 +150,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['sync' => null],
'sync' => null,
],
'errors' => [ 'errors' => [
FormattedError::create( FormattedError::create(
$this->syncError->getMessage(), $this->syncError->getMessage(),
[new SourceLocation(3, 9)] [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 public function testNullsANullableFieldThatThrowsInAPromise() : void
@ -167,18 +175,19 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promise' => null],
'promise' => null,
],
'errors' => [ 'errors' => [
FormattedError::create( FormattedError::create(
$this->promiseError->getMessage(), $this->promiseError->getMessage(),
[new SourceLocation(3, 9)] [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 public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously() : void
@ -195,14 +204,15 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null
],
'errors' => [ '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 public function testNullsAsynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsInAPromise() : void
@ -218,15 +228,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null
],
'errors' => [ '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 public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsSynchronously() : void
@ -242,15 +253,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null
],
'errors' => [ '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 public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsInAPromise() : void
@ -266,15 +278,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null
],
'errors' => [ '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); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => [
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => 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(17, 11)]),
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(20, 13)]), FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(20, 13)]),
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(24, 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 public function testNullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfFieldsThatAreNonNull() : void
@ -413,10 +429,10 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => [
'syncNest' => null, 'syncNest' => null,
'promiseNest' => null, 'promiseNest' => null,
'anotherNest' => null, 'anotherNest' => null,
'anotherPromiseNest' => null, 'anotherPromiseNest' => null,
], ],
'errors' => [ 'errors' => [
@ -424,10 +440,13 @@ class NonNullTest extends TestCase
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(19, 19)]), 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(30, 19)]),
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(41, 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 public function testNullsANullableFieldThatSynchronouslyReturnsNull() : void
@ -441,11 +460,12 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['sync' => null],
'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 public function testNullsANullableFieldThatReturnsNullInAPromise() : void
@ -459,12 +479,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promise' => null],
'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 public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullSynchronously() : void
@ -480,17 +501,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', '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 public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise() : void
@ -506,15 +528,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null,
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -536,15 +556,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null,
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -566,15 +584,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null,
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -618,31 +634,31 @@ class NonNullTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
] ],
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
'syncNest' => [ 'syncNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
] ],
] ],
] ],
]; ];
$actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(); $actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray();
@ -703,18 +719,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => [
'syncNest' => null, 'syncNest' => null,
'promiseNest' => null, 'promiseNest' => null,
'anotherNest' => null, 'anotherNest' => null,
'anotherPromiseNest' => null, 'anotherPromiseNest' => null,
], ],
'errors' => [ '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' => 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.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' => 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.promiseNonNull.', 'locations' => [['line' => 41, 'column' => 19]]],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -734,10 +750,10 @@ class NonNullTest extends TestCase
$expected = [ $expected = [
'errors' => [ '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); $this->assertArraySubset($expected, $actual);
} }
@ -752,10 +768,13 @@ class NonNullTest extends TestCase
$expected = [ $expected = [
'errors' => [ 'errors' => [
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(2, 17)]), 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 public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() : void
@ -769,9 +788,9 @@ class NonNullTest extends TestCase
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
'locations' => [['line' => 2, 'column' => 17]] 'locations' => [['line' => 2, 'column' => 17]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
$expected, $expected,
@ -791,9 +810,9 @@ class NonNullTest extends TestCase
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 2, 'column' => 17]] 'locations' => [['line' => 2, 'column' => 17]],
], ],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(

View File

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

View File

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

View File

@ -1,25 +1,32 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise; namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\SyncPromise; use GraphQL\Executor\Promise\Adapter\SyncPromise;
use PHPUnit\Framework\Error\Error; use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function uniqid;
class SyncPromiseTest extends TestCase class SyncPromiseTest extends TestCase
{ {
public function getFulfilledPromiseResolveData() public function getFulfilledPromiseResolveData()
{ {
$onFulfilledReturnsNull = function() { $onFulfilledReturnsNull = function () {
return null; return null;
}; };
$onFulfilledReturnsSameValue = function($value) {
$onFulfilledReturnsSameValue = function ($value) {
return $value; return $value;
}; };
$onFulfilledReturnsOtherValue = function($value) {
$onFulfilledReturnsOtherValue = function ($value) {
return 'other-' . $value; return 'other-' . $value;
}; };
$onFulfilledThrows = function($value) {
throw new \Exception("onFulfilled throws this!"); $onFulfilledThrows = function ($value) {
throw new \Exception('onFulfilled throws this!');
}; };
return [ return [
@ -28,7 +35,7 @@ class SyncPromiseTest extends TestCase
[uniqid(), $onFulfilledReturnsNull, null, null, SyncPromise::FULFILLED], [uniqid(), $onFulfilledReturnsNull, null, null, SyncPromise::FULFILLED],
['test-value', $onFulfilledReturnsSameValue, 'test-value', null, SyncPromise::FULFILLED], ['test-value', $onFulfilledReturnsSameValue, 'test-value', null, SyncPromise::FULFILLED],
['test-value-2', $onFulfilledReturnsOtherValue, 'other-test-value-2', 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, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -63,8 +69,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -85,21 +90,27 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
$promise->resolve($resolvedValue); $promise->resolve($resolvedValue);
$this->assertEquals(SyncPromise::FULFILLED, $promise->state); $this->assertEquals(SyncPromise::FULFILLED, $promise->state);
$nextPromise = $promise->then(null, function() {}); $nextPromise = $promise->then(
null,
function () {
}
);
$this->assertSame($promise, $nextPromise); $this->assertSame($promise, $nextPromise);
$onRejectedCalled = false; $onRejectedCalled = false;
$nextPromise = $promise->then($onFulfilled, function () use (&$onRejectedCalled) { $nextPromise = $promise->then(
$onRejectedCalled = true; $onFulfilled,
}); function () use (&$onRejectedCalled) {
$onRejectedCalled = true;
}
);
if ($onFulfilled) { if ($onFulfilled) {
$this->assertNotSame($promise, $nextPromise); $this->assertNotSame($promise, $nextPromise);
@ -124,19 +135,57 @@ class SyncPromiseTest extends TestCase
$this->assertValidPromise($nextPromise3, $expectedNextReason, $expectedNextValue, $expectedNextState); $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() public function getRejectedPromiseData()
{ {
$onRejectedReturnsNull = function() { $onRejectedReturnsNull = function () {
return null; return null;
}; };
$onRejectedReturnsSomeValue = function($reason) {
$onRejectedReturnsSomeValue = function ($reason) {
return 'some-value'; return 'some-value';
}; };
$onRejectedThrowsSameReason = function($reason) {
$onRejectedThrowsSameReason = function ($reason) {
throw $reason; throw $reason;
}; };
$onRejectedThrowsOtherReason = function($value) {
throw new \Exception("onRejected throws other!"); $onRejectedThrowsOtherReason = function ($value) {
throw new \Exception('onRejected throws other!');
}; };
return [ return [
@ -158,8 +207,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -169,7 +217,6 @@ class SyncPromiseTest extends TestCase
$this->expectException(\Throwable::class); $this->expectException(\Throwable::class);
$this->expectExceptionMessage('Cannot change rejection reason'); $this->expectExceptionMessage('Cannot change rejection reason');
$promise->reject(new \Exception('other-reason')); $promise->reject(new \Exception('other-reason'));
} }
/** /**
@ -181,8 +228,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -203,8 +249,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -214,22 +259,26 @@ class SyncPromiseTest extends TestCase
try { try {
$promise->reject(new \Exception('other-reason')); $promise->reject(new \Exception('other-reason'));
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertEquals('Cannot change rejection reason', $e->getMessage()); $this->assertEquals('Cannot change rejection reason', $e->getMessage());
} }
try { try {
$promise->resolve('anything'); $promise->resolve('anything');
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertEquals('Cannot resolve rejected promise', $e->getMessage()); $this->assertEquals('Cannot resolve rejected promise', $e->getMessage());
} }
$nextPromise = $promise->then(function() {}, null); $nextPromise = $promise->then(
function () {
},
null
);
$this->assertSame($promise, $nextPromise); $this->assertSame($promise, $nextPromise);
$onFulfilledCalled = false; $onFulfilledCalled = false;
$nextPromise = $promise->then( $nextPromise = $promise->then(
function () use (&$onFulfilledCalled) { function () use (&$onFulfilledCalled) {
$onFulfilledCalled = true; $onFulfilledCalled = true;
}, },
@ -266,7 +315,7 @@ class SyncPromiseTest extends TestCase
try { try {
$promise->resolve($promise); $promise->resolve($promise);
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertEquals('Cannot resolve promise with self', $e->getMessage()); $this->assertEquals('Cannot resolve promise with self', $e->getMessage());
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
} }
@ -299,24 +348,25 @@ class SyncPromiseTest extends TestCase
throw $e; throw $e;
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
} catch (\Exception $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state);
} }
$promise->reject(new \Exception("Rejected Reason")); $promise->reject(new \Exception('Rejected Reason'));
$this->assertValidPromise($promise, "Rejected Reason", null, SyncPromise::REJECTED); $this->assertValidPromise($promise, 'Rejected Reason', null, SyncPromise::REJECTED);
$promise = new SyncPromise(); $promise = new SyncPromise();
$promise2 = $promise->then(null, function() { $promise2 = $promise->then(
return 'value'; null,
}); function () {
$promise->reject(new \Exception("Rejected Again")); return 'value';
}
);
$promise->reject(new \Exception('Rejected Again'));
$this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED); $this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED);
$promise = new SyncPromise(); $promise = new SyncPromise();
$promise2 = $promise->then(); $promise2 = $promise->then();
$promise->reject(new \Exception("Rejected Once Again")); $promise->reject(new \Exception('Rejected Once Again'));
$this->assertValidPromise($promise2, "Rejected Once Again", null, SyncPromise::REJECTED); $this->assertValidPromise($promise2, 'Rejected Once Again', null, SyncPromise::REJECTED);
} }
public function testPendingPromiseThen() : void public function testPendingPromiseThen() : void
@ -331,12 +381,14 @@ class SyncPromiseTest extends TestCase
// Make sure that it queues derivative promises until resolution: // Make sure that it queues derivative promises until resolution:
$onFulfilledCount = 0; $onFulfilledCount = 0;
$onRejectedCount = 0; $onRejectedCount = 0;
$onFulfilled = function($value) use (&$onFulfilledCount) { $onFulfilled = function ($value) use (&$onFulfilledCount) {
$onFulfilledCount++; $onFulfilledCount++;
return $onFulfilledCount; return $onFulfilledCount;
}; };
$onRejected = function($reason) use (&$onRejectedCount) {
$onRejected = function ($reason) use (&$onRejectedCount) {
$onRejectedCount++; $onRejectedCount++;
throw $reason; throw $reason;
}; };
@ -367,35 +419,4 @@ class SyncPromiseTest extends TestCase
$this->assertValidPromise($nextPromise3, null, 2, SyncPromise::FULFILLED); $this->assertValidPromise($nextPromise3, null, 2, SyncPromise::FULFILLED);
$this->assertValidPromise($nextPromise4, null, 3, 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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\Adder;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
require_once __DIR__ . '/TestClasses.php';
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function json_encode;
use function uniqid;
class ResolveTest extends TestCase class ResolveTest extends TestCase
{ {
// Execute: resolve function // Execute: resolve function
private function buildSchema($testField)
{
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'test' => $testField
]
])
]);
}
/** /**
* @see it('default function accesses properties') * @see it('default function accesses properties')
*/ */
@ -32,9 +24,7 @@ class ResolveTest extends TestCase
{ {
$schema = $this->buildSchema(['type' => Type::string()]); $schema = $this->buildSchema(['type' => Type::string()]);
$source = [ $source = ['test' => 'testValue'];
'test' => 'testValue'
];
$this->assertEquals( $this->assertEquals(
['data' => ['test' => 'testValue']], ['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') * @see it('default function calls methods')
*/ */
public function testDefaultFunctionCallsClosures() : void public function testDefaultFunctionCallsClosures() : void
{ {
$schema = $this->buildSchema(['type' => Type::string()]); $schema = $this->buildSchema(['type' => Type::string()]);
$_secret = 'secretValue' . uniqid(); $_secret = 'secretValue' . uniqid();
$source = [ $source = [
'test' => function() use ($_secret) { 'test' => function () use ($_secret) {
return $_secret; return $_secret;
} },
]; ];
$this->assertEquals( $this->assertEquals(
['data' => ['test' => $_secret]], ['data' => ['test' => $_secret]],
@ -69,7 +69,7 @@ class ResolveTest extends TestCase
$schema = $this->buildSchema([ $schema = $this->buildSchema([
'type' => Type::int(), 'type' => Type::int(),
'args' => [ 'args' => [
'addend1' => [ 'type' => Type::int() ], 'addend1' => ['type' => Type::int()],
], ],
]); ]);
@ -85,14 +85,14 @@ class ResolveTest extends TestCase
public function testUsesProvidedResolveFunction() : void public function testUsesProvidedResolveFunction() : void
{ {
$schema = $this->buildSchema([ $schema = $this->buildSchema([
'type' => Type::string(), 'type' => Type::string(),
'args' => [ 'args' => [
'aStr' => ['type' => Type::string()], 'aStr' => ['type' => Type::string()],
'aInt' => ['type' => Type::int()], 'aInt' => ['type' => Type::int()],
], ],
'resolve' => function ($source, $args) { 'resolve' => function ($source, $args) {
return json_encode([$source, $args]); return json_encode([$source, $args]);
} },
]); ]);
$this->assertEquals( $this->assertEquals(

View File

@ -1,8 +1,10 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError; use GraphQL\Error\FormattedError;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
@ -29,37 +31,38 @@ class SyncTest extends TestCase
public function setUp() public function setUp()
{ {
$this->schema = new Schema([ $this->schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'syncField' => [ 'syncField' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($rootValue) { 'resolve' => function ($rootValue) {
return $rootValue; return $rootValue;
} },
], ],
'asyncField' => [ 'asyncField' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($rootValue) { 'resolve' => function ($rootValue) {
return new Deferred(function () use ($rootValue) { return new Deferred(function () use ($rootValue) {
return $rootValue; return $rootValue;
}); });
} },
] ],
] ],
]), ]),
'mutation' => new ObjectType([ 'mutation' => new ObjectType([
'name' => 'Mutation', 'name' => 'Mutation',
'fields' => [ 'fields' => [
'syncMutationField' => [ 'syncMutationField' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($rootValue) { 'resolve' => function ($rootValue) {
return $rootValue; return $rootValue;
} },
] ],
] ],
]) ]),
]); ]);
$this->promiseAdapter = new SyncPromiseAdapter(); $this->promiseAdapter = new SyncPromiseAdapter();
} }
@ -70,7 +73,7 @@ class SyncTest extends TestCase
*/ */
public function testDoesNotReturnAPromiseForInitialErrors() : void public function testDoesNotReturnAPromiseForInitialErrors() : void
{ {
$doc = 'fragment Example on Query { syncField }'; $doc = 'fragment Example on Query { syncField }';
$result = $this->execute( $result = $this->execute(
$this->schema, $this->schema,
Parser::parse($doc), Parser::parse($doc),
@ -79,106 +82,6 @@ class SyncTest extends TestCase
$this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result); $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) private function execute($schema, $doc, $rootValue = null)
{ {
return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue); return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
@ -191,7 +94,56 @@ class SyncTest extends TestCase
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message); $this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
$this->assertEquals(SyncPromise::FULFILLED, $actualResult->adoptedPromise->state, $message); $this->assertEquals(SyncPromise::FULFILLED, $actualResult->adoptedPromise->state, $message);
$this->assertInstanceOf(ExecutionResult::class, $actualResult->adoptedPromise->result, $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) private function assertAsync($expectedFinalArray, $actualResult)
@ -202,6 +154,70 @@ class SyncTest extends TestCase
$this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message); $this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message);
$resolvedResult = $this->promiseAdapter->wait($actualResult); $resolvedResult = $this->promiseAdapter->wait($actualResult);
$this->assertInstanceOf(ExecutionResult::class, $resolvedResult, $message); $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Error\Warning;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Language\Parser; 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\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class UnionInterfaceTest extends TestCase class UnionInterfaceTest extends TestCase
{ {
/** @var */
public $schema; public $schema;
/** @var Cat */
public $garfield; public $garfield;
/** @var Dog */
public $odie; public $odie;
/** @var Person */
public $liz; public $liz;
/** @var Person */
public $john; public $john;
public function setUp() public function setUp()
{ {
$NamedType = new InterfaceType([ $NamedType = new InterfaceType([
'name' => 'Named', 'name' => 'Named',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$NamedType], 'interfaces' => [$NamedType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()] 'woofs' => ['type' => Type::boolean()],
], ],
'isTypeOf' => function ($value) { 'isTypeOf' => function ($value) {
return $value instanceof Dog; return $value instanceof Dog;
} },
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [$NamedType], 'interfaces' => [$NamedType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()] 'meows' => ['type' => Type::boolean()],
], ],
'isTypeOf' => function ($value) { 'isTypeOf' => function ($value) {
return $value instanceof Cat; return $value instanceof Cat;
} },
]); ]);
$PetType = new UnionType([ $PetType = new UnionType([
'name' => 'Pet', 'name' => 'Pet',
'types' => [$DogType, $CatType], 'types' => [$DogType, $CatType],
'resolveType' => function ($value) use ($DogType, $CatType) { 'resolveType' => function ($value) use ($DogType, $CatType) {
if ($value instanceof Dog) { if ($value instanceof Dog) {
return $DogType; return $DogType;
@ -66,32 +78,31 @@ class UnionInterfaceTest extends TestCase
if ($value instanceof Cat) { if ($value instanceof Cat) {
return $CatType; return $CatType;
} }
} },
]); ]);
$PersonType = new ObjectType([ $PersonType = new ObjectType([
'name' => 'Person', 'name' => 'Person',
'interfaces' => [$NamedType], 'interfaces' => [$NamedType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'pets' => ['type' => Type::listOf($PetType)], 'pets' => ['type' => Type::listOf($PetType)],
'friends' => ['type' => Type::listOf($NamedType)] 'friends' => ['type' => Type::listOf($NamedType)],
], ],
'isTypeOf' => function ($value) { 'isTypeOf' => function ($value) {
return $value instanceof Person; return $value instanceof Person;
} },
]); ]);
$this->schema = new Schema([ $this->schema = new Schema([
'query' => $PersonType, 'query' => $PersonType,
'types' => [ $PetType ] 'types' => [$PetType],
]); ]);
$this->garfield = new Cat('Garfield', false); $this->garfield = new Cat('Garfield', false);
$this->odie = new Dog('Odie', true); $this->odie = new Dog('Odie', true);
$this->liz = new Person('Liz'); $this->liz = new Person('Liz');
$this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]); $this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]);
} }
// Execute: Union and intersection types // Execute: Union and intersection types
@ -101,7 +112,6 @@ class UnionInterfaceTest extends TestCase
*/ */
public function testCanIntrospectOnUnionAndIntersectionTypes() : void public function testCanIntrospectOnUnionAndIntersectionTypes() : void
{ {
$ast = Parser::parse(' $ast = Parser::parse('
{ {
Named: __type(name: "Named") { Named: __type(name: "Named") {
@ -128,33 +138,33 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'Named' => [ 'Named' => [
'kind' => 'INTERFACE', 'kind' => 'INTERFACE',
'name' => 'Named', 'name' => 'Named',
'fields' => [ 'fields' => [
['name' => 'name'] ['name' => 'name'],
], ],
'interfaces' => null, 'interfaces' => null,
'possibleTypes' => [ 'possibleTypes' => [
['name' => 'Person'], ['name' => 'Person'],
['name' => 'Dog'], ['name' => 'Dog'],
['name' => 'Cat'] ['name' => 'Cat'],
], ],
'enumValues' => null, 'enumValues' => null,
'inputFields' => null 'inputFields' => null,
], ],
'Pet' => [ 'Pet' => [
'kind' => 'UNION', 'kind' => 'UNION',
'name' => 'Pet', 'name' => 'Pet',
'fields' => null, 'fields' => null,
'interfaces' => null, 'interfaces' => null,
'possibleTypes' => [ 'possibleTypes' => [
['name' => 'Dog'], ['name' => 'Dog'],
['name' => 'Cat'] ['name' => 'Cat'],
], ],
'enumValues' => null, 'enumValues' => null,
'inputFields' => null 'inputFields' => null,
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray()); $this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray());
} }
@ -165,7 +175,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUsingUnionTypes() : void public function testExecutesUsingUnionTypes() : void
{ {
// NOTE: This is an *invalid* query, but it should be an *executable* query. // NOTE: This is an *invalid* query, but it should be an *executable* query.
$ast = Parser::parse(' $ast = Parser::parse('
{ {
__typename __typename
name name
@ -180,12 +190,12 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'pets' => [ 'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__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()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -197,7 +207,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUnionTypesWithInlineFragments() : void public function testExecutesUnionTypesWithInlineFragments() : void
{ {
// This is the valid version of the query in the above test. // This is the valid version of the query in the above test.
$ast = Parser::parse(' $ast = Parser::parse('
{ {
__typename __typename
name name
@ -217,13 +227,13 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'pets' => [ 'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__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()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
} }
@ -234,7 +244,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesUsingInterfaceTypes() : void public function testExecutesUsingInterfaceTypes() : void
{ {
// NOTE: This is an *invalid* query, but it should be an *executable* query. // NOTE: This is an *invalid* query, but it should be an *executable* query.
$ast = Parser::parse(' $ast = Parser::parse('
{ {
__typename __typename
name name
@ -249,12 +259,12 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'friends' => [ 'friends' => [
['__typename' => 'Person', 'name' => 'Liz'], ['__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()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -266,7 +276,7 @@ class UnionInterfaceTest extends TestCase
public function testExecutesInterfaceTypesWithInlineFragments() : void public function testExecutesInterfaceTypesWithInlineFragments() : void
{ {
// This is the valid version of the query in the above test. // This is the valid version of the query in the above test.
$ast = Parser::parse(' $ast = Parser::parse('
{ {
__typename __typename
name name
@ -285,12 +295,12 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'friends' => [ 'friends' => [
['__typename' => 'Person', 'name' => 'Liz'], ['__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)); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true));
@ -336,16 +346,16 @@ class UnionInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'__typename' => 'Person', '__typename' => 'Person',
'name' => 'John', 'name' => 'John',
'pets' => [ 'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__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' => '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()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -356,36 +366,45 @@ class UnionInterfaceTest extends TestCase
*/ */
public function testGetsExecutionInfoInResolver() : void public function testGetsExecutionInfoInResolver() : void
{ {
$encounteredContext = null; $encounteredContext = null;
$encounteredSchema = null; $encounteredSchema = null;
$encounteredRootValue = null; $encounteredRootValue = null;
$PersonType2 = null; $PersonType2 = null;
$NamedType2 = new InterfaceType([ $NamedType2 = new InterfaceType([
'name' => 'Named', 'name' => 'Named',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
], ],
'resolveType' => function ($obj, $context, ResolveInfo $info) use (&$encounteredContext, &$encounteredSchema, &$encounteredRootValue, &$PersonType2) { 'resolveType' => function (
$encounteredContext = $context; $obj,
$encounteredSchema = $info->schema; $context,
ResolveInfo $info
) use (
&$encounteredContext,
&
$encounteredSchema,
&$encounteredRootValue,
&$PersonType2
) {
$encounteredContext = $context;
$encounteredSchema = $info->schema;
$encounteredRootValue = $info->rootValue; $encounteredRootValue = $info->rootValue;
return $PersonType2; return $PersonType2;
} },
]); ]);
$PersonType2 = new ObjectType([ $PersonType2 = new ObjectType([
'name' => 'Person', 'name' => 'Person',
'interfaces' => [$NamedType2], 'interfaces' => [$NamedType2],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'friends' => ['type' => Type::listOf($NamedType2)], 'friends' => ['type' => Type::listOf($NamedType2)],
], ],
]); ]);
$schema2 = new Schema([ $schema2 = new Schema(['query' => $PersonType2]);
'query' => $PersonType2
]);
$john2 = new Person('John', [], [$this->liz]); $john2 = new Person('John', [], [$this->liz]);

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Values; use GraphQL\Executor\Values;
@ -10,19 +13,97 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
use function var_export;
use const PHP_EOL;
class ValuesTest extends TestCase class ValuesTest extends TestCase
{ {
/** @var Schema */
private static $schema;
public function testGetIDVariableValues() : void public function testGetIDVariableValues() : void
{ {
$this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']); $this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']);
$this->assertEquals( $this->assertEquals(
['errors'=> [], 'coerced' => ['idInput' => '123456789']], ['errors' => [], 'coerced' => ['idInput' => '123456789']],
self::runTestCase(['idInput' => 123456789]), $this->runTestCase(['idInput' => 123456789]),
'Integer ID was not converted to string' '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 public function testGetBooleanVariableValues() : void
{ {
$this->expectInputVariablesMatchOutputVariables(['boolInput' => true]); $this->expectInputVariablesMatchOutputVariables(['boolInput' => true]);
@ -64,11 +145,20 @@ class ValuesTest extends TestCase
$this->expectGraphQLError(['idInput' => true]); $this->expectGraphQLError(['idInput' => true]);
} }
private function expectGraphQLError($variables) : void
{
$result = $this->runTestCase($variables);
$this->assertGreaterThan(0, count($result['errors']));
}
public function testFloatForIDVariableThrowsError() : void public function testFloatForIDVariableThrowsError() : void
{ {
$this->expectGraphQLError(['idInput' => 1.0]); $this->expectGraphQLError(['idInput' => 1.0]);
} }
/**
* Helpers for running test cases and making assertions
*/
public function testStringForBooleanVariableThrowsError() : void public function testStringForBooleanVariableThrowsError() : void
{ {
$this->expectGraphQLError(['boolInput' => 'true']); $this->expectGraphQLError(['boolInput' => 'true']);
@ -98,77 +188,4 @@ class ValuesTest extends TestCase
{ {
$this->expectGraphQLError(['intInput' => -2147483649]); $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 <?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php'; declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\ComplexScalar;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function json_encode;
/** /**
* Execute: Handles inputs * Execute: Handles inputs
@ -18,7 +21,6 @@ use PHPUnit\Framework\TestCase;
*/ */
class VariablesTest extends TestCase class VariablesTest extends TestCase
{ {
public function testUsingInlineStructs() : void public function testUsingInlineStructs() : void
{ {
// executes with complex input: // executes with complex input:
@ -29,14 +31,12 @@ class VariablesTest extends TestCase
'); ');
$expected = [ $expected = [
'data' => [ 'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
'fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'
]
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// properly parses single value to list: // properly parses single value to list:
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"})
} }
@ -46,7 +46,7 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// properly parses null value to null // properly parses null value to null
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null}) fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null})
} }
@ -56,7 +56,7 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// properly parses null value in list // properly parses null value in list
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"}) fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"})
} }
@ -73,12 +73,13 @@ class VariablesTest extends TestCase
'); ');
$expected = [ $expected = [
'data' => ['fieldWithObjectInput' => null], 'data' => ['fieldWithObjectInput' => null],
'errors' => [[ 'errors' => [[
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].', 'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
'path' => ['fieldWithObjectInput'], 'path' => ['fieldWithObjectInput'],
'locations' => [['line' => 3, 'column' => 39]] 'locations' => [['line' => 3, 'column' => 39]],
]] ],
],
]; ];
$this->assertArraySubset($expected, $result->toArray()); $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 public function testUsingVariables() : void
{ {
$doc = ' $doc = '
@ -119,7 +191,7 @@ class VariablesTest extends TestCase
'); ');
$expected = [ $expected = [
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'] 'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -132,63 +204,61 @@ class VariablesTest extends TestCase
); );
// executes with complex scalar input: // executes with complex scalar input:
$params = [ 'input' => [ 'c' => 'foo', 'd' => 'SerializedValue' ] ]; $params = ['input' => ['c' => 'foo', 'd' => 'SerializedValue']];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'data' => [ 'data' => ['fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'],
'fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'
]
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on null for nested non-null: // errors on null for nested non-null:
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]]; $params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value ' . 'Variable "$input" got invalid value ' .
'{"a":"foo","b":"bar","c":null}; ' . '{"a":"foo","b":"bar","c":null}; ' .
'Expected non-nullable type String! not to be null at value.c.', 'Expected non-nullable type String! not to be null at value.c.',
'locations' => [['line' => 2, 'column' => 21]], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on incorrect type: // errors on incorrect type:
$params = [ 'input' => 'foo bar' ]; $params = ['input' => 'foo bar'];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value "foo bar"; ' . 'Variable "$input" got invalid value "foo bar"; ' .
'Expected type TestInputObject to be an object.', 'Expected type TestInputObject to be an object.',
'locations' => [ [ 'line' => 2, 'column' => 21 ] ], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on omission of nested non-null: // errors on omission of nested non-null:
$params = ['input' => ['a' => 'foo', 'b' => 'bar']]; $params = ['input' => ['a' => 'foo', 'b' => 'bar']];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; '. 'Variable "$input" got invalid value {"a":"foo","b":"bar"}; ' .
'Field value.c of required type String! was not provided.', 'Field value.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 21]], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -198,62 +268,59 @@ class VariablesTest extends TestCase
fieldWithNestedObjectInput(input: $input) fieldWithNestedObjectInput(input: $input)
} }
'; ';
$params = [ 'input' => [ 'na' => [ 'a' => 'foo' ] ] ]; $params = ['input' => ['na' => ['a' => 'foo']]];
$result = $this->executeQuery($nestedDoc, $params); $result = $this->executeQuery($nestedDoc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' . 'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
'Field value.na.c of required type String! was not provided.', 'Field value.na.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]], 'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql', 'category' => 'graphql',
], ],
[ [
'message' => 'message' =>
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' . 'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
'Field value.nb of required type String! was not provided.', 'Field value.nb of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]], 'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on addition of unknown input field // errors on addition of unknown input field
$params = ['input' => [ 'a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog' ]]; $params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog']];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value ' . 'Variable "$input" got invalid value ' .
'{"a":"foo","b":"bar","c":"baz","extra":"dog"}; ' . '{"a":"foo","b":"bar","c":"baz","extra":"dog"}; ' .
'Field "extra" is not defined by type TestInputObject.', 'Field "extra" is not defined by type TestInputObject.',
'locations' => [['line' => 2, 'column' => 21]], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles nullable scalars
/** /**
* @see it('allows nullable inputs to be omitted') * @see it('allows nullable inputs to be omitted')
*/ */
public function testAllowsNullableInputsToBeOmitted() : void public function testAllowsNullableInputsToBeOmitted() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNullableStringInput fieldWithNullableStringInput
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNullableStringInput' => null] 'data' => ['fieldWithNullableStringInput' => null],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -264,7 +331,7 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNullableInputsToBeOmittedInAVariable() : void public function testAllowsNullableInputsToBeOmittedInAVariable() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
query SetsNullable($value: String) { query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value) fieldWithNullableStringInput(input: $value)
} }
@ -279,7 +346,7 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNullableInputsToBeOmittedInAnUnlistedVariable() : void public function testAllowsNullableInputsToBeOmittedInAnUnlistedVariable() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
query SetsNullable { query SetsNullable {
fieldWithNullableStringInput(input: $value) fieldWithNullableStringInput(input: $value)
} }
@ -288,12 +355,15 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles non-nullable scalars
/** /**
* @see it('allows nullable inputs to be set to null in a variable') * @see it('allows nullable inputs to be set to null in a variable')
*/ */
public function testAllowsNullableInputsToBeSetToNullInAVariable() : void public function testAllowsNullableInputsToBeSetToNullInAVariable() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
query SetsNullable($value: String) { query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value) fieldWithNullableStringInput(input: $value)
} }
@ -308,12 +378,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNullableInputsToBeSetToAValueInAVariable() : void public function testAllowsNullableInputsToBeSetToAValueInAVariable() : void
{ {
$doc = ' $doc = '
query SetsNullable($value: String) { query SetsNullable($value: String) {
fieldWithNullableStringInput(input: $value) fieldWithNullableStringInput(input: $value)
} }
'; ';
$result = $this->executeQuery($doc, ['value' => 'a']); $result = $this->executeQuery($doc, ['value' => 'a']);
$expected = ['data' => ['fieldWithNullableStringInput' => '"a"']]; $expected = ['data' => ['fieldWithNullableStringInput' => '"a"']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -323,7 +393,7 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNullableInputsToBeSetToAValueDirectly() : void public function testAllowsNullableInputsToBeSetToAValueDirectly() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNullableStringInput(input: "a") fieldWithNullableStringInput(input: "a")
} }
@ -332,21 +402,18 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles non-nullable scalars
/** /**
* @see it('allows non-nullable inputs to be omitted given a default') * @see it('allows non-nullable inputs to be omitted given a default')
*/ */
public function testAllowsNonNullableInputsToBeOmittedGivenADefault() : void public function testAllowsNonNullableInputsToBeOmittedGivenADefault() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
query SetsNonNullable($value: String = "default") { query SetsNonNullable($value: String = "default") {
fieldWithNonNullableStringInput(input: $value) fieldWithNonNullableStringInput(input: $value)
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNonNullableStringInput' => '"default"'] 'data' => ['fieldWithNonNullableStringInput' => '"default"'],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -365,11 +432,11 @@ class VariablesTest extends TestCase
$expected = [ $expected = [
'errors' => [ '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]], 'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -379,22 +446,22 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable() : void public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable() : void
{ {
$doc = ' $doc = '
query SetsNonNullable($value: String!) { query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value) fieldWithNonNullableStringInput(input: $value)
} }
'; ';
$result = $this->executeQuery($doc, ['value' => null]); $result = $this->executeQuery($doc, ['value' => null]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$value" got invalid value null; ' . 'Variable "$value" got invalid value null; ' .
'Expected non-nullable type String! not to be null.', 'Expected non-nullable type String! not to be null.',
'locations' => [['line' => 2, 'column' => 31]], 'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -404,12 +471,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullableInputsToBeSetToAValueInAVariable() : void public function testAllowsNonNullableInputsToBeSetToAValueInAVariable() : void
{ {
$doc = ' $doc = '
query SetsNonNullable($value: String!) { query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value) fieldWithNonNullableStringInput(input: $value)
} }
'; ';
$result = $this->executeQuery($doc, ['value' => 'a']); $result = $this->executeQuery($doc, ['value' => 'a']);
$expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']]; $expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -419,7 +486,7 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullableInputsToBeSetToAValueDirectly() : void public function testAllowsNonNullableInputsToBeSetToAValueDirectly() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNonNullableStringInput(input: "a") fieldWithNonNullableStringInput(input: "a")
} }
@ -433,47 +500,50 @@ class VariablesTest extends TestCase
*/ */
public function testReportsErrorForMissingNonNullableInputs() : void public function testReportsErrorForMissingNonNullableInputs() : void
{ {
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNonNullableStringInput fieldWithNonNullableStringInput
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNonNullableStringInput' => null], 'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[ 'errors' => [[
'message' => 'Argument "input" of required type "String!" was not provided.', 'message' => 'Argument "input" of required type "String!" was not provided.',
'locations' => [ [ 'line' => 3, 'column' => 9 ] ], 'locations' => [['line' => 3, 'column' => 9]],
'path' => [ 'fieldWithNonNullableStringInput' ], 'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql', 'category' => 'graphql',
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles lists and nullability
/** /**
* @see it('reports error for array passed into string input') * @see it('reports error for array passed into string input')
*/ */
public function testReportsErrorForArrayPassedIntoStringInput() : void public function testReportsErrorForArrayPassedIntoStringInput() : void
{ {
$doc = '
$doc = '
query SetsNonNullable($value: String!) { query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value) fieldWithNonNullableStringInput(input: $value)
} }
'; ';
$variables = ['value' => [1, 2, 3]]; $variables = ['value' => [1, 2, 3]];
$result = $this->executeQuery($doc, $variables); $result = $this->executeQuery($doc, $variables);
$expected = [ $expected = [
'errors' => [[ 'errors' => [[
'message' => 'message' =>
'Variable "$value" got invalid value [1,2,3]; Expected type ' . 'Variable "$value" got invalid value [1,2,3]; Expected type ' .
'String; String cannot represent an array value: [1,2,3]', 'String; String cannot represent an array value: [1,2,3]',
'category' => 'graphql', 'category' => 'graphql',
'locations' => [ 'locations' => [
['line' => 2, 'column' => 31] ['line' => 2, 'column' => 31],
] ],
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $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 // and are being run against a new schema which have introduced a breaking
// change to make a formerly non-required argument required, this asserts // change to make a formerly non-required argument required, this asserts
// failure before allowing the underlying code to receive a non-null value. // failure before allowing the underlying code to receive a non-null value.
$result = $this->executeQuery(' $result = $this->executeQuery('
{ {
fieldWithNonNullableStringInput(input: $foo) fieldWithNonNullableStringInput(input: $foo)
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNonNullableStringInput' => null], 'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[ 'errors' => [[
'message' => 'message' =>
'Argument "input" of required type "String!" was provided the ' . 'Argument "input" of required type "String!" was provided the ' .
'variable "$foo" which was not provided a runtime value.', 'variable "$foo" which was not provided a runtime value.',
'locations' => [['line' => 3, 'column' => 48]], 'locations' => [['line' => 3, 'column' => 48]],
'path' => ['fieldWithNonNullableStringInput'], 'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql', 'category' => 'graphql',
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles lists and nullability
/** /**
* @see it('allows lists to be null') * @see it('allows lists to be null')
*/ */
public function testAllowsListsToBeNull() : void public function testAllowsListsToBeNull() : void
{ {
$doc = ' $doc = '
query q($input:[String]) { query q($input:[String]) {
list(input: $input) list(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => null]); $result = $this->executeQuery($doc, ['input' => null]);
$expected = ['data' => ['list' => null]]; $expected = ['data' => ['list' => null]];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -540,12 +609,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsListsToContainValues() : void public function testAllowsListsToContainValues() : void
{ {
$doc = ' $doc = '
query q($input:[String]) { query q($input:[String]) {
list(input: $input) list(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A']]); $result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['list' => '["A"]']]; $expected = ['data' => ['list' => '["A"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -555,12 +624,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsListsToContainNull() : void public function testAllowsListsToContainNull() : void
{ {
$doc = ' $doc = '
query q($input:[String]) { query q($input:[String]) {
list(input: $input) 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"]']]; $expected = ['data' => ['list' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -570,22 +639,22 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowNonNullListsToBeNull() : void public function testDoesNotAllowNonNullListsToBeNull() : void
{ {
$doc = ' $doc = '
query q($input:[String]!) { query q($input:[String]!) {
nnList(input: $input) nnList(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => null]); $result = $this->executeQuery($doc, ['input' => null]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value null; ' . 'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String]! not to be null.', 'Expected non-nullable type [String]! not to be null.',
'locations' => [['line' => 2, 'column' => 17]], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -595,12 +664,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullListsToContainValues() : void public function testAllowsNonNullListsToContainValues() : void
{ {
$doc = ' $doc = '
query q($input:[String]!) { query q($input:[String]!) {
nnList(input: $input) nnList(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A']]); $result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['nnList' => '["A"]']]; $expected = ['data' => ['nnList' => '["A"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -610,12 +679,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullListsToContainNull() : void public function testAllowsNonNullListsToContainNull() : void
{ {
$doc = ' $doc = '
query q($input:[String]!) { query q($input:[String]!) {
nnList(input: $input) 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"]']]; $expected = ['data' => ['nnList' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -625,12 +694,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsListsOfNonNullsToBeNull() : void public function testAllowsListsOfNonNullsToBeNull() : void
{ {
$doc = ' $doc = '
query q($input:[String!]) { query q($input:[String!]) {
listNN(input: $input) listNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => null]); $result = $this->executeQuery($doc, ['input' => null]);
$expected = ['data' => ['listNN' => null]]; $expected = ['data' => ['listNN' => null]];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -640,12 +709,12 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsListsOfNonNullsToContainValues() : void public function testAllowsListsOfNonNullsToContainValues() : void
{ {
$doc = ' $doc = '
query q($input:[String!]) { query q($input:[String!]) {
listNN(input: $input) listNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A']]); $result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['listNN' => '["A"]']]; $expected = ['data' => ['listNN' => '["A"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -655,22 +724,22 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowListsOfNonNullsToContainNull() : void public function testDoesNotAllowListsOfNonNullsToContainNull() : void
{ {
$doc = ' $doc = '
query q($input:[String!]) { query q($input:[String!]) {
listNN(input: $input) listNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' . 'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].', 'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -680,22 +749,22 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowNonNullListsOfNonNullsToBeNull() : void public function testDoesNotAllowNonNullListsOfNonNullsToBeNull() : void
{ {
$doc = ' $doc = '
query q($input:[String!]!) { query q($input:[String!]!) {
nnListNN(input: $input) nnListNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => null]); $result = $this->executeQuery($doc, ['input' => null]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value null; ' . 'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String!]! not to be null.', 'Expected non-nullable type [String!]! not to be null.',
'locations' => [ ['line' => 2, 'column' => 17] ], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -705,37 +774,39 @@ class VariablesTest extends TestCase
*/ */
public function testAllowsNonNullListsOfNonNullsToContainValues() : void public function testAllowsNonNullListsOfNonNullsToContainValues() : void
{ {
$doc = ' $doc = '
query q($input:[String!]!) { query q($input:[String!]!) {
nnListNN(input: $input) nnListNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A']]); $result = $this->executeQuery($doc, ['input' => ['A']]);
$expected = ['data' => ['nnListNN' => '["A"]']]; $expected = ['data' => ['nnListNN' => '["A"]']];
$this->assertEquals($expected, $result->toArray()); $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') * @see it('does not allow non-null lists of non-nulls to contain null')
*/ */
public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() : void public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() : void
{ {
$doc = ' $doc = '
query q($input:[String!]!) { query q($input:[String!]!) {
nnListNN(input: $input) nnListNN(input: $input)
} }
'; ';
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' . 'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].', 'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -745,23 +816,23 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowInvalidTypesToBeUsedAsValues() : void public function testDoesNotAllowInvalidTypesToBeUsedAsValues() : void
{ {
$doc = ' $doc = '
query q($input: TestType!) { query q($input: TestType!) {
fieldWithObjectInput(input: $input) fieldWithObjectInput(input: $input)
} }
'; ';
$vars = [ 'input' => [ 'list' => [ 'A', 'B' ] ] ]; $vars = ['input' => ['list' => ['A', 'B']]];
$result = $this->executeQuery($doc, $vars); $result = $this->executeQuery($doc, $vars);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" expected value of type "TestType!" which cannot ' . 'Variable "$input" expected value of type "TestType!" which cannot ' .
'be used as an input type.', 'be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]], 'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -771,29 +842,28 @@ class VariablesTest extends TestCase
*/ */
public function testDoesNotAllowUnknownTypesToBeUsedAsValues() : void public function testDoesNotAllowUnknownTypesToBeUsedAsValues() : void
{ {
$doc = ' $doc = '
query q($input: UnknownType!) { query q($input: UnknownType!) {
fieldWithObjectInput(input: $input) fieldWithObjectInput(input: $input)
} }
'; ';
$vars = ['input' => 'whoknows']; $vars = ['input' => 'whoknows'];
$result = $this->executeQuery($doc, $vars); $result = $this->executeQuery($doc, $vars);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" expected value of type "UnknownType!" which ' . 'Variable "$input" expected value of type "UnknownType!" which ' .
'cannot be used as an input type.', 'cannot be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]], 'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Execute: Uses argument default values
/** /**
* @see it('when no argument provided') * @see it('when no argument provided')
*/ */
@ -834,84 +904,17 @@ class VariablesTest extends TestCase
}'); }');
$expected = [ $expected = [
'data' => ['fieldWithDefaultArgumentValue' => null], 'data' => ['fieldWithDefaultArgumentValue' => null],
'errors' => [[ 'errors' => [[
'message' => 'message' =>
'Argument "input" has invalid value WRONG_TYPE.', 'Argument "input" has invalid value WRONG_TYPE.',
'locations' => [ [ 'line' => 2, 'column' => 50 ] ], 'locations' => [['line' => 2, 'column' => 50]],
'path' => [ 'fieldWithDefaultArgumentValue' ], 'path' => ['fieldWithDefaultArgumentValue'],
'category' => 'graphql', 'category' => 'graphql',
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Language; namespace GraphQL\Tests\Language;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\Lexer; use GraphQL\Language\Lexer;
use GraphQL\Language\Source; use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Language\Token; use GraphQL\Language\Token;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
use function json_decode;
class LexerTest extends TestCase 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') * @see it('accepts BOM header')
*/ */
public function testAcceptsBomHeader() : void public function testAcceptsBomHeader() : void
{ {
$bom = Utils::chr(0xFEFF); $bom = Utils::chr(0xFEFF);
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 2, 'start' => 2,
'end' => 5, 'end' => 5,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo')); $this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo'));
@ -45,12 +78,12 @@ class LexerTest extends TestCase
public function testRecordsLineAndColumn() : void public function testRecordsLineAndColumn() : void
{ {
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 8, 'start' => 8,
'end' => 11, 'end' => 11,
'line' => 4, 'line' => 4,
'column' => 3, 'column' => 3,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n")); $this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n"));
} }
@ -67,10 +100,10 @@ class LexerTest extends TestCase
'; ';
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 6, 'start' => 6,
'end' => 9, 'end' => 9,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne($example1)); $this->assertArraySubset($expected, (array) $this->lexOne($example1));
@ -80,18 +113,18 @@ class LexerTest extends TestCase
'; ';
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 18, 'start' => 18,
'end' => 21, 'end' => 21,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne($example2)); $this->assertArraySubset($expected, (array) $this->lexOne($example2));
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 3, 'start' => 3,
'end' => 6, 'end' => 6,
'value' => 'foo' 'value' => 'foo',
]; ];
$example3 = ',,,foo,,,'; $example3 = ',,,foo,,,';
@ -131,7 +164,7 @@ class LexerTest extends TestCase
*/ */
public function testUpdatesLineNumbersInErrorForFileContext() : void public function testUpdatesLineNumbersInErrorForFileContext() : void
{ {
$str = '' . $str = '' .
"\n" . "\n" .
"\n" . "\n" .
" ?\n" . " ?\n" .
@ -181,63 +214,86 @@ class LexerTest extends TestCase
*/ */
public function testLexesStrings() : void public function testLexesStrings() : void
{ {
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 8, 'start' => 0,
'value' => 'simple' 'end' => 8,
], (array) $this->lexOne('"simple"')); '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([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 15, 'start' => 0,
'value' => ' white space ' 'end' => 10,
], (array) $this->lexOne('" white space "')); 'value' => 'quote "',
],
(array) $this->lexOne('"quote \\""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 10, 'start' => 0,
'value' => 'quote "' 'end' => 25,
], (array) $this->lexOne('"quote \\""')); 'value' => 'escaped \n\r\b\t\f',
],
(array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 25, 'start' => 0,
'value' => 'escaped \n\r\b\t\f' 'end' => 16,
], (array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')); 'value' => 'slashes \\ \/',
],
(array) $this->lexOne('"slashes \\\\ \\\\/"')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 16, 'start' => 0,
'value' => 'slashes \\ \/' 'end' => 13,
], (array) $this->lexOne('"slashes \\\\ \\\\/"')); 'value' => 'unicode яуц',
],
$this->assertArraySubset([ (array) $this->lexOne('"unicode яуц"')
'kind' => Token::STRING, );
'start' => 0,
'end' => 13,
'value' => 'unicode яуц'
], (array) $this->lexOne('"unicode яуц"'));
$unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"'); $unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"');
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 34, 'start' => 0,
'value' => 'unicode ' . $unicode 'end' => 34,
], (array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')); 'value' => 'unicode ' . $unicode,
],
(array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 26, 'start' => 0,
'value' => $unicode 'end' => 26,
], (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')); 'value' => $unicode,
],
(array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')
);
} }
/** /**
@ -245,86 +301,128 @@ class LexerTest extends TestCase
*/ */
public function testLexesBlockString() : void public function testLexesBlockString() : void
{ {
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 12, 'start' => 0,
'value' => 'simple' 'end' => 12,
], (array) $this->lexOne('"""simple"""')); 'value' => 'simple',
],
(array) $this->lexOne('"""simple"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 19, 'start' => 0,
'value' => ' white space ' 'end' => 19,
], (array) $this->lexOne('""" white space """')); 'value' => ' white space ',
],
(array) $this->lexOne('""" white space """')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 22, 'start' => 0,
'value' => 'contains " quote' 'end' => 22,
], (array) $this->lexOne('"""contains " quote"""')); 'value' => 'contains " quote',
],
(array) $this->lexOne('"""contains " quote"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 31, 'start' => 0,
'value' => 'contains """ triplequote' 'end' => 31,
], (array) $this->lexOne('"""contains \\""" triplequote"""')); 'value' => 'contains """ triplequote',
],
(array) $this->lexOne('"""contains \\""" triplequote"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 16, 'start' => 0,
'value' => "multi\nline" 'end' => 16,
], (array) $this->lexOne("\"\"\"multi\nline\"\"\"")); 'value' => "multi\nline",
],
(array) $this->lexOne("\"\"\"multi\nline\"\"\"")
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 28, 'start' => 0,
'value' => "multi\nline\nnormalized" 'end' => 28,
], (array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")); 'value' => "multi\nline\nnormalized",
],
(array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 32, 'start' => 0,
'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234' 'end' => 32,
], (array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""')); 'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234',
],
(array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 19, 'start' => 0,
'value' => 'slashes \\\\ \\/' 'end' => 19,
], (array) $this->lexOne('"""slashes \\\\ \\/"""')); 'value' => 'slashes \\\\ \\/',
],
(array) $this->lexOne('"""slashes \\\\ \\/"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 68, 'start' => 0,
'value' => "spans\n multiple\n lines" 'end' => 68,
], (array) $this->lexOne("\"\"\" 'value' => "spans\n multiple\n lines",
],
(array) $this->lexOne('"""
spans spans
multiple multiple
lines lines
\"\"\"")); """')
);
} }
public function reportsUsefulStringErrors() { public function reportsUsefulStringErrors()
{
return [ return [
['"', "Unterminated string.", $this->loc(1, 2)], ['"', 'Unterminated string.', $this->loc(1, 2)],
['"no end quote', "Unterminated string.", $this->loc(1, 14)], ['"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)], "'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)], ['"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' . "\n" . 'line"', 'Unterminated string.', $this->loc(1, 7)],
['"multi' . "\r" . '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 \\z esc"', 'Invalid character escape sequence: \\z', $this->loc(1, 7)],
['"bad \\x esc"', "Invalid character escape sequence: \\x", $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 \\u1 esc"', "Invalid character escape sequence: \\u1 es", $this->loc(1, 7)],
['"bad \\u0XX1 esc"', "Invalid character escape sequence: \\u0XX1", $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 * @dataProvider reportsUsefulStringErrors
* @see it('lex reports useful string errors') * @see it('lex reports useful string errors')
*/ */
public function testLexReportsUsefulStringErrors($str, $expectedMessage, $location) : void public function testLexReportsUsefulStringErrors($str, $expectedMessage, $location) : void
{ {
$this->expectSyntaxError($str, $expectedMessage, $location); $this->expectSyntaxError($str, $expectedMessage, $location);
} }
public function reportsUsefulBlockStringErrors() { public function reportsUsefulBlockStringErrors()
{
return [ return [
['"""', "Unterminated string.", $this->loc(1, 4)], ['"""', 'Unterminated string.', $this->loc(1, 4)],
['"""no end quote', "Unterminated string.", $this->loc(1, 16)], ['"""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)], '"""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 * @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 public function testReportsUsefulBlockStringErrors($str, $expectedMessage, $location) : void
{ {
@ -435,21 +548,21 @@ class LexerTest extends TestCase
public function reportsUsefulNumberErrors() public function reportsUsefulNumberErrors()
{ {
return [ return [
[ '00', "Invalid number, unexpected digit after 0: \"0\"", $this->loc(1, 2)], ['00', 'Invalid number, unexpected digit after 0: "0"', $this->loc(1, 2)],
[ '+1', "Cannot parse the unexpected character \"+\".", $this->loc(1, 1)], ['+1', 'Cannot parse the unexpected character "+".', $this->loc(1, 1)],
[ '1.', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 3)], ['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)], ['1.e1', 'Invalid number, expected digit but got: "e"', $this->loc(1, 3)],
[ '.123', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)], ['.123', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)],
[ '1.A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 3)], ['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)], ['-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.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)], ['1.0eA', 'Invalid number, expected digit but got: "A"', $this->loc(1, 5)],
]; ];
} }
/** /**
* @dataProvider reportsUsefulNumberErrors * @dataProvider reportsUsefulNumberErrors
* @see it('lex reports useful number errors') * @see it('lex reports useful number errors')
*/ */
public function testReportsUsefulNumberErrors($str, $expectedMessage, $location) : void public function testReportsUsefulNumberErrors($str, $expectedMessage, $location) : void
{ {
@ -521,8 +634,8 @@ class LexerTest extends TestCase
$unicode2 = json_decode('"\u200b"'); $unicode2 = json_decode('"\u200b"');
return [ 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)], [$unicode1, "Cannot parse the unexpected character \"\\u203b\".", $this->loc(1, 1)],
[$unicode2, "Cannot parse the unexpected character \"\\u200b\".", $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 * @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 public function testReportsUsefulUnknownCharErrors($str, $expectedMessage, $location) : void
{ {
@ -542,17 +655,20 @@ class LexerTest extends TestCase
*/ */
public function testReportsUsefulDashesInfo() : void public function testReportsUsefulDashesInfo() : void
{ {
$q = 'a-b'; $q = 'a-b';
$lexer = new Lexer(new Source($q)); $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->expectException(SyntaxError::class);
$this->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"'); $this->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"');
try { try {
$lexer->advance(); $lexer->advance();
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch(SyntaxError $error) { } catch (SyntaxError $error) {
$this->assertEquals([$this->loc(1,3)], $error->getLocations()); $this->assertEquals([$this->loc(1, 3)], $error->getLocations());
throw $error; throw $error;
} }
} }
@ -580,49 +696,28 @@ class LexerTest extends TestCase
$tokens = []; $tokens = [];
for ($tok = $startToken; $tok; $tok = $tok->next) { for ($tok = $startToken; $tok; $tok = $tok->next) {
if (!empty($tokens)) { if (! empty($tokens)) {
// Tokens are double-linked, prev should point to last seen token. // Tokens are double-linked, prev should point to last seen token.
$this->assertSame($tokens[count($tokens) - 1], $tok->prev); $this->assertSame($tokens[count($tokens) - 1], $tok->prev);
} }
$tokens[] = $tok; $tokens[] = $tok;
} }
$this->assertEquals([ $this->assertEquals(
'<SOF>', [
'{', '<SOF>',
'Comment', '{',
'Name', 'Comment',
'}', 'Name',
'<EOF>' '}',
], Utils::map($tokens, function ($tok) { '<EOF>',
return $tok->kind; ],
})); 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;
}
} }
} }

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,5 +1,8 @@
<?php <?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\Location; use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
@ -7,80 +10,68 @@ use GraphQL\Language\AST\NodeList;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Utils\AST; use GraphQL\Utils\AST;
use PHPUnit\Framework\TestCase; 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 class SerializationTest extends TestCase
{ {
public function testSerializesAst() : void public function testSerializesAst() : void
{ {
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $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); $expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
$this->assertEquals($expectedAst, $ast->toArray(true)); $this->assertEquals($expectedAst, $ast->toArray(true));
} }
public function testUnserializesAst() : void 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); $serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
$actualAst = AST::fromArray($serializedAst); $actualAst = AST::fromArray($serializedAst);
$parsedAst = Parser::parse($kitchenSink); $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]);
$this->assertNodesAreEqual($parsedAst, $actualAst); $this->assertNodesAreEqual($parsedAst, $actualAst);
} }
/** /**
* Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc * Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc
* *
* @param $expected * @param string[] $path
* @param $actual
* @param array $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->assertInstanceOf(Node::class, $actual, $err);
$this->assertEquals(get_class($expected), get_class($actual), $err); $this->assertEquals(get_class($expected), get_class($actual), $err);
$expectedVars = get_object_vars($expected); $expectedVars = get_object_vars($expected);
$actualVars = get_object_vars($actual); $actualVars = get_object_vars($actual);
$this->assertSame(count($expectedVars), count($actualVars), $err); $this->assertCount(count($expectedVars), $actualVars, $err);
$this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err); $this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err);
foreach ($expectedVars as $name => $expectedValue) { foreach ($expectedVars as $name => $expectedValue) {
$actualValue = $actualVars[$name]; $actualValue = $actualVars[$name];
$tmpPath = $path; $tmpPath = $path;
$tmpPath[] = $name; $tmpPath[] = $name;
$err = "Mismatch at AST path: " . implode(', ', $tmpPath); $err = 'Mismatch at AST path: ' . implode(', ', $tmpPath);
if ($expectedValue instanceof Node) { if ($expectedValue instanceof Node) {
$this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath); $this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath);
} else if ($expectedValue instanceof NodeList) { } elseif ($expectedValue instanceof NodeList) {
$this->assertEquals(count($expectedValue), count($actualValue), $err); $this->assertEquals(count($expectedValue), count($actualValue), $err);
$this->assertInstanceOf(NodeList::class, $actualValue, $err); $this->assertInstanceOf(NodeList::class, $actualValue, $err);
foreach ($expectedValue as $index => $listNode) { foreach ($expectedValue as $index => $listNode) {
$tmpPath2 = $tmpPath; $tmpPath2 = $tmpPath;
$tmpPath2 [] = $index; $tmpPath2[] = $index;
$this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2); $this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2);
} }
} else if ($expectedValue instanceof Location) { } elseif ($expectedValue instanceof Location) {
$this->assertInstanceOf(Location::class, $actualValue, $err); $this->assertInstanceOf(Location::class, $actualValue, $err);
$this->assertSame($expectedValue->start, $actualValue->start, $err); $this->assertSame($expectedValue->start, $actualValue->start, $err);
$this->assertSame($expectedValue->end, $actualValue->end, $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 <?php
namespace GraphQL\Tests\Language;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\Location; use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NodeList;
use function get_object_vars;
use function is_array;
use function is_scalar;
class TestUtils class TestUtils
{ {
/** /**
* @param Node $node * @return mixed[]
* @return array
*/ */
public static function nodeToArray(Node $node) public static function nodeToArray(Node $node) : array
{ {
$result = [ $result = [
'kind' => $node->kind, 'kind' => $node->kind,
'loc' => self::locationToArray($node->loc) 'loc' => self::locationToArray($node->loc),
]; ];
foreach (get_object_vars($node) as $prop => $propValue) { foreach (get_object_vars($node) as $prop => $propValue) {
if (isset($result[$prop])) if (isset($result[$prop])) {
continue; continue;
}
if (is_array($propValue) || $propValue instanceof NodeList) { if (is_array($propValue) || $propValue instanceof NodeList) {
$tmp = []; $tmp = [];
foreach ($propValue as $tmp1) { foreach ($propValue as $tmp1) {
$tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1; $tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1;
} }
} else if ($propValue instanceof Node) { } elseif ($propValue instanceof Node) {
$tmp = self::nodeToArray($propValue); $tmp = self::nodeToArray($propValue);
} else if (is_scalar($propValue) || null === $propValue) { } elseif (is_scalar($propValue) || $propValue === null) {
$tmp = $propValue; $tmp = $propValue;
} else { } else {
$tmp = null; $tmp = null;
@ -38,27 +43,25 @@ class TestUtils
$result[$prop] = $tmp; $result[$prop] = $tmp;
} }
return $result; return $result;
} }
/** /**
* @param Location $loc * @return int[]
* @return array
*/ */
public static function locationToArray(Location $loc) public static function locationToArray(Location $loc) : array
{ {
return [ return [
'start' => $loc->start, 'start' => $loc->start,
'end' => $loc->end 'end' => $loc->end,
]; ];
} }
/** /**
* @param $start * @return int[]
* @param $end
* @return array
*/ */
public static function locArray($start, $end) public static function locArray(int $start, int $end) : array
{ {
return ['start' => $start, 'end' => $end]; return ['start' => $start, 'end' => $end];
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,17 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server\Psr7; namespace GraphQL\Tests\Server\Psr7;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
use function strtolower;
/**
* phpcs:ignoreFile -- this is not a core file
*/
class PsrRequestStub implements ServerRequestInterface class PsrRequestStub implements ServerRequestInterface
{ {
public $queryParams = []; public $queryParams = [];
@ -13,14 +20,10 @@ class PsrRequestStub implements ServerRequestInterface
public $method; public $method;
/** /** @var PsrStreamStub */
* @var PsrStreamStub
*/
public $body; public $body;
/** /** @var mixed[] */
* @var array
*/
public $parsedBody; public $parsedBody;
/** /**
@ -32,7 +35,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getProtocolVersion() 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) 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() 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) 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) public function getHeader($name)
{ {
$name = strtolower($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) 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 * immutability of the message, and MUST return an instance that has the
* new and/or updated header and value. * 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). * @param string|string[] $value Header value(s).
* @return static * @return static
* @throws \InvalidArgumentException for invalid header names or values. * @throws \InvalidArgumentException for invalid header names or values.
*/ */
public function withHeader($name, $value) 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 * immutability of the message, and MUST return an instance that has the
* new header and/or value. * 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). * @param string|string[] $value Header value(s).
* @return static * @return static
* @throws \InvalidArgumentException for invalid header names or values. * @throws \InvalidArgumentException for invalid header names or values.
*/ */
public function withAddedHeader($name, $value) 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) 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) 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() 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) 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) 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. * new UriInterface instance.
* *
* @link http://tools.ietf.org/html/rfc3986#section-4.3 * @link http://tools.ietf.org/html/rfc3986#section-4.3
* @param UriInterface $uri New request URI to use. * @param UriInterface $uri New request URI to use.
* @param bool $preserveHost Preserve the original state of the Host header. * @param bool $preserveHost Preserve the original state of the Host header.
* @return static * @return static
*/ */
public function withUri(UriInterface $uri, $preserveHost = false) 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() public function getServerParams()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -374,7 +378,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getCookieParams() 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) 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) 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() 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) 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) 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() 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. * specifying a default value to return if the attribute is not found.
* *
* @see getAttributes() * @see getAttributes()
* @param string $name The attribute name. * @param string $name The attribute name.
* @param mixed $default Default value to return if the attribute does not exist. * @param mixed $default Default value to return if the attribute does not exist.
* @return mixed * @return mixed
*/ */
public function getAttribute($name, $default = null) 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. * updated attribute.
* *
* @see getAttributes() * @see getAttributes()
* @param string $name The attribute name. * @param string $name The attribute name.
* @param mixed $value The value of the attribute. * @param mixed $value The value of the attribute.
* @return static * @return static
*/ */
public function withAttribute($name, $value) 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) public function withoutAttribute($name)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
} }

View File

@ -1,10 +1,15 @@
<?php <?php
namespace GraphQL\Tests\Server\Psr7;
declare(strict_types=1);
namespace GraphQL\Tests\Server\Psr7;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
/**
* phpcs:ignoreFile -- this is not a core file
*/
class PsrResponseStub implements ResponseInterface class PsrResponseStub implements ResponseInterface
{ {
public $headers = []; public $headers = [];
@ -22,7 +27,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getProtocolVersion() 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) 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() 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) 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) 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) 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 * immutability of the message, and MUST return an instance that has the
* new and/or updated header and value. * 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). * @param string|string[] $value Header value(s).
* @return static * @return static
* @throws \InvalidArgumentException for invalid header names or values. * @throws \InvalidArgumentException for invalid header names or values.
*/ */
public function withHeader($name, $value) public function withHeader($name, $value)
{ {
$tmp = clone $this; $tmp = clone $this;
$tmp->headers[$name][] = $value; $tmp->headers[$name][] = $value;
return $tmp; return $tmp;
} }
@ -162,14 +168,14 @@ class PsrResponseStub implements ResponseInterface
* immutability of the message, and MUST return an instance that has the * immutability of the message, and MUST return an instance that has the
* new header and/or value. * 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). * @param string|string[] $value Header value(s).
* @return static * @return static
* @throws \InvalidArgumentException for invalid header names or values. * @throws \InvalidArgumentException for invalid header names or values.
*/ */
public function withAddedHeader($name, $value) 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) 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() 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) public function withBody(StreamInterface $body)
{ {
$tmp = clone $this; $tmp = clone $this;
$tmp->body = $body; $tmp->body = $body;
return $tmp; return $tmp;
} }
@ -229,7 +236,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getStatusCode() 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://tools.ietf.org/html/rfc7231#section-6
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml * @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 * @param string $reasonPhrase The reason phrase to use with the
* provided status code; if none is provided, implementations MAY * provided status code; if none is provided, implementations MAY
* use the defaults as suggested in the HTTP specification. * use the defaults as suggested in the HTTP specification.
@ -254,8 +261,9 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function withStatus($code, $reasonPhrase = '') public function withStatus($code, $reasonPhrase = '')
{ {
$tmp = clone $this; $tmp = clone $this;
$tmp->statusCode = $code; $tmp->statusCode = $code;
return $tmp; return $tmp;
} }
@ -274,6 +282,6 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getReasonPhrase() public function getReasonPhrase()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
} }

View File

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

View File

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

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Error\Debug; use GraphQL\Error\Debug;
@ -14,17 +17,17 @@ use GraphQL\Server\ServerConfig;
use GraphQL\Validator\DocumentValidator; use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\CustomValidationRule; use GraphQL\Validator\Rules\CustomValidationRule;
use GraphQL\Validator\ValidationContext; use GraphQL\Validator\ValidationContext;
use function count;
use function sprintf;
class QueryExecutionTest extends ServerTestCase class QueryExecutionTest extends ServerTestCase
{ {
/** /** @var ServerConfig */
* @var ServerConfig
*/
private $config; private $config;
public function setUp() public function setUp()
{ {
$schema = $this->buildSchema(); $schema = $this->buildSchema();
$this->config = ServerConfig::create() $this->config = ServerConfig::create()
->setSchema($schema); ->setSchema($schema);
} }
@ -34,20 +37,36 @@ class QueryExecutionTest extends ServerTestCase
$query = '{f1}'; $query = '{f1}';
$expected = [ $expected = [
'data' => [ 'data' => ['f1' => 'f1'],
'f1' => 'f1'
]
]; ];
$this->assertQueryResultEquals($expected, $query); $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 public function testReturnsSyntaxErrors() : void
{ {
$query = '{f1'; $query = '{f1';
$result = $this->executeQuery($query); $result = $this->executeQuery($query);
$this->assertSame(null, $result->data); $this->assertNull($result->data);
$this->assertCount(1, $result->errors); $this->assertCount(1, $result->errors);
$this->assertContains( $this->assertContains(
'Syntax Error: Expected Name, found <EOF>', 'Syntax Error: Expected Name, found <EOF>',
@ -70,11 +89,12 @@ class QueryExecutionTest extends ServerTestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'fieldWithSafeException' => null, 'fieldWithSafeException' => null,
'f1' => 'f1' 'f1' => 'f1',
], ],
'errors' => [ 'errors' => [
[ [
'message' => 'This is the exception we want', 'message' => 'This is the exception we want',
'path' => ['fieldWithSafeException'], 'path' => ['fieldWithSafeException'],
'trace' => [] 'trace' => []
] ]
@ -100,7 +120,7 @@ class QueryExecutionTest extends ServerTestCase
public function testPassesRootValueAndContext() : void public function testPassesRootValueAndContext() : void
{ {
$rootValue = 'myRootValue'; $rootValue = 'myRootValue';
$context = new \stdClass(); $context = new \stdClass();
$this->config $this->config
->setContext($context) ->setContext($context)
@ -112,7 +132,7 @@ class QueryExecutionTest extends ServerTestCase
} }
'; ';
$this->assertTrue(!isset($context->testedRootValue)); $this->assertTrue(! isset($context->testedRootValue));
$this->executeQuery($query); $this->executeQuery($query);
$this->assertSame($rootValue, $context->testedRootValue); $this->assertSame($rootValue, $context->testedRootValue);
} }
@ -120,30 +140,30 @@ class QueryExecutionTest extends ServerTestCase
public function testPassesVariables() : void public function testPassesVariables() : void
{ {
$variables = ['a' => 'a', 'b' => 'b']; $variables = ['a' => 'a', 'b' => 'b'];
$query = ' $query = '
query ($a: String!, $b: String!) { query ($a: String!, $b: String!) {
a: fieldWithArg(arg: $a) a: fieldWithArg(arg: $a)
b: fieldWithArg(arg: $b) b: fieldWithArg(arg: $b)
} }
'; ';
$expected = [ $expected = [
'data' => [ 'data' => [
'a' => 'a', 'a' => 'a',
'b' => 'b' 'b' => 'b',
] ],
]; ];
$this->assertQueryResultEquals($expected, $query, $variables); $this->assertQueryResultEquals($expected, $query, $variables);
} }
public function testPassesCustomValidationRules() : void public function testPassesCustomValidationRules() : void
{ {
$query = ' $query = '
{nonExistentField} {nonExistentField}
'; ';
$expected = [ $expected = [
'errors' => [ 'errors' => [
['message' => 'Cannot query field "nonExistentField" on type "Query".'] ['message' => 'Cannot query field "nonExistentField" on type "Query".'],
] ],
]; ];
$this->assertQueryResultEquals($expected, $query); $this->assertQueryResultEquals($expected, $query);
@ -151,15 +171,16 @@ class QueryExecutionTest extends ServerTestCase
$called = false; $called = false;
$rules = [ $rules = [
new CustomValidationRule('SomeRule', function() use (&$called) { new CustomValidationRule('SomeRule', function () use (&$called) {
$called = true; $called = true;
return []; return [];
}) }),
]; ];
$this->config->setValidationRules($rules); $this->config->setValidationRules($rules);
$expected = [ $expected = [
'data' => [] 'data' => [],
]; ];
$this->assertQueryResultEquals($expected, $query); $this->assertQueryResultEquals($expected, $query);
$this->assertTrue($called); $this->assertTrue($called);
@ -170,11 +191,12 @@ class QueryExecutionTest extends ServerTestCase
$called = false; $called = false;
$params = $doc = $operationType = null; $params = $doc = $operationType = null;
$this->config->setValidationRules(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) { $this->config->setValidationRules(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true; $called = true;
$params = $p; $params = $p;
$doc = $d; $doc = $d;
$operationType = $o; $operationType = $o;
return []; return [];
}); });
@ -188,23 +210,25 @@ class QueryExecutionTest extends ServerTestCase
public function testAllowsDifferentValidationRulesDependingOnOperation() : void public function testAllowsDifferentValidationRulesDependingOnOperation() : void
{ {
$q1 = '{f1}'; $q1 = '{f1}';
$q2 = '{invalid}'; $q2 = '{invalid}';
$called1 = false; $called1 = false;
$called2 = 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) { if ($params->query === $q1) {
$called1 = true; $called1 = true;
return DocumentValidator::allRules(); 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']]; $expected = ['data' => ['f1' => 'f1']];
@ -212,8 +236,8 @@ class QueryExecutionTest extends ServerTestCase
$this->assertTrue($called1); $this->assertTrue($called1);
$this->assertFalse($called2); $this->assertFalse($called2);
$called1 = false; $called1 = false;
$called2 = false; $called2 = false;
$expected = ['errors' => [['message' => 'This is the error we are looking for!']]]; $expected = ['errors' => [['message' => 'This is the error we are looking for!']]];
$this->assertQueryResultEquals($expected, $q2); $this->assertQueryResultEquals($expected, $q2);
$this->assertFalse($called1); $this->assertFalse($called1);
@ -223,7 +247,7 @@ class QueryExecutionTest extends ServerTestCase
public function testAllowsSkippingValidation() : void public function testAllowsSkippingValidation() : void
{ {
$this->config->setValidationRules([]); $this->config->setValidationRules([]);
$query = '{nonExistentField}'; $query = '{nonExistentField}';
$expected = ['data' => []]; $expected = ['data' => []];
$this->assertQueryResultEquals($expected, $query); $this->assertQueryResultEquals($expected, $query);
} }
@ -235,23 +259,29 @@ class QueryExecutionTest extends ServerTestCase
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'Persisted queries are not supported by this server', 'message' => 'Persisted queries are not supported by this server',
'category' => 'request' 'category' => 'request',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $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 public function testBatchedQueriesAreDisabledByDefault() : void
{ {
$batch = [ $batch = [
[ ['query' => '{invalid}'],
'query' => '{invalid}' ['query' => '{f1,fieldWithSafeException}'],
],
[
'query' => '{f1,fieldWithSafeException}'
]
]; ];
$result = $this->executeBatchedQuery($batch); $result = $this->executeBatchedQuery($batch);
@ -260,18 +290,18 @@ class QueryExecutionTest extends ServerTestCase
[ [
'errors' => [ 'errors' => [
[ [
'message' => 'Batched queries are not supported by this server', 'message' => 'Batched queries are not supported by this server',
'category' => 'request' 'category' => 'request',
] ],
] ],
], ],
[ [
'errors' => [ 'errors' => [
[ [
'message' => 'Batched queries are not supported by this server', 'message' => 'Batched queries are not supported by this server',
'category' => 'request' 'category' => 'request',
] ],
] ],
], ],
]; ];
@ -279,6 +309,31 @@ class QueryExecutionTest extends ServerTestCase
$this->assertEquals($expected[1], $result[1]->toArray()); $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 public function testMutationsAreNotAllowedInReadonlyMode() : void
{ {
$mutation = 'mutation { a }'; $mutation = 'mutation { a }';
@ -286,10 +341,10 @@ class QueryExecutionTest extends ServerTestCase
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'GET supports only query operation', 'message' => 'GET supports only query operation',
'category' => 'request' 'category' => 'request',
] ],
] ],
]; ];
$result = $this->executeQuery($mutation, null, true); $result = $this->executeQuery($mutation, null, true);
@ -299,9 +354,10 @@ class QueryExecutionTest extends ServerTestCase
public function testAllowsPersistentQueries() : void public function testAllowsPersistentQueries() : void
{ {
$called = false; $called = false;
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) { $this->config->setPersistentQueryLoader(function ($queryId, OperationParams $params) use (&$called) {
$called = true; $called = true;
$this->assertEquals('some-id', $queryId); $this->assertEquals('some-id', $queryId);
return '{f1}'; return '{f1}';
}); });
@ -309,17 +365,16 @@ class QueryExecutionTest extends ServerTestCase
$this->assertTrue($called); $this->assertTrue($called);
$expected = [ $expected = [
'data' => [ 'data' => ['f1' => 'f1'],
'f1' => 'f1'
]
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// Make sure it allows returning document node: // Make sure it allows returning document node:
$called = false; $called = false;
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) { $this->config->setPersistentQueryLoader(function ($queryId, OperationParams $params) use (&$called) {
$called = true; $called = true;
$this->assertEquals('some-id', $queryId); $this->assertEquals('some-id', $queryId);
return Parser::parse('{f1}'); return Parser::parse('{f1}');
}); });
$result = $this->executePersistedQuery('some-id'); $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 ' . 'Persistent query loader must return query string or instance of GraphQL\Language\AST\DocumentNode ' .
'but got: {"err":"err"}' 'but got: {"err":"err"}'
); );
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) { $this->config->setPersistentQueryLoader(function () {
return ['err' => 'err']; return ['err' => 'err'];
}); });
$this->executePersistedQuery('some-id'); $this->executePersistedQuery('some-id');
@ -342,56 +397,55 @@ class QueryExecutionTest extends ServerTestCase
public function testPersistedQueriesAreStillValidatedByDefault() : void public function testPersistedQueriesAreStillValidatedByDefault() : void
{ {
$this->config->setPersistentQueryLoader(function() { $this->config->setPersistentQueryLoader(function () {
return '{invalid}'; return '{invalid}';
}); });
$result = $this->executePersistedQuery('some-id'); $result = $this->executePersistedQuery('some-id');
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'Cannot query field "invalid" on type "Query".', 'message' => 'Cannot query field "invalid" on type "Query".',
'locations' => [ ['line' => 1, 'column' => 2] ], 'locations' => [['line' => 1, 'column' => 2]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
public function testAllowSkippingValidationForPersistedQueries() : void public function testAllowSkippingValidationForPersistedQueries() : void
{ {
$this->config $this->config
->setPersistentQueryLoader(function($queryId) { ->setPersistentQueryLoader(function ($queryId) {
if ($queryId === 'some-id') { if ($queryId === 'some-id') {
return '{invalid}'; return '{invalid}';
} else {
return '{invalid2}';
} }
return '{invalid2}';
}) })
->setValidationRules(function(OperationParams $params) { ->setValidationRules(function (OperationParams $params) {
if ($params->queryId === 'some-id') { if ($params->queryId === 'some-id') {
return []; return [];
} else {
return DocumentValidator::allRules();
} }
return DocumentValidator::allRules();
}); });
$result = $this->executePersistedQuery('some-id'); $result = $this->executePersistedQuery('some-id');
$expected = [ $expected = [
'data' => [] 'data' => [],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
$result = $this->executePersistedQuery('some-other-id'); $result = $this->executePersistedQuery('some-other-id');
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'message' => 'Cannot query field "invalid2" on type "Query".', 'message' => 'Cannot query field "invalid2" on type "Query".',
'locations' => [ ['line' => 1, 'column' => 2] ], 'locations' => [['line' => 1, 'column' => 2]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -400,7 +454,7 @@ class QueryExecutionTest extends ServerTestCase
{ {
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Expecting validation rules to be array or callable returning array, but got: instance of stdClass'); $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(); return new \stdClass();
}); });
$this->executeQuery('{f1}'); $this->executeQuery('{f1}');
@ -411,28 +465,24 @@ class QueryExecutionTest extends ServerTestCase
$this->config->setQueryBatching(true); $this->config->setQueryBatching(true);
$batch = [ $batch = [
['query' => '{invalid}'],
['query' => '{f1,fieldWithSafeException}'],
[ [
'query' => '{invalid}' 'query' => '
],
[
'query' => '{f1,fieldWithSafeException}'
],
[
'query' => '
query ($a: String!, $b: String!) { query ($a: String!, $b: String!) {
a: fieldWithArg(arg: $a) a: fieldWithArg(arg: $a)
b: fieldWithArg(arg: $b) b: fieldWithArg(arg: $b)
} }
', ',
'variables' => ['a' => 'a', 'b' => 'b'], 'variables' => ['a' => 'a', 'b' => 'b'],
] ],
]; ];
$result = $this->executeBatchedQuery($batch); $result = $this->executeBatchedQuery($batch);
$expected = [ $expected = [
[ [
'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']] 'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']],
], ],
[ [
'data' => [ 'data' => [
@ -440,15 +490,15 @@ class QueryExecutionTest extends ServerTestCase
'fieldWithSafeException' => null 'fieldWithSafeException' => null
], ],
'errors' => [ 'errors' => [
['message' => 'This is the exception we want'] ['message' => 'This is the exception we want'],
] ],
], ],
[ [
'data' => [ 'data' => [
'a' => 'a', 'a' => 'a',
'b' => 'b' 'b' => 'b',
] ],
] ],
]; ];
$this->assertArraySubset($expected[0], $result[0]->toArray()); $this->assertArraySubset($expected[0], $result[0]->toArray());
@ -459,15 +509,9 @@ class QueryExecutionTest extends ServerTestCase
public function testDeferredsAreSharedAmongAllBatchedQueries() : void public function testDeferredsAreSharedAmongAllBatchedQueries() : void
{ {
$batch = [ $batch = [
[ ['query' => '{dfd(num: 1)}'],
'query' => '{dfd(num: 1)}' ['query' => '{dfd(num: 2)}'],
], ['query' => '{dfd(num: 3)}'],
[
'query' => '{dfd(num: 2)}'
],
[
'query' => '{dfd(num: 3)}',
]
]; ];
$calls = []; $calls = [];
@ -476,13 +520,14 @@ class QueryExecutionTest extends ServerTestCase
->setQueryBatching(true) ->setQueryBatching(true)
->setRootValue('1') ->setRootValue('1')
->setContext([ ->setContext([
'buffer' => function($num) use (&$calls) { 'buffer' => function ($num) use (&$calls) {
$calls[] = "buffer: $num"; $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); $result = $this->executeBatchedQuery($batch);
@ -499,19 +544,13 @@ class QueryExecutionTest extends ServerTestCase
$expected = [ $expected = [
[ [
'data' => [ 'data' => ['dfd' => 'loaded: 1'],
'dfd' => 'loaded: 1'
]
], ],
[ [
'data' => [ 'data' => ['dfd' => 'loaded: 2'],
'dfd' => 'loaded: 2'
]
], ],
[ [
'data' => [ 'data' => ['dfd' => 'loaded: 3'],
'dfd' => 'loaded: 3'
]
], ],
]; ];
@ -522,7 +561,7 @@ class QueryExecutionTest extends ServerTestCase
public function testValidatesParamsBeforeExecution() : void public function testValidatesParamsBeforeExecution() : void
{ {
$op = OperationParams::create(['queryBad' => '{f1}']); $op = OperationParams::create(['queryBad' => '{f1}']);
$helper = new Helper(); $helper = new Helper();
$result = $helper->executeOperation($this->config, $op); $result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result); $this->assertInstanceOf(ExecutionResult::class, $result);
@ -546,10 +585,10 @@ class QueryExecutionTest extends ServerTestCase
$called = false; $called = false;
$params = $doc = $operationType = null; $params = $doc = $operationType = null;
$this->config->setContext(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) { $this->config->setContext(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true; $called = true;
$params = $p; $params = $p;
$doc = $d; $doc = $d;
$operationType = $o; $operationType = $o;
}); });
@ -566,10 +605,10 @@ class QueryExecutionTest extends ServerTestCase
$called = false; $called = false;
$params = $doc = $operationType = null; $params = $doc = $operationType = null;
$this->config->setRootValue(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) { $this->config->setRootValue(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true; $called = true;
$params = $p; $params = $p;
$doc = $d; $doc = $d;
$operationType = $o; $operationType = $o;
}); });
@ -584,20 +623,21 @@ class QueryExecutionTest extends ServerTestCase
public function testAppliesErrorFormatter() : void public function testAppliesErrorFormatter() : void
{ {
$called = false; $called = false;
$error = null; $error = null;
$this->config->setErrorFormatter(function($e) use (&$called, &$error) { $this->config->setErrorFormatter(function ($e) use (&$called, &$error) {
$called = true; $called = true;
$error = $e; $error = $e;
return ['test' => 'formatted']; return ['test' => 'formatted'];
}); });
$result = $this->executeQuery('{fieldWithSafeException}'); $result = $this->executeQuery('{fieldWithSafeException}');
$this->assertFalse($called); $this->assertFalse($called);
$formatted = $result->toArray(); $formatted = $result->toArray();
$expected = [ $expected = [
'errors' => [ 'errors' => [
['test' => 'formatted'] ['test' => 'formatted'],
] ],
]; ];
$this->assertTrue($called); $this->assertTrue($called);
$this->assertArraySubset($expected, $formatted); $this->assertArraySubset($expected, $formatted);
@ -605,28 +645,29 @@ class QueryExecutionTest extends ServerTestCase
// Assert debugging still works even with custom formatter // Assert debugging still works even with custom formatter
$formatted = $result->toArray(Debug::INCLUDE_TRACE); $formatted = $result->toArray(Debug::INCLUDE_TRACE);
$expected = [ $expected = [
'errors' => [ 'errors' => [
[ [
'test' => 'formatted', 'test' => 'formatted',
'trace' => [] 'trace' => [],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $formatted); $this->assertArraySubset($expected, $formatted);
} }
public function testAppliesErrorsHandler() : void public function testAppliesErrorsHandler() : void
{ {
$called = false; $called = false;
$errors = null; $errors = null;
$formatter = null; $formatter = null;
$this->config->setErrorsHandler(function($e, $f) use (&$called, &$errors, &$formatter) { $this->config->setErrorsHandler(function ($e, $f) use (&$called, &$errors, &$formatter) {
$called = true; $called = true;
$errors = $e; $errors = $e;
$formatter = $f; $formatter = $f;
return [ return [
['test' => 'handled'] ['test' => 'handled'],
]; ];
}); });
@ -634,10 +675,10 @@ class QueryExecutionTest extends ServerTestCase
$this->assertFalse($called); $this->assertFalse($called);
$formatted = $result->toArray(); $formatted = $result->toArray();
$expected = [ $expected = [
'errors' => [ 'errors' => [
['test' => 'handled'] ['test' => 'handled'],
] ],
]; ];
$this->assertTrue($called); $this->assertTrue($called);
$this->assertArraySubset($expected, $formatted); $this->assertArraySubset($expected, $formatted);
@ -646,46 +687,4 @@ class QueryExecutionTest extends ServerTestCase
$this->assertInternalType('callable', $formatter); $this->assertInternalType('callable', $formatter);
$this->assertArraySubset($expected, $formatted); $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Server\Helper; use GraphQL\Server\Helper;
use GraphQL\Server\OperationParams; use GraphQL\Server\OperationParams;
@ -9,15 +11,17 @@ use GraphQL\Server\RequestError;
use GraphQL\Tests\Server\Psr7\PsrRequestStub; use GraphQL\Tests\Server\Psr7\PsrRequestStub;
use GraphQL\Tests\Server\Psr7\PsrStreamStub; use GraphQL\Tests\Server\Psr7\PsrStreamStub;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function json_decode;
use function json_encode;
class RequestParsingTest extends TestCase class RequestParsingTest extends TestCase
{ {
public function testParsesGraphqlRequest() : void public function testParsesGraphqlRequest() : void
{ {
$query = '{my query}'; $query = '{my query}';
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/graphql', $query), 'raw' => $this->parseRawRequest('application/graphql', $query),
'psr' => $this->parsePsrRequest('application/graphql', $query) 'psr' => $this->parsePsrRequest('application/graphql', $query),
]; ];
foreach ($parsed as $source => $parsedBody) { 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 public function testParsesUrlencodedRequest() : void
{ {
$query = '{my query}'; $query = '{my query}';
$variables = ['test' => 1, 'test2' => 2]; $variables = ['test' => 1, 'test2' => 2];
$operation = 'op'; $operation = 'op';
$post = [ $post = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawFormUrlencodedRequest($post), 'raw' => $this->parseRawFormUrlencodedRequest($post),
'psr' => $this->parsePsrFormUrlEncodedRequest($post) 'psr' => $this->parsePsrFormUrlEncodedRequest($post),
]; ];
foreach ($parsed as $method => $parsedBody) { 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 public function testParsesGetRequest() : void
{ {
$query = '{my query}'; $query = '{my query}';
$variables = ['test' => 1, 'test2' => 2]; $variables = ['test' => 1, 'test2' => 2];
$operation = 'op'; $operation = 'op';
$get = [ $get = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawGetRequest($get), 'raw' => $this->parseRawGetRequest($get),
'psr' => $this->parsePsrGetRequest($get) 'psr' => $this->parsePsrGetRequest($get),
]; ];
foreach ($parsed as $method => $parsedBody) { 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 public function testParsesMultipartFormdataRequest() : void
{ {
$query = '{my query}'; $query = '{my query}';
$variables = ['test' => 1, 'test2' => 2]; $variables = ['test' => 1, 'test2' => 2];
$operation = 'op'; $operation = 'op';
$post = [ $post = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawMultipartFormdataRequest($post), 'raw' => $this->parseRawMultipartFormdataRequest($post),
'psr' => $this->parsePsrMultipartFormdataRequest($post) 'psr' => $this->parsePsrMultipartFormdataRequest($post),
]; ];
foreach ($parsed as $method => $parsedBody) { 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 public function testParsesJSONRequest() : void
{ {
$query = '{my query}'; $query = '{my query}';
$variables = ['test' => 1, 'test2' => 2]; $variables = ['test' => 1, 'test2' => 2];
$operation = 'op'; $operation = 'op';
$body = [ $body = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)), '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) { foreach ($parsed as $method => $parsedBody) {
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
@ -115,18 +287,18 @@ class RequestParsingTest extends TestCase
public function testParsesVariablesAsJSON() : void public function testParsesVariablesAsJSON() : void
{ {
$query = '{my query}'; $query = '{my query}';
$variables = ['test' => 1, 'test2' => 2]; $variables = ['test' => 1, 'test2' => 2];
$operation = 'op'; $operation = 'op';
$body = [ $body = [
'query' => $query, 'query' => $query,
'variables' => json_encode($variables), 'variables' => json_encode($variables),
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)), '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) { foreach ($parsed as $method => $parsedBody) {
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
@ -136,14 +308,14 @@ class RequestParsingTest extends TestCase
public function testIgnoresInvalidVariablesJson() : void public function testIgnoresInvalidVariablesJson() : void
{ {
$query = '{my query}'; $query = '{my query}';
$variables = '"some invalid json'; $variables = '"some invalid json';
$operation = 'op'; $operation = 'op';
$body = [ $body = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'raw' => $this->parseRawRequest('application/json', json_encode($body)),
@ -157,27 +329,41 @@ class RequestParsingTest extends TestCase
public function testParsesBatchJSONRequest() : void public function testParsesBatchJSONRequest() : void
{ {
$body = [ $body = [
[ [
'query' => '{my query}', 'query' => '{my query}',
'variables' => ['test' => 1, 'test2' => 2], 'variables' => ['test' => 1, 'test2' => 2],
'operationName' => 'op' 'operationName' => 'op',
], ],
[ [
'queryId' => 'my-query-id', 'queryId' => 'my-query-id',
'variables' => ['test' => 1, 'test2' => 2], 'variables' => ['test' => 1, 'test2' => 2],
'operationName' => 'op2' 'operationName' => 'op2',
], ],
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)), '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) { foreach ($parsed as $method => $parsedBody) {
$this->assertInternalType('array', $parsedBody, $method); $this->assertInternalType('array', $parsedBody, $method);
$this->assertCount(2, $parsedBody, $method); $this->assertCount(2, $parsedBody, $method);
$this->assertValidOperationParams($parsedBody[0], $body[0]['query'], null, $body[0]['variables'], $body[0]['operationName'], $method); $this->assertValidOperationParams(
$this->assertValidOperationParams($parsedBody[1], null, $body[1]['queryId'], $body[1]['variables'], $body[1]['operationName'], $method); $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->expectException(RequestError::class);
$this->expectExceptionMessage('Could not parse JSON: Syntax error'); $this->expectExceptionMessage('Could not parse JSON: Syntax error');
$this->parseRawRequest('application/json', $body); $this->parseRawRequest('application/json', $body);
} }
public function testFailsParsingInvalidRawJsonRequestPsr() : void public function testFailsParsingInvalidRawJsonRequestPsr() : void
{ {
@ -196,7 +382,7 @@ class RequestParsingTest extends TestCase
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "application/json" requests but got null'); $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 public function testFailsParsingNonPreParsedPsrRequest() : void
@ -222,8 +408,8 @@ class RequestParsingTest extends TestCase
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"'); $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 public function testFailsParsingNonArrayOrObjectJsonRequestPsr() : void
{ {
@ -231,13 +417,13 @@ class RequestParsingTest extends TestCase
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"'); $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 public function testFailsParsingInvalidContentTypeRaw() : void
{ {
$contentType = 'not-supported-content-type'; $contentType = 'not-supported-content-type';
$body = 'test'; $body = 'test';
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"'); $this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
@ -247,194 +433,38 @@ class RequestParsingTest extends TestCase
public function testFailsParsingInvalidContentTypePsr() : void public function testFailsParsingInvalidContentTypePsr() : void
{ {
$contentType = 'not-supported-content-type'; $contentType = 'not-supported-content-type';
$body = 'test'; $body = 'test';
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"'); $this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
$this->parseRawRequest($contentType, $body); $this->parseRawRequest($contentType, $body);
} }
public function testFailsWithMissingContentTypeRaw() : void public function testFailsWithMissingContentTypeRaw() : void
{ {
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('Missing "Content-Type" header'); $this->expectExceptionMessage('Missing "Content-Type" header');
$this->parseRawRequest(null, 'test'); $this->parseRawRequest(null, 'test');
} }
public function testFailsWithMissingContentTypePsr() : void public function testFailsWithMissingContentTypePsr() : void
{ {
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('Missing "Content-Type" header'); $this->expectExceptionMessage('Missing "Content-Type" header');
$this->parsePsrRequest(null, 'test'); $this->parsePsrRequest(null, 'test');
} }
public function testFailsOnMethodsOtherThanPostOrGetRaw() : void public function testFailsOnMethodsOtherThanPostOrGetRaw() : void
{ {
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('HTTP Method "PUT" is not supported'); $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 public function testFailsOnMethodsOtherThanPostOrGetPsr() : void
{ {
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('HTTP Method "PUT" is not supported'); $this->expectExceptionMessage('HTTP Method "PUT" is not supported');
$this->parsePsrRequest('application/json', json_encode([]), "PUT"); $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);
} }
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Server\Helper; use GraphQL\Server\Helper;
@ -9,28 +12,35 @@ class RequestValidationTest extends TestCase
{ {
public function testSimpleRequestShouldValidate() : void public function testSimpleRequestShouldValidate() : void
{ {
$query = '{my q}'; $query = '{my q}';
$variables = ['a' => 'b', 'c' => 'd']; $variables = ['a' => 'b', 'c' => 'd'];
$operation = 'op'; $operation = 'op';
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation, 'operationName' => $operation,
]); ]);
$this->assertValid($parsedBody); $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 public function testRequestWithQueryIdShouldValidate() : void
{ {
$queryId = 'some-query-id'; $queryId = 'some-query-id';
$variables = ['a' => 'b', 'c' => 'd']; $variables = ['a' => 'b', 'c' => 'd'];
$operation = 'op'; $operation = 'op';
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'queryId' => $queryId, 'queryId' => $queryId,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation, 'operationName' => $operation,
]); ]);
@ -40,7 +50,7 @@ class RequestValidationTest extends TestCase
public function testRequiresQueryOrQueryId() : void public function testRequiresQueryOrQueryId() : void
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'variables' => ['foo' => 'bar'], 'variables' => ['foo' => 'bar'],
'operationName' => 'op', '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 public function testFailsWhenBothQueryAndQueryIdArePresent() : void
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => '{my query}', 'query' => '{my query}',
'queryId' => 'my-query-id', 'queryId' => 'my-query-id',
]); ]);
@ -66,7 +87,7 @@ class RequestValidationTest extends TestCase
public function testFailsWhenQueryParameterIsNotString() : void public function testFailsWhenQueryParameterIsNotString() : void
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => ['t' => '{my query}'] 'query' => ['t' => '{my query}'],
]); ]);
$this->assertInputError( $this->assertInputError(
@ -78,7 +99,7 @@ class RequestValidationTest extends TestCase
public function testFailsWhenQueryIdParameterIsNotString() : void public function testFailsWhenQueryIdParameterIsNotString() : void
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'queryId' => ['t' => '{my query}'] 'queryId' => ['t' => '{my query}'],
]); ]);
$this->assertInputError( $this->assertInputError(
@ -90,8 +111,8 @@ class RequestValidationTest extends TestCase
public function testFailsWhenOperationParameterIsNotString() : void public function testFailsWhenOperationParameterIsNotString() : void
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => '{my query}', 'query' => '{my query}',
'operationName' => [] 'operationName' => [],
]); ]);
$this->assertInputError( $this->assertInputError(
@ -105,17 +126,17 @@ class RequestValidationTest extends TestCase
*/ */
public function testIgnoresNullAndEmptyStringVariables() : void public function testIgnoresNullAndEmptyStringVariables() : void
{ {
$query = '{my q}'; $query = '{my q}';
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => $query, 'query' => $query,
'variables' => null 'variables' => null,
]); ]);
$this->assertValid($parsedBody); $this->assertValid($parsedBody);
$variables = ""; $variables = '';
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => $query, 'query' => $query,
'variables' => $variables 'variables' => $variables,
]); ]);
$this->assertValid($parsedBody); $this->assertValid($parsedBody);
} }
@ -123,8 +144,8 @@ class RequestValidationTest extends TestCase
public function testFailsWhenVariablesParameterIsNotObject() : void public function testFailsWhenVariablesParameterIsNotObject() : void
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => '{my query}', 'query' => '{my query}',
'variables' => 0 'variables' => 0,
]); ]);
$this->assertInputError( $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' '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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter; use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Type\Schema;
use GraphQL\Server\ServerConfig; use GraphQL\Server\ServerConfig;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ServerConfigTest extends TestCase class ServerConfigTest extends TestCase
@ -14,17 +17,17 @@ class ServerConfigTest extends TestCase
public function testDefaults() : void public function testDefaults() : void
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$this->assertEquals(null, $config->getSchema()); $this->assertNull($config->getSchema());
$this->assertEquals(null, $config->getContext()); $this->assertNull($config->getContext());
$this->assertEquals(null, $config->getRootValue()); $this->assertNull($config->getRootValue());
$this->assertEquals(null, $config->getErrorFormatter()); $this->assertNull($config->getErrorFormatter());
$this->assertEquals(null, $config->getErrorsHandler()); $this->assertNull($config->getErrorsHandler());
$this->assertEquals(null, $config->getPromiseAdapter()); $this->assertNull($config->getPromiseAdapter());
$this->assertEquals(null, $config->getValidationRules()); $this->assertNull($config->getValidationRules());
$this->assertEquals(null, $config->getFieldResolver()); $this->assertNull($config->getFieldResolver());
$this->assertEquals(null, $config->getPersistentQueryLoader()); $this->assertNull($config->getPersistentQueryLoader());
$this->assertEquals(false, $config->getDebug()); $this->assertFalse($config->getDebug());
$this->assertEquals(false, $config->getQueryBatching()); $this->assertFalse($config->getQueryBatching());
} }
public function testAllowsSettingSchema() : void public function testAllowsSettingSchema() : void
@ -70,7 +73,8 @@ class ServerConfigTest extends TestCase
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$formatter = function() {}; $formatter = function () {
};
$config->setErrorFormatter($formatter); $config->setErrorFormatter($formatter);
$this->assertSame($formatter, $config->getErrorFormatter()); $this->assertSame($formatter, $config->getErrorFormatter());
@ -83,7 +87,8 @@ class ServerConfigTest extends TestCase
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$handler = function() {}; $handler = function () {
};
$config->setErrorsHandler($handler); $config->setErrorsHandler($handler);
$this->assertSame($handler, $config->getErrorsHandler()); $this->assertSame($handler, $config->getErrorsHandler());
@ -113,11 +118,17 @@ class ServerConfigTest extends TestCase
$config->setValidationRules($rules); $config->setValidationRules($rules);
$this->assertSame($rules, $config->getValidationRules()); $this->assertSame($rules, $config->getValidationRules());
$rules = [function() {}]; $rules = [function () {
},
];
$config->setValidationRules($rules); $config->setValidationRules($rules);
$this->assertSame($rules, $config->getValidationRules()); $this->assertSame($rules, $config->getValidationRules());
$rules = function() {return [function() {}];}; $rules = function () {
return [function () {
},
];
};
$config->setValidationRules($rules); $config->setValidationRules($rules);
$this->assertSame($rules, $config->getValidationRules()); $this->assertSame($rules, $config->getValidationRules());
} }
@ -126,7 +137,8 @@ class ServerConfigTest extends TestCase
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$resolver = function() {}; $resolver = function () {
};
$config->setFieldResolver($resolver); $config->setFieldResolver($resolver);
$this->assertSame($resolver, $config->getFieldResolver()); $this->assertSame($resolver, $config->getFieldResolver());
@ -139,7 +151,8 @@ class ServerConfigTest extends TestCase
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$loader = function() {}; $loader = function () {
};
$config->setPersistentQueryLoader($loader); $config->setPersistentQueryLoader($loader);
$this->assertSame($loader, $config->getPersistentQueryLoader()); $this->assertSame($loader, $config->getPersistentQueryLoader());
@ -153,27 +166,32 @@ class ServerConfigTest extends TestCase
$config = ServerConfig::create(); $config = ServerConfig::create();
$config->setDebug(true); $config->setDebug(true);
$this->assertSame(true, $config->getDebug()); $this->assertTrue($config->getDebug());
$config->setDebug(false); $config->setDebug(false);
$this->assertSame(false, $config->getDebug()); $this->assertFalse($config->getDebug());
} }
public function testAcceptsArray() : void public function testAcceptsArray() : void
{ {
$arr = [ $arr = [
'schema' => new \GraphQL\Type\Schema([ 'schema' => new Schema([
'query' => new ObjectType(['name' => 't', 'fields' => ['a' => Type::string()]]) 'query' => new ObjectType(['name' => 't', 'fields' => ['a' => Type::string()]]),
]), ]),
'context' => new \stdClass(), 'context' => new \stdClass(),
'rootValue' => new \stdClass(), 'rootValue' => new \stdClass(),
'errorFormatter' => function() {}, 'errorFormatter' => function () {
'promiseAdapter' => new SyncPromiseAdapter(), },
'validationRules' => [function() {}], 'promiseAdapter' => new SyncPromiseAdapter(),
'fieldResolver' => function() {}, 'validationRules' => [function () {
'persistentQueryLoader' => function() {}, },
'debug' => true, ],
'queryBatching' => true, 'fieldResolver' => function () {
},
'persistentQueryLoader' => function () {
},
'debug' => true,
'queryBatching' => true,
]; ];
$config = ServerConfig::create($arr); $config = ServerConfig::create($arr);
@ -186,15 +204,13 @@ class ServerConfigTest extends TestCase
$this->assertSame($arr['validationRules'], $config->getValidationRules()); $this->assertSame($arr['validationRules'], $config->getValidationRules());
$this->assertSame($arr['fieldResolver'], $config->getFieldResolver()); $this->assertSame($arr['fieldResolver'], $config->getFieldResolver());
$this->assertSame($arr['persistentQueryLoader'], $config->getPersistentQueryLoader()); $this->assertSame($arr['persistentQueryLoader'], $config->getPersistentQueryLoader());
$this->assertSame(true, $config->getDebug()); $this->assertTrue($config->getDebug());
$this->assertSame(true, $config->getQueryBatching()); $this->assertTrue($config->getQueryBatching());
} }
public function testThrowsOnInvalidArrayKey() : void public function testThrowsOnInvalidArrayKey() : void
{ {
$arr = [ $arr = ['missingKey' => 'value'];
'missingKey' => 'value'
];
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Unknown server config option "missingKey"'); $this->expectExceptionMessage('Unknown server config option "missingKey"');
@ -204,7 +220,7 @@ class ServerConfigTest extends TestCase
public function testInvalidValidationRules() : void public function testInvalidValidationRules() : void
{ {
$rules = new \stdClass(); $rules = new \stdClass();
$config = ServerConfig::create(); $config = ServerConfig::create();
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);

View File

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

View File

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

View File

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

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests; namespace GraphQL\Tests;
use GraphQL\GraphQL; use GraphQL\GraphQL;
@ -14,7 +17,7 @@ class StarWarsIntrospectionTest extends TestCase
*/ */
public function testAllowsQueryingTheSchemaForTypes() : void public function testAllowsQueryingTheSchemaForTypes() : void
{ {
$query = ' $query = '
query IntrospectionTypeQuery { query IntrospectionTypeQuery {
__schema { __schema {
types { types {
@ -44,18 +47,26 @@ class StarWarsIntrospectionTest extends TestCase
['name' => '__EnumValue'], ['name' => '__EnumValue'],
['name' => '__Directive'], ['name' => '__Directive'],
['name' => '__DirectiveLocation'], ['name' => '__DirectiveLocation'],
] ],
] ],
]; ];
$this->assertValidQuery($query, $expected); $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') * @see it('Allows querying the schema for query type')
*/ */
public function testAllowsQueryingTheSchemaForQueryType() : void public function testAllowsQueryingTheSchemaForQueryType() : void
{ {
$query = ' $query = '
query IntrospectionQueryTypeQuery { query IntrospectionQueryTypeQuery {
__schema { __schema {
queryType { queryType {
@ -66,10 +77,8 @@ class StarWarsIntrospectionTest extends TestCase
'; ';
$expected = [ $expected = [
'__schema' => [ '__schema' => [
'queryType' => [ 'queryType' => ['name' => 'Query'],
'name' => 'Query' ],
],
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -79,7 +88,7 @@ class StarWarsIntrospectionTest extends TestCase
*/ */
public function testAllowsQueryingTheSchemaForASpecificType() : void public function testAllowsQueryingTheSchemaForASpecificType() : void
{ {
$query = ' $query = '
query IntrospectionDroidTypeQuery { query IntrospectionDroidTypeQuery {
__type(name: "Droid") { __type(name: "Droid") {
name name
@ -87,9 +96,7 @@ class StarWarsIntrospectionTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'__type' => [ '__type' => ['name' => 'Droid'],
'name' => 'Droid'
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -99,7 +106,7 @@ class StarWarsIntrospectionTest extends TestCase
*/ */
public function testAllowsQueryingForAnObjectKind() : void public function testAllowsQueryingForAnObjectKind() : void
{ {
$query = ' $query = '
query IntrospectionDroidKindQuery { query IntrospectionDroidKindQuery {
__type(name: "Droid") { __type(name: "Droid") {
name name
@ -110,8 +117,8 @@ class StarWarsIntrospectionTest extends TestCase
$expected = [ $expected = [
'__type' => [ '__type' => [
'name' => 'Droid', 'name' => 'Droid',
'kind' => 'OBJECT' 'kind' => 'OBJECT',
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -121,7 +128,7 @@ class StarWarsIntrospectionTest extends TestCase
*/ */
public function testAllowsQueryingForInterfaceKind() : void public function testAllowsQueryingForInterfaceKind() : void
{ {
$query = ' $query = '
query IntrospectionCharacterKindQuery { query IntrospectionCharacterKindQuery {
__type(name: "Character") { __type(name: "Character") {
name name
@ -132,8 +139,8 @@ class StarWarsIntrospectionTest extends TestCase
$expected = [ $expected = [
'__type' => [ '__type' => [
'name' => 'Character', 'name' => 'Character',
'kind' => 'INTERFACE' 'kind' => 'INTERFACE',
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -143,7 +150,7 @@ class StarWarsIntrospectionTest extends TestCase
*/ */
public function testAllowsQueryingForObjectFields() : void public function testAllowsQueryingForObjectFields() : void
{ {
$query = ' $query = '
query IntrospectionDroidFieldsQuery { query IntrospectionDroidFieldsQuery {
__type(name: "Droid") { __type(name: "Droid") {
name name
@ -159,52 +166,52 @@ class StarWarsIntrospectionTest extends TestCase
'; ';
$expected = [ $expected = [
'__type' => [ '__type' => [
'name' => 'Droid', 'name' => 'Droid',
'fields' => [ 'fields' => [
[ [
'name' => 'id', 'name' => 'id',
'type' => [ 'type' => [
'name' => null, 'name' => null,
'kind' => 'NON_NULL' 'kind' => 'NON_NULL',
] ],
], ],
[ [
'name' => 'name', 'name' => 'name',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR' 'kind' => 'SCALAR',
] ],
], ],
[ [
'name' => 'friends', 'name' => 'friends',
'type' => [ 'type' => [
'name' => null, 'name' => null,
'kind' => 'LIST' 'kind' => 'LIST',
] ],
], ],
[ [
'name' => 'appearsIn', 'name' => 'appearsIn',
'type' => [ 'type' => [
'name' => null, 'name' => null,
'kind' => 'LIST' 'kind' => 'LIST',
] ],
], ],
[ [
'name' => 'secretBackstory', 'name' => 'secretBackstory',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR' 'kind' => 'SCALAR',
] ],
], ],
[ [
'name' => 'primaryFunction', 'name' => 'primaryFunction',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR' 'kind' => 'SCALAR',
] ],
] ],
] ],
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -214,7 +221,7 @@ class StarWarsIntrospectionTest extends TestCase
*/ */
public function testAllowsQueryingTheSchemaForNestedObjectFields() : void public function testAllowsQueryingTheSchemaForNestedObjectFields() : void
{ {
$query = ' $query = '
query IntrospectionDroidNestedFieldsQuery { query IntrospectionDroidNestedFieldsQuery {
__type(name: "Droid") { __type(name: "Droid") {
name name
@ -234,67 +241,67 @@ class StarWarsIntrospectionTest extends TestCase
'; ';
$expected = [ $expected = [
'__type' => [ '__type' => [
'name' => 'Droid', 'name' => 'Droid',
'fields' => [ 'fields' => [
[ [
'name' => 'id', 'name' => 'id',
'type' => [ 'type' => [
'name' => null, 'name' => null,
'kind' => 'NON_NULL', 'kind' => 'NON_NULL',
'ofType' => [ 'ofType' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR' 'kind' => 'SCALAR',
] ],
] ],
], ],
[ [
'name' => 'name', 'name' => 'name',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'ofType' => null 'ofType' => null,
] ],
], ],
[ [
'name' => 'friends', 'name' => 'friends',
'type' => [ 'type' => [
'name' => null, 'name' => null,
'kind' => 'LIST', 'kind' => 'LIST',
'ofType' => [ 'ofType' => [
'name' => 'Character', 'name' => 'Character',
'kind' => 'INTERFACE' 'kind' => 'INTERFACE',
] ],
] ],
], ],
[ [
'name' => 'appearsIn', 'name' => 'appearsIn',
'type' => [ 'type' => [
'name' => null, 'name' => null,
'kind' => 'LIST', 'kind' => 'LIST',
'ofType' => [ 'ofType' => [
'name' => 'Episode', 'name' => 'Episode',
'kind' => 'ENUM' 'kind' => 'ENUM',
] ],
] ],
], ],
[ [
'name' => 'secretBackstory', 'name' => 'secretBackstory',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'ofType' => null 'ofType' => null,
] ],
], ],
[ [
'name' => 'primaryFunction', 'name' => 'primaryFunction',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'ofType' => null 'ofType' => null,
] ],
] ],
] ],
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -329,7 +336,7 @@ class StarWarsIntrospectionTest extends TestCase
} }
'; ';
$expected = array( $expected = [
'__schema' => [ '__schema' => [
'queryType' => [ 'queryType' => [
'fields' => [ 'fields' => [
@ -337,13 +344,13 @@ class StarWarsIntrospectionTest extends TestCase
'name' => 'hero', 'name' => 'hero',
'args' => [ 'args' => [
[ [
'defaultValue' => NULL, 'defaultValue' => null,
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.', 'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
'name' => 'episode', 'name' => 'episode',
'type' => [ 'type' => [
'kind' => 'ENUM', 'kind' => 'ENUM',
'name' => 'Episode', 'name' => 'Episode',
'ofType' => NULL, 'ofType' => null,
], ],
], ],
], ],
@ -352,17 +359,17 @@ class StarWarsIntrospectionTest extends TestCase
'name' => 'human', 'name' => 'human',
'args' => [ 'args' => [
[ [
'name' => 'id', 'name' => 'id',
'description' => 'id of the human', 'description' => 'id of the human',
'type' => [ 'type' => [
'kind' => 'NON_NULL', 'kind' => 'NON_NULL',
'name' => NULL, 'name' => null,
'ofType' => [ 'ofType' => [
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'name' => 'String', 'name' => 'String',
], ],
], ],
'defaultValue' => NULL, 'defaultValue' => null,
], ],
], ],
], ],
@ -370,25 +377,25 @@ class StarWarsIntrospectionTest extends TestCase
'name' => 'droid', 'name' => 'droid',
'args' => [ 'args' => [
[ [
'name' => 'id', 'name' => 'id',
'description' => 'id of the droid', 'description' => 'id of the droid',
'type' => [ 'type' => [
'kind' => 'NON_NULL', 'kind' => 'NON_NULL',
'name' => NULL, 'name' => null,
'ofType' => 'ofType' =>
[ [
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'name' => 'String', 'name' => 'String',
], ],
], ],
'defaultValue' => NULL, 'defaultValue' => null,
], ],
], ],
], ],
], ],
], ],
], ],
); ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -398,7 +405,7 @@ class StarWarsIntrospectionTest extends TestCase
*/ */
public function testAllowsQueryingTheSchemaForDocumentation() : void public function testAllowsQueryingTheSchemaForDocumentation() : void
{ {
$query = ' $query = '
query IntrospectionDroidDescriptionQuery { query IntrospectionDroidDescriptionQuery {
__type(name: "Droid") { __type(name: "Droid") {
name name
@ -408,18 +415,10 @@ class StarWarsIntrospectionTest extends TestCase
'; ';
$expected = [ $expected = [
'__type' => [ '__type' => [
'name' => 'Droid', 'name' => 'Droid',
'description' => 'A mechanical creature in the Star Wars universe.' 'description' => 'A mechanical creature in the Star Wars universe.',
] ],
]; ];
$this->assertValidQuery($query, $expected); $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 <?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class StarWarsQueryTest extends 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') * @see it('Correctly identifies R2-D2 as the hero of the Star Wars Saga')
*/ */
public function testCorrectlyIdentifiesR2D2AsTheHeroOfTheStarWarsSaga() : void public function testCorrectlyIdentifiesR2D2AsTheHeroOfTheStarWarsSaga() : void
{ {
$query = ' $query = '
query HeroNameQuery { query HeroNameQuery {
hero { hero {
name name
@ -23,19 +22,30 @@ class StarWarsQueryTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'hero' => [ 'hero' => ['name' => 'R2-D2'],
'name' => 'R2-D2'
]
]; ];
$this->assertValidQuery($query, $expected); $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') * @see it('Allows us to query for the ID and friends of R2-D2')
*/ */
public function testAllowsUsToQueryForTheIDAndFriendsOfR2D2() : void public function testAllowsUsToQueryForTheIDAndFriendsOfR2D2() : void
{ {
$query = ' $query = '
query HeroNameAndFriendsQuery { query HeroNameAndFriendsQuery {
hero { hero {
id id
@ -48,32 +58,26 @@ class StarWarsQueryTest extends TestCase
'; ';
$expected = [ $expected = [
'hero' => [ 'hero' => [
'id' => '2001', 'id' => '2001',
'name' => 'R2-D2', 'name' => 'R2-D2',
'friends' => [ 'friends' => [
[ ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker', ['name' => 'Han Solo'],
], ['name' => 'Leia Organa'],
[ ],
'name' => 'Han Solo', ],
],
[
'name' => 'Leia Organa',
],
]
]
]; ];
$this->assertValidQuery($query, $expected); $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') * @see it('Allows us to query for the friends of friends of R2-D2')
*/ */
public function testAllowsUsToQueryForTheFriendsOfFriendsOfR2D2() : void public function testAllowsUsToQueryForTheFriendsOfFriendsOfR2D2() : void
{ {
$query = ' $query = '
query NestedQuery { query NestedQuery {
hero { hero {
name name
@ -89,52 +93,50 @@ class StarWarsQueryTest extends TestCase
'; ';
$expected = [ $expected = [
'hero' => [ 'hero' => [
'name' => 'R2-D2', 'name' => 'R2-D2',
'friends' => [ 'friends' => [
[ [
'name' => 'Luke Skywalker', 'name' => 'Luke Skywalker',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI',], 'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'friends' => [ 'friends' => [
['name' => 'Han Solo',], ['name' => 'Han Solo'],
['name' => 'Leia Organa',], ['name' => 'Leia Organa'],
['name' => 'C-3PO',], ['name' => 'C-3PO'],
['name' => 'R2-D2',], ['name' => 'R2-D2'],
], ],
], ],
[ [
'name' => 'Han Solo', 'name' => 'Han Solo',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'], 'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'friends' => [ 'friends' => [
['name' => 'Luke Skywalker',], ['name' => 'Luke Skywalker'],
['name' => 'Leia Organa'], ['name' => 'Leia Organa'],
['name' => 'R2-D2',], ['name' => 'R2-D2'],
] ],
], ],
[ [
'name' => 'Leia Organa', 'name' => 'Leia Organa',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'], 'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'friends' => 'friends' =>
[ [
['name' => 'Luke Skywalker',], ['name' => 'Luke Skywalker'],
['name' => 'Han Solo',], ['name' => 'Han Solo'],
['name' => 'C-3PO',], ['name' => 'C-3PO'],
['name' => 'R2-D2',], ['name' => 'R2-D2'],
], ],
], ],
], ],
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
// Describe: Using IDs and query parameters to refetch objects
/** /**
* @see it('Using IDs and query parameters to refetch objects') * @see it('Using IDs and query parameters to refetch objects')
*/ */
public function testAllowsUsToQueryForLukeSkywalkerDirectlyUsingHisID() : void public function testAllowsUsToQueryForLukeSkywalkerDirectlyUsingHisID() : void
{ {
$query = ' $query = '
query FetchLukeQuery { query FetchLukeQuery {
human(id: "1000") { human(id: "1000") {
name name
@ -142,9 +144,7 @@ class StarWarsQueryTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'human' => [ 'human' => ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker'
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
@ -155,44 +155,49 @@ class StarWarsQueryTest extends TestCase
*/ */
public function testGenericQueryToGetLukeSkywalkerById() : void public function testGenericQueryToGetLukeSkywalkerById() : void
{ {
$query = ' $query = '
query FetchSomeIDQuery($someId: String!) { query FetchSomeIDQuery($someId: String!) {
human(id: $someId) { human(id: $someId) {
name name
} }
} }
'; ';
$params = [ $params = ['someId' => '1000'];
'someId' => '1000'
];
$expected = [ $expected = [
'human' => [ 'human' => ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker'
]
]; ];
$this->assertValidQueryWithParams($query, $params, $expected); $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') * @see it('Allows us to create a generic query, then use it to fetch Han Solo using his ID')
*/ */
public function testGenericQueryToGetHanSoloById() : void public function testGenericQueryToGetHanSoloById() : void
{ {
$query = ' $query = '
query FetchSomeIDQuery($someId: String!) { query FetchSomeIDQuery($someId: String!) {
human(id: $someId) { human(id: $someId) {
name name
} }
} }
'; ';
$params = [ $params = ['someId' => '1002'];
'someId' => '1002'
];
$expected = [ $expected = [
'human' => [ 'human' => ['name' => 'Han Solo'],
'name' => 'Han Solo'
]
]; ];
$this->assertValidQueryWithParams($query, $params, $expected); $this->assertValidQueryWithParams($query, $params, $expected);
} }
@ -202,30 +207,26 @@ class StarWarsQueryTest extends TestCase
*/ */
public function testGenericQueryWithInvalidId() : void public function testGenericQueryWithInvalidId() : void
{ {
$query = ' $query = '
query humanQuery($id: String!) { query humanQuery($id: String!) {
human(id: $id) { human(id: $id) {
name name
} }
} }
'; ';
$params = [ $params = ['id' => 'not a valid id'];
'id' => 'not a valid id' $expected = ['human' => null];
];
$expected = [
'human' => null
];
$this->assertValidQueryWithParams($query, $params, $expected); $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') * @see it('Allows us to query for Luke, changing his key with an alias')
*/ */
function testLukeKeyAlias() public function testLukeKeyAlias() : void
{ {
$query = ' $query = '
query FetchLukeAliased { query FetchLukeAliased {
luke: human(id: "1000") { luke: human(id: "1000") {
name name
@ -233,9 +234,7 @@ class StarWarsQueryTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'luke' => [ 'luke' => ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker'
],
]; ];
$this->assertValidQuery($query, $expected); $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') * @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 { query FetchLukeAndLeiaAliased {
luke: human(id: "1000") { luke: human(id: "1000") {
name name
@ -256,24 +255,18 @@ class StarWarsQueryTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'luke' => [ 'luke' => ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker' 'leia' => ['name' => 'Leia Organa'],
],
'leia' => [
'name' => 'Leia Organa'
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
// Uses fragments to express more complex queries
/** /**
* @see it('Allows us to query using duplicated content') * @see it('Allows us to query using duplicated content')
*/ */
function testQueryUsingDuplicatedContent() public function testQueryUsingDuplicatedContent() : void
{ {
$query = ' $query = '
query DuplicateFields { query DuplicateFields {
luke: human(id: "1000") { luke: human(id: "1000") {
name name
@ -287,13 +280,13 @@ class StarWarsQueryTest extends TestCase
'; ';
$expected = [ $expected = [
'luke' => [ 'luke' => [
'name' => 'Luke Skywalker', 'name' => 'Luke Skywalker',
'homePlanet' => 'Tatooine' 'homePlanet' => 'Tatooine',
], ],
'leia' => [ 'leia' => [
'name' => 'Leia Organa', 'name' => 'Leia Organa',
'homePlanet' => 'Alderaan' 'homePlanet' => 'Alderaan',
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -301,7 +294,7 @@ class StarWarsQueryTest extends TestCase
/** /**
* @see it('Allows us to use a fragment to avoid duplicating content') * @see it('Allows us to use a fragment to avoid duplicating content')
*/ */
function testUsingFragment() public function testUsingFragment() : void
{ {
$query = ' $query = '
query UseFragment { query UseFragment {
@ -321,13 +314,13 @@ class StarWarsQueryTest extends TestCase
$expected = [ $expected = [
'luke' => [ 'luke' => [
'name' => 'Luke Skywalker', 'name' => 'Luke Skywalker',
'homePlanet' => 'Tatooine' 'homePlanet' => 'Tatooine',
], ],
'leia' => [ 'leia' => [
'name' => 'Leia Organa', 'name' => 'Leia Organa',
'homePlanet' => 'Alderaan' 'homePlanet' => 'Alderaan',
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -337,7 +330,7 @@ class StarWarsQueryTest extends TestCase
*/ */
public function testVerifyThatR2D2IsADroid() : void public function testVerifyThatR2D2IsADroid() : void
{ {
$query = ' $query = '
query CheckTypeOfR2 { query CheckTypeOfR2 {
hero { hero {
__typename __typename
@ -348,7 +341,7 @@ class StarWarsQueryTest extends TestCase
$expected = [ $expected = [
'hero' => [ 'hero' => [
'__typename' => 'Droid', '__typename' => 'Droid',
'name' => 'R2-D2' 'name' => 'R2-D2',
], ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
@ -371,32 +364,10 @@ class StarWarsQueryTest extends TestCase
$expected = [ $expected = [
'hero' => [ 'hero' => [
'__typename' => 'Human', '__typename' => 'Human',
'name' => 'Luke Skywalker' 'name' => 'Luke Skywalker',
], ],
]; ];
$this->assertValidQuery($query, $expected); $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests; namespace GraphQL\Tests;
/** /**
@ -11,13 +14,16 @@ namespace GraphQL\Tests;
* NOTE: This may contain spoilers for the original Star * NOTE: This may contain spoilers for the original Star
* Wars trilogy. * Wars trilogy.
*/ */
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type; 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 * 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. * We begin by setting up our schema.
*/ */
class StarWarsSchema class StarWarsSchema
{ {
public static function build() public static function build() : Schema
{ {
/** /**
* The original trilogy consists of three movies. * The original trilogy consists of three movies.
@ -68,22 +73,22 @@ class StarWarsSchema
* enum Episode { NEWHOPE, EMPIRE, JEDI } * enum Episode { NEWHOPE, EMPIRE, JEDI }
*/ */
$episodeEnum = new EnumType([ $episodeEnum = new EnumType([
'name' => 'Episode', 'name' => 'Episode',
'description' => 'One of the films in the Star Wars Trilogy', 'description' => 'One of the films in the Star Wars Trilogy',
'values' => [ 'values' => [
'NEWHOPE' => [ 'NEWHOPE' => [
'value' => 4, 'value' => 4,
'description' => 'Released in 1977.' 'description' => 'Released in 1977.',
], ],
'EMPIRE' => [ 'EMPIRE' => [
'value' => 5, 'value' => 5,
'description' => 'Released in 1980.' 'description' => 'Released in 1980.',
], ],
'JEDI' => [ 'JEDI' => [
'value' => 6, 'value' => 6,
'description' => 'Released in 1983.' 'description' => 'Released in 1983.',
], ],
] ],
]); ]);
$humanType = null; $humanType = null;
@ -101,28 +106,28 @@ class StarWarsSchema
* } * }
*/ */
$characterInterface = new InterfaceType([ $characterInterface = new InterfaceType([
'name' => 'Character', 'name' => 'Character',
'description' => 'A character in the Star Wars Trilogy', 'description' => 'A character in the Star Wars Trilogy',
'fields' => function() use (&$characterInterface, $episodeEnum) { 'fields' => function () use (&$characterInterface, $episodeEnum) {
return [ return [
'id' => [ 'id' => [
'type' => Type::nonNull(Type::string()), 'type' => Type::nonNull(Type::string()),
'description' => 'The id of the character.', 'description' => 'The id of the character.',
], ],
'name' => [ 'name' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'The name of the character.' 'description' => 'The name of the character.',
], ],
'friends' => [ 'friends' => [
'type' => Type::listOf($characterInterface), 'type' => Type::listOf($characterInterface),
'description' => 'The friends of the character, or an empty list if they have none.', 'description' => 'The friends of the character, or an empty list if they have none.',
], ],
'appearsIn' => [ 'appearsIn' => [
'type' => Type::listOf($episodeEnum), 'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.' 'description' => 'Which movies they appear in.',
], ],
'secretBackstory' => [ 'secretBackstory' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'All secrets about their past.', 'description' => 'All secrets about their past.',
], ],
]; ];
@ -145,26 +150,26 @@ class StarWarsSchema
* } * }
*/ */
$humanType = new ObjectType([ $humanType = new ObjectType([
'name' => 'Human', 'name' => 'Human',
'description' => 'A humanoid creature in the Star Wars universe.', 'description' => 'A humanoid creature in the Star Wars universe.',
'fields' => [ 'fields' => [
'id' => [ 'id' => [
'type' => new NonNull(Type::string()), 'type' => new NonNull(Type::string()),
'description' => 'The id of the human.', 'description' => 'The id of the human.',
], ],
'name' => [ 'name' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'The name of the human.', 'description' => 'The name of the human.',
], ],
'friends' => [ 'friends' => [
'type' => Type::listOf($characterInterface), 'type' => Type::listOf($characterInterface),
'description' => 'The friends of the human, or an empty list if they have none.', 'description' => 'The friends of the human, or an empty list if they have none.',
'resolve' => function ($human, $args, $context, ResolveInfo $info) { 'resolve' => function ($human, $args, $context, ResolveInfo $info) {
$fieldSelection = $info->getFieldSelection(); $fieldSelection = $info->getFieldSelection();
$fieldSelection['id'] = true; $fieldSelection['id'] = true;
$friends = array_map( $friends = array_map(
function($friend) use ($fieldSelection) { function ($friend) use ($fieldSelection) {
return array_intersect_key($friend, $fieldSelection); return array_intersect_key($friend, $fieldSelection);
}, },
StarWarsData::getFriends($human) StarWarsData::getFriends($human)
@ -173,24 +178,24 @@ class StarWarsSchema
return $friends; return $friends;
}, },
], ],
'appearsIn' => [ 'appearsIn' => [
'type' => Type::listOf($episodeEnum), 'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.' 'description' => 'Which movies they appear in.',
], ],
'homePlanet' => [ 'homePlanet' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'The home planet of the human, or null if unknown.' 'description' => 'The home planet of the human, or null if unknown.',
], ],
'secretBackstory' => [ 'secretBackstory' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'Where are they from and how they came to be who they are.', 'description' => 'Where are they from and how they came to be who they are.',
'resolve' => function() { 'resolve' => function () {
// This is to demonstrate error reporting // This is to demonstrate error reporting
throw new \Exception('secretBackstory is secret.'); throw new \Exception('secretBackstory is secret.');
}, },
], ],
], ],
'interfaces' => [$characterInterface] 'interfaces' => [$characterInterface],
]); ]);
/** /**
@ -207,42 +212,42 @@ class StarWarsSchema
* } * }
*/ */
$droidType = new ObjectType([ $droidType = new ObjectType([
'name' => 'Droid', 'name' => 'Droid',
'description' => 'A mechanical creature in the Star Wars universe.', 'description' => 'A mechanical creature in the Star Wars universe.',
'fields' => [ 'fields' => [
'id' => [ 'id' => [
'type' => Type::nonNull(Type::string()), 'type' => Type::nonNull(Type::string()),
'description' => 'The id of the droid.', 'description' => 'The id of the droid.',
], ],
'name' => [ 'name' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'The name of the droid.' 'description' => 'The name of the droid.',
], ],
'friends' => [ 'friends' => [
'type' => Type::listOf($characterInterface), 'type' => Type::listOf($characterInterface),
'description' => 'The friends of the droid, or an empty list if they have none.', 'description' => 'The friends of the droid, or an empty list if they have none.',
'resolve' => function ($droid) { 'resolve' => function ($droid) {
return StarWarsData::getFriends($droid); return StarWarsData::getFriends($droid);
}, },
], ],
'appearsIn' => [ 'appearsIn' => [
'type' => Type::listOf($episodeEnum), 'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.' 'description' => 'Which movies they appear in.',
], ],
'secretBackstory' => [ 'secretBackstory' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'Construction date and the name of the designer.', 'description' => 'Construction date and the name of the designer.',
'resolve' => function() { 'resolve' => function () {
// This is to demonstrate error reporting // This is to demonstrate error reporting
throw new \Exception('secretBackstory is secret.'); throw new \Exception('secretBackstory is secret.');
}, },
], ],
'primaryFunction' => [ 'primaryFunction' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'The primary function of the droid.' 'description' => 'The primary function of the droid.',
] ],
], ],
'interfaces' => [$characterInterface] 'interfaces' => [$characterInterface],
]); ]);
/** /**
@ -260,49 +265,51 @@ class StarWarsSchema
* *
*/ */
$queryType = new ObjectType([ $queryType = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'hero' => [ 'hero' => [
'type' => $characterInterface, 'type' => $characterInterface,
'args' => [ 'args' => [
'episode' => [ 'episode' => [
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular 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) { 'resolve' => function ($root, $args) {
return StarWarsData::getHero(isset($args['episode']) ? $args['episode'] : null); return StarWarsData::getHero($args['episode'] ?? null);
}, },
], ],
'human' => [ 'human' => [
'type' => $humanType, 'type' => $humanType,
'args' => [ 'args' => [
'id' => [ 'id' => [
'name' => 'id', 'name' => 'id',
'description' => 'id of the human', 'description' => 'id of the human',
'type' => Type::nonNull(Type::string()) 'type' => Type::nonNull(Type::string()),
] ],
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($root, $args) {
$humans = StarWarsData::humans(); $humans = StarWarsData::humans();
return isset($humans[$args['id']]) ? $humans[$args['id']] : null;
} return $humans[$args['id']] ?? null;
},
], ],
'droid' => [ 'droid' => [
'type' => $droidType, 'type' => $droidType,
'args' => [ 'args' => [
'id' => [ 'id' => [
'name' => 'id', 'name' => 'id',
'description' => 'id of the droid', 'description' => 'id of the droid',
'type' => Type::nonNull(Type::string()) 'type' => Type::nonNull(Type::string()),
] ],
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($root, $args) {
$droids = StarWarsData::droids(); $droids = StarWarsData::droids();
return isset($droids[$args['id']]) ? $droids[$args['id']] : null;
} return $droids[$args['id']] ?? null;
] },
] ],
],
]); ]);
return new Schema(['query' => $queryType]); return new Schema(['query' => $queryType]);

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests; namespace GraphQL\Tests;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
@ -15,7 +18,7 @@ class StarWarsValidationTest extends TestCase
*/ */
public function testValidatesAComplexButValidQuery() : void public function testValidatesAComplexButValidQuery() : void
{ {
$query = ' $query = '
query NestedQueryWithFragment { query NestedQueryWithFragment {
hero { hero {
...NameAndAppearances ...NameAndAppearances
@ -37,12 +40,22 @@ class StarWarsValidationTest extends TestCase
$this->assertEquals(true, empty($errors)); $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') * @see it('Notes that non-existent fields are invalid')
*/ */
public function testThatNonExistentFieldsAreInvalid() : void public function testThatNonExistentFieldsAreInvalid() : void
{ {
$query = ' $query = '
query HeroSpaceshipQuery { query HeroSpaceshipQuery {
hero { hero {
favoriteSpaceship favoriteSpaceship
@ -73,7 +86,7 @@ class StarWarsValidationTest extends TestCase
*/ */
public function testDisallowsFieldsOnScalars() : void public function testDisallowsFieldsOnScalars() : void
{ {
$query = ' $query = '
query HeroFieldsOnScalarQuery { query HeroFieldsOnScalarQuery {
hero { hero {
name { name {
@ -91,7 +104,7 @@ class StarWarsValidationTest extends TestCase
*/ */
public function testDisallowsObjectFieldsOnInterfaces() : void public function testDisallowsObjectFieldsOnInterfaces() : void
{ {
$query = ' $query = '
query DroidFieldOnCharacter { query DroidFieldOnCharacter {
hero { hero {
name name
@ -108,7 +121,7 @@ class StarWarsValidationTest extends TestCase
*/ */
public function testAllowsObjectFieldsInFragments() : void public function testAllowsObjectFieldsInFragments() : void
{ {
$query = ' $query = '
query DroidFieldInFragment { query DroidFieldInFragment {
hero { hero {
name name
@ -129,7 +142,7 @@ class StarWarsValidationTest extends TestCase
*/ */
public function testAllowsObjectFieldsInInlineFragments() : void public function testAllowsObjectFieldsInInlineFragments() : void
{ {
$query = ' $query = '
query DroidFieldInFragment { query DroidFieldInFragment {
hero { hero {
name name
@ -142,13 +155,4 @@ class StarWarsValidationTest extends TestCase
$errors = $this->validationErrors($query); $errors = $this->validationErrors($query);
$this->assertEquals(true, empty($errors)); $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Type; namespace GraphQL\Tests\Type;
use ArrayObject;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection; use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
use function is_array;
class EnumTypeTest extends TestCase class EnumTypeTest extends TestCase
{ {
/** /** @var Schema */
* @var Schema
*/
private $schema; private $schema;
/** /** @var EnumType */
* @var EnumType
*/
private $ComplexEnum; private $ComplexEnum;
/** @var mixed[] */
private $Complex1; private $Complex1;
/** @var ArrayObject */
private $Complex2; private $Complex2;
public function setUp() public function setUp()
{ {
$ColorType = new EnumType([ $ColorType = new EnumType([
'name' => 'Color', 'name' => 'Color',
'values' => [ 'values' => [
'RED' => ['value' => 0], 'RED' => ['value' => 0],
'GREEN' => ['value' => 1], 'GREEN' => ['value' => 1],
'BLUE' => ['value' => 2], 'BLUE' => ['value' => 2],
] ],
]); ]);
$simpleEnum = new EnumType([ $simpleEnum = new EnumType([
'name' => 'SimpleEnum', 'name' => 'SimpleEnum',
'values' => [ 'values' => [
'ONE', 'TWO', 'THREE' 'ONE',
] 'TWO',
'THREE',
],
]); ]);
$Complex1 = ['someRandomFunction' => function() {}]; $Complex1 = [
'someRandomFunction' => function () {
},
];
$Complex2 = new \ArrayObject(['someRandomValue' => 123]); $Complex2 = new \ArrayObject(['someRandomValue' => 123]);
$ComplexEnum = new EnumType([ $ComplexEnum = new EnumType([
'name' => 'Complex', 'name' => 'Complex',
'values' => [ 'values' => [
'ONE' => ['value' => $Complex1], 'ONE' => ['value' => $Complex1],
'TWO' => ['value' => $Complex2] 'TWO' => ['value' => $Complex2],
] ],
]); ]);
$QueryType = new ObjectType([ $QueryType = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'colorEnum' => [ 'colorEnum' => [
'type' => $ColorType, 'type' => $ColorType,
'args' => [ 'args' => [
'fromEnum' => ['type' => $ColorType], 'fromEnum' => ['type' => $ColorType],
'fromInt' => ['type' => Type::int()], 'fromInt' => ['type' => Type::int()],
'fromString' => ['type' => Type::string()], 'fromString' => ['type' => Type::string()],
], ],
'resolve' => function ($value, $args) { 'resolve' => function ($value, $args) {
@ -75,28 +84,28 @@ class EnumTypeTest extends TestCase
if (isset($args['fromEnum'])) { if (isset($args['fromEnum'])) {
return $args['fromEnum']; return $args['fromEnum'];
} }
} },
], ],
'simpleEnum' => [ 'simpleEnum' => [
'type' => $simpleEnum, 'type' => $simpleEnum,
'args' => [ 'args' => [
'fromName' => ['type' => Type::string()], 'fromName' => ['type' => Type::string()],
'fromValue' => ['type' => Type::string()] 'fromValue' => ['type' => Type::string()],
], ],
'resolve' => function($value, $args) { 'resolve' => function ($value, $args) {
if (isset($args['fromName'])) { if (isset($args['fromName'])) {
return $args['fromName']; return $args['fromName'];
} }
if (isset($args['fromValue'])) { if (isset($args['fromValue'])) {
return $args['fromValue']; return $args['fromValue'];
} }
} },
], ],
'colorInt' => [ 'colorInt' => [
'type' => Type::int(), 'type' => Type::int(),
'args' => [ 'args' => [
'fromEnum' => ['type' => $ColorType], 'fromEnum' => ['type' => $ColorType],
'fromInt' => ['type' => Type::int()], 'fromInt' => ['type' => Type::int()],
], ],
'resolve' => function ($value, $args) { 'resolve' => function ($value, $args) {
if (isset($args['fromInt'])) { if (isset($args['fromInt'])) {
@ -105,75 +114,76 @@ class EnumTypeTest extends TestCase
if (isset($args['fromEnum'])) { if (isset($args['fromEnum'])) {
return $args['fromEnum']; return $args['fromEnum'];
} }
} },
], ],
'complexEnum' => [ 'complexEnum' => [
'type' => $ComplexEnum, 'type' => $ComplexEnum,
'args' => [ 'args' => [
'fromEnum' => [ 'fromEnum' => [
'type' => $ComplexEnum, 'type' => $ComplexEnum,
// Note: defaultValue is provided an *internal* representation for // Note: defaultValue is provided an *internal* representation for
// Enums, rather than the string name. // Enums, rather than the string name.
'defaultValue' => $Complex1 'defaultValue' => $Complex1,
], ],
'provideGoodValue' => [ 'provideGoodValue' => [
'type' => Type::boolean(), 'type' => Type::boolean(),
], ],
'provideBadValue' => [ 'provideBadValue' => [
'type' => Type::boolean() 'type' => Type::boolean(),
] ],
], ],
'resolve' => function($value, $args) use ($Complex1, $Complex2) { 'resolve' => function ($value, $args) use ($Complex2) {
if (!empty($args['provideGoodValue'])) { if (! empty($args['provideGoodValue'])) {
// Note: this is one of the references of the internal values which // Note: this is one of the references of the internal values which
// ComplexEnum allows. // ComplexEnum allows.
return $Complex2; return $Complex2;
} }
if (!empty($args['provideBadValue'])) { if (! empty($args['provideBadValue'])) {
// Note: similar shape, but not the same *reference* // Note: similar shape, but not the same *reference*
// as Complex2 above. Enum internal values require === equality. // as Complex2 above. Enum internal values require === equality.
return new \ArrayObject(['someRandomValue' => 123]); return new \ArrayObject(['someRandomValue' => 123]);
} }
return $args['fromEnum']; return $args['fromEnum'];
} },
] ],
] ],
]); ]);
$MutationType = new ObjectType([ $MutationType = new ObjectType([
'name' => 'Mutation', 'name' => 'Mutation',
'fields' => [ 'fields' => [
'favoriteEnum' => [ 'favoriteEnum' => [
'type' => $ColorType, 'type' => $ColorType,
'args' => ['color' => ['type' => $ColorType]], 'args' => ['color' => ['type' => $ColorType]],
'resolve' => function ($value, $args) { 'resolve' => function ($value, $args) {
return isset($args['color']) ? $args['color'] : null; return $args['color'] ?? null;
} },
] ],
] ],
]); ]);
$SubscriptionType = new ObjectType([ $SubscriptionType = new ObjectType([
'name' => 'Subscription', 'name' => 'Subscription',
'fields' => [ 'fields' => [
'subscribeToEnum' => [ 'subscribeToEnum' => [
'type' => $ColorType, 'type' => $ColorType,
'args' => ['color' => ['type' => $ColorType]], 'args' => ['color' => ['type' => $ColorType]],
'resolve' => function ($value, $args) { 'resolve' => function ($value, $args) {
return isset($args['color']) ? $args['color'] : null; return $args['color'] ?? null;
} },
] ],
] ],
]); ]);
$this->Complex1 = $Complex1; $this->Complex1 = $Complex1;
$this->Complex2 = $Complex2; $this->Complex2 = $Complex2;
$this->ComplexEnum = $ComplexEnum; $this->ComplexEnum = $ComplexEnum;
$this->schema = new Schema([ $this->schema = new Schema([
'query' => $QueryType, 'query' => $QueryType,
'mutation' => $MutationType, 'mutation' => $MutationType,
'subscription' => $SubscriptionType 'subscription' => $SubscriptionType,
]); ]);
} }
@ -221,12 +231,34 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromEnum: "GREEN") }', '{ colorEnum(fromEnum: "GREEN") }',
null, null,
[ [
'message' => "Expected type Color, found \"GREEN\"; Did you mean the enum value GREEN?", 'message' => 'Expected type Color, found "GREEN"; Did you mean the enum value GREEN?',
'locations' => [new SourceLocation(1, 23)] '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') * @see it('does not accept valuesNotInTheEnum')
*/ */
@ -236,8 +268,8 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromEnum: GREENISH) }', '{ colorEnum(fromEnum: GREENISH) }',
null, null,
[ [
'message' => "Expected type Color, found GREENISH; Did you mean the enum value GREEN?", 'message' => 'Expected type Color, found GREENISH; Did you mean the enum value GREEN?',
'locations' => [new SourceLocation(1, 23)] 'locations' => [new SourceLocation(1, 23)],
] ]
); );
} }
@ -251,8 +283,8 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromEnum: green) }', '{ colorEnum(fromEnum: green) }',
null, null,
[ [
'message' => "Expected type Color, found green; Did you mean the enum value GREEN?", 'message' => 'Expected type Color, found green; Did you mean the enum value GREEN?',
'locations' => [new SourceLocation(1, 23)] 'locations' => [new SourceLocation(1, 23)],
] ]
); );
} }
@ -266,9 +298,9 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromString: "GREEN") }', '{ colorEnum(fromString: "GREEN") }',
null, 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)], 'locations' => [new SourceLocation(1, 3)],
'path' => ['colorEnum'], 'path' => ['colorEnum'],
] ]
); );
} }
@ -281,7 +313,7 @@ class EnumTypeTest extends TestCase
$this->expectFailure( $this->expectFailure(
'{ colorEnum(fromEnum: 1) }', '{ colorEnum(fromEnum: 1) }',
null, null,
"Expected type Color, found 1." 'Expected type Color, found 1.'
); );
} }
@ -293,7 +325,7 @@ class EnumTypeTest extends TestCase
$this->expectFailure( $this->expectFailure(
'{ colorEnum(fromInt: GREEN) }', '{ colorEnum(fromInt: GREEN) }',
null, null,
"Expected type Int, found GREEN." 'Expected type Int, found GREEN.'
); );
} }
@ -381,7 +413,7 @@ class EnumTypeTest extends TestCase
$this->expectFailure( $this->expectFailure(
'query test($color: Int!) { colorEnum(fromEnum: $color) }', 'query test($color: Int!) { colorEnum(fromEnum: $color) }',
['color' => 2], ['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( $this->assertEquals(
['data' => ['colorEnum' => 'RED', 'colorInt' => 0]], ['data' => ['colorEnum' => 'RED', 'colorInt' => 0]],
GraphQL::executeQuery($this->schema, "{ GraphQL::executeQuery(
$this->schema,
'{
colorEnum(fromEnum: RED) colorEnum(fromEnum: RED)
colorInt(fromEnum: RED) colorInt(fromEnum: RED)
}")->toArray() }'
)->toArray()
); );
} }
@ -406,10 +441,13 @@ class EnumTypeTest extends TestCase
{ {
$this->assertEquals( $this->assertEquals(
['data' => ['colorEnum' => null, 'colorInt' => null]], ['data' => ['colorEnum' => null, 'colorInt' => null]],
GraphQL::executeQuery($this->schema, "{ GraphQL::executeQuery(
$this->schema,
'{
colorEnum colorEnum
colorInt colorInt
}")->toArray() }'
)->toArray()
); );
} }
@ -419,7 +457,7 @@ class EnumTypeTest extends TestCase
public function testPresentsGetValuesAPIForComplexEnums() : void public function testPresentsGetValuesAPIForComplexEnums() : void
{ {
$ComplexEnum = $this->ComplexEnum; $ComplexEnum = $this->ComplexEnum;
$values = $ComplexEnum->getValues(); $values = $ComplexEnum->getValues();
$this->assertEquals(2, count($values)); $this->assertEquals(2, count($values));
$this->assertEquals('ONE', $values[0]->name); $this->assertEquals('ONE', $values[0]->name);
@ -446,25 +484,29 @@ class EnumTypeTest extends TestCase
*/ */
public function testMayBeInternallyRepresentedWithComplexValues() : void public function testMayBeInternallyRepresentedWithComplexValues() : void
{ {
$result = GraphQL::executeQuery($this->schema, '{ $result = GraphQL::executeQuery(
$this->schema,
'{
first: complexEnum first: complexEnum
second: complexEnum(fromEnum: TWO) second: complexEnum(fromEnum: TWO)
good: complexEnum(provideGoodValue: true) good: complexEnum(provideGoodValue: true)
bad: complexEnum(provideBadValue: true) bad: complexEnum(provideBadValue: true)
}')->toArray(true); }'
)->toArray(true);
$expected = [ $expected = [
'data' => [ 'data' => [
'first' => 'ONE', 'first' => 'ONE',
'second' => 'TWO', 'second' => 'TWO',
'good' => 'TWO', 'good' => 'TWO',
'bad' => null 'bad' => null,
], ],
'errors' => [[ 'errors' => [[
'debugMessage' => 'debugMessage' =>
'Expected a value of type "Complex" but received: instance of ArrayObject', '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); $this->assertArraySubset($expected, $result);
@ -489,35 +531,14 @@ class EnumTypeTest extends TestCase
$this->assertArraySubset( $this->assertArraySubset(
[ [
'data' => ['first' => 'ONE', 'second' => 'TWO', 'third' => null], 'data' => ['first' => 'ONE', 'second' => 'TWO', 'third' => null],
'errors' => [[ 'errors' => [[
'debugMessage' => 'Expected a value of type "SimpleEnum" but received: WRONG', '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) 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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Type; namespace GraphQL\Tests\Type;
class ObjectIdStub class ObjectIdStub
{ {
/** /** @var int */
* @var int
*/
private $id; private $id;
/** /**

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\BooleanValueNode;
@ -16,10 +19,12 @@ use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Utils\AST; use GraphQL\Utils\AST;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use stdClass;
class AstFromValueTest extends TestCase class AstFromValueTest extends TestCase
{ {
// Describe: astFromValue /** @var stdClass */
private $complexValue;
/** /**
* @see it('converts boolean values to ASTs') * @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 NullValueNode([]), AST::astFromValue(null, Type::boolean()));
$this->assertEquals(new BooleanValueNode(['value' => false]), AST::astFromValue(0, 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' => true]), AST::astFromValue(1, Type::boolean()));
$this->assertEquals(new BooleanValueNode(['value' => false]), AST::astFromValue(0, Type::nonNull(Type::boolean()))); $this->assertEquals(
$this->assertEquals(null, AST::astFromValue(null, Type::nonNull(Type::boolean()))); // Note: null means that AST cannot 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->expectException(\Throwable::class);
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 1.0E+40'); $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 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 public function testConvertsStringValuesToEnumASTsIfPossible() : void
{ {
$this->assertEquals(new EnumValueNode(['value' => 'HELLO']), AST::astFromValue('HELLO', $this->myEnum())); $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 // Note: case sensitive
$this->assertEquals(null, AST::astFromValue('hello', $this->myEnum())); $this->assertNull(AST::astFromValue('hello', $this->myEnum()));
// Note: Not a valid enum value // 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([ $value1 = new ListValueNode([
'values' => [ 'values' => [
new StringValueNode(['value' => 'FOO']), new StringValueNode(['value' => 'FOO']),
new StringValueNode(['value' => 'BAR']) new StringValueNode(['value' => 'BAR']),
] ],
]); ]);
$this->assertEquals($value1, AST::astFromValue(['FOO', 'BAR'], Type::listOf(Type::string()))); $this->assertEquals($value1, AST::astFromValue(['FOO', 'BAR'], Type::listOf(Type::string())));
@ -147,7 +189,7 @@ class AstFromValueTest extends TestCase
'values' => [ 'values' => [
new EnumValueNode(['value' => 'HELLO']), new EnumValueNode(['value' => 'HELLO']),
new EnumValueNode(['value' => 'GOODBYE']), new EnumValueNode(['value' => 'GOODBYE']),
] ],
]); ]);
$this->assertEquals($value2, AST::astFromValue(['HELLO', 'GOODBYE'], Type::listOf($this->myEnum()))); $this->assertEquals($value2, AST::astFromValue(['HELLO', 'GOODBYE'], Type::listOf($this->myEnum())));
} }
@ -157,7 +199,10 @@ class AstFromValueTest extends TestCase
*/ */
public function testConvertsListSingletons() : void 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 public function testConvertsInputObjects() : void
{ {
$inputObj = new InputObjectType([ $inputObj = new InputObjectType([
'name' => 'MyInputObj', 'name' => 'MyInputObj',
'fields' => [ 'fields' => [
'foo' => Type::float(), 'foo' => Type::float(),
'bar' => $this->myEnum() 'bar' => $this->myEnum(),
] ],
]); ]);
$expected = new ObjectValueNode([ $expected = new ObjectValueNode([
'fields' => [ 'fields' => [
$this->objectField('foo', new IntValueNode(['value' => '3'])), $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']; $data = ['foo' => 3, 'bar' => 'HELLO'];
@ -185,62 +230,38 @@ class AstFromValueTest extends TestCase
$this->assertEquals($expected, AST::astFromValue((object) $data, $inputObj)); $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') * @see it('converts input objects with explicit nulls')
*/ */
public function testConvertsInputObjectsWithExplicitNulls() : void public function testConvertsInputObjectsWithExplicitNulls() : void
{ {
$inputObj = new InputObjectType([ $inputObj = new InputObjectType([
'name' => 'MyInputObj', 'name' => 'MyInputObj',
'fields' => [ 'fields' => [
'foo' => Type::float(), 'foo' => Type::float(),
'bar' => $this->myEnum() 'bar' => $this->myEnum(),
] ],
]); ]);
$this->assertEquals(new ObjectValueNode([ $this->assertEquals(
new ObjectValueNode([
'fields' => [ 'fields' => [
$this->objectField('foo', new NullValueNode([])) $this->objectField('foo', new NullValueNode([])),
] ],
]), AST::astFromValue(['foo' => null], $inputObj)); ]),
} 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
]);
} }
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,8 +1,9 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Error\Error;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
@ -12,13 +13,16 @@ use PHPUnit\Framework\TestCase;
class CoerceValueTest extends TestCase class CoerceValueTest extends TestCase
{ {
/** @var EnumType */
private $testEnum; private $testEnum;
/** @var InputObjectType */
private $testInputObject; private $testInputObject;
public function setUp() public function setUp()
{ {
$this->testEnum = new EnumType([ $this->testEnum = new EnumType([
'name' => 'TestEnum', 'name' => 'TestEnum',
'values' => [ 'values' => [
'FOO' => 'InternalFoo', 'FOO' => 'InternalFoo',
'BAR' => 123456789, 'BAR' => 123456789,
@ -26,7 +30,7 @@ class CoerceValueTest extends TestCase
]); ]);
$this->testInputObject = new InputObjectType([ $this->testInputObject = new InputObjectType([
'name' => 'TestInputObject', 'name' => 'TestInputObject',
'fields' => [ 'fields' => [
'foo' => Type::nonNull(Type::int()), 'foo' => Type::nonNull(Type::int()),
'bar' => 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') * @see it('coercing an array to GraphQLString produces an error')
*/ */
public function testCoercingAnArrayToGraphQLStringProducesAnError() : void 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') * @see it('returns no error for int input')
@ -64,6 +78,13 @@ class CoerceValueTest extends TestCase
$this->expectNoErrors($result); $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') * @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') * @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') * @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') * @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') * @see it('returns no error for a known enum name')
*/ */
@ -229,6 +250,8 @@ class CoerceValueTest extends TestCase
$this->assertEquals(123456789, $barResult['value']); $this->assertEquals(123456789, $barResult['value']);
} }
// DESCRIBE: for GraphQLInputObject
/** /**
* @see it('results error for misspelled enum value') * @see it('results error for misspelled enum value')
*/ */
@ -250,8 +273,6 @@ class CoerceValueTest extends TestCase
$this->expectError($result2, 'Expected type TestEnum.'); $this->expectError($result2, 'Expected type TestEnum.');
} }
// DESCRIBE: for GraphQLInputObject
/** /**
* @see it('returns no error for a valid input') * @see it('returns no error for a valid input')
*/ */
@ -277,7 +298,10 @@ class CoerceValueTest extends TestCase
public function testReturnErrorForAnInvalidField() : void public function testReturnErrorForAnInvalidField() : void
{ {
$result = Value::coerceValue(['foo' => 'abc'], $this->testInputObject); $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 public function testReturnsMultipleErrorsForMultipleInvalidFields() : void
{ {
$result = Value::coerceValue(['foo' => 'abc', 'bar' => 'def'], $this->testInputObject); $result = Value::coerceValue(['foo' => 'abc', 'bar' => 'def'], $this->testInputObject);
$this->assertEquals([ $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', 'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc',
], $result['errors']); '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); $result = Value::coerceValue(['foo' => 123, 'bart' => 123], $this->testInputObject);
$this->expectError($result, 'Field "bart" is not defined by type TestInputObject; did you mean bar?'); $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 <?php
namespace Utils;
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
@ -12,121 +15,74 @@ use PHPUnit\Framework\TestCase;
class ExtractTypesTest extends TestCase class ExtractTypesTest extends TestCase
{ {
/** /** @var ObjectType */
* @var ObjectType
*/
private $query; private $query;
/** /** @var ObjectType */
* @var ObjectType
*/
private $mutation; private $mutation;
/** /** @var InterfaceType */
* @var InterfaceType
*/
private $node; private $node;
/** /** @var InterfaceType */
* @var InterfaceType
*/
private $content; private $content;
/** /** @var ObjectType */
* @var ObjectType
*/
private $blogStory; private $blogStory;
/** /** @var ObjectType */
* @var ObjectType
*/
private $link;
/**
* @var ObjectType
*/
private $video;
/**
* @var ObjectType
*/
private $videoMetadata;
/**
* @var ObjectType
*/
private $comment; private $comment;
/** /** @var ObjectType */
* @var ObjectType
*/
private $user; private $user;
/** /** @var ObjectType */
* @var ObjectType
*/
private $category; private $category;
/** /** @var UnionType */
* @var UnionType
*/
private $mention; private $mention;
/** @var ObjectType */
private $postStoryMutation; private $postStoryMutation;
/** @var InputObjectType */
private $postStoryMutationInput; private $postStoryMutationInput;
/** @var ObjectType */
private $postCommentMutation; private $postCommentMutation;
/** @var InputObjectType */
private $postCommentMutationInput; private $postCommentMutationInput;
public function setUp() public function setUp()
{ {
$this->node = new InterfaceType([ $this->node = new InterfaceType([
'name' => 'Node', 'name' => 'Node',
'fields' => [ 'fields' => [
'id' => Type::string() 'id' => Type::string(),
] ],
]); ]);
$this->content = new InterfaceType([ $this->content = new InterfaceType([
'name' => 'Content', 'name' => 'Content',
'fields' => function() { 'fields' => function () {
return [ return [
'title' => Type::string(), 'title' => Type::string(),
'body' => Type::string(), 'body' => Type::string(),
'author' => $this->user, 'author' => $this->user,
'comments' => Type::listOf($this->comment), 'comments' => Type::listOf($this->comment),
'categories' => Type::listOf($this->category) 'categories' => Type::listOf($this->category),
]; ];
} },
]); ]);
$this->blogStory = new ObjectType([ $this->blogStory = new ObjectType([
'name' => 'BlogStory', 'name' => 'BlogStory',
'interfaces' => [ 'interfaces' => [
$this->node, $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')
];
},
]);
$this->link = new ObjectType([
'name' => 'Link',
'interfaces' => [
$this->node,
$this->content
],
'fields' => function() {
return [ return [
$this->node->getField('id'), $this->node->getField('id'),
$this->content->getField('title'), $this->content->getField('title'),
@ -134,156 +90,176 @@ class ExtractTypesTest extends TestCase
$this->content->getField('author'), $this->content->getField('author'),
$this->content->getField('comments'), $this->content->getField('comments'),
$this->content->getField('categories'), $this->content->getField('categories'),
'url' => Type::string()
]; ];
}, },
]); ]);
$this->video = new ObjectType([ new ObjectType([
'name' => 'Video', 'name' => 'Link',
'interfaces' => [ 'interfaces' => [
$this->node, $this->node,
$this->content $this->content,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
$this->content->getField('title'), 'title' => $this->content->getField('title'),
$this->content->getField('body'), 'body' => $this->content->getField('body'),
$this->content->getField('author'), 'author' => $this->content->getField('author'),
$this->content->getField('comments'), 'comments' => $this->content->getField('comments'),
$this->content->getField('categories'), 'categories' => $this->content->getField('categories'),
'streamUrl' => Type::string(), '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(), 'downloadUrl' => Type::string(),
'metadata' => $this->videoMetadata = new ObjectType([ 'metadata' => new ObjectType([
'name' => 'VideoMetadata', 'name' => 'VideoMetadata',
'fields' => [ 'fields' => [
'lat' => Type::float(), 'lat' => Type::float(),
'lng' => Type::float() 'lng' => Type::float(),
] ],
]) ]),
]; ];
} },
]); ]);
$this->comment = new ObjectType([ $this->comment = new ObjectType([
'name' => 'Comment', 'name' => 'Comment',
'interfaces' => [ 'interfaces' => [
$this->node $this->node,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
'author' => $this->user, 'author' => $this->user,
'text' => Type::string(), 'text' => Type::string(),
'replies' => Type::listOf($this->comment), 'replies' => Type::listOf($this->comment),
'parent' => $this->comment, 'parent' => $this->comment,
'content' => $this->content 'content' => $this->content,
]; ];
} },
]); ]);
$this->user = new ObjectType([ $this->user = new ObjectType([
'name' => 'User', 'name' => 'User',
'interfaces' => [ 'interfaces' => [
$this->node $this->node,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
'name' => Type::string(), 'name' => Type::string(),
]; ];
} },
]); ]);
$this->category = new ObjectType([ $this->category = new ObjectType([
'name' => 'Category', 'name' => 'Category',
'interfaces' => [ 'interfaces' => [
$this->node $this->node,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
'name' => Type::string() 'name' => Type::string(),
]; ];
} },
]); ]);
$this->mention = new UnionType([ $this->mention = new UnionType([
'name' => 'Mention', 'name' => 'Mention',
'types' => [ 'types' => [
$this->user, $this->user,
$this->category $this->category,
] ],
]); ]);
$this->query = new ObjectType([ $this->query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'viewer' => $this->user, 'viewer' => $this->user,
'latestContent' => $this->content, 'latestContent' => $this->content,
'node' => $this->node, 'node' => $this->node,
'mentions' => Type::listOf($this->mention) '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([ $this->mutation = new ObjectType([
'name' => 'Mutation', 'name' => 'Mutation',
'fields' => [ 'fields' => [
'postStory' => [ 'postStory' => [
'type' => $this->postStoryMutation = new ObjectType([ 'type' => $this->postStoryMutation = new ObjectType([
'name' => 'PostStoryMutation', 'name' => 'PostStoryMutation',
'fields' => [ 'fields' => [
'story' => $this->blogStory 'story' => $this->blogStory,
] ],
]), ]),
'args' => [ 'args' => [
'input' => Type::nonNull($this->postStoryMutationInput = new InputObjectType([ 'input' => Type::nonNull($this->postStoryMutationInput),
'name' => 'PostStoryMutationInput', 'clientRequestId' => Type::string(),
'fields' => [ ],
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id()
]
])),
'clientRequestId' => Type::string()
]
], ],
'postComment' => [ 'postComment' => [
'type' => $this->postCommentMutation = new ObjectType([ 'type' => $this->postCommentMutation = new ObjectType([
'name' => 'PostCommentMutation', 'name' => 'PostCommentMutation',
'fields' => [ 'fields' => [
'comment' => $this->comment 'comment' => $this->comment,
] ],
]), ]),
'args' => [ 'args' => [
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([ 'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
'name' => 'PostCommentMutationInput', 'name' => 'PostCommentMutationInput',
'fields' => [ 'fields' => [
'text' => Type::nonNull(Type::string()), 'text' => Type::nonNull(Type::string()),
'author' => Type::nonNull(Type::id()), 'author' => Type::nonNull(Type::id()),
'content' => Type::id(), 'content' => Type::id(),
'parent' => Type::id() 'parent' => Type::id(),
] ],
])), ])),
'clientRequestId' => Type::string() 'clientRequestId' => Type::string(),
] ],
] ],
] ],
]); ]);
} }
public function testExtractTypesFromQuery() : void public function testExtractTypesFromQuery() : void
{ {
$expectedTypeMap = [ $expectedTypeMap = [
'Query' => $this->query, 'Query' => $this->query,
'User' => $this->user, 'User' => $this->user,
'Node' => $this->node, 'Node' => $this->node,
'String' => Type::string(), 'String' => Type::string(),
'Content' => $this->content, 'Content' => $this->content,
'Comment' => $this->comment, 'Comment' => $this->comment,
'Mention' => $this->mention, 'Mention' => $this->mention,
'Category' => $this->category, 'Category' => $this->category,
]; ];
@ -294,19 +270,19 @@ class ExtractTypesTest extends TestCase
public function testExtractTypesFromMutation() : void public function testExtractTypesFromMutation() : void
{ {
$expectedTypeMap = [ $expectedTypeMap = [
'Mutation' => $this->mutation, 'Mutation' => $this->mutation,
'User' => $this->user, 'User' => $this->user,
'Node' => $this->node, 'Node' => $this->node,
'String' => Type::string(), 'String' => Type::string(),
'Content' => $this->content, 'Content' => $this->content,
'Comment' => $this->comment, 'Comment' => $this->comment,
'BlogStory' => $this->blogStory, 'BlogStory' => $this->blogStory,
'Category' => $this->category, 'Category' => $this->category,
'PostStoryMutationInput' => $this->postStoryMutationInput, 'PostStoryMutationInput' => $this->postStoryMutationInput,
'ID' => Type::id(), 'ID' => Type::id(),
'PostStoryMutation' => $this->postStoryMutation, 'PostStoryMutation' => $this->postStoryMutation,
'PostCommentMutationInput' => $this->postCommentMutationInput, 'PostCommentMutationInput' => $this->postCommentMutationInput,
'PostCommentMutation' => $this->postCommentMutation, 'PostCommentMutation' => $this->postCommentMutation,
]; ];
$actualTypeMap = TypeInfo::extractTypes($this->mutation); $actualTypeMap = TypeInfo::extractTypes($this->mutation);
@ -316,16 +292,16 @@ class ExtractTypesTest extends TestCase
public function testThrowsOnMultipleTypesWithSameName() : void public function testThrowsOnMultipleTypesWithSameName() : void
{ {
$otherUserType = new ObjectType([ $otherUserType = new ObjectType([
'name' => 'User', 'name' => 'User',
'fields' => ['a' => Type::string()] 'fields' => ['a' => Type::string()],
]); ]);
$queryType = new ObjectType([ $queryType = new ObjectType([
'name' => 'Test', 'name' => 'Test',
'fields' => [ 'fields' => [
'otherUser' => $otherUserType, 'otherUser' => $otherUserType,
'user' => $this->user 'user' => $this->user,
] ],
]); ]);
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);

View File

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

View File

@ -1,16 +1,16 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Utils\Utils;
use GraphQL\Utils\MixedStore; use GraphQL\Utils\MixedStore;
use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class MixedStoreTest extends TestCase class MixedStoreTest extends TestCase
{ {
/** /** @var MixedStore */
* @var MixedStore
*/
private $mixedStore; private $mixedStore;
public function setUp() public function setUp()
@ -18,6 +18,13 @@ class MixedStoreTest extends TestCase
$this->mixedStore = new MixedStore(); $this->mixedStore = new MixedStore();
} }
public function testAcceptsNullKeys() : void
{
foreach ($this->getPossibleValues() as $value) {
$this->assertAcceptsKeyValue(null, $value);
}
}
public function getPossibleValues() public function getPossibleValues()
{ {
return [ return [
@ -30,16 +37,38 @@ class MixedStoreTest extends TestCase
'a', 'a',
[], [],
new \stdClass(), new \stdClass(),
function() {}, function () {
new MixedStore() },
new MixedStore(),
]; ];
} }
public function testAcceptsNullKeys() : void private function assertAcceptsKeyValue($key, $value)
{ {
foreach ($this->getPossibleValues() as $value) { $err = 'Failed assertion that MixedStore accepts key ' .
$this->assertAcceptsKeyValue(null, $value); 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 public function testAcceptsBoolKeys() : void
@ -93,35 +122,11 @@ class MixedStoreTest extends TestCase
foreach ($this->getPossibleValues() as $value) { foreach ($this->getPossibleValues() as $value) {
$this->assertAcceptsKeyValue(new \stdClass(), $value); $this->assertAcceptsKeyValue(new \stdClass(), $value);
$this->assertAcceptsKeyValue(new MixedStore(), $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 <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use GraphQL\Utils\Value;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class QuotedOrListTest extends TestCase class QuotedOrListTest extends TestCase
{ {
// DESCRIBE: quotedOrList // DESCRIBE: quotedOrList
/** /**
* @see it('Does not accept an empty list') * @see it('Does not accept an empty list')
*/ */

View File

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

View File

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

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