mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 06:16:05 +03:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
e7513e356a
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,6 @@
|
||||
.phpcs-cache
|
||||
composer.phar
|
||||
composer.lock
|
||||
phpcs.xml
|
||||
phpstan.neon
|
||||
vendor/
|
||||
|
50
.travis.yml
50
.travis.yml
@ -2,26 +2,19 @@ dist: trusty
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- nightly
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
- 7.1
|
||||
- 7.2
|
||||
- nightly
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
before_install:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
|
||||
- travis_retry composer self-update
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
|
||||
- travis_retry composer self-update
|
||||
|
||||
install:
|
||||
- composer require react/promise:2.*
|
||||
- composer require psr/http-message:1.*
|
||||
- travis_retry composer update --prefer-dist
|
||||
install: travis_retry composer update --prefer-dist
|
||||
|
||||
script: ./vendor/bin/phpunit --group default,ReactPromise
|
||||
|
||||
@ -45,21 +38,16 @@ jobs:
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover clover.xml
|
||||
- stage: Pull request coding standard
|
||||
if: type = pull_request
|
||||
|
||||
- stage: Code Quality
|
||||
php: 7.1
|
||||
env: CODING_STANDARD
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script:
|
||||
- |
|
||||
if [ $TRAVIS_BRANCH != "master" ]; then
|
||||
git remote set-branches --add origin $TRAVIS_BRANCH;
|
||||
git fetch origin $TRAVIS_BRANCH;
|
||||
fi
|
||||
- git merge-base origin/$TRAVIS_BRANCH $TRAVIS_PULL_REQUEST_SHA || git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge --unshallow
|
||||
- wget https://github.com/diff-sniffer/git/releases/download/0.1.0/git-phpcs.phar
|
||||
- php git-phpcs.phar origin/$TRAVIS_BRANCH...$TRAVIS_PULL_REQUEST_SHA
|
||||
# - stage: Coding standard
|
||||
# if: NOT type = pull_request
|
||||
# php: 7.1
|
||||
# install: travis_retry composer install --prefer-dist
|
||||
# script:
|
||||
# - ./vendor/bin/phpcs
|
||||
- ./vendor/bin/phpcs
|
||||
|
||||
- stage: Code Quality
|
||||
php: 7.1
|
||||
env: STATIC_ANALYSIS
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script: composer static-analysis
|
||||
|
@ -25,6 +25,8 @@ composer install
|
||||
./vendor/bin/phpunit
|
||||
```
|
||||
|
||||
Some tests have annotation `@see it('<description>')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description.
|
||||
|
||||
## Coding Standard
|
||||
Coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). To run the inspection:
|
||||
|
||||
|
@ -15,8 +15,11 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^4.0",
|
||||
"phpstan/phpstan": "^0.10.3",
|
||||
"phpstan/phpstan-phpunit": "^0.10.0",
|
||||
"phpunit/phpunit": "^7.2",
|
||||
"psr/http-message": "^1.0"
|
||||
"psr/http-message": "^1.0",
|
||||
"react/promise": "2.*"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist",
|
||||
@ -39,6 +42,7 @@
|
||||
"psr/http-message": "To use standard GraphQL server"
|
||||
},
|
||||
"scripts": {
|
||||
"lint" : "phpcs"
|
||||
"lint" : "phpcs",
|
||||
"static-analysis": "phpstan analyse --ansi -l 1 -c phpstan.neon.dist src tests"
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ first learn about GraphQL on [the official website](http://graphql.org/learn/).
|
||||
|
||||
# Installation
|
||||
|
||||
Using [composer](https://getcomposer.org/doc/00-intro.md), simply run:
|
||||
Using [composer](https://getcomposer.org/doc/00-intro.md), run:
|
||||
|
||||
```sh
|
||||
composer require webonyx/graphql-php
|
||||
|
3
phpstan.neon.dist
Normal file
3
phpstan.neon.dist
Normal file
@ -0,0 +1,3 @@
|
||||
includes:
|
||||
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||
- vendor/phpstan/phpstan-phpunit/rules.neon
|
@ -1,23 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL;
|
||||
|
||||
use GraphQL\Executor\Promise\Adapter\SyncPromise;
|
||||
|
||||
class Deferred
|
||||
{
|
||||
/**
|
||||
* @var \SplQueue
|
||||
*/
|
||||
/** @var \SplQueue */
|
||||
private static $queue;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
/** @var callable */
|
||||
private $callback;
|
||||
|
||||
/**
|
||||
* @var SyncPromise
|
||||
*/
|
||||
/** @var SyncPromise */
|
||||
public $promise;
|
||||
|
||||
public static function getQueue()
|
||||
@ -28,7 +25,7 @@ class Deferred
|
||||
public static function runQueue()
|
||||
{
|
||||
$q = self::$queue;
|
||||
while ($q && !$q->isEmpty()) {
|
||||
while ($q && ! $q->isEmpty()) {
|
||||
/** @var self $dfd */
|
||||
$dfd = $q->dequeue();
|
||||
$dfd->run();
|
||||
@ -38,7 +35,7 @@ class Deferred
|
||||
public function __construct(callable $callback)
|
||||
{
|
||||
$this->callback = $callback;
|
||||
$this->promise = new SyncPromise();
|
||||
$this->promise = new SyncPromise();
|
||||
self::getQueue()->enqueue($this);
|
||||
}
|
||||
|
||||
@ -47,7 +44,7 @@ class Deferred
|
||||
return $this->promise->then($onFulfilled, $onRejected);
|
||||
}
|
||||
|
||||
private function run()
|
||||
public function run() : void
|
||||
{
|
||||
try {
|
||||
$cb = $this->callback;
|
||||
|
@ -35,6 +35,7 @@ use GraphQL\Utils\TypeInfo;
|
||||
use GraphQL\Utils\Utils;
|
||||
use function array_keys;
|
||||
use function array_merge;
|
||||
use function array_reduce;
|
||||
use function array_values;
|
||||
use function get_class;
|
||||
use function is_array;
|
||||
@ -68,7 +69,7 @@ class Executor
|
||||
self::$UNDEFINED = Utils::undefined();
|
||||
}
|
||||
|
||||
$this->exeContext = $context;
|
||||
$this->exeContext = $context;
|
||||
$this->subFieldCache = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
@ -285,7 +286,7 @@ class Executor
|
||||
// field and its descendants will be omitted, and sibling fields will still
|
||||
// be executed. An execution which encounters errors will still result in a
|
||||
// resolved Promise.
|
||||
$data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue);
|
||||
$data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue);
|
||||
$result = $this->buildResponse($data);
|
||||
|
||||
// Note: we deviate here from the reference implementation a bit by always returning promise
|
||||
@ -995,8 +996,7 @@ class Executor
|
||||
* If the callback does not return a Promise, then this function will also not
|
||||
* return a Promise.
|
||||
*
|
||||
* @param mixed[] $values
|
||||
* @param \Closure $callback
|
||||
* @param mixed[] $values
|
||||
* @param Promise|mixed|null $initialValue
|
||||
* @return mixed[]
|
||||
*/
|
||||
@ -1291,23 +1291,18 @@ class Executor
|
||||
return $this->executeFields($returnType, $result, $path, $subFieldNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectType $returnType
|
||||
* @param $fieldNodes
|
||||
* @return ArrayObject
|
||||
*/
|
||||
private function collectSubFields(ObjectType $returnType, $fieldNodes): ArrayObject
|
||||
private function collectSubFields(ObjectType $returnType, $fieldNodes) : ArrayObject
|
||||
{
|
||||
if (!isset($this->subFieldCache[$returnType])) {
|
||||
if (! isset($this->subFieldCache[$returnType])) {
|
||||
$this->subFieldCache[$returnType] = new \SplObjectStorage();
|
||||
}
|
||||
if (!isset($this->subFieldCache[$returnType][$fieldNodes])) {
|
||||
if (! isset($this->subFieldCache[$returnType][$fieldNodes])) {
|
||||
// Collect sub-fields to execute to complete this value.
|
||||
$subFieldNodes = new \ArrayObject();
|
||||
$subFieldNodes = new \ArrayObject();
|
||||
$visitedFragmentNames = new \ArrayObject();
|
||||
|
||||
foreach ($fieldNodes as $fieldNode) {
|
||||
if (!isset($fieldNode->selectionSet)) {
|
||||
if (! isset($fieldNode->selectionSet)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ namespace GraphQL\Executor\Promise;
|
||||
|
||||
use GraphQL\Executor\Promise\Adapter\SyncPromise;
|
||||
use GraphQL\Utils\Utils;
|
||||
use React\Promise\Promise as ReactPromise;
|
||||
|
||||
/**
|
||||
* Convenience wrapper for promises represented by Promise Adapter
|
||||
|
182
src/GraphQL.php
182
src/GraphQL.php
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
@ -6,15 +9,19 @@ use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
|
||||
use GraphQL\Executor\Promise\Promise;
|
||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema as SchemaType;
|
||||
use GraphQL\Validator\DocumentValidator;
|
||||
use GraphQL\Validator\Rules\ValidationRule;
|
||||
use GraphQL\Validator\Rules\QueryComplexity;
|
||||
use GraphQL\Validator\Rules\ValidationRule;
|
||||
use function array_values;
|
||||
use function trigger_error;
|
||||
use const E_USER_DEPRECATED;
|
||||
|
||||
/**
|
||||
* This is the primary facade for fulfilling GraphQL operations.
|
||||
@ -58,32 +65,35 @@ class GraphQL
|
||||
* queries which are validated before persisting and assumed valid during execution)
|
||||
*
|
||||
* @api
|
||||
* @param \GraphQL\Type\Schema $schema
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $context
|
||||
* @param array|null $variableValues
|
||||
* @param string|null $operationName
|
||||
* @param callable $fieldResolver
|
||||
* @param array $validationRules
|
||||
*
|
||||
* @return ExecutionResult
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $context
|
||||
* @param mixed[]|null $variableValues
|
||||
* @param ValidationRule[] $validationRules
|
||||
*/
|
||||
public static function executeQuery(
|
||||
\GraphQL\Type\Schema $schema,
|
||||
SchemaType $schema,
|
||||
$source,
|
||||
$rootValue = null,
|
||||
$context = null,
|
||||
$variableValues = null,
|
||||
$operationName = null,
|
||||
callable $fieldResolver = null,
|
||||
array $validationRules = null
|
||||
)
|
||||
{
|
||||
?string $operationName = null,
|
||||
?callable $fieldResolver = null,
|
||||
?array $validationRules = null
|
||||
) : ExecutionResult {
|
||||
$promiseAdapter = new SyncPromiseAdapter();
|
||||
|
||||
$promise = self::promiseToExecute($promiseAdapter, $schema, $source, $rootValue, $context,
|
||||
$variableValues, $operationName, $fieldResolver, $validationRules);
|
||||
$promise = self::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$source,
|
||||
$rootValue,
|
||||
$context,
|
||||
$variableValues,
|
||||
$operationName,
|
||||
$fieldResolver,
|
||||
$validationRules
|
||||
);
|
||||
|
||||
return $promiseAdapter->wait($promise);
|
||||
}
|
||||
@ -93,30 +103,23 @@ class GraphQL
|
||||
* Useful for Async PHP platforms.
|
||||
*
|
||||
* @api
|
||||
* @param PromiseAdapter $promiseAdapter
|
||||
* @param \GraphQL\Type\Schema $schema
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $context
|
||||
* @param array|null $variableValues
|
||||
* @param string|null $operationName
|
||||
* @param callable $fieldResolver
|
||||
* @param array $validationRules
|
||||
*
|
||||
* @return Promise
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $context
|
||||
* @param mixed[]|null $variableValues
|
||||
* @param ValidationRule[]|null $validationRules
|
||||
*/
|
||||
public static function promiseToExecute(
|
||||
PromiseAdapter $promiseAdapter,
|
||||
\GraphQL\Type\Schema $schema,
|
||||
SchemaType $schema,
|
||||
$source,
|
||||
$rootValue = null,
|
||||
$context = null,
|
||||
$variableValues = null,
|
||||
$operationName = null,
|
||||
callable $fieldResolver = null,
|
||||
array $validationRules = null
|
||||
)
|
||||
{
|
||||
?string $operationName = null,
|
||||
?callable $fieldResolver = null,
|
||||
?array $validationRules = null
|
||||
) : Promise {
|
||||
try {
|
||||
if ($source instanceof DocumentNode) {
|
||||
$documentNode = $source;
|
||||
@ -125,36 +128,38 @@ class GraphQL
|
||||
}
|
||||
|
||||
// FIXME
|
||||
if (!empty($validationRules)) {
|
||||
foreach ($validationRules as $rule) {
|
||||
if ($rule instanceof QueryComplexity) {
|
||||
$rule->setRawVariableValues($variableValues);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (empty($validationRules)) {
|
||||
/** @var QueryComplexity $queryComplexity */
|
||||
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
|
||||
$queryComplexity->setRawVariableValues($variableValues);
|
||||
} else {
|
||||
foreach ($validationRules as $rule) {
|
||||
if (! ($rule instanceof QueryComplexity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rule->setRawVariableValues($variableValues);
|
||||
}
|
||||
}
|
||||
|
||||
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
|
||||
|
||||
if (!empty($validationErrors)) {
|
||||
if (! empty($validationErrors)) {
|
||||
return $promiseAdapter->createFulfilled(
|
||||
new ExecutionResult(null, $validationErrors)
|
||||
);
|
||||
} else {
|
||||
return Executor::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$documentNode,
|
||||
$rootValue,
|
||||
$context,
|
||||
$variableValues,
|
||||
$operationName,
|
||||
$fieldResolver
|
||||
);
|
||||
}
|
||||
|
||||
return Executor::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$documentNode,
|
||||
$rootValue,
|
||||
$context,
|
||||
$variableValues,
|
||||
$operationName,
|
||||
$fieldResolver
|
||||
);
|
||||
} catch (Error $e) {
|
||||
return $promiseAdapter->createFulfilled(
|
||||
new ExecutionResult(null, [$e])
|
||||
@ -165,29 +170,28 @@ class GraphQL
|
||||
/**
|
||||
* @deprecated Use executeQuery()->toArray() instead
|
||||
*
|
||||
* @param \GraphQL\Type\Schema $schema
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $contextValue
|
||||
* @param array|null $variableValues
|
||||
* @param string|null $operationName
|
||||
* @return Promise|array
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $contextValue
|
||||
* @param mixed[]|null $variableValues
|
||||
* @return Promise|mixed[]
|
||||
*/
|
||||
public static function execute(
|
||||
\GraphQL\Type\Schema $schema,
|
||||
SchemaType $schema,
|
||||
$source,
|
||||
$rootValue = null,
|
||||
$contextValue = null,
|
||||
$variableValues = null,
|
||||
$operationName = null
|
||||
)
|
||||
{
|
||||
?string $operationName = null
|
||||
) {
|
||||
trigger_error(
|
||||
__METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$result = self::promiseToExecute(
|
||||
$promiseAdapter = Executor::getPromiseAdapter(),
|
||||
|
||||
$promiseAdapter = Executor::getPromiseAdapter();
|
||||
$result = self::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$source,
|
||||
$rootValue,
|
||||
@ -199,40 +203,40 @@ class GraphQL
|
||||
if ($promiseAdapter instanceof SyncPromiseAdapter) {
|
||||
$result = $promiseAdapter->wait($result)->toArray();
|
||||
} else {
|
||||
$result = $result->then(function(ExecutionResult $r) {
|
||||
$result = $result->then(function (ExecutionResult $r) {
|
||||
return $r->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated renamed to executeQuery()
|
||||
*
|
||||
* @param \GraphQL\Type\Schema $schema
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $contextValue
|
||||
* @param array|null $variableValues
|
||||
* @param string|null $operationName
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $contextValue
|
||||
* @param mixed[]|null $variableValues
|
||||
*
|
||||
* @return ExecutionResult|Promise
|
||||
*/
|
||||
public static function executeAndReturnResult(
|
||||
\GraphQL\Type\Schema $schema,
|
||||
SchemaType $schema,
|
||||
$source,
|
||||
$rootValue = null,
|
||||
$contextValue = null,
|
||||
$variableValues = null,
|
||||
$operationName = null
|
||||
)
|
||||
{
|
||||
?string $operationName = null
|
||||
) {
|
||||
trigger_error(
|
||||
__METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$result = self::promiseToExecute(
|
||||
$promiseAdapter = Executor::getPromiseAdapter(),
|
||||
|
||||
$promiseAdapter = Executor::getPromiseAdapter();
|
||||
$result = self::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$source,
|
||||
$rootValue,
|
||||
@ -240,9 +244,11 @@ class GraphQL
|
||||
$variableValues,
|
||||
$operationName
|
||||
);
|
||||
|
||||
if ($promiseAdapter instanceof SyncPromiseAdapter) {
|
||||
$result = $promiseAdapter->wait($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@ -252,7 +258,7 @@ class GraphQL
|
||||
* @api
|
||||
* @return Directive[]
|
||||
*/
|
||||
public static function getStandardDirectives()
|
||||
public static function getStandardDirectives() : array
|
||||
{
|
||||
return array_values(Directive::getInternalDirectives());
|
||||
}
|
||||
@ -263,7 +269,7 @@ class GraphQL
|
||||
* @api
|
||||
* @return Type[]
|
||||
*/
|
||||
public static function getStandardTypes()
|
||||
public static function getStandardTypes() : array
|
||||
{
|
||||
return array_values(Type::getInternalTypes());
|
||||
}
|
||||
@ -274,23 +280,17 @@ class GraphQL
|
||||
* @api
|
||||
* @return ValidationRule[]
|
||||
*/
|
||||
public static function getStandardValidationRules()
|
||||
public static function getStandardValidationRules() : array
|
||||
{
|
||||
return array_values(DocumentValidator::defaultRules());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $fn
|
||||
*/
|
||||
public static function setDefaultFieldResolver(callable $fn)
|
||||
public static function setDefaultFieldResolver(callable $fn) : void
|
||||
{
|
||||
Executor::setDefaultFieldResolver($fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PromiseAdapter|null $promiseAdapter
|
||||
*/
|
||||
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
|
||||
public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) : void
|
||||
{
|
||||
Executor::setPromiseAdapter($promiseAdapter);
|
||||
}
|
||||
@ -301,7 +301,7 @@ class GraphQL
|
||||
* @deprecated Renamed to getStandardDirectives
|
||||
* @return Directive[]
|
||||
*/
|
||||
public static function getInternalDirectives()
|
||||
public static function getInternalDirectives() : array
|
||||
{
|
||||
return self::getStandardDirectives();
|
||||
}
|
||||
|
@ -263,7 +263,8 @@ class Visitor
|
||||
$visitFn = self::getVisitFn($visitor, $node->kind, $isLeaving);
|
||||
|
||||
if ($visitFn) {
|
||||
$result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
|
||||
$result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
|
||||
$editValue = null;
|
||||
|
||||
if ($result !== null) {
|
||||
if ($result instanceof VisitorOperation) {
|
||||
|
@ -1,6 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL;
|
||||
|
||||
use function trigger_error;
|
||||
use const E_USER_DEPRECATED;
|
||||
|
||||
trigger_error(
|
||||
'GraphQL\Schema is moved to GraphQL\Type\Schema',
|
||||
E_USER_DEPRECATED
|
||||
|
@ -4,11 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Server;
|
||||
|
||||
use const CASE_LOWER;
|
||||
use function array_change_key_case;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
use function json_last_error;
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* Structure representing parsed HTTP parameters for GraphQL operation
|
||||
|
@ -8,7 +8,7 @@ use GraphQL\Error\FormattedError;
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\Executor\Promise\Promise;
|
||||
use GraphQL\Utils;
|
||||
use GraphQL\Utils\Utils;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
@ -21,7 +21,7 @@ class InputObjectField
|
||||
/** @var string|null */
|
||||
public $description;
|
||||
|
||||
/** @var callback|InputType */
|
||||
/** @var callable|InputType */
|
||||
public $type;
|
||||
|
||||
/** @var InputValueDefinitionNode|null */
|
||||
|
@ -12,7 +12,6 @@ use function is_array;
|
||||
use function is_callable;
|
||||
use function is_string;
|
||||
use function sprintf;
|
||||
use function spritnf;
|
||||
|
||||
/**
|
||||
* Class InputObjectType
|
||||
@ -70,7 +69,7 @@ class InputObjectType extends Type implements InputType, NamedType
|
||||
|
||||
if (! is_array($fields)) {
|
||||
throw new InvariantViolation(
|
||||
spritnf('%s fields must be an array or a callable which returns such an array.', $this->name)
|
||||
sprintf('%s fields must be an array or a callable which returns such an array.', $this->name)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
|
||||
}
|
||||
$int = intval($num);
|
||||
// int cast with == used for performance reasons
|
||||
// @codingStandardsIgnoreLine
|
||||
// phpcs:ignore
|
||||
if ($int != $num) {
|
||||
throw new Error(
|
||||
'Int cannot represent non-integer value: ' .
|
||||
|
@ -255,7 +255,7 @@ class AST
|
||||
}
|
||||
if (is_float($serialized)) {
|
||||
// int cast with == used for performance reasons
|
||||
// @codingStandardsIgnoreLine
|
||||
// phpcs:ignore
|
||||
if ((int) $serialized == $serialized) {
|
||||
return new IntValueNode(['value' => $serialized]);
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ namespace GraphQL\Validator\Rules;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\DirectiveNode;
|
||||
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Language\DirectiveLocation;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
use function count;
|
||||
@ -59,7 +61,9 @@ class KnownDirectives extends ValidationRule
|
||||
}
|
||||
|
||||
/**
|
||||
* @param (Node|NodeList)[] $ancestors
|
||||
* @param Node[]|NodeList[] $ancestors The type is actually (Node|NodeList)[] but this PSR-5 syntax is so far not supported by most of the tools
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getDirectiveLocationForASTPath(array $ancestors)
|
||||
{
|
||||
|
@ -44,10 +44,10 @@ class VariablesDefaultValueAllowed extends ValidationRule
|
||||
|
||||
return Visitor::skipNode();
|
||||
},
|
||||
NodeKind::SELECTION_SET => function (SelectionSetNode $node) use ($context) {
|
||||
NodeKind::SELECTION_SET => function (SelectionSetNode $node) {
|
||||
return Visitor::skipNode();
|
||||
},
|
||||
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
|
||||
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) {
|
||||
return Visitor::skipNode();
|
||||
},
|
||||
];
|
||||
|
@ -1,79 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Deferred;
|
||||
use GraphQL\Error\UserError;
|
||||
use GraphQL\Error\Warning;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Tests\Executor\TestClasses\Cat;
|
||||
use GraphQL\Tests\Executor\TestClasses\Dog;
|
||||
use GraphQL\Tests\Executor\TestClasses\Human;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
|
||||
require_once __DIR__ . '/TestClasses.php';
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* DESCRIBE: Execute: Handles execution of abstract types with promises
|
||||
*/
|
||||
class AbstractPromiseTest extends TestCase
|
||||
{
|
||||
// DESCRIBE: Execute: Handles execution of abstract types with promises
|
||||
|
||||
/**
|
||||
* @see it('isTypeOf used to resolve runtime type for Interface')
|
||||
*/
|
||||
public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void
|
||||
{
|
||||
$PetType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'name' => 'Pet',
|
||||
'fields' => [
|
||||
'name' => [ 'type' => Type::string() ]
|
||||
]
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [ $PetType ],
|
||||
'isTypeOf' => function($obj) {
|
||||
return new Deferred(function() use ($obj) {
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$PetType],
|
||||
'isTypeOf' => function ($obj) {
|
||||
return new Deferred(function () use ($obj) {
|
||||
return $obj instanceof Dog;
|
||||
});
|
||||
},
|
||||
'fields' => [
|
||||
'name' => [ 'type' => Type::string() ],
|
||||
'woofs' => [ 'type' => Type::boolean() ],
|
||||
]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [ $PetType ],
|
||||
'isTypeOf' => function($obj) {
|
||||
return new Deferred(function() use ($obj) {
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$PetType],
|
||||
'isTypeOf' => function ($obj) {
|
||||
return new Deferred(function () use ($obj) {
|
||||
return $obj instanceof Cat;
|
||||
});
|
||||
},
|
||||
'fields' => [
|
||||
'name' => [ 'type' => Type::string() ],
|
||||
'meows' => [ 'type' => Type::boolean() ],
|
||||
]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function() {
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false)
|
||||
new Cat('Garfield', false),
|
||||
];
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'types' => [ $CatType, $DogType ]
|
||||
'types' => [$CatType, $DogType],
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -93,10 +97,10 @@ class AbstractPromiseTest extends TestCase
|
||||
$expected = [
|
||||
'data' => [
|
||||
'pets' => [
|
||||
[ 'name' => 'Odie', 'woofs' => true ],
|
||||
[ 'name' => 'Garfield', 'meows' => false ]
|
||||
]
|
||||
]
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
@ -107,58 +111,57 @@ class AbstractPromiseTest extends TestCase
|
||||
*/
|
||||
public function testIsTypeOfCanBeRejected() : void
|
||||
{
|
||||
|
||||
$PetType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'name' => 'Pet',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$PetType],
|
||||
'isTypeOf' => function () {
|
||||
'isTypeOf' => function () {
|
||||
return new Deferred(function () {
|
||||
throw new UserError('We are testing this error');
|
||||
});
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$PetType],
|
||||
'isTypeOf' => function ($obj) {
|
||||
'isTypeOf' => function ($obj) {
|
||||
return new Deferred(function () use ($obj) {
|
||||
return $obj instanceof Cat;
|
||||
});
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false)
|
||||
new Cat('Garfield', false),
|
||||
];
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'types' => [$CatType, $DogType]
|
||||
'types' => [$CatType, $DogType],
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -176,21 +179,21 @@ class AbstractPromiseTest extends TestCase
|
||||
$result = GraphQL::executeQuery($schema, $query)->toArray();
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'pets' => [null, null]
|
||||
'data' => [
|
||||
'pets' => [null, null],
|
||||
],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'We are testing this error',
|
||||
'message' => 'We are testing this error',
|
||||
'locations' => [['line' => 2, 'column' => 7]],
|
||||
'path' => ['pets', 0],
|
||||
'path' => ['pets', 0],
|
||||
],
|
||||
[
|
||||
'message' => 'We are testing this error',
|
||||
'message' => 'We are testing this error',
|
||||
'locations' => [['line' => 2, 'column' => 7]],
|
||||
'path' => ['pets', 1]
|
||||
]
|
||||
]
|
||||
'path' => ['pets', 1],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, $result);
|
||||
@ -201,50 +204,49 @@ class AbstractPromiseTest extends TestCase
|
||||
*/
|
||||
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
|
||||
{
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'isTypeOf' => function ($obj) {
|
||||
return new Deferred(function () use ($obj) {
|
||||
return $obj instanceof Dog;
|
||||
});
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'isTypeOf' => function ($obj) {
|
||||
return new Deferred(function () use ($obj) {
|
||||
return $obj instanceof Cat;
|
||||
});
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$PetType = new UnionType([
|
||||
'name' => 'Pet',
|
||||
'types' => [$DogType, $CatType]
|
||||
'name' => 'Pet',
|
||||
'types' => [$DogType, $CatType],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [new Dog('Odie', true), new Cat('Garfield', false)];
|
||||
}
|
||||
]
|
||||
]
|
||||
])
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -266,9 +268,9 @@ class AbstractPromiseTest extends TestCase
|
||||
'data' => [
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false]
|
||||
]
|
||||
]
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
@ -280,7 +282,7 @@ class AbstractPromiseTest extends TestCase
|
||||
public function testResolveTypeOnInterfaceYieldsUsefulError() : void
|
||||
{
|
||||
$PetType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'name' => 'Pet',
|
||||
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
|
||||
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
|
||||
if ($obj instanceof Dog) {
|
||||
@ -292,58 +294,59 @@ class AbstractPromiseTest extends TestCase
|
||||
if ($obj instanceof Human) {
|
||||
return $HumanType;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$HumanType = new ObjectType([
|
||||
'name' => 'Human',
|
||||
'name' => 'Human',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return new Deferred(function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false),
|
||||
new Human('Jon')
|
||||
new Human('Jon'),
|
||||
];
|
||||
});
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'types' => [$CatType, $DogType]
|
||||
'types' => [$CatType, $DogType],
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -361,20 +364,20 @@ class AbstractPromiseTest extends TestCase
|
||||
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'data' => [
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
null
|
||||
]
|
||||
null,
|
||||
],
|
||||
],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
|
||||
'locations' => [['line' => 2, 'column' => 7]],
|
||||
'path' => ['pets', 2]
|
||||
'locations' => [['line' => 2, 'column' => 7]],
|
||||
'path' => ['pets', 2],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, $result);
|
||||
@ -385,32 +388,31 @@ class AbstractPromiseTest extends TestCase
|
||||
*/
|
||||
public function testResolveTypeOnUnionYieldsUsefulError() : void
|
||||
{
|
||||
|
||||
$HumanType = new ObjectType([
|
||||
'name' => 'Human',
|
||||
'name' => 'Human',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$PetType = new UnionType([
|
||||
'name' => 'Pet',
|
||||
'name' => 'Pet',
|
||||
'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) {
|
||||
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
|
||||
if ($obj instanceof Dog) {
|
||||
@ -422,28 +424,29 @@ class AbstractPromiseTest extends TestCase
|
||||
if ($obj instanceof Human) {
|
||||
return $HumanType;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
},
|
||||
'types' => [$DogType, $CatType]
|
||||
'types' => [$DogType, $CatType],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false),
|
||||
new Human('Jon')
|
||||
new Human('Jon'),
|
||||
];
|
||||
}
|
||||
]
|
||||
]
|
||||
])
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -462,20 +465,20 @@ class AbstractPromiseTest extends TestCase
|
||||
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'data' => [
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
null
|
||||
]
|
||||
null,
|
||||
],
|
||||
],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
|
||||
'locations' => [['line' => 2, 'column' => 7]],
|
||||
'path' => ['pets', 2]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 2, 'column' => 7]],
|
||||
'path' => ['pets', 2],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, $result);
|
||||
@ -487,7 +490,7 @@ class AbstractPromiseTest extends TestCase
|
||||
public function testResolveTypeAllowsResolvingWithTypeName() : void
|
||||
{
|
||||
$PetType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'name' => 'Pet',
|
||||
'resolveType' => function ($obj) {
|
||||
return new Deferred(function () use ($obj) {
|
||||
if ($obj instanceof Dog) {
|
||||
@ -496,49 +499,49 @@ class AbstractPromiseTest extends TestCase
|
||||
if ($obj instanceof Cat) {
|
||||
return 'Cat';
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false)
|
||||
new Cat('Garfield', false),
|
||||
];
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'types' => [$CatType, $DogType]
|
||||
'types' => [$CatType, $DogType],
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -560,8 +563,8 @@ class AbstractPromiseTest extends TestCase
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
@ -571,53 +574,52 @@ class AbstractPromiseTest extends TestCase
|
||||
*/
|
||||
public function testResolveTypeCanBeCaught() : void
|
||||
{
|
||||
|
||||
$PetType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'name' => 'Pet',
|
||||
'resolveType' => function () {
|
||||
return new Deferred(function () {
|
||||
throw new UserError('We are testing this error');
|
||||
});
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false)
|
||||
new Cat('Garfield', false),
|
||||
];
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'types' => [$CatType, $DogType]
|
||||
'types' => [$CatType, $DogType],
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -635,21 +637,21 @@ class AbstractPromiseTest extends TestCase
|
||||
$result = GraphQL::executeQuery($schema, $query)->toArray();
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'pets' => [null, null]
|
||||
'data' => [
|
||||
'pets' => [null, null],
|
||||
],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'We are testing this error',
|
||||
'message' => 'We are testing this error',
|
||||
'locations' => [['line' => 2, 'column' => 7]],
|
||||
'path' => ['pets', 0]
|
||||
'path' => ['pets', 0],
|
||||
],
|
||||
[
|
||||
'message' => 'We are testing this error',
|
||||
'message' => 'We are testing this error',
|
||||
'locations' => [['line' => 2, 'column' => 7]],
|
||||
'path' => ['pets', 1]
|
||||
]
|
||||
]
|
||||
'path' => ['pets', 1],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, $result);
|
||||
|
@ -1,23 +1,28 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
require_once __DIR__ . '/TestClasses.php';
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Tests\Executor\TestClasses\Cat;
|
||||
use GraphQL\Tests\Executor\TestClasses\Dog;
|
||||
use GraphQL\Tests\Executor\TestClasses\Human;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Execute: Handles execution of abstract types
|
||||
*/
|
||||
class AbstractTest extends TestCase
|
||||
{
|
||||
// Execute: Handles execution of abstract types
|
||||
|
||||
/**
|
||||
* @see it('isTypeOf used to resolve runtime type for Interface')
|
||||
*/
|
||||
@ -25,48 +30,50 @@ class AbstractTest extends TestCase
|
||||
{
|
||||
// isTypeOf used to resolve runtime type for Interface
|
||||
$petType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'name' => 'Pet',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
// Added to interface type when defined
|
||||
$dogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$petType],
|
||||
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()]
|
||||
]
|
||||
'isTypeOf' => function ($obj) {
|
||||
return $obj instanceof Dog;
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
],
|
||||
]);
|
||||
|
||||
$catType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$petType],
|
||||
'isTypeOf' => function ($obj) {
|
||||
'isTypeOf' => function ($obj) {
|
||||
return $obj instanceof Cat;
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($petType),
|
||||
'type' => Type::listOf($petType),
|
||||
'resolve' => function () {
|
||||
return [new Dog('Odie', true), new Cat('Garfield', false)];
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'types' => [$catType, $dogType]
|
||||
'types' => [$catType, $dogType],
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -84,8 +91,8 @@ class AbstractTest extends TestCase
|
||||
$expected = new ExecutionResult([
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false]
|
||||
]
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
],
|
||||
]);
|
||||
|
||||
$result = Executor::execute($schema, Parser::parse($query));
|
||||
@ -98,42 +105,44 @@ class AbstractTest extends TestCase
|
||||
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
|
||||
{
|
||||
$dogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()]
|
||||
]
|
||||
'name' => 'Dog',
|
||||
'isTypeOf' => function ($obj) {
|
||||
return $obj instanceof Dog;
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
],
|
||||
]);
|
||||
|
||||
$catType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'isTypeOf' => function ($obj) {
|
||||
return $obj instanceof Cat;
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$petType = new UnionType([
|
||||
'name' => 'Pet',
|
||||
'types' => [$dogType, $catType]
|
||||
'name' => 'Pet',
|
||||
'types' => [$dogType, $catType],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($petType),
|
||||
'resolve' => function() {
|
||||
return [ new Dog('Odie', true), new Cat('Garfield', false) ];
|
||||
}
|
||||
]
|
||||
]
|
||||
])
|
||||
'type' => Type::listOf($petType),
|
||||
'resolve' => function () {
|
||||
return [new Dog('Odie', true), new Cat('Garfield', false)];
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -151,8 +160,8 @@ class AbstractTest extends TestCase
|
||||
$expected = new ExecutionResult([
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false]
|
||||
]
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($schema, Parser::parse($query)));
|
||||
@ -161,14 +170,14 @@ class AbstractTest extends TestCase
|
||||
/**
|
||||
* @see it('resolveType on Interface yields useful error')
|
||||
*/
|
||||
function testResolveTypeOnInterfaceYieldsUsefulError()
|
||||
public function testResolveTypeOnInterfaceYieldsUsefulError() : void
|
||||
{
|
||||
$DogType = null;
|
||||
$CatType = null;
|
||||
$DogType = null;
|
||||
$CatType = null;
|
||||
$HumanType = null;
|
||||
|
||||
$PetType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'name' => 'Pet',
|
||||
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
|
||||
if ($obj instanceof Dog) {
|
||||
return $DogType;
|
||||
@ -179,58 +188,58 @@ class AbstractTest extends TestCase
|
||||
if ($obj instanceof Human) {
|
||||
return $HumanType;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$HumanType = new ObjectType([
|
||||
'name' => 'Human',
|
||||
'name' => 'Human',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false),
|
||||
new Human('Jon')
|
||||
new Human('Jon'),
|
||||
];
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'types' => [$DogType, $CatType]
|
||||
'types' => [$DogType, $CatType],
|
||||
]);
|
||||
|
||||
|
||||
$query = '{
|
||||
pets {
|
||||
name
|
||||
@ -244,20 +253,21 @@ class AbstractTest extends TestCase
|
||||
}';
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'data' => [
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
null
|
||||
]
|
||||
null,
|
||||
],
|
||||
],
|
||||
'errors' => [[
|
||||
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
|
||||
'locations' => [['line' => 2, 'column' => 11]],
|
||||
'path' => ['pets', 2]
|
||||
]]
|
||||
'locations' => [['line' => 2, 'column' => 11]],
|
||||
'path' => ['pets', 2],
|
||||
],
|
||||
],
|
||||
];
|
||||
$actual = GraphQL::executeQuery($schema, $query)->toArray(true);
|
||||
$actual = GraphQL::executeQuery($schema, $query)->toArray(true);
|
||||
|
||||
$this->assertArraySubset($expected, $actual);
|
||||
}
|
||||
@ -268,30 +278,30 @@ class AbstractTest extends TestCase
|
||||
public function testResolveTypeOnUnionYieldsUsefulError() : void
|
||||
{
|
||||
$HumanType = new ObjectType([
|
||||
'name' => 'Human',
|
||||
'name' => 'Human',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$PetType = new UnionType([
|
||||
'name' => 'Pet',
|
||||
'name' => 'Pet',
|
||||
'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) {
|
||||
if ($obj instanceof Dog) {
|
||||
return $DogType;
|
||||
@ -303,25 +313,25 @@ class AbstractTest extends TestCase
|
||||
return $HumanType;
|
||||
}
|
||||
},
|
||||
'types' => [$DogType, $CatType]
|
||||
'types' => [$DogType, $CatType],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false),
|
||||
new Human('Jon')
|
||||
new Human('Jon'),
|
||||
];
|
||||
}
|
||||
]
|
||||
]
|
||||
])
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -337,22 +347,27 @@ class AbstractTest extends TestCase
|
||||
}
|
||||
}';
|
||||
|
||||
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
|
||||
$result = GraphQL::executeQuery($schema, $query)->toArray(true);
|
||||
$expected = [
|
||||
'data' => [
|
||||
'data' => [
|
||||
'pets' => [
|
||||
['name' => 'Odie',
|
||||
'woofs' => true],
|
||||
['name' => 'Garfield',
|
||||
'meows' => false],
|
||||
null
|
||||
]
|
||||
[
|
||||
'name' => 'Odie',
|
||||
'woofs' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'Garfield',
|
||||
'meows' => false,
|
||||
],
|
||||
null,
|
||||
],
|
||||
],
|
||||
'errors' => [[
|
||||
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
|
||||
'locations' => [['line' => 2, 'column' => 11]],
|
||||
'path' => ['pets', 2]
|
||||
]]
|
||||
'locations' => [['line' => 2, 'column' => 11]],
|
||||
'path' => ['pets', 2],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertArraySubset($expected, $result);
|
||||
}
|
||||
@ -363,25 +378,25 @@ class AbstractTest extends TestCase
|
||||
public function testReturningInvalidValueFromResolveTypeYieldsUsefulError() : void
|
||||
{
|
||||
$fooInterface = new InterfaceType([
|
||||
'name' => 'FooInterface',
|
||||
'fields' => ['bar' => ['type' => Type::string()]],
|
||||
'name' => 'FooInterface',
|
||||
'fields' => ['bar' => ['type' => Type::string()]],
|
||||
'resolveType' => function () {
|
||||
return [];
|
||||
},
|
||||
]);
|
||||
|
||||
$fooObject = new ObjectType([
|
||||
'name' => 'FooObject',
|
||||
'fields' => ['bar' => ['type' => Type::string()]],
|
||||
'name' => 'FooObject',
|
||||
'fields' => ['bar' => ['type' => Type::string()]],
|
||||
'interfaces' => [$fooInterface],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'foo' => [
|
||||
'type' => $fooInterface,
|
||||
'type' => $fooInterface,
|
||||
'resolve' => function () {
|
||||
return 'dummy';
|
||||
},
|
||||
@ -394,18 +409,18 @@ class AbstractTest extends TestCase
|
||||
$result = GraphQL::executeQuery($schema, '{ foo { bar } }');
|
||||
|
||||
$expected = [
|
||||
'data' => ['foo' => null],
|
||||
'data' => ['foo' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'Internal server error',
|
||||
'message' => 'Internal server error',
|
||||
'debugMessage' =>
|
||||
'Abstract type FooInterface must resolve to an Object type at ' .
|
||||
'runtime for field Query.foo with value "dummy", received "[]". ' .
|
||||
'Either the FooInterface type should provide a "resolveType" ' .
|
||||
'function or each possible type should provide an "isTypeOf" function.',
|
||||
'locations' => [['line' => 1, 'column' => 3]],
|
||||
'path' => ['foo'],
|
||||
'category' => 'internal',
|
||||
'locations' => [['line' => 1, 'column' => 3]],
|
||||
'path' => ['foo'],
|
||||
'category' => 'internal',
|
||||
],
|
||||
],
|
||||
];
|
||||
@ -418,51 +433,56 @@ class AbstractTest extends TestCase
|
||||
public function testResolveTypeAllowsResolvingWithTypeName() : void
|
||||
{
|
||||
$PetType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'resolveType' => function($obj) {
|
||||
if ($obj instanceof Dog) return 'Dog';
|
||||
if ($obj instanceof Cat) return 'Cat';
|
||||
'name' => 'Pet',
|
||||
'resolveType' => function ($obj) {
|
||||
if ($obj instanceof Dog) {
|
||||
return 'Dog';
|
||||
}
|
||||
if ($obj instanceof Cat) {
|
||||
return 'Cat';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
'fields' => [
|
||||
'name' => [ 'type' => Type::string() ]
|
||||
]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [ $PetType ],
|
||||
'fields' => [
|
||||
'name' => [ 'type' => Type::string() ],
|
||||
'woofs' => [ 'type' => Type::boolean() ],
|
||||
]
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
],
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [ $PetType ],
|
||||
'fields' => [
|
||||
'name' => [ 'type' => Type::string() ],
|
||||
'meows' => [ 'type' => Type::boolean() ],
|
||||
]
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function() {
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false)
|
||||
new Cat('Garfield', false),
|
||||
];
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'types' => [ $CatType, $DogType ]
|
||||
'types' => [$CatType, $DogType],
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -479,51 +499,52 @@ class AbstractTest extends TestCase
|
||||
|
||||
$result = GraphQL::executeQuery($schema, $query)->toArray();
|
||||
|
||||
$this->assertEquals([
|
||||
'data' => [
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false]
|
||||
]
|
||||
]
|
||||
], $result);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'data' => [
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
],
|
||||
],
|
||||
],
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
public function testHintsOnConflictingTypeInstancesInResolveType() : void
|
||||
{
|
||||
$createTest = function() use (&$iface) {
|
||||
$createTest = function () use (&$iface) {
|
||||
return new ObjectType([
|
||||
'name' => 'Test',
|
||||
'fields' => [
|
||||
'a' => Type::string()
|
||||
'name' => 'Test',
|
||||
'fields' => [
|
||||
'a' => Type::string(),
|
||||
],
|
||||
'interfaces' => function() use ($iface) {
|
||||
'interfaces' => function () use ($iface) {
|
||||
return [$iface];
|
||||
}
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
$iface = new InterfaceType([
|
||||
'name' => 'Node',
|
||||
'fields' => [
|
||||
'a' => Type::string()
|
||||
'name' => 'Node',
|
||||
'fields' => [
|
||||
'a' => Type::string(),
|
||||
],
|
||||
'resolveType' => function() use (&$createTest) {
|
||||
'resolveType' => function () use (&$createTest) {
|
||||
return $createTest();
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'node' => $iface,
|
||||
'test' => $createTest()
|
||||
]
|
||||
'test' => $createTest(),
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $query,
|
||||
]);
|
||||
$schema = new Schema(['query' => $query]);
|
||||
$schema->assertValid();
|
||||
|
||||
$query = '
|
||||
@ -537,9 +558,9 @@ class AbstractTest extends TestCase
|
||||
$result = Executor::execute($schema, Parser::parse($query), ['node' => ['a' => 'value']]);
|
||||
|
||||
$this->assertEquals(
|
||||
'Schema must contain unique named types but contains multiple types named "Test". '.
|
||||
'Make sure that `resolveType` function of abstract type "Node" returns the same type instance '.
|
||||
'as referenced anywhere else within the schema '.
|
||||
'Schema must contain unique named types but contains multiple types named "Test". ' .
|
||||
'Make sure that `resolveType` function of abstract type "Node" returns the same type instance ' .
|
||||
'as referenced anywhere else within the schema ' .
|
||||
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
|
||||
$result->errors[0]->getMessage()
|
||||
);
|
||||
|
@ -1,33 +1,44 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Deferred;
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Utils\Utils;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function in_array;
|
||||
|
||||
class DeferredFieldsTest extends TestCase
|
||||
{
|
||||
/** @var ObjectType */
|
||||
private $userType;
|
||||
|
||||
/** @var ObjectType */
|
||||
private $storyType;
|
||||
|
||||
/** @var ObjectType */
|
||||
private $categoryType;
|
||||
|
||||
/** @var */
|
||||
private $path;
|
||||
|
||||
/** @var mixed[][] */
|
||||
private $storyDataSource;
|
||||
|
||||
/** @var mixed[][] */
|
||||
private $userDataSource;
|
||||
|
||||
/** @var mixed[][] */
|
||||
private $categoryDataSource;
|
||||
|
||||
/** @var ObjectType */
|
||||
private $queryType;
|
||||
|
||||
public function setUp()
|
||||
@ -54,127 +65,152 @@ class DeferredFieldsTest extends TestCase
|
||||
$this->categoryDataSource = [
|
||||
['id' => 1, 'name' => 'Category #1', 'topStoryId' => 8],
|
||||
['id' => 2, 'name' => 'Category #2', 'topStoryId' => 3],
|
||||
['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9]
|
||||
['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9],
|
||||
];
|
||||
|
||||
$this->path = [];
|
||||
$this->path = [];
|
||||
$this->userType = new ObjectType([
|
||||
'name' => 'User',
|
||||
'fields' => function() {
|
||||
'name' => 'User',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($user, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return $user['name'];
|
||||
}
|
||||
},
|
||||
],
|
||||
'bestFriend' => [
|
||||
'type' => $this->userType,
|
||||
'resolve' => function($user, $args, $context, ResolveInfo $info) {
|
||||
'type' => $this->userType,
|
||||
'resolve' => function ($user, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return new Deferred(function() use ($user) {
|
||||
return new Deferred(function () use ($user) {
|
||||
$this->path[] = 'deferred-for-best-friend-of-' . $user['id'];
|
||||
return Utils::find($this->userDataSource, function($entry) use ($user) {
|
||||
return $entry['id'] === $user['bestFriendId'];
|
||||
});
|
||||
|
||||
return Utils::find(
|
||||
$this->userDataSource,
|
||||
function ($entry) use ($user) {
|
||||
return $entry['id'] === $user['bestFriendId'];
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->storyType = new ObjectType([
|
||||
'name' => 'Story',
|
||||
'name' => 'Story',
|
||||
'fields' => [
|
||||
'title' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function($entry, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
return $entry['title'];
|
||||
}
|
||||
],
|
||||
'author' => [
|
||||
'type' => $this->userType,
|
||||
'resolve' => function($story, $args, $context, ResolveInfo $info) {
|
||||
'title' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($entry, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return new Deferred(function() use ($story) {
|
||||
return $entry['title'];
|
||||
},
|
||||
],
|
||||
'author' => [
|
||||
'type' => $this->userType,
|
||||
'resolve' => function ($story, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return new Deferred(function () use ($story) {
|
||||
$this->path[] = 'deferred-for-story-' . $story['id'] . '-author';
|
||||
return Utils::find($this->userDataSource, function($entry) use ($story) {
|
||||
return $entry['id'] === $story['authorId'];
|
||||
});
|
||||
|
||||
return Utils::find(
|
||||
$this->userDataSource,
|
||||
function ($entry) use ($story) {
|
||||
return $entry['id'] === $story['authorId'];
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->categoryType = new ObjectType([
|
||||
'name' => 'Category',
|
||||
'name' => 'Category',
|
||||
'fields' => [
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function($category, $args, $context, ResolveInfo $info) {
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return $category['name'];
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
'stories' => [
|
||||
'type' => Type::listOf($this->storyType),
|
||||
'resolve' => function($category, $args, $context, ResolveInfo $info) {
|
||||
'stories' => [
|
||||
'type' => Type::listOf($this->storyType),
|
||||
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
return Utils::filter($this->storyDataSource, function($story) use ($category) {
|
||||
return in_array($category['id'], $story['categoryIds']);
|
||||
});
|
||||
}
|
||||
|
||||
return Utils::filter(
|
||||
$this->storyDataSource,
|
||||
function ($story) use ($category) {
|
||||
return in_array($category['id'], $story['categoryIds']);
|
||||
}
|
||||
);
|
||||
},
|
||||
],
|
||||
'topStory' => [
|
||||
'type' => $this->storyType,
|
||||
'resolve' => function($category, $args, $context, ResolveInfo $info) {
|
||||
'type' => $this->storyType,
|
||||
'resolve' => function ($category, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return new Deferred(function () use ($category) {
|
||||
$this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory';
|
||||
return Utils::find($this->storyDataSource, function($story) use ($category) {
|
||||
return $story['id'] === $category['topStoryId'];
|
||||
});
|
||||
|
||||
return Utils::find(
|
||||
$this->storyDataSource,
|
||||
function ($story) use ($category) {
|
||||
return $story['id'] === $category['topStoryId'];
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->queryType = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'topStories' => [
|
||||
'type' => Type::listOf($this->storyType),
|
||||
'resolve' => function($val, $args, $context, ResolveInfo $info) {
|
||||
'topStories' => [
|
||||
'type' => Type::listOf($this->storyType),
|
||||
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
return Utils::filter($this->storyDataSource, function($story) {
|
||||
return $story['id'] % 2 === 1;
|
||||
});
|
||||
}
|
||||
|
||||
return Utils::filter(
|
||||
$this->storyDataSource,
|
||||
function ($story) {
|
||||
return $story['id'] % 2 === 1;
|
||||
}
|
||||
);
|
||||
},
|
||||
],
|
||||
'featuredCategory' => [
|
||||
'type' => $this->categoryType,
|
||||
'resolve' => function($val, $args, $context, ResolveInfo $info) {
|
||||
'type' => $this->categoryType,
|
||||
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return $this->categoryDataSource[0];
|
||||
}
|
||||
},
|
||||
],
|
||||
'categories' => [
|
||||
'type' => Type::listOf($this->categoryType),
|
||||
'resolve' => function($val, $args, $context, ResolveInfo $info) {
|
||||
'categories' => [
|
||||
'type' => Type::listOf($this->categoryType),
|
||||
'resolve' => function ($val, $args, $context, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return $this->categoryDataSource;
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
parent::setUp();
|
||||
@ -202,12 +238,12 @@ class DeferredFieldsTest extends TestCase
|
||||
');
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $this->queryType
|
||||
'query' => $this->queryType,
|
||||
]);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'topStories' => [
|
||||
'topStories' => [
|
||||
['title' => 'Story #1', 'author' => ['name' => 'John']],
|
||||
['title' => 'Story #3', 'author' => ['name' => 'Joe']],
|
||||
['title' => 'Story #5', 'author' => ['name' => 'John']],
|
||||
@ -220,9 +256,9 @@ class DeferredFieldsTest extends TestCase
|
||||
['title' => 'Story #4', 'author' => ['name' => 'Joe']],
|
||||
['title' => 'Story #6', 'author' => ['name' => 'Jane']],
|
||||
['title' => 'Story #8', 'author' => ['name' => 'John']],
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = Executor::execute($schema, $query);
|
||||
@ -292,12 +328,12 @@ class DeferredFieldsTest extends TestCase
|
||||
');
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $this->queryType
|
||||
'query' => $this->queryType,
|
||||
]);
|
||||
|
||||
$author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']];
|
||||
$author2 = ['name' => 'Jane', 'bestFriend' => ['name' => 'Joe']];
|
||||
$author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']];
|
||||
$author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']];
|
||||
$author4 = ['name' => 'Dirk', 'bestFriend' => ['name' => 'John']];
|
||||
|
||||
$expected = [
|
||||
@ -306,8 +342,8 @@ class DeferredFieldsTest extends TestCase
|
||||
['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]],
|
||||
['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]],
|
||||
['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]],
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = Executor::execute($schema, $query);
|
||||
@ -352,54 +388,56 @@ class DeferredFieldsTest extends TestCase
|
||||
public function testComplexRecursiveDeferredFields() : void
|
||||
{
|
||||
$complexType = new ObjectType([
|
||||
'name' => 'ComplexType',
|
||||
'fields' => function() use (&$complexType) {
|
||||
'name' => 'ComplexType',
|
||||
'fields' => function () use (&$complexType) {
|
||||
return [
|
||||
'sync' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function($v, $a, $c, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
return 'sync';
|
||||
}
|
||||
],
|
||||
'deferred' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function($v, $a, $c, ResolveInfo $info) {
|
||||
'sync' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return new Deferred(function() use ($info) {
|
||||
return 'sync';
|
||||
},
|
||||
],
|
||||
'deferred' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return new Deferred(function () use ($info) {
|
||||
$this->path[] = ['!dfd for: ', $info->path];
|
||||
|
||||
return 'deferred';
|
||||
});
|
||||
}
|
||||
},
|
||||
],
|
||||
'nest' => [
|
||||
'type' => $complexType,
|
||||
'resolve' => function($v, $a, $c, ResolveInfo $info) {
|
||||
'nest' => [
|
||||
'type' => $complexType,
|
||||
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
],
|
||||
'deferredNest' => [
|
||||
'type' => $complexType,
|
||||
'resolve' => function($v, $a, $c, ResolveInfo $info) {
|
||||
'type' => $complexType,
|
||||
'resolve' => function ($v, $a, $c, ResolveInfo $info) {
|
||||
$this->path[] = $info->path;
|
||||
|
||||
return new Deferred(function() use ($info) {
|
||||
return new Deferred(function () use ($info) {
|
||||
$this->path[] = ['!dfd nest for: ', $info->path];
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $complexType
|
||||
]);
|
||||
$schema = new Schema(['query' => $complexType]);
|
||||
|
||||
$query = Parser::parse('
|
||||
$query = Parser::parse('
|
||||
{
|
||||
nest {
|
||||
sync
|
||||
@ -427,34 +465,34 @@ class DeferredFieldsTest extends TestCase
|
||||
}
|
||||
}
|
||||
');
|
||||
$result = Executor::execute($schema, $query);
|
||||
$result = Executor::execute($schema, $query);
|
||||
$expected = [
|
||||
'data' => [
|
||||
'nest' => [
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred',
|
||||
'nest' => [
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred'
|
||||
'nest' => [
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred',
|
||||
'nest' => [
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred',
|
||||
],
|
||||
'deferredNest' => [
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred'
|
||||
]
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred',
|
||||
],
|
||||
],
|
||||
'deferredNest' => [
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred',
|
||||
'nest' => [
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred'
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred',
|
||||
'nest' => [
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred',
|
||||
],
|
||||
'deferredNest' => [
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred'
|
||||
]
|
||||
]
|
||||
]
|
||||
'sync' => 'sync',
|
||||
'deferred' => 'deferred',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
@ -1,16 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Describe: Execute: handles directives
|
||||
*/
|
||||
class DirectivesTest extends TestCase
|
||||
{
|
||||
// Describe: Execute: handles directives
|
||||
/** @var Schema */
|
||||
private static $schema;
|
||||
|
||||
/** @var string[] */
|
||||
private static $data;
|
||||
|
||||
/**
|
||||
* @see it('basic query works')
|
||||
@ -20,10 +31,50 @@ class DirectivesTest extends TestCase
|
||||
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b }'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Source|string $doc
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function executeTestQuery($doc) : array
|
||||
{
|
||||
return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray();
|
||||
}
|
||||
|
||||
private static function getSchema() : Schema
|
||||
{
|
||||
if (! self::$schema) {
|
||||
self::$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'TestType',
|
||||
'fields' => [
|
||||
'a' => ['type' => Type::string()],
|
||||
'b' => ['type' => Type::string()],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
return self::$schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getData() : array
|
||||
{
|
||||
return self::$data ?: (self::$data = [
|
||||
'a' => 'a',
|
||||
'b' => 'b',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testWorksOnScalars() : void
|
||||
{
|
||||
// if true includes scalar
|
||||
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b @include(if: true) }'));
|
||||
$this->assertEquals(
|
||||
['data' => ['a' => 'a', 'b' => 'b']],
|
||||
$this->executeTestQuery('{ a, b @include(if: true) }')
|
||||
);
|
||||
|
||||
// if false omits on scalar
|
||||
$this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @include(if: false) }'));
|
||||
@ -200,37 +251,4 @@ class DirectivesTest extends TestCase
|
||||
$this->executeTestQuery('{ a, b @include(if: false) @skip(if: false) }')
|
||||
);
|
||||
}
|
||||
|
||||
private static $schema;
|
||||
|
||||
private static $data;
|
||||
|
||||
private static function getSchema()
|
||||
{
|
||||
if (!self::$schema) {
|
||||
self::$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'TestType',
|
||||
'fields' => [
|
||||
'a' => ['type' => Type::string()],
|
||||
'b' => ['type' => Type::string()]
|
||||
]
|
||||
])
|
||||
]);
|
||||
}
|
||||
return self::$schema;
|
||||
}
|
||||
|
||||
private static function getData()
|
||||
{
|
||||
return self::$data ?: (self::$data = [
|
||||
'a' => 'a',
|
||||
'b' => 'b'
|
||||
]);
|
||||
}
|
||||
|
||||
private function executeTestQuery($doc)
|
||||
{
|
||||
return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
|
@ -1,99 +1,119 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
require_once __DIR__ . '/TestClasses.php';
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Error\Warning;
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Tests\Executor\TestClasses\Cat;
|
||||
use GraphQL\Tests\Executor\TestClasses\Dog;
|
||||
use GraphQL\Type\Definition\CustomScalarType;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\Error\Error;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function count;
|
||||
|
||||
class ExecutorLazySchemaTest extends TestCase
|
||||
{
|
||||
public $SomeScalarType;
|
||||
/** @var ScalarType */
|
||||
public $someScalarType;
|
||||
|
||||
public $SomeObjectType;
|
||||
/** @var ObjectType */
|
||||
public $someObjectType;
|
||||
|
||||
public $OtherObjectType;
|
||||
/** @var ObjectType */
|
||||
public $otherObjectType;
|
||||
|
||||
public $DeeperObjectType;
|
||||
/** @var ObjectType */
|
||||
public $deeperObjectType;
|
||||
|
||||
public $SomeUnionType;
|
||||
/** @var UnionType */
|
||||
public $someUnionType;
|
||||
|
||||
public $SomeInterfaceType;
|
||||
/** @var InterfaceType */
|
||||
public $someInterfaceType;
|
||||
|
||||
public $SomeEnumType;
|
||||
/** @var EnumType */
|
||||
public $someEnumType;
|
||||
|
||||
public $SomeInputObjectType;
|
||||
/** @var InputObjectType */
|
||||
public $someInputObjectType;
|
||||
|
||||
public $QueryType;
|
||||
/** @var ObjectType */
|
||||
public $queryType;
|
||||
|
||||
/** @var string[] */
|
||||
public $calls = [];
|
||||
|
||||
/** @var bool[] */
|
||||
public $loadedTypes = [];
|
||||
|
||||
public function testWarnsAboutSlowIsTypeOfForLazySchema() : void
|
||||
{
|
||||
// isTypeOf used to resolve runtime type for Interface
|
||||
$petType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'fields' => function() {
|
||||
'name' => 'Pet',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'name' => ['type' => Type::string()]
|
||||
'name' => ['type' => Type::string()],
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
// Added to interface type when defined
|
||||
$dogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$petType],
|
||||
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
|
||||
'fields' => function() {
|
||||
'isTypeOf' => function ($obj) {
|
||||
return $obj instanceof Dog;
|
||||
},
|
||||
'fields' => function () {
|
||||
return [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()]
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$catType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$petType],
|
||||
'isTypeOf' => function ($obj) {
|
||||
'isTypeOf' => function ($obj) {
|
||||
return $obj instanceof Cat;
|
||||
},
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
'name' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($petType),
|
||||
'type' => Type::listOf($petType),
|
||||
'resolve' => function () {
|
||||
return [new Dog('Odie', true), new Cat('Garfield', false)];
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'types' => [$catType, $dogType],
|
||||
'typeLoader' => function($name) use ($dogType, $petType, $catType) {
|
||||
'types' => [$catType, $dogType],
|
||||
'typeLoader' => function ($name) use ($dogType, $petType, $catType) {
|
||||
switch ($name) {
|
||||
case 'Dog':
|
||||
return $dogType;
|
||||
@ -102,7 +122,7 @@ class ExecutorLazySchemaTest extends TestCase
|
||||
case 'Cat':
|
||||
return $catType;
|
||||
}
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$query = '{
|
||||
@ -120,7 +140,7 @@ class ExecutorLazySchemaTest extends TestCase
|
||||
$expected = new ExecutionResult([
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false]
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
],
|
||||
]);
|
||||
|
||||
@ -134,43 +154,46 @@ class ExecutorLazySchemaTest extends TestCase
|
||||
$this->assertInstanceOf(Error::class, $result->errors[0]->getPrevious());
|
||||
|
||||
$this->assertEquals(
|
||||
'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of '.
|
||||
'GraphQL\Tests\Executor\Dog. Switching to slow resolution method using `isTypeOf` of all possible '.
|
||||
'implementations. It requires full schema scan and degrades query performance significantly. '.
|
||||
'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of ' .
|
||||
'GraphQL\Tests\Executor\TestClasses\Dog. Switching to slow resolution method using `isTypeOf` of all possible ' .
|
||||
'implementations. It requires full schema scan and degrades query performance significantly. ' .
|
||||
'Make sure your `resolveType` always returns valid implementation or throws.',
|
||||
$result->errors[0]->getMessage());
|
||||
$result->errors[0]->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
public function testHintsOnConflictingTypeInstancesInDefinitions() : void
|
||||
{
|
||||
$calls = [];
|
||||
$typeLoader = function($name) use (&$calls) {
|
||||
$calls = [];
|
||||
$typeLoader = function ($name) use (&$calls) {
|
||||
$calls[] = $name;
|
||||
switch ($name) {
|
||||
case 'Test':
|
||||
return new ObjectType([
|
||||
'name' => 'Test',
|
||||
'fields' => function() {
|
||||
'name' => 'Test',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'test' => Type::string(),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => function() use ($typeLoader) {
|
||||
'name' => 'Query',
|
||||
'fields' => function () use ($typeLoader) {
|
||||
return [
|
||||
'test' => $typeLoader('Test')
|
||||
'test' => $typeLoader('Test'),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $query,
|
||||
'typeLoader' => $typeLoader
|
||||
'query' => $query,
|
||||
'typeLoader' => $typeLoader,
|
||||
]);
|
||||
|
||||
$query = '
|
||||
@ -186,8 +209,8 @@ class ExecutorLazySchemaTest extends TestCase
|
||||
$this->assertEquals(['Test', 'Test'], $calls);
|
||||
|
||||
$this->assertEquals(
|
||||
'Schema must contain unique named types but contains multiple types named "Test". '.
|
||||
'Make sure that type loader returns the same instance as defined in Query.test '.
|
||||
'Schema must contain unique named types but contains multiple types named "Test". ' .
|
||||
'Make sure that type loader returns the same instance as defined in Query.test ' .
|
||||
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
|
||||
$result->errors[0]->getMessage()
|
||||
);
|
||||
@ -200,54 +223,161 @@ class ExecutorLazySchemaTest extends TestCase
|
||||
public function testSimpleQuery() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->loadType('Query'),
|
||||
'typeLoader' => function($name) {
|
||||
'query' => $this->loadType('Query'),
|
||||
'typeLoader' => function ($name) {
|
||||
return $this->loadType($name, true);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$query = '{ object { string } }';
|
||||
$query = '{ object { string } }';
|
||||
$result = Executor::execute(
|
||||
$schema,
|
||||
Parser::parse($query),
|
||||
['object' => ['string' => 'test']]
|
||||
);
|
||||
|
||||
$expected = [
|
||||
$expected = [
|
||||
'data' => ['object' => ['string' => 'test']],
|
||||
];
|
||||
$expectedExecutorCalls = [
|
||||
'Query.fields',
|
||||
'SomeObject',
|
||||
'SomeObject.fields'
|
||||
'SomeObject.fields',
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray(true));
|
||||
$this->assertEquals($expectedExecutorCalls, $this->calls);
|
||||
}
|
||||
|
||||
public function loadType($name, $isExecutorCall = false)
|
||||
{
|
||||
if ($isExecutorCall) {
|
||||
$this->calls[] = $name;
|
||||
}
|
||||
$this->loadedTypes[$name] = true;
|
||||
|
||||
switch ($name) {
|
||||
case 'Query':
|
||||
return $this->queryType ?: $this->queryType = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => function () {
|
||||
$this->calls[] = 'Query.fields';
|
||||
|
||||
return [
|
||||
'object' => ['type' => $this->loadType('SomeObject')],
|
||||
'other' => ['type' => $this->loadType('OtherObject')],
|
||||
];
|
||||
},
|
||||
]);
|
||||
case 'SomeObject':
|
||||
return $this->someObjectType ?: $this->someObjectType = new ObjectType([
|
||||
'name' => 'SomeObject',
|
||||
'fields' => function () {
|
||||
$this->calls[] = 'SomeObject.fields';
|
||||
|
||||
return [
|
||||
'string' => ['type' => Type::string()],
|
||||
'object' => ['type' => $this->someObjectType],
|
||||
];
|
||||
},
|
||||
'interfaces' => function () {
|
||||
$this->calls[] = 'SomeObject.interfaces';
|
||||
|
||||
return [
|
||||
$this->loadType('SomeInterface'),
|
||||
];
|
||||
},
|
||||
]);
|
||||
case 'OtherObject':
|
||||
return $this->otherObjectType ?: $this->otherObjectType = new ObjectType([
|
||||
'name' => 'OtherObject',
|
||||
'fields' => function () {
|
||||
$this->calls[] = 'OtherObject.fields';
|
||||
|
||||
return [
|
||||
'union' => ['type' => $this->loadType('SomeUnion')],
|
||||
'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))],
|
||||
];
|
||||
},
|
||||
]);
|
||||
case 'DeeperObject':
|
||||
return $this->deeperObjectType ?: $this->deeperObjectType = new ObjectType([
|
||||
'name' => 'DeeperObject',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'scalar' => ['type' => $this->loadType('SomeScalar')],
|
||||
];
|
||||
},
|
||||
]);
|
||||
case 'SomeScalar':
|
||||
return $this->someScalarType ?: $this->someScalarType = new CustomScalarType([
|
||||
'name' => 'SomeScalar',
|
||||
'serialize' => function ($value) {
|
||||
return $value;
|
||||
},
|
||||
'parseValue' => function ($value) {
|
||||
return $value;
|
||||
},
|
||||
'parseLiteral' => function () {
|
||||
},
|
||||
]);
|
||||
case 'SomeUnion':
|
||||
return $this->someUnionType ?: $this->someUnionType = new UnionType([
|
||||
'name' => 'SomeUnion',
|
||||
'resolveType' => function () {
|
||||
$this->calls[] = 'SomeUnion.resolveType';
|
||||
|
||||
return $this->loadType('DeeperObject');
|
||||
},
|
||||
'types' => function () {
|
||||
$this->calls[] = 'SomeUnion.types';
|
||||
|
||||
return [$this->loadType('DeeperObject')];
|
||||
},
|
||||
]);
|
||||
case 'SomeInterface':
|
||||
return $this->someInterfaceType ?: $this->someInterfaceType = new InterfaceType([
|
||||
'name' => 'SomeInterface',
|
||||
'resolveType' => function () {
|
||||
$this->calls[] = 'SomeInterface.resolveType';
|
||||
|
||||
return $this->loadType('SomeObject');
|
||||
},
|
||||
'fields' => function () {
|
||||
$this->calls[] = 'SomeInterface.fields';
|
||||
|
||||
return [
|
||||
'string' => ['type' => Type::string()],
|
||||
];
|
||||
},
|
||||
]);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function testDeepQuery() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->loadType('Query'),
|
||||
'typeLoader' => function($name) {
|
||||
'query' => $this->loadType('Query'),
|
||||
'typeLoader' => function ($name) {
|
||||
return $this->loadType($name, true);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$query = '{ object { object { object { string } } } }';
|
||||
$query = '{ object { object { object { string } } } }';
|
||||
$result = Executor::execute(
|
||||
$schema,
|
||||
Parser::parse($query),
|
||||
['object' => ['object' => ['object' => ['string' => 'test']]]]
|
||||
);
|
||||
|
||||
$expected = [
|
||||
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]]
|
||||
$expected = [
|
||||
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]],
|
||||
];
|
||||
$expectedLoadedTypes = [
|
||||
'Query' => true,
|
||||
'SomeObject' => true,
|
||||
'OtherObject' => true
|
||||
'Query' => true,
|
||||
'SomeObject' => true,
|
||||
'OtherObject' => true,
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result->toArray(true));
|
||||
@ -256,7 +386,7 @@ class ExecutorLazySchemaTest extends TestCase
|
||||
$expectedExecutorCalls = [
|
||||
'Query.fields',
|
||||
'SomeObject',
|
||||
'SomeObject.fields'
|
||||
'SomeObject.fields',
|
||||
];
|
||||
$this->assertEquals($expectedExecutorCalls, $this->calls);
|
||||
}
|
||||
@ -264,13 +394,13 @@ class ExecutorLazySchemaTest extends TestCase
|
||||
public function testResolveUnion() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->loadType('Query'),
|
||||
'typeLoader' => function($name) {
|
||||
'query' => $this->loadType('Query'),
|
||||
'typeLoader' => function ($name) {
|
||||
return $this->loadType($name, true);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$query = '
|
||||
$query = '
|
||||
{
|
||||
other {
|
||||
union {
|
||||
@ -285,17 +415,17 @@ class ExecutorLazySchemaTest extends TestCase
|
||||
['other' => ['union' => ['scalar' => 'test']]]
|
||||
);
|
||||
|
||||
$expected = [
|
||||
$expected = [
|
||||
'data' => ['other' => ['union' => ['scalar' => 'test']]],
|
||||
];
|
||||
$expectedLoadedTypes = [
|
||||
'Query' => true,
|
||||
'SomeObject' => true,
|
||||
'OtherObject' => true,
|
||||
'SomeUnion' => true,
|
||||
'Query' => true,
|
||||
'SomeObject' => true,
|
||||
'OtherObject' => true,
|
||||
'SomeUnion' => true,
|
||||
'SomeInterface' => true,
|
||||
'DeeperObject' => true,
|
||||
'SomeScalar' => true,
|
||||
'DeeperObject' => true,
|
||||
'SomeScalar' => true,
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result->toArray(true));
|
||||
@ -313,98 +443,4 @@ class ExecutorLazySchemaTest extends TestCase
|
||||
];
|
||||
$this->assertEquals($expectedCalls, $this->calls);
|
||||
}
|
||||
|
||||
public function loadType($name, $isExecutorCall = false)
|
||||
{
|
||||
if ($isExecutorCall) {
|
||||
$this->calls[] = $name;
|
||||
}
|
||||
$this->loadedTypes[$name] = true;
|
||||
|
||||
switch ($name) {
|
||||
case 'Query':
|
||||
return $this->QueryType ?: $this->QueryType = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => function() {
|
||||
$this->calls[] = 'Query.fields';
|
||||
return [
|
||||
'object' => ['type' => $this->loadType('SomeObject')],
|
||||
'other' => ['type' => $this->loadType('OtherObject')],
|
||||
];
|
||||
}
|
||||
]);
|
||||
case 'SomeObject':
|
||||
return $this->SomeObjectType ?: $this->SomeObjectType = new ObjectType([
|
||||
'name' => 'SomeObject',
|
||||
'fields' => function() {
|
||||
$this->calls[] = 'SomeObject.fields';
|
||||
return [
|
||||
'string' => ['type' => Type::string()],
|
||||
'object' => ['type' => $this->SomeObjectType]
|
||||
];
|
||||
},
|
||||
'interfaces' => function() {
|
||||
$this->calls[] = 'SomeObject.interfaces';
|
||||
return [
|
||||
$this->loadType('SomeInterface')
|
||||
];
|
||||
}
|
||||
]);
|
||||
case 'OtherObject':
|
||||
return $this->OtherObjectType ?: $this->OtherObjectType = new ObjectType([
|
||||
'name' => 'OtherObject',
|
||||
'fields' => function() {
|
||||
$this->calls[] = 'OtherObject.fields';
|
||||
return [
|
||||
'union' => ['type' => $this->loadType('SomeUnion')],
|
||||
'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))],
|
||||
];
|
||||
}
|
||||
]);
|
||||
case 'DeeperObject':
|
||||
return $this->DeeperObjectType ?: $this->DeeperObjectType = new ObjectType([
|
||||
'name' => 'DeeperObject',
|
||||
'fields' => function() {
|
||||
return [
|
||||
'scalar' => ['type' => $this->loadType('SomeScalar')],
|
||||
];
|
||||
}
|
||||
]);
|
||||
case 'SomeScalar';
|
||||
return $this->SomeScalarType ?: $this->SomeScalarType = new CustomScalarType([
|
||||
'name' => 'SomeScalar',
|
||||
'serialize' => function($value) {return $value;},
|
||||
'parseValue' => function($value) {return $value;},
|
||||
'parseLiteral' => function() {}
|
||||
]);
|
||||
case 'SomeUnion':
|
||||
return $this->SomeUnionType ?: $this->SomeUnionType = new UnionType([
|
||||
'name' => 'SomeUnion',
|
||||
'resolveType' => function() {
|
||||
$this->calls[] = 'SomeUnion.resolveType';
|
||||
return $this->loadType('DeeperObject');
|
||||
},
|
||||
'types' => function() {
|
||||
$this->calls[] = 'SomeUnion.types';
|
||||
return [ $this->loadType('DeeperObject') ];
|
||||
}
|
||||
]);
|
||||
case 'SomeInterface':
|
||||
return $this->SomeInterfaceType ?: $this->SomeInterfaceType = new InterfaceType([
|
||||
'name' => 'SomeInterface',
|
||||
'resolveType' => function() {
|
||||
$this->calls[] = 'SomeInterface.resolveType';
|
||||
return $this->loadType('SomeObject');
|
||||
},
|
||||
'fields' => function() {
|
||||
$this->calls[] = 'SomeInterface.fields';
|
||||
return [
|
||||
'string' => ['type' => Type::string() ]
|
||||
];
|
||||
}
|
||||
]);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,74 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function sprintf;
|
||||
|
||||
class ExecutorSchemaTest extends TestCase
|
||||
{
|
||||
// Execute: Handles execution with a complex schema
|
||||
|
||||
/**
|
||||
* @see it('executes using a schema')
|
||||
*/
|
||||
public function testExecutesUsingASchema() : void
|
||||
{
|
||||
$BlogArticle = null;
|
||||
$BlogImage = new ObjectType([
|
||||
'name' => 'Image',
|
||||
$BlogImage = new ObjectType([
|
||||
'name' => 'Image',
|
||||
'fields' => [
|
||||
'url' => ['type' => Type::string()],
|
||||
'width' => ['type' => Type::int()],
|
||||
'url' => ['type' => Type::string()],
|
||||
'width' => ['type' => Type::int()],
|
||||
'height' => ['type' => Type::int()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$BlogAuthor = new ObjectType([
|
||||
'name' => 'Author',
|
||||
'fields' => function() use (&$BlogArticle, &$BlogImage) {
|
||||
'name' => 'Author',
|
||||
'fields' => function () use (&$BlogArticle, &$BlogImage) {
|
||||
return [
|
||||
'id' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'pic' => [
|
||||
'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]],
|
||||
'type' => $BlogImage,
|
||||
'id' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'pic' => [
|
||||
'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]],
|
||||
'type' => $BlogImage,
|
||||
'resolve' => function ($obj, $args) {
|
||||
return $obj['pic']($args['width'], $args['height']);
|
||||
}
|
||||
},
|
||||
],
|
||||
'recentArticle' => $BlogArticle
|
||||
'recentArticle' => $BlogArticle,
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$BlogArticle = new ObjectType([
|
||||
'name' => 'Article',
|
||||
'name' => 'Article',
|
||||
'fields' => [
|
||||
'id' => ['type' => Type::nonNull(Type::string())],
|
||||
'id' => ['type' => Type::nonNull(Type::string())],
|
||||
'isPublished' => ['type' => Type::boolean()],
|
||||
'author' => ['type' => $BlogAuthor],
|
||||
'title' => ['type' => Type::string()],
|
||||
'body' => ['type' => Type::string()],
|
||||
'keywords' => ['type' => Type::listOf(Type::string())]
|
||||
]
|
||||
'author' => ['type' => $BlogAuthor],
|
||||
'title' => ['type' => Type::string()],
|
||||
'body' => ['type' => Type::string()],
|
||||
'keywords' => ['type' => Type::listOf(Type::string())],
|
||||
],
|
||||
]);
|
||||
|
||||
$BlogQuery = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'article' => [
|
||||
'type' => $BlogArticle,
|
||||
'args' => ['id' => ['type' => Type::id()]],
|
||||
'type' => $BlogArticle,
|
||||
'args' => ['id' => ['type' => Type::id()]],
|
||||
'resolve' => function ($_, $args) {
|
||||
return $this->article($args['id']);
|
||||
}
|
||||
},
|
||||
],
|
||||
'feed' => [
|
||||
'type' => Type::listOf($BlogArticle),
|
||||
'feed' => [
|
||||
'type' => Type::listOf($BlogArticle),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
$this->article(1),
|
||||
@ -80,16 +83,15 @@ class ExecutorSchemaTest extends TestCase
|
||||
$this->article(7),
|
||||
$this->article(8),
|
||||
$this->article(9),
|
||||
$this->article(10)
|
||||
$this->article(10),
|
||||
];
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$BlogSchema = new Schema(['query' => $BlogQuery]);
|
||||
|
||||
|
||||
$request = '
|
||||
{
|
||||
feed {
|
||||
@ -126,51 +128,71 @@ class ExecutorSchemaTest extends TestCase
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'feed' => [
|
||||
['id' => '1',
|
||||
'title' => 'My Article 1'],
|
||||
['id' => '2',
|
||||
'title' => 'My Article 2'],
|
||||
['id' => '3',
|
||||
'title' => 'My Article 3'],
|
||||
['id' => '4',
|
||||
'title' => 'My Article 4'],
|
||||
['id' => '5',
|
||||
'title' => 'My Article 5'],
|
||||
['id' => '6',
|
||||
'title' => 'My Article 6'],
|
||||
['id' => '7',
|
||||
'title' => 'My Article 7'],
|
||||
['id' => '8',
|
||||
'title' => 'My Article 8'],
|
||||
['id' => '9',
|
||||
'title' => 'My Article 9'],
|
||||
['id' => '10',
|
||||
'title' => 'My Article 10']
|
||||
'feed' => [
|
||||
[
|
||||
'id' => '1',
|
||||
'title' => 'My Article 1',
|
||||
],
|
||||
[
|
||||
'id' => '2',
|
||||
'title' => 'My Article 2',
|
||||
],
|
||||
[
|
||||
'id' => '3',
|
||||
'title' => 'My Article 3',
|
||||
],
|
||||
[
|
||||
'id' => '4',
|
||||
'title' => 'My Article 4',
|
||||
],
|
||||
[
|
||||
'id' => '5',
|
||||
'title' => 'My Article 5',
|
||||
],
|
||||
[
|
||||
'id' => '6',
|
||||
'title' => 'My Article 6',
|
||||
],
|
||||
[
|
||||
'id' => '7',
|
||||
'title' => 'My Article 7',
|
||||
],
|
||||
[
|
||||
'id' => '8',
|
||||
'title' => 'My Article 8',
|
||||
],
|
||||
[
|
||||
'id' => '9',
|
||||
'title' => 'My Article 9',
|
||||
],
|
||||
[
|
||||
'id' => '10',
|
||||
'title' => 'My Article 10',
|
||||
],
|
||||
],
|
||||
'article' => [
|
||||
'id' => '1',
|
||||
'id' => '1',
|
||||
'isPublished' => true,
|
||||
'title' => 'My Article 1',
|
||||
'body' => 'This is a post',
|
||||
'author' => [
|
||||
'id' => '123',
|
||||
'name' => 'John Smith',
|
||||
'pic' => [
|
||||
'url' => 'cdn://123',
|
||||
'width' => 640,
|
||||
'height' => 480
|
||||
'title' => 'My Article 1',
|
||||
'body' => 'This is a post',
|
||||
'author' => [
|
||||
'id' => '123',
|
||||
'name' => 'John Smith',
|
||||
'pic' => [
|
||||
'url' => 'cdn://123',
|
||||
'width' => 640,
|
||||
'height' => 480,
|
||||
],
|
||||
'recentArticle' => [
|
||||
'id' => '1',
|
||||
'id' => '1',
|
||||
'isPublished' => true,
|
||||
'title' => 'My Article 1',
|
||||
'body' => 'This is a post',
|
||||
'keywords' => ['foo', 'bar', '1', 'true', null]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
'title' => 'My Article 1',
|
||||
'body' => 'This is a post',
|
||||
'keywords' => ['foo', 'bar', '1', 'true', null],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray());
|
||||
@ -179,30 +201,32 @@ class ExecutorSchemaTest extends TestCase
|
||||
private function article($id)
|
||||
{
|
||||
$johnSmith = null;
|
||||
$article = function($id) use (&$johnSmith) {
|
||||
$article = function ($id) use (&$johnSmith) {
|
||||
return [
|
||||
'id' => $id,
|
||||
'id' => $id,
|
||||
'isPublished' => 'true',
|
||||
'author' => $johnSmith,
|
||||
'title' => 'My Article ' . $id,
|
||||
'body' => 'This is a post',
|
||||
'hidden' => 'This data is not exposed in the schema',
|
||||
'keywords' => ['foo', 'bar', 1, true, null]
|
||||
'author' => $johnSmith,
|
||||
'title' => 'My Article ' . $id,
|
||||
'body' => 'This is a post',
|
||||
'hidden' => 'This data is not exposed in the schema',
|
||||
'keywords' => ['foo', 'bar', 1, true, null],
|
||||
];
|
||||
};
|
||||
|
||||
$getPic = function($uid, $width, $height) {
|
||||
$getPic = function ($uid, $width, $height) {
|
||||
return [
|
||||
'url' => "cdn://$uid",
|
||||
'width' => $width,
|
||||
'height' => $height
|
||||
'url' => sprintf('cdn://%s', $uid),
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
];
|
||||
};
|
||||
|
||||
$johnSmith = [
|
||||
'id' => 123,
|
||||
'name' => 'John Smith',
|
||||
'pic' => function($width, $height) use ($getPic) {return $getPic(123, $width, $height);},
|
||||
'id' => 123,
|
||||
'name' => 'John Smith',
|
||||
'pic' => function ($width, $height) use ($getPic) {
|
||||
return $getPic(123, $width, $height);
|
||||
},
|
||||
'recentArticle' => $article(1),
|
||||
];
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,104 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @author: Ivo Meißner
|
||||
* Date: 03.05.16
|
||||
* Time: 13:14
|
||||
*/
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class LazyInterfaceTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Schema
|
||||
*/
|
||||
/** @var Schema */
|
||||
protected $schema;
|
||||
|
||||
/**
|
||||
* @var InterfaceType
|
||||
*/
|
||||
/** @var InterfaceType */
|
||||
protected $lazyInterface;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
protected $testObject;
|
||||
|
||||
/**
|
||||
* Setup schema
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
$query = new ObjectType([
|
||||
'name' => 'query',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'lazyInterface' => [
|
||||
'type' => $this->getLazyInterfaceType(),
|
||||
'resolve' => function() {
|
||||
return [];
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
]);
|
||||
|
||||
$this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LazyInterface
|
||||
*
|
||||
* @return InterfaceType
|
||||
*/
|
||||
protected function getLazyInterfaceType()
|
||||
{
|
||||
if (!$this->lazyInterface) {
|
||||
$this->lazyInterface = new InterfaceType([
|
||||
'name' => 'LazyInterface',
|
||||
'fields' => [
|
||||
'a' => Type::string()
|
||||
],
|
||||
'resolveType' => function() {
|
||||
return $this->getTestObjectType();
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->lazyInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test ObjectType
|
||||
* @return ObjectType
|
||||
*/
|
||||
protected function getTestObjectType()
|
||||
{
|
||||
if (!$this->testObject) {
|
||||
$this->testObject = new ObjectType([
|
||||
'name' => 'TestObject',
|
||||
'fields' => [
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function() {
|
||||
return 'testname';
|
||||
}
|
||||
]
|
||||
],
|
||||
'interfaces' => [$this->getLazyInterfaceType()]
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->testObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles execution of a lazily created interface
|
||||
*/
|
||||
@ -116,12 +46,78 @@ class LazyInterfaceTest extends TestCase
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'lazyInterface' => [
|
||||
'name' => 'testname'
|
||||
]
|
||||
]
|
||||
'lazyInterface' => ['name' => 'testname'],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup schema
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
$query = new ObjectType([
|
||||
'name' => 'query',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'lazyInterface' => [
|
||||
'type' => $this->getLazyInterfaceType(),
|
||||
'resolve' => function () {
|
||||
return [];
|
||||
},
|
||||
],
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LazyInterface
|
||||
*
|
||||
* @return InterfaceType
|
||||
*/
|
||||
protected function getLazyInterfaceType()
|
||||
{
|
||||
if (! $this->lazyInterface) {
|
||||
$this->lazyInterface = new InterfaceType([
|
||||
'name' => 'LazyInterface',
|
||||
'fields' => [
|
||||
'a' => Type::string(),
|
||||
],
|
||||
'resolveType' => function () {
|
||||
return $this->getTestObjectType();
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->lazyInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test ObjectType
|
||||
* @return ObjectType
|
||||
*/
|
||||
protected function getTestObjectType()
|
||||
{
|
||||
if (! $this->testObject) {
|
||||
$this->testObject = new ObjectType([
|
||||
'name' => 'TestObject',
|
||||
'fields' => [
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function () {
|
||||
return 'testname';
|
||||
},
|
||||
],
|
||||
],
|
||||
'interfaces' => [$this->getLazyInterfaceType()],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->testObject;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Deferred;
|
||||
use GraphQL\Error\UserError;
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Error\FormattedError;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ListsTest extends TestCase
|
||||
{
|
||||
// Describe: Execute: Handles list nullability
|
||||
|
||||
/**
|
||||
* [T]
|
||||
*/
|
||||
@ -24,23 +23,57 @@ class ListsTest extends TestCase
|
||||
{
|
||||
// Contains values
|
||||
$this->checkHandlesNullableLists(
|
||||
[ 1, 2 ],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
[1, 2],
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesNullableLists(
|
||||
[ 1, null, 2 ],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||
[1, null, 2],
|
||||
['data' => ['nest' => ['test' => [1, null, 2]]]]
|
||||
);
|
||||
|
||||
// Returns null
|
||||
$this->checkHandlesNullableLists(
|
||||
null,
|
||||
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||
['data' => ['nest' => ['test' => null]]]
|
||||
);
|
||||
}
|
||||
|
||||
private function checkHandlesNullableLists($testData, $expected)
|
||||
{
|
||||
$testType = Type::listOf(Type::int());
|
||||
$this->check($testType, $testData, $expected);
|
||||
}
|
||||
|
||||
private function check($testType, $testData, $expected, $debug = false)
|
||||
{
|
||||
$data = ['test' => $testData];
|
||||
$dataType = null;
|
||||
|
||||
$dataType = new ObjectType([
|
||||
'name' => 'DataType',
|
||||
'fields' => function () use (&$testType, &$dataType, $data) {
|
||||
return [
|
||||
'test' => ['type' => $testType],
|
||||
'nest' => [
|
||||
'type' => $dataType,
|
||||
'resolve' => function () use ($data) {
|
||||
return $data;
|
||||
},
|
||||
],
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $dataType]);
|
||||
|
||||
$ast = Parser::parse('{ nest { test } }');
|
||||
|
||||
$result = Executor::execute($schema, $ast, $data);
|
||||
$this->assertArraySubset($expected, $result->toArray($debug));
|
||||
}
|
||||
|
||||
/**
|
||||
* [T]
|
||||
*/
|
||||
@ -48,26 +81,26 @@ class ListsTest extends TestCase
|
||||
{
|
||||
// Contains values
|
||||
$this->checkHandlesNullableLists(
|
||||
new Deferred(function() {
|
||||
return [1,2];
|
||||
new Deferred(function () {
|
||||
return [1, 2];
|
||||
}),
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesNullableLists(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return [1, null, 2];
|
||||
}),
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, null, 2]]]]
|
||||
);
|
||||
|
||||
// Returns null
|
||||
$this->checkHandlesNullableLists(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return null;
|
||||
}),
|
||||
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||
['data' => ['nest' => ['test' => null]]]
|
||||
);
|
||||
|
||||
// Rejected
|
||||
@ -78,14 +111,14 @@ class ListsTest extends TestCase
|
||||
});
|
||||
},
|
||||
[
|
||||
'data' => ['nest' => ['test' => null]],
|
||||
'data' => ['nest' => ['test' => null]],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'bad',
|
||||
'message' => 'bad',
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
'path' => ['nest', 'test']
|
||||
]
|
||||
]
|
||||
'path' => ['nest', 'test'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -98,57 +131,64 @@ class ListsTest extends TestCase
|
||||
// Contains values
|
||||
$this->checkHandlesNullableLists(
|
||||
[
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
}),
|
||||
],
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesNullableLists(
|
||||
[
|
||||
new Deferred(function() {return 1;}),
|
||||
new Deferred(function() {return null;}),
|
||||
new Deferred(function() {return 2;})
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function () {
|
||||
return null;
|
||||
}),
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
}),
|
||||
],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, null, 2]]]]
|
||||
);
|
||||
|
||||
// Returns null
|
||||
$this->checkHandlesNullableLists(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return null;
|
||||
}),
|
||||
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||
['data' => ['nest' => ['test' => null]]]
|
||||
);
|
||||
|
||||
// Contains reject
|
||||
$this->checkHandlesNullableLists(
|
||||
function () {
|
||||
return [
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
throw new UserError('bad');
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})
|
||||
}),
|
||||
];
|
||||
},
|
||||
[
|
||||
'data' => ['nest' => ['test' => [1, null, 2]]],
|
||||
'data' => ['nest' => ['test' => [1, null, 2]]],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'bad',
|
||||
'message' => 'bad',
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
'path' => ['nest', 'test', 1]
|
||||
]
|
||||
]
|
||||
'path' => ['nest', 'test', 1],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -160,32 +200,38 @@ class ListsTest extends TestCase
|
||||
{
|
||||
// Contains values
|
||||
$this->checkHandlesNonNullableLists(
|
||||
[ 1, 2 ],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
[1, 2],
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesNonNullableLists(
|
||||
[ 1, null, 2 ],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||
[1, null, 2],
|
||||
['data' => ['nest' => ['test' => [1, null, 2]]]]
|
||||
);
|
||||
|
||||
// Returns null
|
||||
$this->checkHandlesNonNullableLists(
|
||||
null,
|
||||
[
|
||||
'data' => [ 'nest' => null ],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
|
||||
'locations' => [['line' => 1, 'column' => 10]]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
],
|
||||
],
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
|
||||
{
|
||||
$testType = Type::nonNull(Type::listOf(Type::int()));
|
||||
$this->check($testType, $testData, $expected, $debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* [T]!
|
||||
*/
|
||||
@ -193,31 +239,31 @@ class ListsTest extends TestCase
|
||||
{
|
||||
// Contains values
|
||||
$this->checkHandlesNonNullableLists(
|
||||
new Deferred(function() {
|
||||
return [1,2];
|
||||
new Deferred(function () {
|
||||
return [1, 2];
|
||||
}),
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesNonNullableLists(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return [1, null, 2];
|
||||
}),
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, null, 2]]]]
|
||||
);
|
||||
|
||||
// Returns null
|
||||
$this->checkHandlesNonNullableLists(
|
||||
null,
|
||||
[
|
||||
'data' => [ 'nest' => null ],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
|
||||
'locations' => [['line' => 1, 'column' => 10]]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
],
|
||||
],
|
||||
],
|
||||
true
|
||||
);
|
||||
@ -225,19 +271,19 @@ class ListsTest extends TestCase
|
||||
// Rejected
|
||||
$this->checkHandlesNonNullableLists(
|
||||
function () {
|
||||
return new Deferred(function() {
|
||||
return new Deferred(function () {
|
||||
throw new UserError('bad');
|
||||
});
|
||||
},
|
||||
[
|
||||
'data' => ['nest' => null],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'bad',
|
||||
'message' => 'bad',
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
'path' => ['nest', 'test']
|
||||
]
|
||||
]
|
||||
'path' => ['nest', 'test'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -250,56 +296,56 @@ class ListsTest extends TestCase
|
||||
// Contains values
|
||||
$this->checkHandlesNonNullableLists(
|
||||
[
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})
|
||||
}),
|
||||
],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesNonNullableLists(
|
||||
[
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return null;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})
|
||||
}),
|
||||
],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, null, 2]]]]
|
||||
);
|
||||
|
||||
// Contains reject
|
||||
$this->checkHandlesNonNullableLists(
|
||||
function () {
|
||||
return [
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
throw new UserError('bad');
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})
|
||||
}),
|
||||
];
|
||||
},
|
||||
[
|
||||
'data' => ['nest' => ['test' => [1, null, 2]]],
|
||||
'data' => ['nest' => ['test' => [1, null, 2]]],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'bad',
|
||||
'message' => 'bad',
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
'path' => ['nest', 'test', 1]
|
||||
]
|
||||
]
|
||||
'path' => ['nest', 'test', 1],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -311,21 +357,21 @@ class ListsTest extends TestCase
|
||||
{
|
||||
// Contains values
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
[ 1, 2 ],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
[1, 2],
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
[ 1, null, 2 ],
|
||||
[1, null, 2],
|
||||
[
|
||||
'data' => [ 'nest' => [ 'test' => null ] ],
|
||||
'data' => ['nest' => ['test' => null]],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
|
||||
'locations' => [ ['line' => 1, 'column' => 10] ]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
],
|
||||
],
|
||||
],
|
||||
true
|
||||
);
|
||||
@ -333,10 +379,16 @@ class ListsTest extends TestCase
|
||||
// Returns null
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
null,
|
||||
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||
['data' => ['nest' => ['test' => null]]]
|
||||
);
|
||||
}
|
||||
|
||||
private function checkHandlesListOfNonNulls($testData, $expected, $debug = false)
|
||||
{
|
||||
$testType = Type::listOf(Type::nonNull(Type::int()));
|
||||
$this->check($testType, $testData, $expected, $debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* [T!]
|
||||
*/
|
||||
@ -344,53 +396,53 @@ class ListsTest extends TestCase
|
||||
{
|
||||
// Contains values
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return [1, 2];
|
||||
}),
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return [1, null, 2];
|
||||
}),
|
||||
[
|
||||
'data' => [ 'nest' => [ 'test' => null ] ],
|
||||
'data' => ['nest' => ['test' => null]],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
|
||||
'locations' => [['line' => 1, 'column' => 10]]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
],
|
||||
],
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
// Returns null
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return null;
|
||||
}),
|
||||
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||
['data' => ['nest' => ['test' => null]]]
|
||||
);
|
||||
|
||||
// Rejected
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
function () {
|
||||
return new Deferred(function() {
|
||||
return new Deferred(function () {
|
||||
throw new UserError('bad');
|
||||
});
|
||||
},
|
||||
[
|
||||
'data' => ['nest' => ['test' => null]],
|
||||
'data' => ['nest' => ['test' => null]],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'bad',
|
||||
'message' => 'bad',
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
'path' => ['nest', 'test']
|
||||
]
|
||||
]
|
||||
'path' => ['nest', 'test'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -403,50 +455,56 @@ class ListsTest extends TestCase
|
||||
// Contains values
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
[
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})
|
||||
}),
|
||||
],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
[
|
||||
new Deferred(function() {return 1;}),
|
||||
new Deferred(function() {return null;}),
|
||||
new Deferred(function() {return 2;})
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function () {
|
||||
return null;
|
||||
}),
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
}),
|
||||
],
|
||||
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||
['data' => ['nest' => ['test' => null]]]
|
||||
);
|
||||
|
||||
// Contains reject
|
||||
$this->checkHandlesListOfNonNulls(
|
||||
function () {
|
||||
return [
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
throw new UserError('bad');
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})
|
||||
}),
|
||||
];
|
||||
},
|
||||
[
|
||||
'data' => ['nest' => ['test' => null]],
|
||||
'data' => ['nest' => ['test' => null]],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'bad',
|
||||
'message' => 'bad',
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
'path' => ['nest', 'test', 1]
|
||||
]
|
||||
]
|
||||
'path' => ['nest', 'test', 1],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -458,22 +516,21 @@ class ListsTest extends TestCase
|
||||
{
|
||||
// Contains values
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
[ 1, 2 ],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
[1, 2],
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
[ 1, null, 2 ],
|
||||
[1, null, 2],
|
||||
[
|
||||
'data' => [ 'nest' => null ],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
|
||||
'locations' => [['line' => 1, 'column' => 10 ]]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
],
|
||||
],
|
||||
],
|
||||
true
|
||||
);
|
||||
@ -482,18 +539,24 @@ class ListsTest extends TestCase
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
null,
|
||||
[
|
||||
'data' => [ 'nest' => null ],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
|
||||
'locations' => [ ['line' => 1, 'column' => 10] ]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
],
|
||||
],
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
|
||||
{
|
||||
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
|
||||
$this->check($testType, $testData, $expected, $debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* [T!]!
|
||||
*/
|
||||
@ -501,42 +564,42 @@ class ListsTest extends TestCase
|
||||
{
|
||||
// Contains values
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return [1, 2];
|
||||
}),
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return [1, null, 2];
|
||||
}),
|
||||
[
|
||||
'data' => [ 'nest' => null ],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
|
||||
'locations' => [ ['line' => 1, 'column' => 10] ]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
],
|
||||
],
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
// Returns null
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return null;
|
||||
}),
|
||||
[
|
||||
'data' => [ 'nest' => null ],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
|
||||
'locations' => [ ['line' => 1, 'column' => 10] ]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
],
|
||||
],
|
||||
],
|
||||
true
|
||||
);
|
||||
@ -544,19 +607,19 @@ class ListsTest extends TestCase
|
||||
// Rejected
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
function () {
|
||||
return new Deferred(function() {
|
||||
return new Deferred(function () {
|
||||
throw new UserError('bad');
|
||||
});
|
||||
},
|
||||
[
|
||||
'data' => ['nest' => null ],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'bad',
|
||||
'message' => 'bad',
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
'path' => ['nest', 'test']
|
||||
]
|
||||
]
|
||||
'path' => ['nest', 'test'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -569,38 +632,38 @@ class ListsTest extends TestCase
|
||||
// Contains values
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
[
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})
|
||||
}),
|
||||
|
||||
],
|
||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||
['data' => ['nest' => ['test' => [1, 2]]]]
|
||||
);
|
||||
|
||||
// Contains null
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
[
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return null;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})
|
||||
}),
|
||||
],
|
||||
[
|
||||
'data' => [ 'nest' => null ],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
|
||||
'locations' => [['line' => 1, 'column' => 10]]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
],
|
||||
],
|
||||
],
|
||||
true
|
||||
);
|
||||
@ -609,83 +672,27 @@ class ListsTest extends TestCase
|
||||
$this->checkHandlesNonNullListOfNonNulls(
|
||||
function () {
|
||||
return [
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 1;
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
throw new UserError('bad');
|
||||
}),
|
||||
new Deferred(function() {
|
||||
new Deferred(function () {
|
||||
return 2;
|
||||
})
|
||||
}),
|
||||
];
|
||||
},
|
||||
[
|
||||
'data' => ['nest' => null ],
|
||||
'data' => ['nest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'bad',
|
||||
'message' => 'bad',
|
||||
'locations' => [['line' => 1, 'column' => 10]],
|
||||
'path' => ['nest', 'test']
|
||||
]
|
||||
]
|
||||
'path' => ['nest', 'test'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function checkHandlesNullableLists($testData, $expected)
|
||||
{
|
||||
$testType = Type::listOf(Type::int());
|
||||
$this->check($testType, $testData, $expected);
|
||||
}
|
||||
|
||||
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
|
||||
{
|
||||
$testType = Type::nonNull(Type::listOf(Type::int()));
|
||||
$this->check($testType, $testData, $expected, $debug);
|
||||
}
|
||||
|
||||
private function checkHandlesListOfNonNulls($testData, $expected, $debug = false)
|
||||
{
|
||||
$testType = Type::listOf(Type::nonNull(Type::int()));
|
||||
$this->check($testType, $testData, $expected, $debug);
|
||||
}
|
||||
|
||||
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
|
||||
{
|
||||
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
|
||||
$this->check($testType, $testData, $expected, $debug);
|
||||
}
|
||||
|
||||
private function check($testType, $testData, $expected, $debug = false)
|
||||
{
|
||||
$data = ['test' => $testData];
|
||||
$dataType = null;
|
||||
|
||||
$dataType = new ObjectType([
|
||||
'name' => 'DataType',
|
||||
'fields' => function () use (&$testType, &$dataType, $data) {
|
||||
return [
|
||||
'test' => [
|
||||
'type' => $testType
|
||||
],
|
||||
'nest' => [
|
||||
'type' => $dataType,
|
||||
'resolve' => function () use ($data) {
|
||||
return $data;
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $dataType
|
||||
]);
|
||||
|
||||
$ast = Parser::parse('{ nest { test } }');
|
||||
|
||||
$result = Executor::execute($schema, $ast, $data);
|
||||
$this->assertArraySubset($expected, $result->toArray($debug));
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Deferred;
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Error\FormattedError;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Tests\Executor\TestClasses\Root;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MutationsTest extends TestCase
|
||||
{
|
||||
// Execute: Handles mutation execution ordering
|
||||
|
||||
/**
|
||||
* @see it('evaluates mutations serially')
|
||||
*/
|
||||
public function testEvaluatesMutationsSerially() : void
|
||||
{
|
||||
$doc = 'mutation M {
|
||||
$doc = 'mutation M {
|
||||
first: immediatelyChangeTheNumber(newNumber: 1) {
|
||||
theNumber
|
||||
},
|
||||
@ -38,36 +37,79 @@ class MutationsTest extends TestCase
|
||||
theNumber
|
||||
}
|
||||
}';
|
||||
$ast = Parser::parse($doc);
|
||||
$ast = Parser::parse($doc);
|
||||
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
|
||||
$expected = [
|
||||
$expected = [
|
||||
'data' => [
|
||||
'first' => [
|
||||
'theNumber' => 1
|
||||
],
|
||||
'second' => [
|
||||
'theNumber' => 2
|
||||
],
|
||||
'third' => [
|
||||
'theNumber' => 3
|
||||
],
|
||||
'fourth' => [
|
||||
'theNumber' => 4
|
||||
],
|
||||
'fifth' => [
|
||||
'theNumber' => 5
|
||||
]
|
||||
]
|
||||
'first' => ['theNumber' => 1],
|
||||
'second' => ['theNumber' => 2],
|
||||
'third' => ['theNumber' => 3],
|
||||
'fourth' => ['theNumber' => 4],
|
||||
'fifth' => ['theNumber' => 5],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $mutationResult->toArray());
|
||||
}
|
||||
|
||||
private function schema() : Schema
|
||||
{
|
||||
$numberHolderType = new ObjectType([
|
||||
'fields' => [
|
||||
'theNumber' => ['type' => Type::int()],
|
||||
],
|
||||
'name' => 'NumberHolder',
|
||||
]);
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'fields' => [
|
||||
'numberHolder' => ['type' => $numberHolderType],
|
||||
],
|
||||
'name' => 'Query',
|
||||
]),
|
||||
'mutation' => new ObjectType([
|
||||
'fields' => [
|
||||
'immediatelyChangeTheNumber' => [
|
||||
'type' => $numberHolderType,
|
||||
'args' => ['newNumber' => ['type' => Type::int()]],
|
||||
'resolve' => function (Root $obj, $args) {
|
||||
return $obj->immediatelyChangeTheNumber($args['newNumber']);
|
||||
},
|
||||
],
|
||||
'promiseToChangeTheNumber' => [
|
||||
'type' => $numberHolderType,
|
||||
'args' => ['newNumber' => ['type' => Type::int()]],
|
||||
'resolve' => function (Root $obj, $args) {
|
||||
return $obj->promiseToChangeTheNumber($args['newNumber']);
|
||||
},
|
||||
],
|
||||
'failToChangeTheNumber' => [
|
||||
'type' => $numberHolderType,
|
||||
'args' => ['newNumber' => ['type' => Type::int()]],
|
||||
'resolve' => function (Root $obj, $args) {
|
||||
$obj->failToChangeTheNumber();
|
||||
},
|
||||
],
|
||||
'promiseAndFailToChangeTheNumber' => [
|
||||
'type' => $numberHolderType,
|
||||
'args' => ['newNumber' => ['type' => Type::int()]],
|
||||
'resolve' => function (Root $obj, $args) {
|
||||
return $obj->promiseAndFailToChangeTheNumber();
|
||||
},
|
||||
],
|
||||
],
|
||||
'name' => 'Mutation',
|
||||
]),
|
||||
]);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('evaluates mutations correctly in the presense of a failed mutation')
|
||||
*/
|
||||
public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() : void
|
||||
{
|
||||
$doc = 'mutation M {
|
||||
$doc = 'mutation M {
|
||||
first: immediatelyChangeTheNumber(newNumber: 1) {
|
||||
theNumber
|
||||
},
|
||||
@ -87,147 +129,28 @@ class MutationsTest extends TestCase
|
||||
theNumber
|
||||
}
|
||||
}';
|
||||
$ast = Parser::parse($doc);
|
||||
$ast = Parser::parse($doc);
|
||||
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
|
||||
$expected = [
|
||||
'data' => [
|
||||
'first' => [
|
||||
'theNumber' => 1
|
||||
],
|
||||
'second' => [
|
||||
'theNumber' => 2
|
||||
],
|
||||
'third' => null,
|
||||
'fourth' => [
|
||||
'theNumber' => 4
|
||||
],
|
||||
'fifth' => [
|
||||
'theNumber' => 5
|
||||
],
|
||||
'sixth' => null,
|
||||
$expected = [
|
||||
'data' => [
|
||||
'first' => ['theNumber' => 1],
|
||||
'second' => ['theNumber' => 2],
|
||||
'third' => null,
|
||||
'fourth' => ['theNumber' => 4],
|
||||
'fifth' => ['theNumber' => 5],
|
||||
'sixth' => null,
|
||||
],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot change the number',
|
||||
'locations' => [['line' => 8, 'column' => 7]]
|
||||
'locations' => [['line' => 8, 'column' => 7]],
|
||||
],
|
||||
[
|
||||
'debugMessage' => 'Cannot change the number',
|
||||
'locations' => [['line' => 17, 'column' => 7]]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 17, 'column' => 7]],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertArraySubset($expected, $mutationResult->toArray(true));
|
||||
}
|
||||
|
||||
private function schema()
|
||||
{
|
||||
$numberHolderType = new ObjectType([
|
||||
'fields' => [
|
||||
'theNumber' => ['type' => Type::int()],
|
||||
],
|
||||
'name' => 'NumberHolder',
|
||||
]);
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'fields' => [
|
||||
'numberHolder' => ['type' => $numberHolderType],
|
||||
],
|
||||
'name' => 'Query',
|
||||
]),
|
||||
'mutation' => new ObjectType([
|
||||
'fields' => [
|
||||
'immediatelyChangeTheNumber' => [
|
||||
'type' => $numberHolderType,
|
||||
'args' => ['newNumber' => ['type' => Type::int()]],
|
||||
'resolve' => (function (Root $obj, $args) {
|
||||
return $obj->immediatelyChangeTheNumber($args['newNumber']);
|
||||
})
|
||||
],
|
||||
'promiseToChangeTheNumber' => [
|
||||
'type' => $numberHolderType,
|
||||
'args' => ['newNumber' => ['type' => Type::int()]],
|
||||
'resolve' => (function (Root $obj, $args) {
|
||||
return $obj->promiseToChangeTheNumber($args['newNumber']);
|
||||
})
|
||||
],
|
||||
'failToChangeTheNumber' => [
|
||||
'type' => $numberHolderType,
|
||||
'args' => ['newNumber' => ['type' => Type::int()]],
|
||||
'resolve' => (function (Root $obj, $args) {
|
||||
return $obj->failToChangeTheNumber($args['newNumber']);
|
||||
})
|
||||
],
|
||||
'promiseAndFailToChangeTheNumber' => [
|
||||
'type' => $numberHolderType,
|
||||
'args' => ['newNumber' => ['type' => Type::int()]],
|
||||
'resolve' => (function (Root $obj, $args) {
|
||||
return $obj->promiseAndFailToChangeTheNumber($args['newNumber']);
|
||||
})
|
||||
]
|
||||
],
|
||||
'name' => 'Mutation',
|
||||
])
|
||||
]);
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
|
||||
class NumberHolder
|
||||
{
|
||||
public $theNumber;
|
||||
|
||||
public function __construct($originalNumber)
|
||||
{
|
||||
$this->theNumber = $originalNumber;
|
||||
}
|
||||
}
|
||||
|
||||
class Root {
|
||||
public $numberHolder;
|
||||
|
||||
public function __construct($originalNumber)
|
||||
{
|
||||
$this->numberHolder = new NumberHolder($originalNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $newNumber
|
||||
* @return NumberHolder
|
||||
*/
|
||||
public function immediatelyChangeTheNumber($newNumber)
|
||||
{
|
||||
$this->numberHolder->theNumber = $newNumber;
|
||||
return $this->numberHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $newNumber
|
||||
*
|
||||
* @return Deferred
|
||||
*/
|
||||
public function promiseToChangeTheNumber($newNumber)
|
||||
{
|
||||
return new Deferred(function () use ($newNumber) {
|
||||
return $this->immediatelyChangeTheNumber($newNumber);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function failToChangeTheNumber()
|
||||
{
|
||||
throw new \Exception('Cannot change the number');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Deferred
|
||||
*/
|
||||
public function promiseAndFailToChangeTheNumber()
|
||||
{
|
||||
return new Deferred(function () {
|
||||
throw new \Exception("Cannot change the number");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Deferred;
|
||||
use GraphQL\Error\FormattedError;
|
||||
use GraphQL\Error\UserError;
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Error\FormattedError;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class NonNullTest extends TestCase
|
||||
@ -27,41 +29,46 @@ class NonNullTest extends TestCase
|
||||
/** @var \Exception */
|
||||
public $promiseNonNullError;
|
||||
|
||||
/** @var callable[] */
|
||||
public $throwingData;
|
||||
|
||||
/** @var callable[] */
|
||||
public $nullingData;
|
||||
|
||||
/** @var Schema */
|
||||
public $schema;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->syncError = new UserError('sync');
|
||||
$this->syncNonNullError = new UserError('syncNonNull');
|
||||
$this->promiseError = new UserError('promise');
|
||||
$this->syncError = new UserError('sync');
|
||||
$this->syncNonNullError = new UserError('syncNonNull');
|
||||
$this->promiseError = new UserError('promise');
|
||||
$this->promiseNonNullError = new UserError('promiseNonNull');
|
||||
|
||||
$this->throwingData = [
|
||||
'sync' => function () {
|
||||
'sync' => function () {
|
||||
throw $this->syncError;
|
||||
},
|
||||
'syncNonNull' => function () {
|
||||
'syncNonNull' => function () {
|
||||
throw $this->syncNonNullError;
|
||||
},
|
||||
'promise' => function () {
|
||||
'promise' => function () {
|
||||
return new Deferred(function () {
|
||||
throw $this->promiseError;
|
||||
});
|
||||
},
|
||||
'promiseNonNull' => function () {
|
||||
'promiseNonNull' => function () {
|
||||
return new Deferred(function () {
|
||||
throw $this->promiseNonNullError;
|
||||
});
|
||||
},
|
||||
'syncNest' => function () {
|
||||
'syncNest' => function () {
|
||||
return $this->throwingData;
|
||||
},
|
||||
'syncNonNullNest' => function () {
|
||||
'syncNonNullNest' => function () {
|
||||
return $this->throwingData;
|
||||
},
|
||||
'promiseNest' => function () {
|
||||
'promiseNest' => function () {
|
||||
return new Deferred(function () {
|
||||
return $this->throwingData;
|
||||
});
|
||||
@ -74,29 +81,29 @@ class NonNullTest extends TestCase
|
||||
];
|
||||
|
||||
$this->nullingData = [
|
||||
'sync' => function () {
|
||||
'sync' => function () {
|
||||
return null;
|
||||
},
|
||||
'syncNonNull' => function () {
|
||||
'syncNonNull' => function () {
|
||||
return null;
|
||||
},
|
||||
'promise' => function () {
|
||||
'promise' => function () {
|
||||
return new Deferred(function () {
|
||||
return null;
|
||||
});
|
||||
},
|
||||
'promiseNonNull' => function () {
|
||||
'promiseNonNull' => function () {
|
||||
return new Deferred(function () {
|
||||
return null;
|
||||
});
|
||||
},
|
||||
'syncNest' => function () {
|
||||
'syncNest' => function () {
|
||||
return $this->nullingData;
|
||||
},
|
||||
'syncNonNullNest' => function () {
|
||||
'syncNonNullNest' => function () {
|
||||
return $this->nullingData;
|
||||
},
|
||||
'promiseNest' => function () {
|
||||
'promiseNest' => function () {
|
||||
return new Deferred(function () {
|
||||
return $this->nullingData;
|
||||
});
|
||||
@ -109,19 +116,19 @@ class NonNullTest extends TestCase
|
||||
];
|
||||
|
||||
$dataType = new ObjectType([
|
||||
'name' => 'DataType',
|
||||
'fields' => function() use (&$dataType) {
|
||||
'name' => 'DataType',
|
||||
'fields' => function () use (&$dataType) {
|
||||
return [
|
||||
'sync' => ['type' => Type::string()],
|
||||
'syncNonNull' => ['type' => Type::nonNull(Type::string())],
|
||||
'promise' => Type::string(),
|
||||
'promiseNonNull' => Type::nonNull(Type::string()),
|
||||
'syncNest' => $dataType,
|
||||
'syncNonNullNest' => Type::nonNull($dataType),
|
||||
'promiseNest' => $dataType,
|
||||
'sync' => ['type' => Type::string()],
|
||||
'syncNonNull' => ['type' => Type::nonNull(Type::string())],
|
||||
'promise' => Type::string(),
|
||||
'promiseNonNull' => Type::nonNull(Type::string()),
|
||||
'syncNest' => $dataType,
|
||||
'syncNonNullNest' => Type::nonNull($dataType),
|
||||
'promiseNest' => $dataType,
|
||||
'promiseNonNullNest' => Type::nonNull($dataType),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->schema = new Schema(['query' => $dataType]);
|
||||
@ -143,17 +150,18 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'sync' => null,
|
||||
],
|
||||
'data' => ['sync' => null],
|
||||
'errors' => [
|
||||
FormattedError::create(
|
||||
$this->syncError->getMessage(),
|
||||
[new SourceLocation(3, 9)]
|
||||
)
|
||||
]
|
||||
),
|
||||
],
|
||||
];
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsANullableFieldThatThrowsInAPromise() : void
|
||||
@ -167,18 +175,19 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'promise' => null,
|
||||
],
|
||||
'data' => ['promise' => null],
|
||||
'errors' => [
|
||||
FormattedError::create(
|
||||
$this->promiseError->getMessage(),
|
||||
[new SourceLocation(3, 9)]
|
||||
)
|
||||
]
|
||||
),
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously() : void
|
||||
@ -195,14 +204,15 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'syncNest' => null
|
||||
],
|
||||
'data' => ['syncNest' => null],
|
||||
'errors' => [
|
||||
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)])
|
||||
]
|
||||
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]),
|
||||
],
|
||||
];
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsAsynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsInAPromise() : void
|
||||
@ -218,15 +228,16 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'syncNest' => null
|
||||
],
|
||||
'data' => ['syncNest' => null],
|
||||
'errors' => [
|
||||
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)])
|
||||
]
|
||||
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]),
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsSynchronously() : void
|
||||
@ -242,15 +253,16 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'promiseNest' => null
|
||||
],
|
||||
'data' => ['promiseNest' => null],
|
||||
'errors' => [
|
||||
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)])
|
||||
]
|
||||
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]),
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsInAPromise() : void
|
||||
@ -266,15 +278,16 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'promiseNest' => null
|
||||
],
|
||||
'data' => ['promiseNest' => null],
|
||||
'errors' => [
|
||||
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)])
|
||||
]
|
||||
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]),
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,28 +327,28 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'data' => [
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
],
|
||||
'promiseNest' => [
|
||||
'sync' => null,
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
],
|
||||
],
|
||||
'promiseNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
],
|
||||
'promiseNest' => [
|
||||
'sync' => null,
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
],
|
||||
],
|
||||
@ -353,10 +366,13 @@ class NonNullTest extends TestCase
|
||||
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(17, 11)]),
|
||||
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(20, 13)]),
|
||||
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(24, 13)]),
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfFieldsThatAreNonNull() : void
|
||||
@ -413,10 +429,10 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'syncNest' => null,
|
||||
'promiseNest' => null,
|
||||
'anotherNest' => null,
|
||||
'data' => [
|
||||
'syncNest' => null,
|
||||
'promiseNest' => null,
|
||||
'anotherNest' => null,
|
||||
'anotherPromiseNest' => null,
|
||||
],
|
||||
'errors' => [
|
||||
@ -424,10 +440,13 @@ class NonNullTest extends TestCase
|
||||
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(19, 19)]),
|
||||
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(30, 19)]),
|
||||
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(41, 19)]),
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsANullableFieldThatSynchronouslyReturnsNull() : void
|
||||
@ -441,11 +460,12 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'sync' => null,
|
||||
]
|
||||
'data' => ['sync' => null],
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray());
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsANullableFieldThatReturnsNullInAPromise() : void
|
||||
@ -459,12 +479,13 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'promise' => null,
|
||||
]
|
||||
'data' => ['promise' => null],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullSynchronously() : void
|
||||
@ -480,17 +501,18 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'syncNest' => null
|
||||
],
|
||||
'data' => ['syncNest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
|
||||
'locations' => [['line' => 4, 'column' => 11]]
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 4, 'column' => 11]],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true));
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true)
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise() : void
|
||||
@ -506,15 +528,13 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'syncNest' => null,
|
||||
],
|
||||
'data' => ['syncNest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
|
||||
'locations' => [['line' => 4, 'column' => 11]]
|
||||
'locations' => [['line' => 4, 'column' => 11]],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset(
|
||||
@ -536,15 +556,13 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'promiseNest' => null,
|
||||
],
|
||||
'data' => ['promiseNest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
|
||||
'locations' => [['line' => 4, 'column' => 11]]
|
||||
'locations' => [['line' => 4, 'column' => 11]],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset(
|
||||
@ -566,15 +584,13 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'promiseNest' => null,
|
||||
],
|
||||
'data' => ['promiseNest' => null],
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
|
||||
'locations' => [['line' => 4, 'column' => 11]]
|
||||
'locations' => [['line' => 4, 'column' => 11]],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset(
|
||||
@ -618,31 +634,31 @@ class NonNullTest extends TestCase
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
],
|
||||
'promiseNest' => [
|
||||
'sync' => null,
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
]
|
||||
],
|
||||
],
|
||||
'promiseNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
'syncNest' => [
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
],
|
||||
'promiseNest' => [
|
||||
'sync' => null,
|
||||
'sync' => null,
|
||||
'promise' => null,
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray();
|
||||
@ -703,18 +719,18 @@ class NonNullTest extends TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'syncNest' => null,
|
||||
'promiseNest' => null,
|
||||
'anotherNest' => null,
|
||||
'data' => [
|
||||
'syncNest' => null,
|
||||
'promiseNest' => null,
|
||||
'anotherNest' => null,
|
||||
'anotherPromiseNest' => null,
|
||||
],
|
||||
'errors' => [
|
||||
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [ ['line' => 8, 'column' => 19]]],
|
||||
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [ ['line' => 19, 'column' => 19]]],
|
||||
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [ ['line' => 30, 'column' => 19]]],
|
||||
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [ ['line' => 41, 'column' => 19]]],
|
||||
]
|
||||
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 8, 'column' => 19]]],
|
||||
['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 19, 'column' => 19]]],
|
||||
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 30, 'column' => 19]]],
|
||||
['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 41, 'column' => 19]]],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset(
|
||||
@ -734,10 +750,10 @@ class NonNullTest extends TestCase
|
||||
|
||||
$expected = [
|
||||
'errors' => [
|
||||
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(2, 17)])
|
||||
]
|
||||
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(2, 17)]),
|
||||
],
|
||||
];
|
||||
$actual = Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray();
|
||||
$actual = Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray();
|
||||
$this->assertArraySubset($expected, $actual);
|
||||
}
|
||||
|
||||
@ -752,10 +768,13 @@ class NonNullTest extends TestCase
|
||||
$expected = [
|
||||
'errors' => [
|
||||
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(2, 17)]),
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() : void
|
||||
@ -769,9 +788,9 @@ class NonNullTest extends TestCase
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
|
||||
'locations' => [['line' => 2, 'column' => 17]]
|
||||
'locations' => [['line' => 2, 'column' => 17]],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
$this->assertArraySubset(
|
||||
$expected,
|
||||
@ -791,9 +810,9 @@ class NonNullTest extends TestCase
|
||||
'errors' => [
|
||||
[
|
||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
|
||||
'locations' => [['line' => 2, 'column' => 17]]
|
||||
'locations' => [['line' => 2, 'column' => 17]],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset(
|
||||
|
@ -1,17 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor\Promise;
|
||||
|
||||
|
||||
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
|
||||
use GraphQL\Executor\Promise\Promise;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\FulfilledPromise;
|
||||
use React\Promise\LazyPromise;
|
||||
use React\Promise\Promise as ReactPromise;
|
||||
use React\Promise\RejectedPromise;
|
||||
use function class_exists;
|
||||
|
||||
/**
|
||||
* @group ReactPromise
|
||||
@ -20,27 +20,35 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
if(! class_exists('React\Promise\Promise')) {
|
||||
$this->markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest');
|
||||
if (class_exists('React\Promise\Promise')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest');
|
||||
}
|
||||
|
||||
public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven() : void
|
||||
{
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
|
||||
$this->assertSame(true, $reactAdapter->isThenable(new ReactPromise(function() {})));
|
||||
$this->assertSame(true, $reactAdapter->isThenable(new FulfilledPromise()));
|
||||
$this->assertSame(true, $reactAdapter->isThenable(new RejectedPromise()));
|
||||
$this->assertSame(true, $reactAdapter->isThenable(new LazyPromise(function() {})));
|
||||
$this->assertSame(false, $reactAdapter->isThenable(false));
|
||||
$this->assertSame(false, $reactAdapter->isThenable(true));
|
||||
$this->assertSame(false, $reactAdapter->isThenable(1));
|
||||
$this->assertSame(false, $reactAdapter->isThenable(0));
|
||||
$this->assertSame(false, $reactAdapter->isThenable('test'));
|
||||
$this->assertSame(false, $reactAdapter->isThenable(''));
|
||||
$this->assertSame(false, $reactAdapter->isThenable([]));
|
||||
$this->assertSame(false, $reactAdapter->isThenable(new \stdClass()));
|
||||
$this->assertTrue(
|
||||
$reactAdapter->isThenable(new ReactPromise(function () {
|
||||
}))
|
||||
);
|
||||
$this->assertTrue($reactAdapter->isThenable(new FulfilledPromise()));
|
||||
$this->assertTrue($reactAdapter->isThenable(new RejectedPromise()));
|
||||
$this->assertTrue(
|
||||
$reactAdapter->isThenable(new LazyPromise(function () {
|
||||
}))
|
||||
);
|
||||
$this->assertFalse($reactAdapter->isThenable(false));
|
||||
$this->assertFalse($reactAdapter->isThenable(true));
|
||||
$this->assertFalse($reactAdapter->isThenable(1));
|
||||
$this->assertFalse($reactAdapter->isThenable(0));
|
||||
$this->assertFalse($reactAdapter->isThenable('test'));
|
||||
$this->assertFalse($reactAdapter->isThenable(''));
|
||||
$this->assertFalse($reactAdapter->isThenable([]));
|
||||
$this->assertFalse($reactAdapter->isThenable(new \stdClass()));
|
||||
}
|
||||
|
||||
public function testConvertsReactPromisesToGraphQlOnes() : void
|
||||
@ -58,13 +66,16 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
{
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
$reactPromise = new FulfilledPromise(1);
|
||||
$promise = $reactAdapter->convertThenable($reactPromise);
|
||||
$promise = $reactAdapter->convertThenable($reactPromise);
|
||||
|
||||
$result = null;
|
||||
|
||||
$resultPromise = $reactAdapter->then($promise, function ($value) use (&$result) {
|
||||
$result = $value;
|
||||
});
|
||||
$resultPromise = $reactAdapter->then(
|
||||
$promise,
|
||||
function ($value) use (&$result) {
|
||||
$result = $value;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertSame(1, $result);
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise);
|
||||
@ -73,9 +84,9 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
|
||||
public function testCreate() : void
|
||||
{
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
$resolvedPromise = $reactAdapter->create(function ($resolve) {
|
||||
$resolve(1);
|
||||
$resolve(1);
|
||||
});
|
||||
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resolvedPromise);
|
||||
@ -84,7 +95,7 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
$result = null;
|
||||
|
||||
$resolvedPromise->then(function ($value) use (&$result) {
|
||||
$result = $value;
|
||||
$result = $value;
|
||||
});
|
||||
|
||||
$this->assertSame(1, $result);
|
||||
@ -92,7 +103,7 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
|
||||
public function testCreateFulfilled() : void
|
||||
{
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
$fulfilledPromise = $reactAdapter->createFulfilled(1);
|
||||
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $fulfilledPromise);
|
||||
@ -109,7 +120,7 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
|
||||
public function testCreateRejected() : void
|
||||
{
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
$rejectedPromise = $reactAdapter->createRejected(new \Exception('I am a bad promise'));
|
||||
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $rejectedPromise);
|
||||
@ -117,9 +128,12 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
|
||||
$exception = null;
|
||||
|
||||
$rejectedPromise->then(null, function ($error) use (&$exception) {
|
||||
$exception = $error;
|
||||
});
|
||||
$rejectedPromise->then(
|
||||
null,
|
||||
function ($error) use (&$exception) {
|
||||
$exception = $error;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertInstanceOf('\Exception', $exception);
|
||||
$this->assertEquals('I am a bad promise', $exception->getMessage());
|
||||
@ -128,7 +142,7 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
public function testAll() : void
|
||||
{
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
$promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)];
|
||||
$promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)];
|
||||
|
||||
$allPromise = $reactAdapter->all($promises);
|
||||
|
||||
@ -138,7 +152,7 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
$result = null;
|
||||
|
||||
$allPromise->then(function ($values) use (&$result) {
|
||||
$result = $values;
|
||||
$result = $values;
|
||||
});
|
||||
|
||||
$this->assertSame([1, 2, 3], $result);
|
||||
@ -147,9 +161,9 @@ class ReactPromiseAdapterTest extends TestCase
|
||||
public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromises() : void
|
||||
{
|
||||
$reactAdapter = new ReactPromiseAdapter();
|
||||
$deferred = new Deferred();
|
||||
$promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)];
|
||||
$result = null;
|
||||
$deferred = new Deferred();
|
||||
$promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)];
|
||||
$result = null;
|
||||
|
||||
$reactAdapter->all($promises)->then(function ($values) use (&$result) {
|
||||
$result = $values;
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor\Promise;
|
||||
|
||||
use GraphQL\Deferred;
|
||||
@ -10,9 +13,7 @@ use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SyncPromiseAdapterTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var SyncPromiseAdapter
|
||||
*/
|
||||
/** @var SyncPromiseAdapter */
|
||||
private $promises;
|
||||
|
||||
public function setUp()
|
||||
@ -22,7 +23,11 @@ class SyncPromiseAdapterTest extends TestCase
|
||||
|
||||
public function testIsThenable() : void
|
||||
{
|
||||
$this->assertEquals(true, $this->promises->isThenable(new Deferred(function() {})));
|
||||
$this->assertEquals(
|
||||
true,
|
||||
$this->promises->isThenable(new Deferred(function () {
|
||||
}))
|
||||
);
|
||||
$this->assertEquals(false, $this->promises->isThenable(false));
|
||||
$this->assertEquals(false, $this->promises->isThenable(true));
|
||||
$this->assertEquals(false, $this->promises->isThenable(1));
|
||||
@ -35,7 +40,8 @@ class SyncPromiseAdapterTest extends TestCase
|
||||
|
||||
public function testConvert() : void
|
||||
{
|
||||
$dfd = new Deferred(function() {});
|
||||
$dfd = new Deferred(function () {
|
||||
});
|
||||
$result = $this->promises->convertThenable($dfd);
|
||||
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $result);
|
||||
@ -48,7 +54,8 @@ class SyncPromiseAdapterTest extends TestCase
|
||||
|
||||
public function testThen() : void
|
||||
{
|
||||
$dfd = new Deferred(function() {});
|
||||
$dfd = new Deferred(function () {
|
||||
});
|
||||
$promise = $this->promises->convertThenable($dfd);
|
||||
|
||||
$result = $this->promises->then($promise);
|
||||
@ -59,18 +66,55 @@ class SyncPromiseAdapterTest extends TestCase
|
||||
|
||||
public function testCreatePromise() : void
|
||||
{
|
||||
$promise = $this->promises->create(function($resolve, $reject) {});
|
||||
$promise = $this->promises->create(function ($resolve, $reject) {
|
||||
});
|
||||
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise);
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise);
|
||||
|
||||
$promise = $this->promises->create(function($resolve, $reject) {
|
||||
$promise = $this->promises->create(function ($resolve, $reject) {
|
||||
$resolve('A');
|
||||
});
|
||||
|
||||
$this->assertValidPromise($promise, null, 'A', SyncPromise::FULFILLED);
|
||||
}
|
||||
|
||||
private function assertValidPromise($promise, $expectedNextReason, $expectedNextValue, $expectedNextState)
|
||||
{
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise);
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise);
|
||||
|
||||
$actualNextValue = null;
|
||||
$actualNextReason = null;
|
||||
$onFulfilledCalled = false;
|
||||
$onRejectedCalled = false;
|
||||
|
||||
$promise->then(
|
||||
function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
|
||||
$onFulfilledCalled = true;
|
||||
$actualNextValue = $nextValue;
|
||||
},
|
||||
function (\Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) {
|
||||
$onRejectedCalled = true;
|
||||
$actualNextReason = $reason->getMessage();
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertSame($onFulfilledCalled, false);
|
||||
$this->assertSame($onRejectedCalled, false);
|
||||
|
||||
SyncPromise::runQueue();
|
||||
|
||||
if ($expectedNextState !== SyncPromise::PENDING) {
|
||||
$this->assertSame(! $expectedNextReason, $onFulfilledCalled);
|
||||
$this->assertSame(! ! $expectedNextReason, $onRejectedCalled);
|
||||
}
|
||||
|
||||
$this->assertSame($expectedNextValue, $actualNextValue);
|
||||
$this->assertSame($expectedNextReason, $actualNextReason);
|
||||
$this->assertSame($expectedNextState, $promise->adoptedPromise->state);
|
||||
}
|
||||
|
||||
public function testCreateFulfilledPromise() : void
|
||||
{
|
||||
$promise = $this->promises->createFulfilled('test');
|
||||
@ -94,8 +138,8 @@ class SyncPromiseAdapterTest extends TestCase
|
||||
$promise1 = new SyncPromise();
|
||||
$promise2 = new SyncPromise();
|
||||
$promise3 = $promise2->then(
|
||||
function($value) {
|
||||
return $value .'-value3';
|
||||
function ($value) {
|
||||
return $value . '-value3';
|
||||
}
|
||||
);
|
||||
|
||||
@ -105,7 +149,7 @@ class SyncPromiseAdapterTest extends TestCase
|
||||
new Promise($promise2, $this->promises),
|
||||
3,
|
||||
new Promise($promise3, $this->promises),
|
||||
[]
|
||||
[],
|
||||
];
|
||||
|
||||
$promise = $this->promises->all($data);
|
||||
@ -114,36 +158,46 @@ class SyncPromiseAdapterTest extends TestCase
|
||||
$promise1->resolve('value1');
|
||||
$this->assertValidPromise($promise, null, null, SyncPromise::PENDING);
|
||||
$promise2->resolve('value2');
|
||||
$this->assertValidPromise($promise, null, ['1', 'value1', 'value2', 3, 'value2-value3', []], SyncPromise::FULFILLED);
|
||||
$this->assertValidPromise(
|
||||
$promise,
|
||||
null,
|
||||
['1', 'value1', 'value2', 3, 'value2-value3', []],
|
||||
SyncPromise::FULFILLED
|
||||
);
|
||||
}
|
||||
|
||||
public function testWait() : void
|
||||
{
|
||||
$called = [];
|
||||
|
||||
$deferred1 = new Deferred(function() use (&$called) {
|
||||
$deferred1 = new Deferred(function () use (&$called) {
|
||||
$called[] = 1;
|
||||
|
||||
return 1;
|
||||
});
|
||||
$deferred2 = new Deferred(function() use (&$called) {
|
||||
$deferred2 = new Deferred(function () use (&$called) {
|
||||
$called[] = 2;
|
||||
|
||||
return 2;
|
||||
});
|
||||
|
||||
$p1 = $this->promises->convertThenable($deferred1);
|
||||
$p2 = $this->promises->convertThenable($deferred2);
|
||||
|
||||
$p3 = $p2->then(function() use (&$called) {
|
||||
$dfd = new Deferred(function() use (&$called) {
|
||||
$p3 = $p2->then(function () use (&$called) {
|
||||
$dfd = new Deferred(function () use (&$called) {
|
||||
$called[] = 3;
|
||||
|
||||
return 3;
|
||||
});
|
||||
|
||||
return $this->promises->convertThenable($dfd);
|
||||
});
|
||||
|
||||
$p4 = $p3->then(function() use (&$called) {
|
||||
return new Deferred(function() use (&$called) {
|
||||
$p4 = $p3->then(function () use (&$called) {
|
||||
return new Deferred(function () use (&$called) {
|
||||
$called[] = 4;
|
||||
|
||||
return 4;
|
||||
});
|
||||
});
|
||||
@ -156,46 +210,10 @@ class SyncPromiseAdapterTest extends TestCase
|
||||
$this->assertEquals(SyncPromise::PENDING, $all->adoptedPromise->state);
|
||||
$this->assertEquals([1, 2], $called);
|
||||
|
||||
$expectedResult = [0,1,2,3,4];
|
||||
$result = $this->promises->wait($all);
|
||||
$expectedResult = [0, 1, 2, 3, 4];
|
||||
$result = $this->promises->wait($all);
|
||||
$this->assertEquals($expectedResult, $result);
|
||||
$this->assertEquals([1, 2, 3, 4], $called);
|
||||
$this->assertValidPromise($all, null, [0,1,2,3,4], SyncPromise::FULFILLED);
|
||||
}
|
||||
|
||||
private function assertValidPromise($promise, $expectedNextReason, $expectedNextValue, $expectedNextState)
|
||||
{
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise);
|
||||
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise);
|
||||
|
||||
$actualNextValue = null;
|
||||
$actualNextReason = null;
|
||||
$onFulfilledCalled = false;
|
||||
$onRejectedCalled = false;
|
||||
|
||||
$promise->then(
|
||||
function($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
|
||||
$onFulfilledCalled = true;
|
||||
$actualNextValue = $nextValue;
|
||||
},
|
||||
function(\Exception $reason) use (&$actualNextReason, &$onRejectedCalled) {
|
||||
$onRejectedCalled = true;
|
||||
$actualNextReason = $reason->getMessage();
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertSame($onFulfilledCalled, false);
|
||||
$this->assertSame($onRejectedCalled, false);
|
||||
|
||||
SyncPromise::runQueue();
|
||||
|
||||
if ($expectedNextState !== SyncPromise::PENDING) {
|
||||
$this->assertSame(!$expectedNextReason, $onFulfilledCalled);
|
||||
$this->assertSame(!!$expectedNextReason, $onRejectedCalled);
|
||||
}
|
||||
|
||||
$this->assertSame($expectedNextValue, $actualNextValue);
|
||||
$this->assertSame($expectedNextReason, $actualNextReason);
|
||||
$this->assertSame($expectedNextState, $promise->adoptedPromise->state);
|
||||
$this->assertValidPromise($all, null, [0, 1, 2, 3, 4], SyncPromise::FULFILLED);
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor\Promise;
|
||||
|
||||
use GraphQL\Executor\Promise\Adapter\SyncPromise;
|
||||
use PHPUnit\Framework\Error\Error;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function uniqid;
|
||||
|
||||
class SyncPromiseTest extends TestCase
|
||||
{
|
||||
public function getFulfilledPromiseResolveData()
|
||||
{
|
||||
$onFulfilledReturnsNull = function() {
|
||||
$onFulfilledReturnsNull = function () {
|
||||
return null;
|
||||
};
|
||||
$onFulfilledReturnsSameValue = function($value) {
|
||||
|
||||
$onFulfilledReturnsSameValue = function ($value) {
|
||||
return $value;
|
||||
};
|
||||
$onFulfilledReturnsOtherValue = function($value) {
|
||||
|
||||
$onFulfilledReturnsOtherValue = function ($value) {
|
||||
return 'other-' . $value;
|
||||
};
|
||||
$onFulfilledThrows = function($value) {
|
||||
throw new \Exception("onFulfilled throws this!");
|
||||
|
||||
$onFulfilledThrows = function ($value) {
|
||||
throw new \Exception('onFulfilled throws this!');
|
||||
};
|
||||
|
||||
return [
|
||||
@ -28,7 +35,7 @@ class SyncPromiseTest extends TestCase
|
||||
[uniqid(), $onFulfilledReturnsNull, null, null, SyncPromise::FULFILLED],
|
||||
['test-value', $onFulfilledReturnsSameValue, 'test-value', null, SyncPromise::FULFILLED],
|
||||
['test-value-2', $onFulfilledReturnsOtherValue, 'other-test-value-2', null, SyncPromise::FULFILLED],
|
||||
['test-value-3', $onFulfilledThrows, null, "onFulfilled throws this!", SyncPromise::REJECTED],
|
||||
['test-value-3', $onFulfilledThrows, null, 'onFulfilled throws this!', SyncPromise::REJECTED],
|
||||
];
|
||||
}
|
||||
|
||||
@ -41,8 +48,7 @@ class SyncPromiseTest extends TestCase
|
||||
$expectedNextValue,
|
||||
$expectedNextReason,
|
||||
$expectedNextState
|
||||
)
|
||||
{
|
||||
) {
|
||||
$promise = new SyncPromise();
|
||||
$this->assertEquals(SyncPromise::PENDING, $promise->state);
|
||||
|
||||
@ -63,8 +69,7 @@ class SyncPromiseTest extends TestCase
|
||||
$expectedNextValue,
|
||||
$expectedNextReason,
|
||||
$expectedNextState
|
||||
)
|
||||
{
|
||||
) {
|
||||
$promise = new SyncPromise();
|
||||
$this->assertEquals(SyncPromise::PENDING, $promise->state);
|
||||
|
||||
@ -85,21 +90,27 @@ class SyncPromiseTest extends TestCase
|
||||
$expectedNextValue,
|
||||
$expectedNextReason,
|
||||
$expectedNextState
|
||||
)
|
||||
{
|
||||
) {
|
||||
$promise = new SyncPromise();
|
||||
$this->assertEquals(SyncPromise::PENDING, $promise->state);
|
||||
|
||||
$promise->resolve($resolvedValue);
|
||||
$this->assertEquals(SyncPromise::FULFILLED, $promise->state);
|
||||
|
||||
$nextPromise = $promise->then(null, function() {});
|
||||
$nextPromise = $promise->then(
|
||||
null,
|
||||
function () {
|
||||
}
|
||||
);
|
||||
$this->assertSame($promise, $nextPromise);
|
||||
|
||||
$onRejectedCalled = false;
|
||||
$nextPromise = $promise->then($onFulfilled, function () use (&$onRejectedCalled) {
|
||||
$onRejectedCalled = true;
|
||||
});
|
||||
$nextPromise = $promise->then(
|
||||
$onFulfilled,
|
||||
function () use (&$onRejectedCalled) {
|
||||
$onRejectedCalled = true;
|
||||
}
|
||||
);
|
||||
|
||||
if ($onFulfilled) {
|
||||
$this->assertNotSame($promise, $nextPromise);
|
||||
@ -124,19 +135,57 @@ class SyncPromiseTest extends TestCase
|
||||
$this->assertValidPromise($nextPromise3, $expectedNextReason, $expectedNextValue, $expectedNextState);
|
||||
}
|
||||
|
||||
private function assertValidPromise(
|
||||
SyncPromise $promise,
|
||||
$expectedNextReason,
|
||||
$expectedNextValue,
|
||||
$expectedNextState
|
||||
) {
|
||||
$actualNextValue = null;
|
||||
$actualNextReason = null;
|
||||
$onFulfilledCalled = false;
|
||||
$onRejectedCalled = false;
|
||||
|
||||
$promise->then(
|
||||
function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
|
||||
$onFulfilledCalled = true;
|
||||
$actualNextValue = $nextValue;
|
||||
},
|
||||
function (\Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) {
|
||||
$onRejectedCalled = true;
|
||||
$actualNextReason = $reason->getMessage();
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertEquals($onFulfilledCalled, false);
|
||||
$this->assertEquals($onRejectedCalled, false);
|
||||
|
||||
SyncPromise::runQueue();
|
||||
|
||||
$this->assertEquals(! $expectedNextReason, $onFulfilledCalled);
|
||||
$this->assertEquals(! ! $expectedNextReason, $onRejectedCalled);
|
||||
|
||||
$this->assertEquals($expectedNextValue, $actualNextValue);
|
||||
$this->assertEquals($expectedNextReason, $actualNextReason);
|
||||
$this->assertEquals($expectedNextState, $promise->state);
|
||||
}
|
||||
|
||||
public function getRejectedPromiseData()
|
||||
{
|
||||
$onRejectedReturnsNull = function() {
|
||||
$onRejectedReturnsNull = function () {
|
||||
return null;
|
||||
};
|
||||
$onRejectedReturnsSomeValue = function($reason) {
|
||||
|
||||
$onRejectedReturnsSomeValue = function ($reason) {
|
||||
return 'some-value';
|
||||
};
|
||||
$onRejectedThrowsSameReason = function($reason) {
|
||||
|
||||
$onRejectedThrowsSameReason = function ($reason) {
|
||||
throw $reason;
|
||||
};
|
||||
$onRejectedThrowsOtherReason = function($value) {
|
||||
throw new \Exception("onRejected throws other!");
|
||||
|
||||
$onRejectedThrowsOtherReason = function ($value) {
|
||||
throw new \Exception('onRejected throws other!');
|
||||
};
|
||||
|
||||
return [
|
||||
@ -158,8 +207,7 @@ class SyncPromiseTest extends TestCase
|
||||
$expectedNextValue,
|
||||
$expectedNextReason,
|
||||
$expectedNextState
|
||||
)
|
||||
{
|
||||
) {
|
||||
$promise = new SyncPromise();
|
||||
$this->assertEquals(SyncPromise::PENDING, $promise->state);
|
||||
|
||||
@ -169,7 +217,6 @@ class SyncPromiseTest extends TestCase
|
||||
$this->expectException(\Throwable::class);
|
||||
$this->expectExceptionMessage('Cannot change rejection reason');
|
||||
$promise->reject(new \Exception('other-reason'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,8 +228,7 @@ class SyncPromiseTest extends TestCase
|
||||
$expectedNextValue,
|
||||
$expectedNextReason,
|
||||
$expectedNextState
|
||||
)
|
||||
{
|
||||
) {
|
||||
$promise = new SyncPromise();
|
||||
$this->assertEquals(SyncPromise::PENDING, $promise->state);
|
||||
|
||||
@ -203,8 +249,7 @@ class SyncPromiseTest extends TestCase
|
||||
$expectedNextValue,
|
||||
$expectedNextReason,
|
||||
$expectedNextState
|
||||
)
|
||||
{
|
||||
) {
|
||||
$promise = new SyncPromise();
|
||||
$this->assertEquals(SyncPromise::PENDING, $promise->state);
|
||||
|
||||
@ -214,22 +259,26 @@ class SyncPromiseTest extends TestCase
|
||||
try {
|
||||
$promise->reject(new \Exception('other-reason'));
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->assertEquals('Cannot change rejection reason', $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$promise->resolve('anything');
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->assertEquals('Cannot resolve rejected promise', $e->getMessage());
|
||||
}
|
||||
|
||||
$nextPromise = $promise->then(function() {}, null);
|
||||
$nextPromise = $promise->then(
|
||||
function () {
|
||||
},
|
||||
null
|
||||
);
|
||||
$this->assertSame($promise, $nextPromise);
|
||||
|
||||
$onFulfilledCalled = false;
|
||||
$nextPromise = $promise->then(
|
||||
$nextPromise = $promise->then(
|
||||
function () use (&$onFulfilledCalled) {
|
||||
$onFulfilledCalled = true;
|
||||
},
|
||||
@ -266,7 +315,7 @@ class SyncPromiseTest extends TestCase
|
||||
try {
|
||||
$promise->resolve($promise);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->assertEquals('Cannot resolve promise with self', $e->getMessage());
|
||||
$this->assertEquals(SyncPromise::PENDING, $promise->state);
|
||||
}
|
||||
@ -299,24 +348,25 @@ class SyncPromiseTest extends TestCase
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
$this->assertEquals(SyncPromise::PENDING, $promise->state);
|
||||
} catch (\Exception $e) {
|
||||
$this->assertEquals(SyncPromise::PENDING, $promise->state);
|
||||
}
|
||||
|
||||
$promise->reject(new \Exception("Rejected Reason"));
|
||||
$this->assertValidPromise($promise, "Rejected Reason", null, SyncPromise::REJECTED);
|
||||
$promise->reject(new \Exception('Rejected Reason'));
|
||||
$this->assertValidPromise($promise, 'Rejected Reason', null, SyncPromise::REJECTED);
|
||||
|
||||
$promise = new SyncPromise();
|
||||
$promise2 = $promise->then(null, function() {
|
||||
return 'value';
|
||||
});
|
||||
$promise->reject(new \Exception("Rejected Again"));
|
||||
$promise = new SyncPromise();
|
||||
$promise2 = $promise->then(
|
||||
null,
|
||||
function () {
|
||||
return 'value';
|
||||
}
|
||||
);
|
||||
$promise->reject(new \Exception('Rejected Again'));
|
||||
$this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED);
|
||||
|
||||
$promise = new SyncPromise();
|
||||
$promise = new SyncPromise();
|
||||
$promise2 = $promise->then();
|
||||
$promise->reject(new \Exception("Rejected Once Again"));
|
||||
$this->assertValidPromise($promise2, "Rejected Once Again", null, SyncPromise::REJECTED);
|
||||
$promise->reject(new \Exception('Rejected Once Again'));
|
||||
$this->assertValidPromise($promise2, 'Rejected Once Again', null, SyncPromise::REJECTED);
|
||||
}
|
||||
|
||||
public function testPendingPromiseThen() : void
|
||||
@ -331,12 +381,14 @@ class SyncPromiseTest extends TestCase
|
||||
|
||||
// Make sure that it queues derivative promises until resolution:
|
||||
$onFulfilledCount = 0;
|
||||
$onRejectedCount = 0;
|
||||
$onFulfilled = function($value) use (&$onFulfilledCount) {
|
||||
$onRejectedCount = 0;
|
||||
$onFulfilled = function ($value) use (&$onFulfilledCount) {
|
||||
$onFulfilledCount++;
|
||||
|
||||
return $onFulfilledCount;
|
||||
};
|
||||
$onRejected = function($reason) use (&$onRejectedCount) {
|
||||
|
||||
$onRejected = function ($reason) use (&$onRejectedCount) {
|
||||
$onRejectedCount++;
|
||||
throw $reason;
|
||||
};
|
||||
@ -367,35 +419,4 @@ class SyncPromiseTest extends TestCase
|
||||
$this->assertValidPromise($nextPromise3, null, 2, SyncPromise::FULFILLED);
|
||||
$this->assertValidPromise($nextPromise4, null, 3, SyncPromise::FULFILLED);
|
||||
}
|
||||
|
||||
private function assertValidPromise(SyncPromise $promise, $expectedNextReason, $expectedNextValue, $expectedNextState)
|
||||
{
|
||||
$actualNextValue = null;
|
||||
$actualNextReason = null;
|
||||
$onFulfilledCalled = false;
|
||||
$onRejectedCalled = false;
|
||||
|
||||
$promise->then(
|
||||
function($nextValue) use (&$actualNextValue, &$onFulfilledCalled) {
|
||||
$onFulfilledCalled = true;
|
||||
$actualNextValue = $nextValue;
|
||||
},
|
||||
function(\Exception $reason) use (&$actualNextReason, &$onRejectedCalled) {
|
||||
$onRejectedCalled = true;
|
||||
$actualNextReason = $reason->getMessage();
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertEquals($onFulfilledCalled, false);
|
||||
$this->assertEquals($onRejectedCalled, false);
|
||||
|
||||
SyncPromise::runQueue();
|
||||
|
||||
$this->assertEquals(!$expectedNextReason, $onFulfilledCalled);
|
||||
$this->assertEquals(!!$expectedNextReason, $onRejectedCalled);
|
||||
|
||||
$this->assertEquals($expectedNextValue, $actualNextValue);
|
||||
$this->assertEquals($expectedNextReason, $actualNextReason);
|
||||
$this->assertEquals($expectedNextState, $promise->state);
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Tests\Executor\TestClasses\Adder;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
require_once __DIR__ . '/TestClasses.php';
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function json_encode;
|
||||
use function uniqid;
|
||||
|
||||
class ResolveTest extends TestCase
|
||||
{
|
||||
// Execute: resolve function
|
||||
|
||||
private function buildSchema($testField)
|
||||
{
|
||||
return new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'test' => $testField
|
||||
]
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('default function accesses properties')
|
||||
*/
|
||||
@ -32,9 +24,7 @@ class ResolveTest extends TestCase
|
||||
{
|
||||
$schema = $this->buildSchema(['type' => Type::string()]);
|
||||
|
||||
$source = [
|
||||
'test' => 'testValue'
|
||||
];
|
||||
$source = ['test' => 'testValue'];
|
||||
|
||||
$this->assertEquals(
|
||||
['data' => ['test' => 'testValue']],
|
||||
@ -42,18 +32,28 @@ class ResolveTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
private function buildSchema($testField)
|
||||
{
|
||||
return new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['test' => $testField],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('default function calls methods')
|
||||
*/
|
||||
public function testDefaultFunctionCallsClosures() : void
|
||||
{
|
||||
$schema = $this->buildSchema(['type' => Type::string()]);
|
||||
$schema = $this->buildSchema(['type' => Type::string()]);
|
||||
$_secret = 'secretValue' . uniqid();
|
||||
|
||||
$source = [
|
||||
'test' => function() use ($_secret) {
|
||||
'test' => function () use ($_secret) {
|
||||
return $_secret;
|
||||
}
|
||||
},
|
||||
];
|
||||
$this->assertEquals(
|
||||
['data' => ['test' => $_secret]],
|
||||
@ -69,7 +69,7 @@ class ResolveTest extends TestCase
|
||||
$schema = $this->buildSchema([
|
||||
'type' => Type::int(),
|
||||
'args' => [
|
||||
'addend1' => [ 'type' => Type::int() ],
|
||||
'addend1' => ['type' => Type::int()],
|
||||
],
|
||||
]);
|
||||
|
||||
@ -85,14 +85,14 @@ class ResolveTest extends TestCase
|
||||
public function testUsesProvidedResolveFunction() : void
|
||||
{
|
||||
$schema = $this->buildSchema([
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'aStr' => ['type' => Type::string()],
|
||||
'aInt' => ['type' => Type::int()],
|
||||
],
|
||||
'resolve' => function ($source, $args) {
|
||||
return json_encode([$source, $args]);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->assertEquals(
|
||||
|
@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Deferred;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Error\FormattedError;
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\Executor\Executor;
|
||||
@ -29,37 +31,38 @@ class SyncTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
$this->schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'syncField' => [
|
||||
'type' => Type::string(),
|
||||
'syncField' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($rootValue) {
|
||||
return $rootValue;
|
||||
}
|
||||
},
|
||||
],
|
||||
'asyncField' => [
|
||||
'type' => Type::string(),
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($rootValue) {
|
||||
return new Deferred(function () use ($rootValue) {
|
||||
return $rootValue;
|
||||
});
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'mutation' => new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'name' => 'Mutation',
|
||||
'fields' => [
|
||||
'syncMutationField' => [
|
||||
'type' => Type::string(),
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($rootValue) {
|
||||
return $rootValue;
|
||||
}
|
||||
]
|
||||
]
|
||||
])
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
|
||||
$this->promiseAdapter = new SyncPromiseAdapter();
|
||||
}
|
||||
|
||||
@ -70,7 +73,7 @@ class SyncTest extends TestCase
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseForInitialErrors() : void
|
||||
{
|
||||
$doc = 'fragment Example on Query { syncField }';
|
||||
$doc = 'fragment Example on Query { syncField }';
|
||||
$result = $this->execute(
|
||||
$this->schema,
|
||||
Parser::parse($doc),
|
||||
@ -79,106 +82,6 @@ class SyncTest extends TestCase
|
||||
$this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise if fields are all synchronous')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void
|
||||
{
|
||||
$doc = 'query Example { syncField }';
|
||||
$result = $this->execute(
|
||||
$this->schema,
|
||||
Parser::parse($doc),
|
||||
'rootValue'
|
||||
);
|
||||
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise if mutation fields are all synchronous')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void
|
||||
{
|
||||
$doc = 'mutation Example { syncMutationField }';
|
||||
$result = $this->execute(
|
||||
$this->schema,
|
||||
Parser::parse($doc),
|
||||
'rootValue'
|
||||
);
|
||||
$this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('returns a Promise if any field is asynchronous')
|
||||
*/
|
||||
public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void
|
||||
{
|
||||
$doc = 'query Example { syncField, asyncField }';
|
||||
$result = $this->execute(
|
||||
$this->schema,
|
||||
Parser::parse($doc),
|
||||
'rootValue'
|
||||
);
|
||||
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
|
||||
}
|
||||
|
||||
// Describe: graphqlSync
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise for syntax errors')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseForSyntaxErrors() : void
|
||||
{
|
||||
$doc = 'fragment Example on Query { { { syncField }';
|
||||
$result = $this->graphqlSync(
|
||||
$this->schema,
|
||||
$doc
|
||||
);
|
||||
$this->assertSync([
|
||||
'errors' => [
|
||||
['message' => 'Syntax Error: Expected Name, found {',
|
||||
'locations' => [['line' => 1, 'column' => 29]]]
|
||||
]
|
||||
], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise for validation errors')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseForValidationErrors() : void
|
||||
{
|
||||
$doc = 'fragment Example on Query { unknownField }';
|
||||
$validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc));
|
||||
$result = $this->graphqlSync(
|
||||
$this->schema,
|
||||
$doc
|
||||
);
|
||||
$expected = [
|
||||
'errors' => Utils::map($validationErrors, function ($e) {
|
||||
return FormattedError::createFromException($e);
|
||||
})
|
||||
];
|
||||
$this->assertSync($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise for sync execution')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseForSyncExecution() : void
|
||||
{
|
||||
$doc = 'query Example { syncField }';
|
||||
$result = $this->graphqlSync(
|
||||
$this->schema,
|
||||
$doc,
|
||||
'rootValue'
|
||||
);
|
||||
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
|
||||
}
|
||||
|
||||
private function graphqlSync($schema, $doc, $rootValue = null)
|
||||
{
|
||||
return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
|
||||
}
|
||||
|
||||
private function execute($schema, $doc, $rootValue = null)
|
||||
{
|
||||
return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
|
||||
@ -191,7 +94,56 @@ class SyncTest extends TestCase
|
||||
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
|
||||
$this->assertEquals(SyncPromise::FULFILLED, $actualResult->adoptedPromise->state, $message);
|
||||
$this->assertInstanceOf(ExecutionResult::class, $actualResult->adoptedPromise->result, $message);
|
||||
$this->assertArraySubset($expectedFinalArray, $actualResult->adoptedPromise->result->toArray(), $message);
|
||||
$this->assertArraySubset(
|
||||
$expectedFinalArray,
|
||||
$actualResult->adoptedPromise->result->toArray(),
|
||||
false,
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise if fields are all synchronous')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void
|
||||
{
|
||||
$doc = 'query Example { syncField }';
|
||||
$result = $this->execute(
|
||||
$this->schema,
|
||||
Parser::parse($doc),
|
||||
'rootValue'
|
||||
);
|
||||
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
|
||||
}
|
||||
|
||||
// Describe: graphqlSync
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise if mutation fields are all synchronous')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void
|
||||
{
|
||||
$doc = 'mutation Example { syncMutationField }';
|
||||
$result = $this->execute(
|
||||
$this->schema,
|
||||
Parser::parse($doc),
|
||||
'rootValue'
|
||||
);
|
||||
$this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('returns a Promise if any field is asynchronous')
|
||||
*/
|
||||
public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void
|
||||
{
|
||||
$doc = 'query Example { syncField, asyncField }';
|
||||
$result = $this->execute(
|
||||
$this->schema,
|
||||
Parser::parse($doc),
|
||||
'rootValue'
|
||||
);
|
||||
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
|
||||
}
|
||||
|
||||
private function assertAsync($expectedFinalArray, $actualResult)
|
||||
@ -202,6 +154,70 @@ class SyncTest extends TestCase
|
||||
$this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message);
|
||||
$resolvedResult = $this->promiseAdapter->wait($actualResult);
|
||||
$this->assertInstanceOf(ExecutionResult::class, $resolvedResult, $message);
|
||||
$this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), $message);
|
||||
$this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), false, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise for syntax errors')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseForSyntaxErrors() : void
|
||||
{
|
||||
$doc = 'fragment Example on Query { { { syncField }';
|
||||
$result = $this->graphqlSync(
|
||||
$this->schema,
|
||||
$doc
|
||||
);
|
||||
$this->assertSync(
|
||||
[
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'Syntax Error: Expected Name, found {',
|
||||
'locations' => [['line' => 1, 'column' => 29]],
|
||||
],
|
||||
],
|
||||
],
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
private function graphqlSync($schema, $doc, $rootValue = null)
|
||||
{
|
||||
return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise for validation errors')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseForValidationErrors() : void
|
||||
{
|
||||
$doc = 'fragment Example on Query { unknownField }';
|
||||
$validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc));
|
||||
$result = $this->graphqlSync(
|
||||
$this->schema,
|
||||
$doc
|
||||
);
|
||||
$expected = [
|
||||
'errors' => Utils::map(
|
||||
$validationErrors,
|
||||
function ($e) {
|
||||
return FormattedError::createFromException($e);
|
||||
}
|
||||
),
|
||||
];
|
||||
$this->assertSync($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not return a Promise for sync execution')
|
||||
*/
|
||||
public function testDoesNotReturnAPromiseForSyncExecution() : void
|
||||
{
|
||||
$doc = 'query Example { syncField }';
|
||||
$result = $this->graphqlSync(
|
||||
$this->schema,
|
||||
$doc,
|
||||
'rootValue'
|
||||
);
|
||||
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
|
||||
}
|
||||
}
|
||||
|
@ -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'];
|
||||
};
|
||||
}
|
||||
}
|
23
tests/Executor/TestClasses/Adder.php
Normal file
23
tests/Executor/TestClasses/Adder.php
Normal 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'];
|
||||
};
|
||||
}
|
||||
}
|
20
tests/Executor/TestClasses/Cat.php
Normal file
20
tests/Executor/TestClasses/Cat.php
Normal 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;
|
||||
}
|
||||
}
|
56
tests/Executor/TestClasses/ComplexScalar.php
Normal file
56
tests/Executor/TestClasses/ComplexScalar.php
Normal 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));
|
||||
}
|
||||
}
|
20
tests/Executor/TestClasses/Dog.php
Normal file
20
tests/Executor/TestClasses/Dog.php
Normal 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;
|
||||
}
|
||||
}
|
16
tests/Executor/TestClasses/Human.php
Normal file
16
tests/Executor/TestClasses/Human.php
Normal 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;
|
||||
}
|
||||
}
|
19
tests/Executor/TestClasses/NotSpecial.php
Normal file
19
tests/Executor/TestClasses/NotSpecial.php
Normal 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;
|
||||
}
|
||||
}
|
16
tests/Executor/TestClasses/NumberHolder.php
Normal file
16
tests/Executor/TestClasses/NumberHolder.php
Normal 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;
|
||||
}
|
||||
}
|
28
tests/Executor/TestClasses/Person.php
Normal file
28
tests/Executor/TestClasses/Person.php
Normal 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;
|
||||
}
|
||||
}
|
44
tests/Executor/TestClasses/Root.php
Normal file
44
tests/Executor/TestClasses/Root.php
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
19
tests/Executor/TestClasses/Special.php
Normal file
19
tests/Executor/TestClasses/Special.php
Normal 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;
|
||||
}
|
||||
}
|
@ -1,64 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
require_once __DIR__ . '/TestClasses.php';
|
||||
|
||||
use GraphQL\Error\Warning;
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Tests\Executor\TestClasses\Cat;
|
||||
use GraphQL\Tests\Executor\TestClasses\Dog;
|
||||
use GraphQL\Tests\Executor\TestClasses\Person;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class UnionInterfaceTest extends TestCase
|
||||
{
|
||||
/** @var */
|
||||
public $schema;
|
||||
|
||||
/** @var Cat */
|
||||
public $garfield;
|
||||
|
||||
/** @var Dog */
|
||||
public $odie;
|
||||
|
||||
/** @var Person */
|
||||
public $liz;
|
||||
|
||||
/** @var Person */
|
||||
public $john;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$NamedType = new InterfaceType([
|
||||
'name' => 'Named',
|
||||
'name' => 'Named',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$NamedType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
],
|
||||
'isTypeOf' => function ($value) {
|
||||
'isTypeOf' => function ($value) {
|
||||
return $value instanceof Dog;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$NamedType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
],
|
||||
'isTypeOf' => function ($value) {
|
||||
'isTypeOf' => function ($value) {
|
||||
return $value instanceof Cat;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$PetType = new UnionType([
|
||||
'name' => 'Pet',
|
||||
'types' => [$DogType, $CatType],
|
||||
'name' => 'Pet',
|
||||
'types' => [$DogType, $CatType],
|
||||
'resolveType' => function ($value) use ($DogType, $CatType) {
|
||||
if ($value instanceof Dog) {
|
||||
return $DogType;
|
||||
@ -66,32 +78,31 @@ class UnionInterfaceTest extends TestCase
|
||||
if ($value instanceof Cat) {
|
||||
return $CatType;
|
||||
}
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$PersonType = new ObjectType([
|
||||
'name' => 'Person',
|
||||
'name' => 'Person',
|
||||
'interfaces' => [$NamedType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'pets' => ['type' => Type::listOf($PetType)],
|
||||
'friends' => ['type' => Type::listOf($NamedType)]
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'pets' => ['type' => Type::listOf($PetType)],
|
||||
'friends' => ['type' => Type::listOf($NamedType)],
|
||||
],
|
||||
'isTypeOf' => function ($value) {
|
||||
'isTypeOf' => function ($value) {
|
||||
return $value instanceof Person;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->schema = new Schema([
|
||||
'query' => $PersonType,
|
||||
'types' => [ $PetType ]
|
||||
'types' => [$PetType],
|
||||
]);
|
||||
|
||||
$this->garfield = new Cat('Garfield', false);
|
||||
$this->odie = new Dog('Odie', true);
|
||||
$this->liz = new Person('Liz');
|
||||
$this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]);
|
||||
|
||||
$this->odie = new Dog('Odie', true);
|
||||
$this->liz = new Person('Liz');
|
||||
$this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]);
|
||||
}
|
||||
|
||||
// Execute: Union and intersection types
|
||||
@ -101,7 +112,6 @@ class UnionInterfaceTest extends TestCase
|
||||
*/
|
||||
public function testCanIntrospectOnUnionAndIntersectionTypes() : void
|
||||
{
|
||||
|
||||
$ast = Parser::parse('
|
||||
{
|
||||
Named: __type(name: "Named") {
|
||||
@ -128,33 +138,33 @@ class UnionInterfaceTest extends TestCase
|
||||
$expected = [
|
||||
'data' => [
|
||||
'Named' => [
|
||||
'kind' => 'INTERFACE',
|
||||
'name' => 'Named',
|
||||
'fields' => [
|
||||
['name' => 'name']
|
||||
'kind' => 'INTERFACE',
|
||||
'name' => 'Named',
|
||||
'fields' => [
|
||||
['name' => 'name'],
|
||||
],
|
||||
'interfaces' => null,
|
||||
'interfaces' => null,
|
||||
'possibleTypes' => [
|
||||
['name' => 'Person'],
|
||||
['name' => 'Dog'],
|
||||
['name' => 'Cat']
|
||||
['name' => 'Cat'],
|
||||
],
|
||||
'enumValues' => null,
|
||||
'inputFields' => null
|
||||
'enumValues' => null,
|
||||
'inputFields' => null,
|
||||
],
|
||||
'Pet' => [
|
||||
'kind' => 'UNION',
|
||||
'name' => 'Pet',
|
||||
'fields' => null,
|
||||
'interfaces' => null,
|
||||
'Pet' => [
|
||||
'kind' => 'UNION',
|
||||
'name' => 'Pet',
|
||||
'fields' => null,
|
||||
'interfaces' => null,
|
||||
'possibleTypes' => [
|
||||
['name' => 'Dog'],
|
||||
['name' => 'Cat']
|
||||
['name' => 'Cat'],
|
||||
],
|
||||
'enumValues' => null,
|
||||
'inputFields' => null
|
||||
]
|
||||
]
|
||||
'enumValues' => null,
|
||||
'inputFields' => null,
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray());
|
||||
}
|
||||
@ -165,7 +175,7 @@ class UnionInterfaceTest extends TestCase
|
||||
public function testExecutesUsingUnionTypes() : void
|
||||
{
|
||||
// NOTE: This is an *invalid* query, but it should be an *executable* query.
|
||||
$ast = Parser::parse('
|
||||
$ast = Parser::parse('
|
||||
{
|
||||
__typename
|
||||
name
|
||||
@ -180,12 +190,12 @@ class UnionInterfaceTest extends TestCase
|
||||
$expected = [
|
||||
'data' => [
|
||||
'__typename' => 'Person',
|
||||
'name' => 'John',
|
||||
'pets' => [
|
||||
'name' => 'John',
|
||||
'pets' => [
|
||||
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
|
||||
@ -197,7 +207,7 @@ class UnionInterfaceTest extends TestCase
|
||||
public function testExecutesUnionTypesWithInlineFragments() : void
|
||||
{
|
||||
// This is the valid version of the query in the above test.
|
||||
$ast = Parser::parse('
|
||||
$ast = Parser::parse('
|
||||
{
|
||||
__typename
|
||||
name
|
||||
@ -217,13 +227,13 @@ class UnionInterfaceTest extends TestCase
|
||||
$expected = [
|
||||
'data' => [
|
||||
'__typename' => 'Person',
|
||||
'name' => 'John',
|
||||
'pets' => [
|
||||
'name' => 'John',
|
||||
'pets' => [
|
||||
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
|
||||
],
|
||||
|
||||
]
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
|
||||
}
|
||||
@ -234,7 +244,7 @@ class UnionInterfaceTest extends TestCase
|
||||
public function testExecutesUsingInterfaceTypes() : void
|
||||
{
|
||||
// NOTE: This is an *invalid* query, but it should be an *executable* query.
|
||||
$ast = Parser::parse('
|
||||
$ast = Parser::parse('
|
||||
{
|
||||
__typename
|
||||
name
|
||||
@ -249,12 +259,12 @@ class UnionInterfaceTest extends TestCase
|
||||
$expected = [
|
||||
'data' => [
|
||||
'__typename' => 'Person',
|
||||
'name' => 'John',
|
||||
'friends' => [
|
||||
'name' => 'John',
|
||||
'friends' => [
|
||||
['__typename' => 'Person', 'name' => 'Liz'],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
|
||||
@ -266,7 +276,7 @@ class UnionInterfaceTest extends TestCase
|
||||
public function testExecutesInterfaceTypesWithInlineFragments() : void
|
||||
{
|
||||
// This is the valid version of the query in the above test.
|
||||
$ast = Parser::parse('
|
||||
$ast = Parser::parse('
|
||||
{
|
||||
__typename
|
||||
name
|
||||
@ -285,12 +295,12 @@ class UnionInterfaceTest extends TestCase
|
||||
$expected = [
|
||||
'data' => [
|
||||
'__typename' => 'Person',
|
||||
'name' => 'John',
|
||||
'friends' => [
|
||||
'name' => 'John',
|
||||
'friends' => [
|
||||
['__typename' => 'Person', 'name' => 'Liz'],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true));
|
||||
@ -336,16 +346,16 @@ class UnionInterfaceTest extends TestCase
|
||||
$expected = [
|
||||
'data' => [
|
||||
'__typename' => 'Person',
|
||||
'name' => 'John',
|
||||
'pets' => [
|
||||
'name' => 'John',
|
||||
'pets' => [
|
||||
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
|
||||
],
|
||||
'friends' => [
|
||||
'friends' => [
|
||||
['__typename' => 'Person', 'name' => 'Liz'],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
|
||||
@ -356,36 +366,45 @@ class UnionInterfaceTest extends TestCase
|
||||
*/
|
||||
public function testGetsExecutionInfoInResolver() : void
|
||||
{
|
||||
$encounteredContext = null;
|
||||
$encounteredSchema = null;
|
||||
$encounteredContext = null;
|
||||
$encounteredSchema = null;
|
||||
$encounteredRootValue = null;
|
||||
$PersonType2 = null;
|
||||
$PersonType2 = null;
|
||||
|
||||
$NamedType2 = new InterfaceType([
|
||||
'name' => 'Named',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
'name' => 'Named',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
],
|
||||
'resolveType' => function ($obj, $context, ResolveInfo $info) use (&$encounteredContext, &$encounteredSchema, &$encounteredRootValue, &$PersonType2) {
|
||||
$encounteredContext = $context;
|
||||
$encounteredSchema = $info->schema;
|
||||
'resolveType' => function (
|
||||
$obj,
|
||||
$context,
|
||||
ResolveInfo $info
|
||||
) use (
|
||||
&$encounteredContext,
|
||||
&
|
||||
$encounteredSchema,
|
||||
&$encounteredRootValue,
|
||||
&$PersonType2
|
||||
) {
|
||||
$encounteredContext = $context;
|
||||
$encounteredSchema = $info->schema;
|
||||
$encounteredRootValue = $info->rootValue;
|
||||
|
||||
return $PersonType2;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$PersonType2 = new ObjectType([
|
||||
'name' => 'Person',
|
||||
'name' => 'Person',
|
||||
'interfaces' => [$NamedType2],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'friends' => ['type' => Type::listOf($NamedType2)],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema2 = new Schema([
|
||||
'query' => $PersonType2
|
||||
]);
|
||||
$schema2 = new Schema(['query' => $PersonType2]);
|
||||
|
||||
$john2 = new Person('John', [], [$this->liz]);
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Executor\Values;
|
||||
@ -10,19 +13,97 @@ use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function count;
|
||||
use function var_export;
|
||||
use const PHP_EOL;
|
||||
|
||||
class ValuesTest extends TestCase
|
||||
{
|
||||
/** @var Schema */
|
||||
private static $schema;
|
||||
|
||||
public function testGetIDVariableValues() : void
|
||||
{
|
||||
$this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']);
|
||||
$this->assertEquals(
|
||||
['errors'=> [], 'coerced' => ['idInput' => '123456789']],
|
||||
self::runTestCase(['idInput' => 123456789]),
|
||||
['errors' => [], 'coerced' => ['idInput' => '123456789']],
|
||||
$this->runTestCase(['idInput' => 123456789]),
|
||||
'Integer ID was not converted to string'
|
||||
);
|
||||
}
|
||||
|
||||
private function expectInputVariablesMatchOutputVariables($variables) : void
|
||||
{
|
||||
$this->assertEquals(
|
||||
$variables,
|
||||
$this->runTestCase($variables)['coerced'],
|
||||
'Output variables did not match input variables' . PHP_EOL . var_export($variables, true) . PHP_EOL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $variables
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function runTestCase($variables) : array
|
||||
{
|
||||
return Values::getVariableValues(self::getSchema(), self::getVariableDefinitionNodes(), $variables);
|
||||
}
|
||||
|
||||
private static function getSchema() : Schema
|
||||
{
|
||||
if (! self::$schema) {
|
||||
self::$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'test' => [
|
||||
'type' => Type::boolean(),
|
||||
'args' => [
|
||||
'idInput' => Type::id(),
|
||||
'boolInput' => Type::boolean(),
|
||||
'intInput' => Type::int(),
|
||||
'stringInput' => Type::string(),
|
||||
'floatInput' => Type::float(),
|
||||
],
|
||||
],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
return self::$schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return VariableDefinitionNode[]
|
||||
*/
|
||||
private static function getVariableDefinitionNodes() : array
|
||||
{
|
||||
$idInputDefinition = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'idInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'ID'])]),
|
||||
]);
|
||||
$boolInputDefinition = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'boolInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Boolean'])]),
|
||||
]);
|
||||
$intInputDefinition = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'intInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Int'])]),
|
||||
]);
|
||||
$stringInputDefintion = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'stringInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'String'])]),
|
||||
]);
|
||||
$floatInputDefinition = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'floatInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Float'])]),
|
||||
]);
|
||||
|
||||
return [$idInputDefinition, $boolInputDefinition, $intInputDefinition, $stringInputDefintion, $floatInputDefinition];
|
||||
}
|
||||
|
||||
public function testGetBooleanVariableValues() : void
|
||||
{
|
||||
$this->expectInputVariablesMatchOutputVariables(['boolInput' => true]);
|
||||
@ -64,11 +145,20 @@ class ValuesTest extends TestCase
|
||||
$this->expectGraphQLError(['idInput' => true]);
|
||||
}
|
||||
|
||||
private function expectGraphQLError($variables) : void
|
||||
{
|
||||
$result = $this->runTestCase($variables);
|
||||
$this->assertGreaterThan(0, count($result['errors']));
|
||||
}
|
||||
|
||||
public function testFloatForIDVariableThrowsError() : void
|
||||
{
|
||||
$this->expectGraphQLError(['idInput' => 1.0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpers for running test cases and making assertions
|
||||
*/
|
||||
public function testStringForBooleanVariableThrowsError() : void
|
||||
{
|
||||
$this->expectGraphQLError(['boolInput' => 'true']);
|
||||
@ -98,77 +188,4 @@ class ValuesTest extends TestCase
|
||||
{
|
||||
$this->expectGraphQLError(['intInput' => -2147483649]);
|
||||
}
|
||||
|
||||
// Helpers for running test cases and making assertions
|
||||
|
||||
private function expectInputVariablesMatchOutputVariables($variables)
|
||||
{
|
||||
$this->assertEquals(
|
||||
$variables,
|
||||
self::runTestCase($variables)['coerced'],
|
||||
'Output variables did not match input variables' . PHP_EOL . var_export($variables, true) . PHP_EOL
|
||||
);
|
||||
}
|
||||
|
||||
private function expectGraphQLError($variables)
|
||||
{
|
||||
$result = self::runTestCase($variables);
|
||||
$this->assertGreaterThan(0, count($result['errors']));
|
||||
}
|
||||
|
||||
private static $schema;
|
||||
|
||||
private static function getSchema()
|
||||
{
|
||||
if (!self::$schema) {
|
||||
self::$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'test' => [
|
||||
'type' => Type::boolean(),
|
||||
'args' => [
|
||||
'idInput' => Type::id(),
|
||||
'boolInput' => Type::boolean(),
|
||||
'intInput' => Type::int(),
|
||||
'stringInput' => Type::string(),
|
||||
'floatInput' => Type::float()
|
||||
]
|
||||
],
|
||||
]
|
||||
])
|
||||
]);
|
||||
}
|
||||
return self::$schema;
|
||||
}
|
||||
|
||||
private static function getVariableDefinitionNodes()
|
||||
{
|
||||
$idInputDefinition = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'idInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'ID'])])
|
||||
]);
|
||||
$boolInputDefinition = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'boolInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Boolean'])])
|
||||
]);
|
||||
$intInputDefinition = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'intInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Int'])])
|
||||
]);
|
||||
$stringInputDefintion = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'stringInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'String'])])
|
||||
]);
|
||||
$floatInputDefinition = new VariableDefinitionNode([
|
||||
'variable' => new VariableNode(['name' => new NameNode(['value' => 'floatInput'])]),
|
||||
'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Float'])])
|
||||
]);
|
||||
return [$idInputDefinition, $boolInputDefinition, $intInputDefinition, $stringInputDefintion, $floatInputDefinition];
|
||||
}
|
||||
|
||||
private function runTestCase($variables)
|
||||
{
|
||||
return Values::getVariableValues(self::getSchema(), self::getVariableDefinitionNodes(), $variables);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
require_once __DIR__ . '/TestClasses.php';
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Tests\Executor\TestClasses\ComplexScalar;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function json_encode;
|
||||
|
||||
/**
|
||||
* Execute: Handles inputs
|
||||
@ -18,7 +21,6 @@ use PHPUnit\Framework\TestCase;
|
||||
*/
|
||||
class VariablesTest extends TestCase
|
||||
{
|
||||
|
||||
public function testUsingInlineStructs() : void
|
||||
{
|
||||
// executes with complex input:
|
||||
@ -29,14 +31,12 @@ class VariablesTest extends TestCase
|
||||
');
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'
|
||||
]
|
||||
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
// properly parses single value to list:
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
{
|
||||
fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"})
|
||||
}
|
||||
@ -46,7 +46,7 @@ class VariablesTest extends TestCase
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
// properly parses null value to null
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
{
|
||||
fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null})
|
||||
}
|
||||
@ -56,7 +56,7 @@ class VariablesTest extends TestCase
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
// properly parses null value in list
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
{
|
||||
fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"})
|
||||
}
|
||||
@ -73,12 +73,13 @@ class VariablesTest extends TestCase
|
||||
');
|
||||
|
||||
$expected = [
|
||||
'data' => ['fieldWithObjectInput' => null],
|
||||
'data' => ['fieldWithObjectInput' => null],
|
||||
'errors' => [[
|
||||
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
|
||||
'path' => ['fieldWithObjectInput'],
|
||||
'locations' => [['line' => 3, 'column' => 39]]
|
||||
]]
|
||||
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
|
||||
'path' => ['fieldWithObjectInput'],
|
||||
'locations' => [['line' => 3, 'column' => 39]],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertArraySubset($expected, $result->toArray());
|
||||
|
||||
@ -94,6 +95,77 @@ class VariablesTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
private function executeQuery($query, $variableValues = null)
|
||||
{
|
||||
$document = Parser::parse($query);
|
||||
|
||||
return Executor::execute($this->schema(), $document, null, null, $variableValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe: Handles nullable scalars
|
||||
*/
|
||||
public function schema() : Schema
|
||||
{
|
||||
$ComplexScalarType = ComplexScalar::create();
|
||||
|
||||
$TestInputObject = new InputObjectType([
|
||||
'name' => 'TestInputObject',
|
||||
'fields' => [
|
||||
'a' => ['type' => Type::string()],
|
||||
'b' => ['type' => Type::listOf(Type::string())],
|
||||
'c' => ['type' => Type::nonNull(Type::string())],
|
||||
'd' => ['type' => $ComplexScalarType],
|
||||
],
|
||||
]);
|
||||
|
||||
$TestNestedInputObject = new InputObjectType([
|
||||
'name' => 'TestNestedInputObject',
|
||||
'fields' => [
|
||||
'na' => ['type' => Type::nonNull($TestInputObject)],
|
||||
'nb' => ['type' => Type::nonNull(Type::string())],
|
||||
],
|
||||
]);
|
||||
|
||||
$TestType = new ObjectType([
|
||||
'name' => 'TestType',
|
||||
'fields' => [
|
||||
'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]),
|
||||
'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]),
|
||||
'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]),
|
||||
'fieldWithDefaultArgumentValue' => $this->fieldWithInputArg([
|
||||
'type' => Type::string(),
|
||||
'defaultValue' => 'Hello World',
|
||||
]),
|
||||
'fieldWithNestedInputObject' => $this->fieldWithInputArg([
|
||||
'type' => $TestNestedInputObject,
|
||||
'defaultValue' => 'Hello World',
|
||||
]),
|
||||
'list' => $this->fieldWithInputArg(['type' => Type::listOf(Type::string())]),
|
||||
'nnList' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::string()))]),
|
||||
'listNN' => $this->fieldWithInputArg(['type' => Type::listOf(Type::nonNull(Type::string()))]),
|
||||
'nnListNN' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]),
|
||||
],
|
||||
]);
|
||||
|
||||
return new Schema(['query' => $TestType]);
|
||||
}
|
||||
|
||||
private function fieldWithInputArg($inputArg)
|
||||
{
|
||||
return [
|
||||
'type' => Type::string(),
|
||||
'args' => ['input' => $inputArg],
|
||||
'resolve' => function ($_, $args) {
|
||||
if (isset($args['input'])) {
|
||||
return json_encode($args['input']);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public function testUsingVariables() : void
|
||||
{
|
||||
$doc = '
|
||||
@ -119,7 +191,7 @@ class VariablesTest extends TestCase
|
||||
');
|
||||
|
||||
$expected = [
|
||||
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}']
|
||||
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
@ -132,63 +204,61 @@ class VariablesTest extends TestCase
|
||||
);
|
||||
|
||||
// executes with complex scalar input:
|
||||
$params = [ 'input' => [ 'c' => 'foo', 'd' => 'SerializedValue' ] ];
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$params = ['input' => ['c' => 'foo', 'd' => 'SerializedValue']];
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$expected = [
|
||||
'data' => [
|
||||
'fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'
|
||||
]
|
||||
'data' => ['fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
// errors on null for nested non-null:
|
||||
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]];
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]];
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value ' .
|
||||
'{"a":"foo","b":"bar","c":null}; ' .
|
||||
'Expected non-nullable type String! not to be null at value.c.',
|
||||
'locations' => [['line' => 2, 'column' => 21]],
|
||||
'category' => 'graphql'
|
||||
]
|
||||
]
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
// errors on incorrect type:
|
||||
$params = [ 'input' => 'foo bar' ];
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$params = ['input' => 'foo bar'];
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value "foo bar"; ' .
|
||||
'Expected type TestInputObject to be an object.',
|
||||
'locations' => [ [ 'line' => 2, 'column' => 21 ] ],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 2, 'column' => 21]],
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
// errors on omission of nested non-null:
|
||||
$params = ['input' => ['a' => 'foo', 'b' => 'bar']];
|
||||
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; '.
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; ' .
|
||||
'Field value.c of required type String! was not provided.',
|
||||
'locations' => [['line' => 2, 'column' => 21]],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
@ -198,62 +268,59 @@ class VariablesTest extends TestCase
|
||||
fieldWithNestedObjectInput(input: $input)
|
||||
}
|
||||
';
|
||||
$params = [ 'input' => [ 'na' => [ 'a' => 'foo' ] ] ];
|
||||
$params = ['input' => ['na' => ['a' => 'foo']]];
|
||||
|
||||
$result = $this->executeQuery($nestedDoc, $params);
|
||||
$result = $this->executeQuery($nestedDoc, $params);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
|
||||
'Field value.na.c of required type String! was not provided.',
|
||||
'locations' => [['line' => 2, 'column' => 19]],
|
||||
'category' => 'graphql',
|
||||
'category' => 'graphql',
|
||||
],
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' .
|
||||
'Field value.nb of required type String! was not provided.',
|
||||
'locations' => [['line' => 2, 'column' => 19]],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
|
||||
// errors on addition of unknown input field
|
||||
$params = ['input' => [ 'a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog' ]];
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog']];
|
||||
$result = $this->executeQuery($doc, $params);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value ' .
|
||||
'{"a":"foo","b":"bar","c":"baz","extra":"dog"}; ' .
|
||||
'Field "extra" is not defined by type TestInputObject.',
|
||||
'locations' => [['line' => 2, 'column' => 21]],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
// Describe: Handles nullable scalars
|
||||
|
||||
/**
|
||||
* @see it('allows nullable inputs to be omitted')
|
||||
*/
|
||||
public function testAllowsNullableInputsToBeOmitted() : void
|
||||
{
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
{
|
||||
fieldWithNullableStringInput
|
||||
}
|
||||
');
|
||||
$expected = [
|
||||
'data' => ['fieldWithNullableStringInput' => null]
|
||||
'data' => ['fieldWithNullableStringInput' => null],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
@ -264,7 +331,7 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsNullableInputsToBeOmittedInAVariable() : void
|
||||
{
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
query SetsNullable($value: String) {
|
||||
fieldWithNullableStringInput(input: $value)
|
||||
}
|
||||
@ -279,7 +346,7 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsNullableInputsToBeOmittedInAnUnlistedVariable() : void
|
||||
{
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
query SetsNullable {
|
||||
fieldWithNullableStringInput(input: $value)
|
||||
}
|
||||
@ -288,12 +355,15 @@ class VariablesTest extends TestCase
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
|
||||
// Describe: Handles non-nullable scalars
|
||||
|
||||
/**
|
||||
* @see it('allows nullable inputs to be set to null in a variable')
|
||||
*/
|
||||
public function testAllowsNullableInputsToBeSetToNullInAVariable() : void
|
||||
{
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
query SetsNullable($value: String) {
|
||||
fieldWithNullableStringInput(input: $value)
|
||||
}
|
||||
@ -308,12 +378,12 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsNullableInputsToBeSetToAValueInAVariable() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query SetsNullable($value: String) {
|
||||
fieldWithNullableStringInput(input: $value)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['value' => 'a']);
|
||||
$result = $this->executeQuery($doc, ['value' => 'a']);
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => '"a"']];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -323,7 +393,7 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsNullableInputsToBeSetToAValueDirectly() : void
|
||||
{
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
{
|
||||
fieldWithNullableStringInput(input: "a")
|
||||
}
|
||||
@ -332,21 +402,18 @@ class VariablesTest extends TestCase
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
|
||||
// Describe: Handles non-nullable scalars
|
||||
|
||||
/**
|
||||
* @see it('allows non-nullable inputs to be omitted given a default')
|
||||
*/
|
||||
public function testAllowsNonNullableInputsToBeOmittedGivenADefault() : void
|
||||
{
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
query SetsNonNullable($value: String = "default") {
|
||||
fieldWithNonNullableStringInput(input: $value)
|
||||
}
|
||||
');
|
||||
$expected = [
|
||||
'data' => ['fieldWithNonNullableStringInput' => '"default"']
|
||||
'data' => ['fieldWithNonNullableStringInput' => '"default"'],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -365,11 +432,11 @@ class VariablesTest extends TestCase
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'Variable "$value" of required type "String!" was not provided.',
|
||||
'message' => 'Variable "$value" of required type "String!" was not provided.',
|
||||
'locations' => [['line' => 2, 'column' => 31]],
|
||||
'category' => 'graphql'
|
||||
]
|
||||
]
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -379,22 +446,22 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query SetsNonNullable($value: String!) {
|
||||
fieldWithNonNullableStringInput(input: $value)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['value' => null]);
|
||||
$result = $this->executeQuery($doc, ['value' => null]);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$value" got invalid value null; ' .
|
||||
'Expected non-nullable type String! not to be null.',
|
||||
'locations' => [['line' => 2, 'column' => 31]],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -404,12 +471,12 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsNonNullableInputsToBeSetToAValueInAVariable() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query SetsNonNullable($value: String!) {
|
||||
fieldWithNonNullableStringInput(input: $value)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['value' => 'a']);
|
||||
$result = $this->executeQuery($doc, ['value' => 'a']);
|
||||
$expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -419,7 +486,7 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsNonNullableInputsToBeSetToAValueDirectly() : void
|
||||
{
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
{
|
||||
fieldWithNonNullableStringInput(input: "a")
|
||||
}
|
||||
@ -433,47 +500,50 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testReportsErrorForMissingNonNullableInputs() : void
|
||||
{
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
{
|
||||
fieldWithNonNullableStringInput
|
||||
}
|
||||
');
|
||||
$expected = [
|
||||
'data' => ['fieldWithNonNullableStringInput' => null],
|
||||
'data' => ['fieldWithNonNullableStringInput' => null],
|
||||
'errors' => [[
|
||||
'message' => 'Argument "input" of required type "String!" was not provided.',
|
||||
'locations' => [ [ 'line' => 3, 'column' => 9 ] ],
|
||||
'path' => [ 'fieldWithNonNullableStringInput' ],
|
||||
'category' => 'graphql',
|
||||
]]
|
||||
'message' => 'Argument "input" of required type "String!" was not provided.',
|
||||
'locations' => [['line' => 3, 'column' => 9]],
|
||||
'path' => ['fieldWithNonNullableStringInput'],
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
// Describe: Handles lists and nullability
|
||||
|
||||
/**
|
||||
* @see it('reports error for array passed into string input')
|
||||
*/
|
||||
public function testReportsErrorForArrayPassedIntoStringInput() : void
|
||||
{
|
||||
|
||||
$doc = '
|
||||
$doc = '
|
||||
query SetsNonNullable($value: String!) {
|
||||
fieldWithNonNullableStringInput(input: $value)
|
||||
}
|
||||
';
|
||||
$variables = ['value' => [1, 2, 3]];
|
||||
$result = $this->executeQuery($doc, $variables);
|
||||
$result = $this->executeQuery($doc, $variables);
|
||||
|
||||
$expected = [
|
||||
'errors' => [[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$value" got invalid value [1,2,3]; Expected type ' .
|
||||
'String; String cannot represent an array value: [1,2,3]',
|
||||
'category' => 'graphql',
|
||||
'category' => 'graphql',
|
||||
'locations' => [
|
||||
['line' => 2, 'column' => 31]
|
||||
]
|
||||
]]
|
||||
['line' => 2, 'column' => 31],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -498,38 +568,37 @@ class VariablesTest extends TestCase
|
||||
// and are being run against a new schema which have introduced a breaking
|
||||
// change to make a formerly non-required argument required, this asserts
|
||||
// failure before allowing the underlying code to receive a non-null value.
|
||||
$result = $this->executeQuery('
|
||||
$result = $this->executeQuery('
|
||||
{
|
||||
fieldWithNonNullableStringInput(input: $foo)
|
||||
}
|
||||
');
|
||||
$expected = [
|
||||
'data' => ['fieldWithNonNullableStringInput' => null],
|
||||
'data' => ['fieldWithNonNullableStringInput' => null],
|
||||
'errors' => [[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Argument "input" of required type "String!" was provided the ' .
|
||||
'variable "$foo" which was not provided a runtime value.',
|
||||
'locations' => [['line' => 3, 'column' => 48]],
|
||||
'path' => ['fieldWithNonNullableStringInput'],
|
||||
'category' => 'graphql',
|
||||
]]
|
||||
'path' => ['fieldWithNonNullableStringInput'],
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
// Describe: Handles lists and nullability
|
||||
|
||||
/**
|
||||
* @see it('allows lists to be null')
|
||||
*/
|
||||
public function testAllowsListsToBeNull() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String]) {
|
||||
list(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => null]);
|
||||
$result = $this->executeQuery($doc, ['input' => null]);
|
||||
$expected = ['data' => ['list' => null]];
|
||||
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
@ -540,12 +609,12 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsListsToContainValues() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String]) {
|
||||
list(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => ['A']]);
|
||||
$result = $this->executeQuery($doc, ['input' => ['A']]);
|
||||
$expected = ['data' => ['list' => '["A"]']];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -555,12 +624,12 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsListsToContainNull() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String]) {
|
||||
list(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => ['A',null,'B']]);
|
||||
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
|
||||
$expected = ['data' => ['list' => '["A",null,"B"]']];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -570,22 +639,22 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testDoesNotAllowNonNullListsToBeNull() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String]!) {
|
||||
nnList(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => null]);
|
||||
$result = $this->executeQuery($doc, ['input' => null]);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value null; ' .
|
||||
'Expected non-nullable type [String]! not to be null.',
|
||||
'locations' => [['line' => 2, 'column' => 17]],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -595,12 +664,12 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsNonNullListsToContainValues() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String]!) {
|
||||
nnList(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => ['A']]);
|
||||
$result = $this->executeQuery($doc, ['input' => ['A']]);
|
||||
$expected = ['data' => ['nnList' => '["A"]']];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -610,12 +679,12 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsNonNullListsToContainNull() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String]!) {
|
||||
nnList(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => ['A',null,'B']]);
|
||||
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
|
||||
$expected = ['data' => ['nnList' => '["A",null,"B"]']];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -625,12 +694,12 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsListsOfNonNullsToBeNull() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String!]) {
|
||||
listNN(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => null]);
|
||||
$result = $this->executeQuery($doc, ['input' => null]);
|
||||
$expected = ['data' => ['listNN' => null]];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -640,12 +709,12 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsListsOfNonNullsToContainValues() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String!]) {
|
||||
listNN(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => ['A']]);
|
||||
$result = $this->executeQuery($doc, ['input' => ['A']]);
|
||||
$expected = ['data' => ['listNN' => '["A"]']];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -655,22 +724,22 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testDoesNotAllowListsOfNonNullsToContainNull() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String!]) {
|
||||
listNN(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
|
||||
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value ["A",null,"B"]; ' .
|
||||
'Expected non-nullable type String! not to be null at value[1].',
|
||||
'locations' => [ ['line' => 2, 'column' => 17] ],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 2, 'column' => 17]],
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -680,22 +749,22 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testDoesNotAllowNonNullListsOfNonNullsToBeNull() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String!]!) {
|
||||
nnListNN(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => null]);
|
||||
$result = $this->executeQuery($doc, ['input' => null]);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value null; ' .
|
||||
'Expected non-nullable type [String!]! not to be null.',
|
||||
'locations' => [ ['line' => 2, 'column' => 17] ],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 2, 'column' => 17]],
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -705,37 +774,39 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testAllowsNonNullListsOfNonNullsToContainValues() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String!]!) {
|
||||
nnListNN(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => ['A']]);
|
||||
$result = $this->executeQuery($doc, ['input' => ['A']]);
|
||||
$expected = ['data' => ['nnListNN' => '["A"]']];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
// Describe: Execute: Uses argument default values
|
||||
|
||||
/**
|
||||
* @see it('does not allow non-null lists of non-nulls to contain null')
|
||||
*/
|
||||
public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input:[String!]!) {
|
||||
nnListNN(input: $input)
|
||||
}
|
||||
';
|
||||
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
|
||||
$result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" got invalid value ["A",null,"B"]; ' .
|
||||
'Expected non-nullable type String! not to be null at value[1].',
|
||||
'locations' => [ ['line' => 2, 'column' => 17] ],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'locations' => [['line' => 2, 'column' => 17]],
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -745,23 +816,23 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testDoesNotAllowInvalidTypesToBeUsedAsValues() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input: TestType!) {
|
||||
fieldWithObjectInput(input: $input)
|
||||
}
|
||||
';
|
||||
$vars = [ 'input' => [ 'list' => [ 'A', 'B' ] ] ];
|
||||
$result = $this->executeQuery($doc, $vars);
|
||||
$vars = ['input' => ['list' => ['A', 'B']]];
|
||||
$result = $this->executeQuery($doc, $vars);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" expected value of type "TestType!" which cannot ' .
|
||||
'be used as an input type.',
|
||||
'locations' => [['line' => 2, 'column' => 25]],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -771,29 +842,28 @@ class VariablesTest extends TestCase
|
||||
*/
|
||||
public function testDoesNotAllowUnknownTypesToBeUsedAsValues() : void
|
||||
{
|
||||
$doc = '
|
||||
$doc = '
|
||||
query q($input: UnknownType!) {
|
||||
fieldWithObjectInput(input: $input)
|
||||
}
|
||||
';
|
||||
$vars = ['input' => 'whoknows'];
|
||||
|
||||
$result = $this->executeQuery($doc, $vars);
|
||||
$result = $this->executeQuery($doc, $vars);
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Variable "$input" expected value of type "UnknownType!" which ' .
|
||||
'cannot be used as an input type.',
|
||||
'locations' => [['line' => 2, 'column' => 25]],
|
||||
'category' => 'graphql',
|
||||
]
|
||||
]
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
// Describe: Execute: Uses argument default values
|
||||
/**
|
||||
* @see it('when no argument provided')
|
||||
*/
|
||||
@ -834,84 +904,17 @@ class VariablesTest extends TestCase
|
||||
}');
|
||||
|
||||
$expected = [
|
||||
'data' => ['fieldWithDefaultArgumentValue' => null],
|
||||
'data' => ['fieldWithDefaultArgumentValue' => null],
|
||||
'errors' => [[
|
||||
'message' =>
|
||||
'message' =>
|
||||
'Argument "input" has invalid value WRONG_TYPE.',
|
||||
'locations' => [ [ 'line' => 2, 'column' => 50 ] ],
|
||||
'path' => [ 'fieldWithDefaultArgumentValue' ],
|
||||
'category' => 'graphql',
|
||||
]]
|
||||
'locations' => [['line' => 2, 'column' => 50]],
|
||||
'path' => ['fieldWithDefaultArgumentValue'],
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
|
||||
public function schema()
|
||||
{
|
||||
$ComplexScalarType = ComplexScalar::create();
|
||||
|
||||
$TestInputObject = new InputObjectType([
|
||||
'name' => 'TestInputObject',
|
||||
'fields' => [
|
||||
'a' => ['type' => Type::string()],
|
||||
'b' => ['type' => Type::listOf(Type::string())],
|
||||
'c' => ['type' => Type::nonNull(Type::string())],
|
||||
'd' => ['type' => $ComplexScalarType],
|
||||
]
|
||||
]);
|
||||
|
||||
$TestNestedInputObject = new InputObjectType([
|
||||
'name' => 'TestNestedInputObject',
|
||||
'fields' => [
|
||||
'na' => [ 'type' => Type::nonNull($TestInputObject) ],
|
||||
'nb' => [ 'type' => Type::nonNull(Type::string()) ],
|
||||
],
|
||||
]);
|
||||
|
||||
$TestType = new ObjectType([
|
||||
'name' => 'TestType',
|
||||
'fields' => [
|
||||
'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]),
|
||||
'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]),
|
||||
'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]),
|
||||
'fieldWithDefaultArgumentValue' => $this->fieldWithInputArg([
|
||||
'type' => Type::string(),
|
||||
'defaultValue' => 'Hello World',
|
||||
]),
|
||||
'fieldWithNestedInputObject' => $this->fieldWithInputArg([
|
||||
'type' => $TestNestedInputObject,
|
||||
'defaultValue' => 'Hello World'
|
||||
]),
|
||||
'list' => $this->fieldWithInputArg(['type' => Type::listOf(Type::string())]),
|
||||
'nnList' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::string()))]),
|
||||
'listNN' => $this->fieldWithInputArg(['type' => Type::listOf(Type::nonNull(Type::string()))]),
|
||||
'nnListNN' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]),
|
||||
]
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $TestType]);
|
||||
return $schema;
|
||||
}
|
||||
|
||||
private function fieldWithInputArg($inputArg)
|
||||
{
|
||||
return [
|
||||
'type' => Type::string(),
|
||||
'args' => ['input' => $inputArg],
|
||||
'resolve' => function ($_, $args) {
|
||||
if (isset($args['input'])) {
|
||||
return json_encode($args['input']);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private function executeQuery($query, $variableValues = null)
|
||||
{
|
||||
$document = Parser::parse($query);
|
||||
return Executor::execute($this->schema(), $document, null, null, $variableValues);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Language;
|
||||
|
||||
use GraphQL\Error\SyntaxError;
|
||||
use GraphQL\Language\Lexer;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Language\Token;
|
||||
use GraphQL\Error\SyntaxError;
|
||||
use GraphQL\Utils\Utils;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function count;
|
||||
use function json_decode;
|
||||
|
||||
class LexerTest extends TestCase
|
||||
{
|
||||
@ -23,17 +28,45 @@ class LexerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
private function expectSyntaxError($text, $message, $location)
|
||||
{
|
||||
$this->expectException(SyntaxError::class);
|
||||
$this->expectExceptionMessage($message);
|
||||
try {
|
||||
$this->lexOne($text);
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals([$location], $error->getLocations());
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $body
|
||||
* @return Token
|
||||
*/
|
||||
private function lexOne($body)
|
||||
{
|
||||
$lexer = new Lexer(new Source($body));
|
||||
|
||||
return $lexer->advance();
|
||||
}
|
||||
|
||||
private function loc($line, $column)
|
||||
{
|
||||
return new SourceLocation($line, $column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('accepts BOM header')
|
||||
*/
|
||||
public function testAcceptsBomHeader() : void
|
||||
{
|
||||
$bom = Utils::chr(0xFEFF);
|
||||
$bom = Utils::chr(0xFEFF);
|
||||
$expected = [
|
||||
'kind' => Token::NAME,
|
||||
'kind' => Token::NAME,
|
||||
'start' => 2,
|
||||
'end' => 5,
|
||||
'value' => 'foo'
|
||||
'end' => 5,
|
||||
'value' => 'foo',
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo'));
|
||||
@ -45,12 +78,12 @@ class LexerTest extends TestCase
|
||||
public function testRecordsLineAndColumn() : void
|
||||
{
|
||||
$expected = [
|
||||
'kind' => Token::NAME,
|
||||
'start' => 8,
|
||||
'end' => 11,
|
||||
'line' => 4,
|
||||
'kind' => Token::NAME,
|
||||
'start' => 8,
|
||||
'end' => 11,
|
||||
'line' => 4,
|
||||
'column' => 3,
|
||||
'value' => 'foo'
|
||||
'value' => 'foo',
|
||||
];
|
||||
$this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n"));
|
||||
}
|
||||
@ -67,10 +100,10 @@ class LexerTest extends TestCase
|
||||
|
||||
';
|
||||
$expected = [
|
||||
'kind' => Token::NAME,
|
||||
'kind' => Token::NAME,
|
||||
'start' => 6,
|
||||
'end' => 9,
|
||||
'value' => 'foo'
|
||||
'end' => 9,
|
||||
'value' => 'foo',
|
||||
];
|
||||
$this->assertArraySubset($expected, (array) $this->lexOne($example1));
|
||||
|
||||
@ -80,18 +113,18 @@ class LexerTest extends TestCase
|
||||
';
|
||||
|
||||
$expected = [
|
||||
'kind' => Token::NAME,
|
||||
'kind' => Token::NAME,
|
||||
'start' => 18,
|
||||
'end' => 21,
|
||||
'value' => 'foo'
|
||||
'end' => 21,
|
||||
'value' => 'foo',
|
||||
];
|
||||
$this->assertArraySubset($expected, (array) $this->lexOne($example2));
|
||||
|
||||
$expected = [
|
||||
'kind' => Token::NAME,
|
||||
'kind' => Token::NAME,
|
||||
'start' => 3,
|
||||
'end' => 6,
|
||||
'value' => 'foo'
|
||||
'end' => 6,
|
||||
'value' => 'foo',
|
||||
];
|
||||
|
||||
$example3 = ',,,foo,,,';
|
||||
@ -131,7 +164,7 @@ class LexerTest extends TestCase
|
||||
*/
|
||||
public function testUpdatesLineNumbersInErrorForFileContext() : void
|
||||
{
|
||||
$str = '' .
|
||||
$str = '' .
|
||||
"\n" .
|
||||
"\n" .
|
||||
" ?\n" .
|
||||
@ -181,63 +214,86 @@ class LexerTest extends TestCase
|
||||
*/
|
||||
public function testLexesStrings() : void
|
||||
{
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 8,
|
||||
'value' => 'simple'
|
||||
], (array) $this->lexOne('"simple"'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 8,
|
||||
'value' => 'simple',
|
||||
],
|
||||
(array) $this->lexOne('"simple"')
|
||||
);
|
||||
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 15,
|
||||
'value' => ' white space ',
|
||||
],
|
||||
(array) $this->lexOne('" white space "')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 15,
|
||||
'value' => ' white space '
|
||||
], (array) $this->lexOne('" white space "'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 10,
|
||||
'value' => 'quote "',
|
||||
],
|
||||
(array) $this->lexOne('"quote \\""')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 10,
|
||||
'value' => 'quote "'
|
||||
], (array) $this->lexOne('"quote \\""'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 25,
|
||||
'value' => 'escaped \n\r\b\t\f',
|
||||
],
|
||||
(array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 25,
|
||||
'value' => 'escaped \n\r\b\t\f'
|
||||
], (array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 16,
|
||||
'value' => 'slashes \\ \/',
|
||||
],
|
||||
(array) $this->lexOne('"slashes \\\\ \\\\/"')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 16,
|
||||
'value' => 'slashes \\ \/'
|
||||
], (array) $this->lexOne('"slashes \\\\ \\\\/"'));
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 13,
|
||||
'value' => 'unicode яуц'
|
||||
], (array) $this->lexOne('"unicode яуц"'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 13,
|
||||
'value' => 'unicode яуц',
|
||||
],
|
||||
(array) $this->lexOne('"unicode яуц"')
|
||||
);
|
||||
|
||||
$unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"');
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 34,
|
||||
'value' => 'unicode ' . $unicode
|
||||
], (array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 34,
|
||||
'value' => 'unicode ' . $unicode,
|
||||
],
|
||||
(array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 26,
|
||||
'value' => $unicode
|
||||
], (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::STRING,
|
||||
'start' => 0,
|
||||
'end' => 26,
|
||||
'value' => $unicode,
|
||||
],
|
||||
(array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -245,86 +301,128 @@ class LexerTest extends TestCase
|
||||
*/
|
||||
public function testLexesBlockString() : void
|
||||
{
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 12,
|
||||
'value' => 'simple'
|
||||
], (array) $this->lexOne('"""simple"""'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 12,
|
||||
'value' => 'simple',
|
||||
],
|
||||
(array) $this->lexOne('"""simple"""')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 19,
|
||||
'value' => ' white space '
|
||||
], (array) $this->lexOne('""" white space """'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 19,
|
||||
'value' => ' white space ',
|
||||
],
|
||||
(array) $this->lexOne('""" white space """')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 22,
|
||||
'value' => 'contains " quote'
|
||||
], (array) $this->lexOne('"""contains " quote"""'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 22,
|
||||
'value' => 'contains " quote',
|
||||
],
|
||||
(array) $this->lexOne('"""contains " quote"""')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 31,
|
||||
'value' => 'contains """ triplequote'
|
||||
], (array) $this->lexOne('"""contains \\""" triplequote"""'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 31,
|
||||
'value' => 'contains """ triplequote',
|
||||
],
|
||||
(array) $this->lexOne('"""contains \\""" triplequote"""')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 16,
|
||||
'value' => "multi\nline"
|
||||
], (array) $this->lexOne("\"\"\"multi\nline\"\"\""));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 16,
|
||||
'value' => "multi\nline",
|
||||
],
|
||||
(array) $this->lexOne("\"\"\"multi\nline\"\"\"")
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 28,
|
||||
'value' => "multi\nline\nnormalized"
|
||||
], (array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\""));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 28,
|
||||
'value' => "multi\nline\nnormalized",
|
||||
],
|
||||
(array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 32,
|
||||
'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234'
|
||||
], (array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 32,
|
||||
'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234',
|
||||
],
|
||||
(array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 19,
|
||||
'value' => 'slashes \\\\ \\/'
|
||||
], (array) $this->lexOne('"""slashes \\\\ \\/"""'));
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 19,
|
||||
'value' => 'slashes \\\\ \\/',
|
||||
],
|
||||
(array) $this->lexOne('"""slashes \\\\ \\/"""')
|
||||
);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 68,
|
||||
'value' => "spans\n multiple\n lines"
|
||||
], (array) $this->lexOne("\"\"\"
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 68,
|
||||
'value' => "spans\n multiple\n lines",
|
||||
],
|
||||
(array) $this->lexOne('"""
|
||||
|
||||
spans
|
||||
multiple
|
||||
lines
|
||||
|
||||
\"\"\""));
|
||||
"""')
|
||||
);
|
||||
}
|
||||
|
||||
public function reportsUsefulStringErrors() {
|
||||
public function reportsUsefulStringErrors()
|
||||
{
|
||||
return [
|
||||
['"', "Unterminated string.", $this->loc(1, 2)],
|
||||
['"no end quote', "Unterminated string.", $this->loc(1, 14)],
|
||||
["'single quotes'", "Unexpected single quote character ('), did you mean to use a double quote (\")?", $this->loc(1, 1)],
|
||||
['"contains unescaped \u0007 control char"', "Invalid character within String: \"\\u0007\"", $this->loc(1, 21)],
|
||||
['"', 'Unterminated string.', $this->loc(1, 2)],
|
||||
['"no end quote', 'Unterminated string.', $this->loc(1, 14)],
|
||||
[
|
||||
"'single quotes'",
|
||||
"Unexpected single quote character ('), did you mean to use a double quote (\")?",
|
||||
$this->loc(
|
||||
1,
|
||||
1
|
||||
),
|
||||
],
|
||||
[
|
||||
'"contains unescaped \u0007 control char"',
|
||||
"Invalid character within String: \"\\u0007\"",
|
||||
$this->loc(
|
||||
1,
|
||||
21
|
||||
),
|
||||
],
|
||||
['"null-byte is not \u0000 end of file"', 'Invalid character within String: "\\u0000"', $this->loc(1, 19)],
|
||||
['"multi' . "\n" . 'line"', "Unterminated string.", $this->loc(1, 7)],
|
||||
['"multi' . "\r" . 'line"', "Unterminated string.", $this->loc(1, 7)],
|
||||
['"bad \\z esc"', "Invalid character escape sequence: \\z", $this->loc(1, 7)],
|
||||
['"multi' . "\n" . 'line"', 'Unterminated string.', $this->loc(1, 7)],
|
||||
['"multi' . "\r" . 'line"', 'Unterminated string.', $this->loc(1, 7)],
|
||||
['"bad \\z esc"', 'Invalid character escape sequence: \\z', $this->loc(1, 7)],
|
||||
['"bad \\x esc"', "Invalid character escape sequence: \\x", $this->loc(1, 7)],
|
||||
['"bad \\u1 esc"', "Invalid character escape sequence: \\u1 es", $this->loc(1, 7)],
|
||||
['"bad \\u0XX1 esc"', "Invalid character escape sequence: \\u0XX1", $this->loc(1, 7)],
|
||||
@ -336,25 +434,40 @@ class LexerTest extends TestCase
|
||||
|
||||
/**
|
||||
* @dataProvider reportsUsefulStringErrors
|
||||
* @see it('lex reports useful string errors')
|
||||
* @see it('lex reports useful string errors')
|
||||
*/
|
||||
public function testLexReportsUsefulStringErrors($str, $expectedMessage, $location) : void
|
||||
{
|
||||
$this->expectSyntaxError($str, $expectedMessage, $location);
|
||||
}
|
||||
|
||||
public function reportsUsefulBlockStringErrors() {
|
||||
public function reportsUsefulBlockStringErrors()
|
||||
{
|
||||
return [
|
||||
['"""', "Unterminated string.", $this->loc(1, 4)],
|
||||
['"""no end quote', "Unterminated string.", $this->loc(1, 16)],
|
||||
['"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""', "Invalid character within String: \"\\u0007\"", $this->loc(1, 23)],
|
||||
['"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""', "Invalid character within String: \"\\u0000\"", $this->loc(1, 21)],
|
||||
['"""', 'Unterminated string.', $this->loc(1, 4)],
|
||||
['"""no end quote', 'Unterminated string.', $this->loc(1, 16)],
|
||||
[
|
||||
'"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""',
|
||||
"Invalid character within String: \"\\u0007\"",
|
||||
$this->loc(
|
||||
1,
|
||||
23
|
||||
),
|
||||
],
|
||||
[
|
||||
'"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""',
|
||||
"Invalid character within String: \"\\u0000\"",
|
||||
$this->loc(
|
||||
1,
|
||||
21
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider reportsUsefulBlockStringErrors
|
||||
* @see it('lex reports useful block string errors')
|
||||
* @see it('lex reports useful block string errors')
|
||||
*/
|
||||
public function testReportsUsefulBlockStringErrors($str, $expectedMessage, $location) : void
|
||||
{
|
||||
@ -435,21 +548,21 @@ class LexerTest extends TestCase
|
||||
public function reportsUsefulNumberErrors()
|
||||
{
|
||||
return [
|
||||
[ '00', "Invalid number, unexpected digit after 0: \"0\"", $this->loc(1, 2)],
|
||||
[ '+1', "Cannot parse the unexpected character \"+\".", $this->loc(1, 1)],
|
||||
[ '1.', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 3)],
|
||||
[ '1.e1', "Invalid number, expected digit but got: \"e\"", $this->loc(1, 3)],
|
||||
[ '.123', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)],
|
||||
[ '1.A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 3)],
|
||||
[ '-A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 2)],
|
||||
[ '1.0e', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 5)],
|
||||
[ '1.0eA', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 5)],
|
||||
['00', 'Invalid number, unexpected digit after 0: "0"', $this->loc(1, 2)],
|
||||
['+1', 'Cannot parse the unexpected character "+".', $this->loc(1, 1)],
|
||||
['1.', 'Invalid number, expected digit but got: <EOF>', $this->loc(1, 3)],
|
||||
['1.e1', 'Invalid number, expected digit but got: "e"', $this->loc(1, 3)],
|
||||
['.123', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)],
|
||||
['1.A', 'Invalid number, expected digit but got: "A"', $this->loc(1, 3)],
|
||||
['-A', 'Invalid number, expected digit but got: "A"', $this->loc(1, 2)],
|
||||
['1.0e', 'Invalid number, expected digit but got: <EOF>', $this->loc(1, 5)],
|
||||
['1.0eA', 'Invalid number, expected digit but got: "A"', $this->loc(1, 5)],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider reportsUsefulNumberErrors
|
||||
* @see it('lex reports useful number errors')
|
||||
* @see it('lex reports useful number errors')
|
||||
*/
|
||||
public function testReportsUsefulNumberErrors($str, $expectedMessage, $location) : void
|
||||
{
|
||||
@ -521,8 +634,8 @@ class LexerTest extends TestCase
|
||||
$unicode2 = json_decode('"\u200b"');
|
||||
|
||||
return [
|
||||
['..', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)],
|
||||
['?', "Cannot parse the unexpected character \"?\".", $this->loc(1, 1)],
|
||||
['..', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)],
|
||||
['?', 'Cannot parse the unexpected character "?".', $this->loc(1, 1)],
|
||||
[$unicode1, "Cannot parse the unexpected character \"\\u203b\".", $this->loc(1, 1)],
|
||||
[$unicode2, "Cannot parse the unexpected character \"\\u200b\".", $this->loc(1, 1)],
|
||||
];
|
||||
@ -530,7 +643,7 @@ class LexerTest extends TestCase
|
||||
|
||||
/**
|
||||
* @dataProvider reportsUsefulUnknownCharErrors
|
||||
* @see it('lex reports useful unknown character error')
|
||||
* @see it('lex reports useful unknown character error')
|
||||
*/
|
||||
public function testReportsUsefulUnknownCharErrors($str, $expectedMessage, $location) : void
|
||||
{
|
||||
@ -542,17 +655,20 @@ class LexerTest extends TestCase
|
||||
*/
|
||||
public function testReportsUsefulDashesInfo() : void
|
||||
{
|
||||
$q = 'a-b';
|
||||
$q = 'a-b';
|
||||
$lexer = new Lexer(new Source($q));
|
||||
$this->assertArraySubset(['kind' => Token::NAME, 'start' => 0, 'end' => 1, 'value' => 'a'], (array) $lexer->advance());
|
||||
$this->assertArraySubset(
|
||||
['kind' => Token::NAME, 'start' => 0, 'end' => 1, 'value' => 'a'],
|
||||
(array) $lexer->advance()
|
||||
);
|
||||
|
||||
$this->expectException(SyntaxError::class);
|
||||
$this->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"');
|
||||
try {
|
||||
$lexer->advance();
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch(SyntaxError $error) {
|
||||
$this->assertEquals([$this->loc(1,3)], $error->getLocations());
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals([$this->loc(1, 3)], $error->getLocations());
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
@ -580,49 +696,28 @@ class LexerTest extends TestCase
|
||||
|
||||
$tokens = [];
|
||||
for ($tok = $startToken; $tok; $tok = $tok->next) {
|
||||
if (!empty($tokens)) {
|
||||
if (! empty($tokens)) {
|
||||
// Tokens are double-linked, prev should point to last seen token.
|
||||
$this->assertSame($tokens[count($tokens) - 1], $tok->prev);
|
||||
}
|
||||
$tokens[] = $tok;
|
||||
}
|
||||
|
||||
$this->assertEquals([
|
||||
'<SOF>',
|
||||
'{',
|
||||
'Comment',
|
||||
'Name',
|
||||
'}',
|
||||
'<EOF>'
|
||||
], Utils::map($tokens, function ($tok) {
|
||||
return $tok->kind;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $body
|
||||
* @return Token
|
||||
*/
|
||||
private function lexOne($body)
|
||||
{
|
||||
$lexer = new Lexer(new Source($body));
|
||||
return $lexer->advance();
|
||||
}
|
||||
|
||||
private function loc($line, $column)
|
||||
{
|
||||
return new SourceLocation($line, $column);
|
||||
}
|
||||
|
||||
private function expectSyntaxError($text, $message, $location)
|
||||
{
|
||||
$this->expectException(SyntaxError::class);
|
||||
$this->expectExceptionMessage($message);
|
||||
try {
|
||||
$this->lexOne($text);
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals([$location], $error->getLocations());
|
||||
throw $error;
|
||||
}
|
||||
$this->assertEquals(
|
||||
[
|
||||
'<SOF>',
|
||||
'{',
|
||||
'Comment',
|
||||
'Name',
|
||||
'}',
|
||||
'<EOF>',
|
||||
],
|
||||
Utils::map(
|
||||
$tokens,
|
||||
function ($tok) {
|
||||
return $tok->kind;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Language;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Error\SyntaxError;
|
||||
use GraphQL\Language\AST\ArgumentNode;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\NameNode;
|
||||
@ -13,9 +17,10 @@ use GraphQL\Language\AST\StringValueNode;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Error\SyntaxError;
|
||||
use GraphQL\Utils\Utils;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function file_get_contents;
|
||||
use function sprintf;
|
||||
|
||||
class ParserTest extends TestCase
|
||||
{
|
||||
@ -43,22 +48,40 @@ class ParserTest extends TestCase
|
||||
public function parseProvidesUsefulErrors()
|
||||
{
|
||||
return [
|
||||
['{', "Syntax Error: Expected Name, found <EOF>", "Syntax Error: Expected Name, found <EOF>\n\nGraphQL request (1:2)\n1: {\n ^\n", [1], [new SourceLocation(1, 2)]],
|
||||
['{ ...MissingOn }
|
||||
[
|
||||
'{',
|
||||
'Syntax Error: Expected Name, found <EOF>',
|
||||
"Syntax Error: Expected Name, found <EOF>\n\nGraphQL request (1:2)\n1: {\n ^\n",
|
||||
[1],
|
||||
[new SourceLocation(
|
||||
1,
|
||||
2
|
||||
),
|
||||
],
|
||||
],
|
||||
[
|
||||
'{ ...MissingOn }
|
||||
fragment MissingOn Type
|
||||
', "Syntax Error: Expected \"on\", found Name \"Type\"", "Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n",],
|
||||
['{ field: {} }', "Syntax Error: Expected Name, found {", "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"],
|
||||
['notanoperation Foo { field }', "Syntax Error: Unexpected Name \"notanoperation\"", "Syntax Error: Unexpected Name \"notanoperation\"\n\nGraphQL request (1:1)\n1: notanoperation Foo { field }\n ^\n"],
|
||||
['...', "Syntax Error: Unexpected ...", "Syntax Error: Unexpected ...\n\nGraphQL request (1:1)\n1: ...\n ^\n"],
|
||||
', 'Syntax Error: Expected "on", found Name "Type"',
|
||||
"Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n",
|
||||
],
|
||||
['{ field: {} }', 'Syntax Error: Expected Name, found {', "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"],
|
||||
['notanoperation Foo { field }', 'Syntax Error: Unexpected Name "notanoperation"', "Syntax Error: Unexpected Name \"notanoperation\"\n\nGraphQL request (1:1)\n1: notanoperation Foo { field }\n ^\n"],
|
||||
['...', 'Syntax Error: Unexpected ...', "Syntax Error: Unexpected ...\n\nGraphQL request (1:1)\n1: ...\n ^\n"],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider parseProvidesUsefulErrors
|
||||
* @see it('parse provides useful errors')
|
||||
* @see it('parse provides useful errors')
|
||||
*/
|
||||
public function testParseProvidesUsefulErrors($str, $expectedMessage, $stringRepresentation, $expectedPositions = null, $expectedLocations = null) : void
|
||||
{
|
||||
public function testParseProvidesUsefulErrors(
|
||||
$str,
|
||||
$expectedMessage,
|
||||
$stringRepresentation,
|
||||
$expectedPositions = null,
|
||||
$expectedLocations = null
|
||||
) : void {
|
||||
try {
|
||||
Parser::parse($str);
|
||||
$this->fail('Expected exception not thrown');
|
||||
@ -110,10 +133,27 @@ fragment MissingOn Type
|
||||
$this->expectSyntaxError(
|
||||
'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }',
|
||||
'Unexpected $',
|
||||
$this->loc(1,37)
|
||||
$this->loc(1, 37)
|
||||
);
|
||||
}
|
||||
|
||||
private function expectSyntaxError($text, $message, $location)
|
||||
{
|
||||
$this->expectException(SyntaxError::class);
|
||||
$this->expectExceptionMessage($message);
|
||||
try {
|
||||
Parser::parse($text);
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals([$location], $error->getLocations());
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
|
||||
private function loc($line, $column)
|
||||
{
|
||||
return new SourceLocation($line, $column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not accept fragments spread of "on"')
|
||||
*/
|
||||
@ -122,7 +162,7 @@ fragment MissingOn Type
|
||||
$this->expectSyntaxError(
|
||||
'fragment on on on { on }',
|
||||
'Unexpected Name "on"',
|
||||
$this->loc(1,10)
|
||||
$this->loc(1, 10)
|
||||
);
|
||||
}
|
||||
|
||||
@ -134,7 +174,7 @@ fragment MissingOn Type
|
||||
$this->expectSyntaxError(
|
||||
'{ ...on }',
|
||||
'Expected Name, found }',
|
||||
$this->loc(1,9)
|
||||
$this->loc(1, 9)
|
||||
);
|
||||
}
|
||||
|
||||
@ -145,7 +185,7 @@ fragment MissingOn Type
|
||||
{
|
||||
// Note: \u0A0A could be naively interpretted as two line-feed chars.
|
||||
|
||||
$char = Utils::chr(0x0A0A);
|
||||
$char = Utils::chr(0x0A0A);
|
||||
$query = <<<HEREDOC
|
||||
# This comment has a $char multi-byte character.
|
||||
{ field(arg: "Has a $char multi-byte character.") }
|
||||
@ -156,18 +196,18 @@ HEREDOC;
|
||||
$expected = new SelectionSetNode([
|
||||
'selections' => new NodeList([
|
||||
new FieldNode([
|
||||
'name' => new NameNode(['value' => 'field']),
|
||||
'arguments' => new NodeList([
|
||||
'name' => new NameNode(['value' => 'field']),
|
||||
'arguments' => new NodeList([
|
||||
new ArgumentNode([
|
||||
'name' => new NameNode(['value' => 'arg']),
|
||||
'value' => new StringValueNode([
|
||||
'value' => "Has a $char multi-byte character."
|
||||
])
|
||||
])
|
||||
'name' => new NameNode(['value' => 'arg']),
|
||||
'value' => new StringValueNode(
|
||||
['value' => sprintf('Has a %s multi-byte character.', $char)]
|
||||
),
|
||||
]),
|
||||
]),
|
||||
'directives' => new NodeList([])
|
||||
])
|
||||
])
|
||||
'directives' => new NodeList([]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected, $result->definitions[0]->selectionSet);
|
||||
@ -180,7 +220,7 @@ HEREDOC;
|
||||
{
|
||||
// Following should not throw:
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$result = Parser::parse($kitchenSink);
|
||||
$result = Parser::parse($kitchenSink);
|
||||
$this->assertNotEmpty($result);
|
||||
}
|
||||
|
||||
@ -196,7 +236,7 @@ HEREDOC;
|
||||
'mutation',
|
||||
'subscription',
|
||||
'true',
|
||||
'false'
|
||||
'false',
|
||||
];
|
||||
foreach ($nonKeywords as $keyword) {
|
||||
$fragmentName = $keyword;
|
||||
@ -205,14 +245,19 @@ HEREDOC;
|
||||
}
|
||||
|
||||
// Expected not to throw:
|
||||
$result = Parser::parse("query $keyword {
|
||||
... $fragmentName
|
||||
... on $keyword { field }
|
||||
$result = Parser::parse(<<<GRAPHQL
|
||||
query $keyword {
|
||||
... $fragmentName
|
||||
... on $keyword { field }
|
||||
}
|
||||
fragment $fragmentName on Type {
|
||||
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
|
||||
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
|
||||
}
|
||||
");
|
||||
fragment $fragmentName on Type {
|
||||
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
|
||||
}
|
||||
GRAPHQL
|
||||
);
|
||||
$this->assertNotEmpty($result);
|
||||
}
|
||||
}
|
||||
@ -286,94 +331,102 @@ fragment $fragmentName on Type {
|
||||
');
|
||||
$result = Parser::parse($source);
|
||||
|
||||
$loc = function($start, $end) use ($source) {
|
||||
$loc = function ($start, $end) {
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end
|
||||
'end' => $end,
|
||||
];
|
||||
};
|
||||
|
||||
$expected = [
|
||||
'kind' => NodeKind::DOCUMENT,
|
||||
'loc' => $loc(0, 41),
|
||||
'kind' => NodeKind::DOCUMENT,
|
||||
'loc' => $loc(0, 41),
|
||||
'definitions' => [
|
||||
[
|
||||
'kind' => NodeKind::OPERATION_DEFINITION,
|
||||
'loc' => $loc(0, 40),
|
||||
'operation' => 'query',
|
||||
'name' => null,
|
||||
'kind' => NodeKind::OPERATION_DEFINITION,
|
||||
'loc' => $loc(0, 40),
|
||||
'operation' => 'query',
|
||||
'name' => null,
|
||||
'variableDefinitions' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => [
|
||||
'kind' => NodeKind::SELECTION_SET,
|
||||
'loc' => $loc(0, 40),
|
||||
'directives' => [],
|
||||
'selectionSet' => [
|
||||
'kind' => NodeKind::SELECTION_SET,
|
||||
'loc' => $loc(0, 40),
|
||||
'selections' => [
|
||||
[
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(4, 38),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(4, 8),
|
||||
'value' => 'node'
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(4, 38),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(4, 8),
|
||||
'value' => 'node',
|
||||
],
|
||||
'arguments' => [
|
||||
'arguments' => [
|
||||
[
|
||||
'kind' => NodeKind::ARGUMENT,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(9, 11),
|
||||
'value' => 'id'
|
||||
'kind' => NodeKind::ARGUMENT,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(9, 11),
|
||||
'value' => 'id',
|
||||
],
|
||||
'value' => [
|
||||
'kind' => NodeKind::INT,
|
||||
'loc' => $loc(13, 14),
|
||||
'value' => '4'
|
||||
'kind' => NodeKind::INT,
|
||||
'loc' => $loc(13, 14),
|
||||
'value' => '4',
|
||||
],
|
||||
'loc' => $loc(9, 14, $source)
|
||||
]
|
||||
'loc' => $loc(9, 14, $source),
|
||||
],
|
||||
],
|
||||
'directives' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => [
|
||||
'kind' => NodeKind::SELECTION_SET,
|
||||
'loc' => $loc(16, 38),
|
||||
'kind' => NodeKind::SELECTION_SET,
|
||||
'loc' => $loc(16, 38),
|
||||
'selections' => [
|
||||
[
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(22, 24),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(22, 24),
|
||||
'value' => 'id'
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(22, 24),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(22, 24),
|
||||
'value' => 'id',
|
||||
],
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => null
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => null,
|
||||
],
|
||||
[
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(30, 34),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(30, 34),
|
||||
'value' => 'name'
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(30, 34),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(30, 34),
|
||||
'value' => 'name',
|
||||
],
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $this->nodeToArray($result));
|
||||
$this->assertEquals($expected, self::nodeToArray($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public static function nodeToArray(Node $node) : array
|
||||
{
|
||||
return TestUtils::nodeToArray($node);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -389,63 +442,63 @@ fragment $fragmentName on Type {
|
||||
');
|
||||
$result = Parser::parse($source);
|
||||
|
||||
$loc = function($start, $end) use ($source) {
|
||||
$loc = function ($start, $end) {
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end
|
||||
'end' => $end,
|
||||
];
|
||||
};
|
||||
|
||||
$expected = [
|
||||
'kind' => NodeKind::DOCUMENT,
|
||||
'loc' => $loc(0, 30),
|
||||
'kind' => NodeKind::DOCUMENT,
|
||||
'loc' => $loc(0, 30),
|
||||
'definitions' => [
|
||||
[
|
||||
'kind' => NodeKind::OPERATION_DEFINITION,
|
||||
'loc' => $loc(0, 29),
|
||||
'operation' => 'query',
|
||||
'name' => null,
|
||||
'kind' => NodeKind::OPERATION_DEFINITION,
|
||||
'loc' => $loc(0, 29),
|
||||
'operation' => 'query',
|
||||
'name' => null,
|
||||
'variableDefinitions' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => [
|
||||
'kind' => NodeKind::SELECTION_SET,
|
||||
'loc' => $loc(6, 29),
|
||||
'directives' => [],
|
||||
'selectionSet' => [
|
||||
'kind' => NodeKind::SELECTION_SET,
|
||||
'loc' => $loc(6, 29),
|
||||
'selections' => [
|
||||
[
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(10, 27),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(10, 14),
|
||||
'value' => 'node'
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(10, 27),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(10, 14),
|
||||
'value' => 'node',
|
||||
],
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => [
|
||||
'kind' => NodeKind::SELECTION_SET,
|
||||
'loc' => $loc(15, 27),
|
||||
'kind' => NodeKind::SELECTION_SET,
|
||||
'loc' => $loc(15, 27),
|
||||
'selections' => [
|
||||
[
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(21, 23),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(21, 23),
|
||||
'value' => 'id'
|
||||
'kind' => NodeKind::FIELD,
|
||||
'loc' => $loc(21, 23),
|
||||
'alias' => null,
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => $loc(21, 23),
|
||||
'value' => 'id',
|
||||
],
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $this->nodeToArray($result));
|
||||
@ -475,6 +528,8 @@ fragment $fragmentName on Type {
|
||||
Parser::parse($source);
|
||||
}
|
||||
|
||||
// Describe: parseValue
|
||||
|
||||
/**
|
||||
* @see it('contains location information that only stringifys start/end')
|
||||
*/
|
||||
@ -495,6 +550,8 @@ fragment $fragmentName on Type {
|
||||
$this->assertEquals($source, $result->loc->source);
|
||||
}
|
||||
|
||||
// Describe: parseType
|
||||
|
||||
/**
|
||||
* @see it('contains references to start and end tokens')
|
||||
*/
|
||||
@ -506,17 +563,18 @@ fragment $fragmentName on Type {
|
||||
$this->assertEquals('<EOF>', $result->loc->endToken->kind);
|
||||
}
|
||||
|
||||
// Describe: parseValue
|
||||
|
||||
/**
|
||||
* @see it('parses null value')
|
||||
*/
|
||||
public function testParsesNullValues() : void
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => NodeKind::NULL,
|
||||
'loc' => ['start' => 0, 'end' => 4]
|
||||
], $this->nodeToArray(Parser::parseValue('null')));
|
||||
$this->assertEquals(
|
||||
[
|
||||
'kind' => NodeKind::NULL,
|
||||
'loc' => ['start' => 0, 'end' => 4],
|
||||
],
|
||||
$this->nodeToArray(Parser::parseValue('null'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -524,41 +582,45 @@ fragment $fragmentName on Type {
|
||||
*/
|
||||
public function testParsesListValues() : void
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => NodeKind::LST,
|
||||
'loc' => ['start' => 0, 'end' => 11],
|
||||
'values' => [
|
||||
[
|
||||
'kind' => NodeKind::INT,
|
||||
'loc' => ['start' => 1, 'end' => 4],
|
||||
'value' => '123'
|
||||
$this->assertEquals(
|
||||
[
|
||||
'kind' => NodeKind::LST,
|
||||
'loc' => ['start' => 0, 'end' => 11],
|
||||
'values' => [
|
||||
[
|
||||
'kind' => NodeKind::INT,
|
||||
'loc' => ['start' => 1, 'end' => 4],
|
||||
'value' => '123',
|
||||
],
|
||||
[
|
||||
'kind' => NodeKind::STRING,
|
||||
'loc' => ['start' => 5, 'end' => 10],
|
||||
'value' => 'abc',
|
||||
'block' => false,
|
||||
],
|
||||
],
|
||||
[
|
||||
'kind' => NodeKind::STRING,
|
||||
'loc' => ['start' => 5, 'end' => 10],
|
||||
'value' => 'abc',
|
||||
'block' => false
|
||||
]
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseValue('[123 "abc"]')));
|
||||
],
|
||||
$this->nodeToArray(Parser::parseValue('[123 "abc"]'))
|
||||
);
|
||||
}
|
||||
|
||||
// Describe: parseType
|
||||
|
||||
/**
|
||||
* @see it('parses well known types')
|
||||
*/
|
||||
public function testParsesWellKnownTypes() : void
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'value' => 'String'
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('String')));
|
||||
$this->assertEquals(
|
||||
[
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'value' => 'String',
|
||||
],
|
||||
],
|
||||
$this->nodeToArray(Parser::parseType('String'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -566,15 +628,18 @@ fragment $fragmentName on Type {
|
||||
*/
|
||||
public function testParsesCustomTypes() : void
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'value' => 'MyType'
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('MyType')));
|
||||
$this->assertEquals(
|
||||
[
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'value' => 'MyType',
|
||||
],
|
||||
],
|
||||
$this->nodeToArray(Parser::parseType('MyType'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -582,19 +647,22 @@ fragment $fragmentName on Type {
|
||||
*/
|
||||
public function testParsesListTypes() : void
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => NodeKind::LIST_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 8],
|
||||
'type' => [
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'value' => 'MyType'
|
||||
]
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('[MyType]')));
|
||||
$this->assertEquals(
|
||||
[
|
||||
'kind' => NodeKind::LIST_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 8],
|
||||
'type' => [
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'value' => 'MyType',
|
||||
],
|
||||
],
|
||||
],
|
||||
$this->nodeToArray(Parser::parseType('[MyType]'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -602,19 +670,22 @@ fragment $fragmentName on Type {
|
||||
*/
|
||||
public function testParsesNonNullTypes() : void
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => NodeKind::NON_NULL_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 7],
|
||||
'type' => [
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'value' => 'MyType'
|
||||
]
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('MyType!')));
|
||||
$this->assertEquals(
|
||||
[
|
||||
'kind' => NodeKind::NON_NULL_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 7],
|
||||
'type' => [
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'value' => 'MyType',
|
||||
],
|
||||
],
|
||||
],
|
||||
$this->nodeToArray(Parser::parseType('MyType!'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -622,48 +693,25 @@ fragment $fragmentName on Type {
|
||||
*/
|
||||
public function testParsesNestedTypes() : void
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => NodeKind::LIST_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 9],
|
||||
'type' => [
|
||||
'kind' => NodeKind::NON_NULL_TYPE,
|
||||
'loc' => ['start' => 1, 'end' => 8],
|
||||
$this->assertEquals(
|
||||
[
|
||||
'kind' => NodeKind::LIST_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 9],
|
||||
'type' => [
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'value' => 'MyType'
|
||||
]
|
||||
]
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('[MyType!]')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return array
|
||||
*/
|
||||
public static function nodeToArray(Node $node)
|
||||
{
|
||||
return TestUtils::nodeToArray($node);
|
||||
}
|
||||
|
||||
private function loc($line, $column)
|
||||
{
|
||||
return new SourceLocation($line, $column);
|
||||
}
|
||||
|
||||
private function expectSyntaxError($text, $message, $location)
|
||||
{
|
||||
$this->expectException(SyntaxError::class);
|
||||
$this->expectExceptionMessage($message);
|
||||
try {
|
||||
Parser::parse($text);
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals([$location], $error->getLocations());
|
||||
throw $error;
|
||||
}
|
||||
'kind' => NodeKind::NON_NULL_TYPE,
|
||||
'loc' => ['start' => 1, 'end' => 8],
|
||||
'type' => [
|
||||
'kind' => NodeKind::NAMED_TYPE,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'name' => [
|
||||
'kind' => NodeKind::NAME,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'value' => 'MyType',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
$this->nodeToArray(Parser::parseType('[MyType!]'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Language;
|
||||
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
use GraphQL\Language\AST\EnumValueNode;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\NameNode;
|
||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
use GraphQL\Language\AST\SelectionSetNode;
|
||||
use GraphQL\Language\AST\StringValueNode;
|
||||
use GraphQL\Language\AST\VariableNode;
|
||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\Printer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function file_get_contents;
|
||||
|
||||
class PrinterTest extends TestCase
|
||||
{
|
||||
@ -22,7 +19,7 @@ class PrinterTest extends TestCase
|
||||
public function testDoesntAlterAST() : void
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
|
||||
$astCopy = $ast->cloneDeep();
|
||||
$this->assertEquals($astCopy, $ast);
|
||||
@ -66,7 +63,7 @@ class PrinterTest extends TestCase
|
||||
$this->assertEquals($expected, Printer::doPrint($queryAstShorthanded));
|
||||
|
||||
$mutationAst = Parser::parse('mutation { id, name }');
|
||||
$expected = 'mutation {
|
||||
$expected = 'mutation {
|
||||
id
|
||||
name
|
||||
}
|
||||
@ -76,7 +73,7 @@ class PrinterTest extends TestCase
|
||||
$queryAstWithArtifacts = Parser::parse(
|
||||
'query ($foo: TestType) @testDirective { id, name }'
|
||||
);
|
||||
$expected = 'query ($foo: TestType) @testDirective {
|
||||
$expected = 'query ($foo: TestType) @testDirective {
|
||||
id
|
||||
name
|
||||
}
|
||||
@ -86,7 +83,7 @@ class PrinterTest extends TestCase
|
||||
$mutationAstWithArtifacts = Parser::parse(
|
||||
'mutation ($foo: TestType) @testDirective { id, name }'
|
||||
);
|
||||
$expected = 'mutation ($foo: TestType) @testDirective {
|
||||
$expected = 'mutation ($foo: TestType) @testDirective {
|
||||
id
|
||||
name
|
||||
}
|
||||
@ -100,13 +97,13 @@ class PrinterTest extends TestCase
|
||||
public function testCorrectlyPrintsSingleLineBlockStringsWithLeadingSpace() : void
|
||||
{
|
||||
$mutationAstWithArtifacts = Parser::parse(
|
||||
'{ field(arg: """ space-led value""") }'
|
||||
'{ field(arg: """ space-led value""") }'
|
||||
);
|
||||
$expected = '{
|
||||
$expected = '{
|
||||
field(arg: """ space-led value""")
|
||||
}
|
||||
';
|
||||
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
|
||||
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,8 +119,8 @@ class PrinterTest extends TestCase
|
||||
indentation
|
||||
""")
|
||||
}'
|
||||
);
|
||||
$expected = '{
|
||||
);
|
||||
$expected = '{
|
||||
field(arg: """
|
||||
first
|
||||
line
|
||||
@ -131,7 +128,7 @@ class PrinterTest extends TestCase
|
||||
""")
|
||||
}
|
||||
';
|
||||
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
|
||||
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,7 +142,7 @@ class PrinterTest extends TestCase
|
||||
""")
|
||||
}
|
||||
');
|
||||
$expected = <<<END
|
||||
$expected = <<<END
|
||||
{
|
||||
field(arg: """ space-led value "quoted string"
|
||||
""")
|
||||
@ -160,7 +157,8 @@ END;
|
||||
*/
|
||||
public function testExperimentalCorrectlyPrintsFragmentDefinedVariables() : void
|
||||
{
|
||||
$fragmentWithVariable = Parser::parse('
|
||||
$fragmentWithVariable = Parser::parse(
|
||||
'
|
||||
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
|
||||
id
|
||||
}
|
||||
@ -187,13 +185,13 @@ END;
|
||||
field(arg: """ space-led value "quoted string"
|
||||
""")
|
||||
}'
|
||||
);
|
||||
$expected = '{
|
||||
);
|
||||
$expected = '{
|
||||
field(arg: """ space-led value "quoted string"
|
||||
""")
|
||||
}
|
||||
';
|
||||
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
|
||||
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -202,7 +200,7 @@ END;
|
||||
public function testPrintsKitchenSink() : void
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
|
||||
$printed = Printer::doPrint($ast);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,8 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Language;
|
||||
|
||||
use GraphQL\Language\AST\NameNode;
|
||||
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
|
||||
@ -7,6 +10,7 @@ use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\Printer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Throwable;
|
||||
use function file_get_contents;
|
||||
|
||||
class SchemaPrinterTest extends TestCase
|
||||
{
|
||||
@ -16,7 +20,7 @@ class SchemaPrinterTest extends TestCase
|
||||
public function testPrintsMinimalAst() : void
|
||||
{
|
||||
$ast = new ScalarTypeDefinitionNode([
|
||||
'name' => new NameNode(['value' => 'foo'])
|
||||
'name' => new NameNode(['value' => 'foo']),
|
||||
]);
|
||||
$this->assertEquals('scalar foo', Printer::doPrint($ast));
|
||||
}
|
||||
@ -41,7 +45,7 @@ class SchemaPrinterTest extends TestCase
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql');
|
||||
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
$astCopy = $ast->cloneDeep();
|
||||
Printer::doPrint($ast);
|
||||
|
||||
@ -52,7 +56,7 @@ class SchemaPrinterTest extends TestCase
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql');
|
||||
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
$printed = Printer::doPrint($ast);
|
||||
|
||||
$expected = 'schema {
|
||||
|
@ -1,5 +1,8 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Language;
|
||||
|
||||
use GraphQL\Language\AST\Location;
|
||||
use GraphQL\Language\AST\Node;
|
||||
@ -7,80 +10,68 @@ use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Utils\AST;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function file_get_contents;
|
||||
use function get_class;
|
||||
use function get_object_vars;
|
||||
use function implode;
|
||||
use function json_decode;
|
||||
|
||||
class SerializationTest extends TestCase
|
||||
{
|
||||
public function testSerializesAst() : void
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
|
||||
$this->assertEquals($expectedAst, $ast->toArray(true));
|
||||
}
|
||||
|
||||
public function testUnserializesAst() : void
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
|
||||
$actualAst = AST::fromArray($serializedAst);
|
||||
$parsedAst = Parser::parse($kitchenSink);
|
||||
$this->assertNodesAreEqual($parsedAst, $actualAst);
|
||||
}
|
||||
|
||||
public function testSerializeSupportsNoLocationOption() : void
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$ast = Parser::parse($kitchenSink, ['noLocation' => true]);
|
||||
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
|
||||
$this->assertEquals($expectedAst, $ast->toArray(true));
|
||||
}
|
||||
|
||||
public function testUnserializeSupportsNoLocationOption() : void
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
|
||||
$actualAst = AST::fromArray($serializedAst);
|
||||
$parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]);
|
||||
$actualAst = AST::fromArray($serializedAst);
|
||||
$parsedAst = Parser::parse($kitchenSink);
|
||||
$this->assertNodesAreEqual($parsedAst, $actualAst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc
|
||||
*
|
||||
* @param $expected
|
||||
* @param $actual
|
||||
* @param array $path
|
||||
* @param string[] $path
|
||||
*/
|
||||
private function assertNodesAreEqual($expected, $actual, $path = [])
|
||||
private function assertNodesAreEqual(Node $expected, Node $actual, array $path = []) : void
|
||||
{
|
||||
$err = "Mismatch at AST path: " . implode(', ', $path);
|
||||
$err = 'Mismatch at AST path: ' . implode(', ', $path);
|
||||
|
||||
$this->assertInstanceOf(Node::class, $actual, $err);
|
||||
$this->assertEquals(get_class($expected), get_class($actual), $err);
|
||||
|
||||
$expectedVars = get_object_vars($expected);
|
||||
$actualVars = get_object_vars($actual);
|
||||
$this->assertSame(count($expectedVars), count($actualVars), $err);
|
||||
$actualVars = get_object_vars($actual);
|
||||
$this->assertCount(count($expectedVars), $actualVars, $err);
|
||||
$this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err);
|
||||
|
||||
foreach ($expectedVars as $name => $expectedValue) {
|
||||
$actualValue = $actualVars[$name];
|
||||
$tmpPath = $path;
|
||||
$tmpPath[] = $name;
|
||||
$err = "Mismatch at AST path: " . implode(', ', $tmpPath);
|
||||
$tmpPath = $path;
|
||||
$tmpPath[] = $name;
|
||||
$err = 'Mismatch at AST path: ' . implode(', ', $tmpPath);
|
||||
|
||||
if ($expectedValue instanceof Node) {
|
||||
$this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath);
|
||||
} else if ($expectedValue instanceof NodeList) {
|
||||
} elseif ($expectedValue instanceof NodeList) {
|
||||
$this->assertEquals(count($expectedValue), count($actualValue), $err);
|
||||
$this->assertInstanceOf(NodeList::class, $actualValue, $err);
|
||||
|
||||
foreach ($expectedValue as $index => $listNode) {
|
||||
$tmpPath2 = $tmpPath;
|
||||
$tmpPath2 [] = $index;
|
||||
$tmpPath2 = $tmpPath;
|
||||
$tmpPath2[] = $index;
|
||||
$this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2);
|
||||
}
|
||||
} else if ($expectedValue instanceof Location) {
|
||||
} elseif ($expectedValue instanceof Location) {
|
||||
$this->assertInstanceOf(Location::class, $actualValue, $err);
|
||||
$this->assertSame($expectedValue->start, $actualValue->start, $err);
|
||||
$this->assertSame($expectedValue->end, $actualValue->end, $err);
|
||||
@ -89,4 +80,21 @@ class SerializationTest extends TestCase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testSerializeSupportsNoLocationOption() : void
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$ast = Parser::parse($kitchenSink, ['noLocation' => true]);
|
||||
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
|
||||
$this->assertEquals($expectedAst, $ast->toArray(true));
|
||||
}
|
||||
|
||||
public function testUnserializeSupportsNoLocationOption() : void
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
|
||||
$actualAst = AST::fromArray($serializedAst);
|
||||
$parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]);
|
||||
$this->assertNodesAreEqual($parsedAst, $actualAst);
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +1,41 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Language;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Language;
|
||||
|
||||
use GraphQL\Language\AST\Location;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use function get_object_vars;
|
||||
use function is_array;
|
||||
use function is_scalar;
|
||||
|
||||
class TestUtils
|
||||
{
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return array
|
||||
* @return mixed[]
|
||||
*/
|
||||
public static function nodeToArray(Node $node)
|
||||
public static function nodeToArray(Node $node) : array
|
||||
{
|
||||
$result = [
|
||||
'kind' => $node->kind,
|
||||
'loc' => self::locationToArray($node->loc)
|
||||
'loc' => self::locationToArray($node->loc),
|
||||
];
|
||||
|
||||
foreach (get_object_vars($node) as $prop => $propValue) {
|
||||
if (isset($result[$prop]))
|
||||
if (isset($result[$prop])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($propValue) || $propValue instanceof NodeList) {
|
||||
$tmp = [];
|
||||
foreach ($propValue as $tmp1) {
|
||||
$tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1;
|
||||
}
|
||||
} else if ($propValue instanceof Node) {
|
||||
} elseif ($propValue instanceof Node) {
|
||||
$tmp = self::nodeToArray($propValue);
|
||||
} else if (is_scalar($propValue) || null === $propValue) {
|
||||
} elseif (is_scalar($propValue) || $propValue === null) {
|
||||
$tmp = $propValue;
|
||||
} else {
|
||||
$tmp = null;
|
||||
@ -38,27 +43,25 @@ class TestUtils
|
||||
|
||||
$result[$prop] = $tmp;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Location $loc
|
||||
* @return array
|
||||
* @return int[]
|
||||
*/
|
||||
public static function locationToArray(Location $loc)
|
||||
public static function locationToArray(Location $loc) : array
|
||||
{
|
||||
return [
|
||||
'start' => $loc->start,
|
||||
'end' => $loc->end
|
||||
'end' => $loc->end,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $start
|
||||
* @param $end
|
||||
* @return array
|
||||
* @return int[]
|
||||
*/
|
||||
public static function locArray($start, $end)
|
||||
public static function locArray(int $start, int $end) : array
|
||||
{
|
||||
return ['start' => $start, 'end' => $end];
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Language;
|
||||
|
||||
use GraphQL\Language\Token;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@ -8,12 +11,12 @@ class TokenTest extends TestCase
|
||||
{
|
||||
public function testReturnTokenOnArray() : void
|
||||
{
|
||||
$token = new Token('Kind', 1, 10, 3, 5);
|
||||
$token = new Token('Kind', 1, 10, 3, 5);
|
||||
$expected = [
|
||||
'kind' => 'Kind',
|
||||
'value' => null,
|
||||
'line' => 3,
|
||||
'column' => 5
|
||||
'kind' => 'Kind',
|
||||
'value' => null,
|
||||
'line' => 3,
|
||||
'column' => 5,
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $token->toArray());
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server\Psr7;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
* phpcs:ignoreFile -- this is not a core file
|
||||
*/
|
||||
class PsrRequestStub implements ServerRequestInterface
|
||||
{
|
||||
public $queryParams = [];
|
||||
@ -13,14 +20,10 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
|
||||
public $method;
|
||||
|
||||
/**
|
||||
* @var PsrStreamStub
|
||||
*/
|
||||
/** @var PsrStreamStub */
|
||||
public $body;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var mixed[] */
|
||||
public $parsedBody;
|
||||
|
||||
/**
|
||||
@ -32,7 +35,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function getProtocolVersion()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,7 +53,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withProtocolVersion($version)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,7 +83,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function getHeaders()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,7 +96,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function hasHeader($name)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,7 +116,8 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
public function getHeader($name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
return isset($this->headers[$name]) ? $this->headers[$name] : [];
|
||||
|
||||
return $this->headers[$name] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,7 +141,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function getHeaderLine($name)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,14 +154,14 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new and/or updated header and value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @param string|string[] $value Header value(s).
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException for invalid header names or values.
|
||||
*/
|
||||
public function withHeader($name, $value)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,14 +175,14 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new header and/or value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name to add.
|
||||
* @param string $name Case-insensitive header field name to add.
|
||||
* @param string|string[] $value Header value(s).
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException for invalid header names or values.
|
||||
*/
|
||||
public function withAddedHeader($name, $value)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,7 +199,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withoutHeader($name)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,7 +227,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withBody(StreamInterface $body)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -244,7 +248,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function getRequestTarget()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -266,7 +270,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withRequestTarget($requestTarget)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -296,7 +300,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withMethod($method)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -339,13 +343,13 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
* new UriInterface instance.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc3986#section-4.3
|
||||
* @param UriInterface $uri New request URI to use.
|
||||
* @param bool $preserveHost Preserve the original state of the Host header.
|
||||
* @param UriInterface $uri New request URI to use.
|
||||
* @param bool $preserveHost Preserve the original state of the Host header.
|
||||
* @return static
|
||||
*/
|
||||
public function withUri(UriInterface $uri, $preserveHost = false)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -359,7 +363,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function getServerParams()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -374,7 +378,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function getCookieParams()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -396,7 +400,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withCookieParams(array $cookies)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -440,7 +444,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withQueryParams(array $query)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -457,7 +461,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function getUploadedFiles()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -473,7 +477,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withUploadedFiles(array $uploadedFiles)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -526,7 +530,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withParsedBody($data)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -542,7 +546,7 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -556,13 +560,13 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
* specifying a default value to return if the attribute is not found.
|
||||
*
|
||||
* @see getAttributes()
|
||||
* @param string $name The attribute name.
|
||||
* @param mixed $default Default value to return if the attribute does not exist.
|
||||
* @param string $name The attribute name.
|
||||
* @param mixed $default Default value to return if the attribute does not exist.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute($name, $default = null)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -576,13 +580,13 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
* updated attribute.
|
||||
*
|
||||
* @see getAttributes()
|
||||
* @param string $name The attribute name.
|
||||
* @param mixed $value The value of the attribute.
|
||||
* @param string $name The attribute name.
|
||||
* @param mixed $value The value of the attribute.
|
||||
* @return static
|
||||
*/
|
||||
public function withAttribute($name, $value)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -601,6 +605,6 @@ class PsrRequestStub implements ServerRequestInterface
|
||||
*/
|
||||
public function withoutAttribute($name)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Server\Psr7;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server\Psr7;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* phpcs:ignoreFile -- this is not a core file
|
||||
*/
|
||||
class PsrResponseStub implements ResponseInterface
|
||||
{
|
||||
public $headers = [];
|
||||
@ -22,7 +27,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function getProtocolVersion()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,7 +45,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function withProtocolVersion($version)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,7 +75,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function getHeaders()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,7 +88,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function hasHeader($name)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,7 +107,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function getHeader($name)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,7 +131,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function getHeaderLine($name)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,15 +144,16 @@ class PsrResponseStub implements ResponseInterface
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new and/or updated header and value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @param string|string[] $value Header value(s).
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException for invalid header names or values.
|
||||
*/
|
||||
public function withHeader($name, $value)
|
||||
{
|
||||
$tmp = clone $this;
|
||||
$tmp = clone $this;
|
||||
$tmp->headers[$name][] = $value;
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
@ -162,14 +168,14 @@ class PsrResponseStub implements ResponseInterface
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new header and/or value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name to add.
|
||||
* @param string $name Case-insensitive header field name to add.
|
||||
* @param string|string[] $value Header value(s).
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException for invalid header names or values.
|
||||
*/
|
||||
public function withAddedHeader($name, $value)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,7 +192,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function withoutHeader($name)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,7 +202,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function getBody()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,8 +220,9 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function withBody(StreamInterface $body)
|
||||
{
|
||||
$tmp = clone $this;
|
||||
$tmp = clone $this;
|
||||
$tmp->body = $body;
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
@ -229,7 +236,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function getStatusCode()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -245,7 +252,7 @@ class PsrResponseStub implements ResponseInterface
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
* @param int $code The 3-digit integer result code to set.
|
||||
* @param int $code The 3-digit integer result code to set.
|
||||
* @param string $reasonPhrase The reason phrase to use with the
|
||||
* provided status code; if none is provided, implementations MAY
|
||||
* use the defaults as suggested in the HTTP specification.
|
||||
@ -254,8 +261,9 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function withStatus($code, $reasonPhrase = '')
|
||||
{
|
||||
$tmp = clone $this;
|
||||
$tmp = clone $this;
|
||||
$tmp->statusCode = $code;
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
@ -274,6 +282,6 @@ class PsrResponseStub implements ResponseInterface
|
||||
*/
|
||||
public function getReasonPhrase()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server\Psr7;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use function strlen;
|
||||
use const SEEK_SET;
|
||||
|
||||
/**
|
||||
* phpcs:ignoreFile -- this is not a core file
|
||||
*/
|
||||
class PsrStreamStub implements StreamInterface
|
||||
{
|
||||
public $content;
|
||||
@ -33,7 +41,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,7 +53,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function detach()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,7 +63,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return strlen($this->content?:'');
|
||||
return strlen($this->content ?: '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,7 +74,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function tell()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,7 +84,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function eof()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,7 +94,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function isSeekable()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,7 +111,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,7 +126,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,6 +149,7 @@ class PsrStreamStub implements StreamInterface
|
||||
public function write($string)
|
||||
{
|
||||
$this->content = $string;
|
||||
|
||||
return strlen($string);
|
||||
}
|
||||
|
||||
@ -151,7 +160,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function isReadable()
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,7 +175,7 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function read($length)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,6 +204,6 @@ class PsrStreamStub implements StreamInterface
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
throw new \Exception("Not implemented");
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server;
|
||||
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\Server\Helper;
|
||||
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
|
||||
use GraphQL\Tests\Server\Psr7\PsrResponseStub;
|
||||
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function json_encode;
|
||||
|
||||
class PsrResponseTest extends TestCase
|
||||
{
|
||||
public function testConvertsResultToPsrResponse() : void
|
||||
{
|
||||
$result = new ExecutionResult(['key' => 'value']);
|
||||
$stream = new PsrStreamStub();
|
||||
$result = new ExecutionResult(['key' => 'value']);
|
||||
$stream = new PsrStreamStub();
|
||||
$psrResponse = new PsrResponseStub();
|
||||
|
||||
$helper = new Helper();
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server;
|
||||
|
||||
use GraphQL\Error\Debug;
|
||||
@ -14,17 +17,17 @@ use GraphQL\Server\ServerConfig;
|
||||
use GraphQL\Validator\DocumentValidator;
|
||||
use GraphQL\Validator\Rules\CustomValidationRule;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
use function count;
|
||||
use function sprintf;
|
||||
|
||||
class QueryExecutionTest extends ServerTestCase
|
||||
{
|
||||
/**
|
||||
* @var ServerConfig
|
||||
*/
|
||||
/** @var ServerConfig */
|
||||
private $config;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$schema = $this->buildSchema();
|
||||
$schema = $this->buildSchema();
|
||||
$this->config = ServerConfig::create()
|
||||
->setSchema($schema);
|
||||
}
|
||||
@ -34,20 +37,36 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$query = '{f1}';
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'f1' => 'f1'
|
||||
]
|
||||
'data' => ['f1' => 'f1'],
|
||||
];
|
||||
|
||||
$this->assertQueryResultEquals($expected, $query);
|
||||
}
|
||||
|
||||
private function assertQueryResultEquals($expected, $query, $variables = null)
|
||||
{
|
||||
$result = $this->executeQuery($query, $variables);
|
||||
$this->assertArraySubset($expected, $result->toArray(true));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function executeQuery($query, $variables = null, $readonly = false)
|
||||
{
|
||||
$op = OperationParams::create(['query' => $query, 'variables' => $variables], $readonly);
|
||||
$helper = new Helper();
|
||||
$result = $helper->executeOperation($this->config, $op);
|
||||
$this->assertInstanceOf(ExecutionResult::class, $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function testReturnsSyntaxErrors() : void
|
||||
{
|
||||
$query = '{f1';
|
||||
|
||||
$result = $this->executeQuery($query);
|
||||
$this->assertSame(null, $result->data);
|
||||
$this->assertNull($result->data);
|
||||
$this->assertCount(1, $result->errors);
|
||||
$this->assertContains(
|
||||
'Syntax Error: Expected Name, found <EOF>',
|
||||
@ -70,11 +89,12 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$expected = [
|
||||
'data' => [
|
||||
'fieldWithSafeException' => null,
|
||||
'f1' => 'f1'
|
||||
'f1' => 'f1',
|
||||
],
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'This is the exception we want',
|
||||
|
||||
'path' => ['fieldWithSafeException'],
|
||||
'trace' => []
|
||||
]
|
||||
@ -100,7 +120,7 @@ class QueryExecutionTest extends ServerTestCase
|
||||
public function testPassesRootValueAndContext() : void
|
||||
{
|
||||
$rootValue = 'myRootValue';
|
||||
$context = new \stdClass();
|
||||
$context = new \stdClass();
|
||||
|
||||
$this->config
|
||||
->setContext($context)
|
||||
@ -112,7 +132,7 @@ class QueryExecutionTest extends ServerTestCase
|
||||
}
|
||||
';
|
||||
|
||||
$this->assertTrue(!isset($context->testedRootValue));
|
||||
$this->assertTrue(! isset($context->testedRootValue));
|
||||
$this->executeQuery($query);
|
||||
$this->assertSame($rootValue, $context->testedRootValue);
|
||||
}
|
||||
@ -120,30 +140,30 @@ class QueryExecutionTest extends ServerTestCase
|
||||
public function testPassesVariables() : void
|
||||
{
|
||||
$variables = ['a' => 'a', 'b' => 'b'];
|
||||
$query = '
|
||||
$query = '
|
||||
query ($a: String!, $b: String!) {
|
||||
a: fieldWithArg(arg: $a)
|
||||
b: fieldWithArg(arg: $b)
|
||||
}
|
||||
';
|
||||
$expected = [
|
||||
$expected = [
|
||||
'data' => [
|
||||
'a' => 'a',
|
||||
'b' => 'b'
|
||||
]
|
||||
'b' => 'b',
|
||||
],
|
||||
];
|
||||
$this->assertQueryResultEquals($expected, $query, $variables);
|
||||
}
|
||||
|
||||
public function testPassesCustomValidationRules() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
{nonExistentField}
|
||||
';
|
||||
$expected = [
|
||||
'errors' => [
|
||||
['message' => 'Cannot query field "nonExistentField" on type "Query".']
|
||||
]
|
||||
['message' => 'Cannot query field "nonExistentField" on type "Query".'],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertQueryResultEquals($expected, $query);
|
||||
@ -151,15 +171,16 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$called = false;
|
||||
|
||||
$rules = [
|
||||
new CustomValidationRule('SomeRule', function() use (&$called) {
|
||||
new CustomValidationRule('SomeRule', function () use (&$called) {
|
||||
$called = true;
|
||||
|
||||
return [];
|
||||
})
|
||||
}),
|
||||
];
|
||||
|
||||
$this->config->setValidationRules($rules);
|
||||
$expected = [
|
||||
'data' => []
|
||||
'data' => [],
|
||||
];
|
||||
$this->assertQueryResultEquals($expected, $query);
|
||||
$this->assertTrue($called);
|
||||
@ -170,11 +191,12 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$called = false;
|
||||
$params = $doc = $operationType = null;
|
||||
|
||||
$this->config->setValidationRules(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
|
||||
$called = true;
|
||||
$params = $p;
|
||||
$doc = $d;
|
||||
$this->config->setValidationRules(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
|
||||
$called = true;
|
||||
$params = $p;
|
||||
$doc = $d;
|
||||
$operationType = $o;
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
@ -188,23 +210,25 @@ class QueryExecutionTest extends ServerTestCase
|
||||
|
||||
public function testAllowsDifferentValidationRulesDependingOnOperation() : void
|
||||
{
|
||||
$q1 = '{f1}';
|
||||
$q2 = '{invalid}';
|
||||
$q1 = '{f1}';
|
||||
$q2 = '{invalid}';
|
||||
$called1 = false;
|
||||
$called2 = false;
|
||||
|
||||
$this->config->setValidationRules(function(OperationParams $params) use ($q1, $q2, &$called1, &$called2) {
|
||||
$this->config->setValidationRules(function (OperationParams $params) use ($q1, &$called1, &$called2) {
|
||||
if ($params->query === $q1) {
|
||||
$called1 = true;
|
||||
|
||||
return DocumentValidator::allRules();
|
||||
} else {
|
||||
$called2 = true;
|
||||
return [
|
||||
new CustomValidationRule('MyRule', function(ValidationContext $context) {
|
||||
$context->reportError(new Error("This is the error we are looking for!"));
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
$called2 = true;
|
||||
|
||||
return [
|
||||
new CustomValidationRule('MyRule', function (ValidationContext $context) {
|
||||
$context->reportError(new Error('This is the error we are looking for!'));
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
$expected = ['data' => ['f1' => 'f1']];
|
||||
@ -212,8 +236,8 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$this->assertTrue($called1);
|
||||
$this->assertFalse($called2);
|
||||
|
||||
$called1 = false;
|
||||
$called2 = false;
|
||||
$called1 = false;
|
||||
$called2 = false;
|
||||
$expected = ['errors' => [['message' => 'This is the error we are looking for!']]];
|
||||
$this->assertQueryResultEquals($expected, $q2);
|
||||
$this->assertFalse($called1);
|
||||
@ -223,7 +247,7 @@ class QueryExecutionTest extends ServerTestCase
|
||||
public function testAllowsSkippingValidation() : void
|
||||
{
|
||||
$this->config->setValidationRules([]);
|
||||
$query = '{nonExistentField}';
|
||||
$query = '{nonExistentField}';
|
||||
$expected = ['data' => []];
|
||||
$this->assertQueryResultEquals($expected, $query);
|
||||
}
|
||||
@ -235,23 +259,29 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'Persisted queries are not supported by this server',
|
||||
'category' => 'request'
|
||||
]
|
||||
]
|
||||
'message' => 'Persisted queries are not supported by this server',
|
||||
'category' => 'request',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
private function executePersistedQuery($queryId, $variables = null)
|
||||
{
|
||||
$op = OperationParams::create(['queryId' => $queryId, 'variables' => $variables]);
|
||||
$helper = new Helper();
|
||||
$result = $helper->executeOperation($this->config, $op);
|
||||
$this->assertInstanceOf(ExecutionResult::class, $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function testBatchedQueriesAreDisabledByDefault() : void
|
||||
{
|
||||
$batch = [
|
||||
[
|
||||
'query' => '{invalid}'
|
||||
],
|
||||
[
|
||||
'query' => '{f1,fieldWithSafeException}'
|
||||
]
|
||||
['query' => '{invalid}'],
|
||||
['query' => '{f1,fieldWithSafeException}'],
|
||||
];
|
||||
|
||||
$result = $this->executeBatchedQuery($batch);
|
||||
@ -260,18 +290,18 @@ class QueryExecutionTest extends ServerTestCase
|
||||
[
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'Batched queries are not supported by this server',
|
||||
'category' => 'request'
|
||||
]
|
||||
]
|
||||
'message' => 'Batched queries are not supported by this server',
|
||||
'category' => 'request',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'Batched queries are not supported by this server',
|
||||
'category' => 'request'
|
||||
]
|
||||
]
|
||||
'message' => 'Batched queries are not supported by this server',
|
||||
'category' => 'request',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@ -279,6 +309,31 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$this->assertEquals($expected[1], $result[1]->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[][] $qs
|
||||
*/
|
||||
private function executeBatchedQuery(array $qs)
|
||||
{
|
||||
$batch = [];
|
||||
foreach ($qs as $params) {
|
||||
$batch[] = OperationParams::create($params);
|
||||
}
|
||||
$helper = new Helper();
|
||||
$result = $helper->executeBatch($this->config, $batch);
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(count($qs), $result);
|
||||
|
||||
foreach ($result as $index => $entry) {
|
||||
$this->assertInstanceOf(
|
||||
ExecutionResult::class,
|
||||
$entry,
|
||||
sprintf('Result at %s is not an instance of %s', $index, ExecutionResult::class)
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function testMutationsAreNotAllowedInReadonlyMode() : void
|
||||
{
|
||||
$mutation = 'mutation { a }';
|
||||
@ -286,10 +341,10 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'GET supports only query operation',
|
||||
'category' => 'request'
|
||||
]
|
||||
]
|
||||
'message' => 'GET supports only query operation',
|
||||
'category' => 'request',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = $this->executeQuery($mutation, null, true);
|
||||
@ -299,9 +354,10 @@ class QueryExecutionTest extends ServerTestCase
|
||||
public function testAllowsPersistentQueries() : void
|
||||
{
|
||||
$called = false;
|
||||
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) {
|
||||
$this->config->setPersistentQueryLoader(function ($queryId, OperationParams $params) use (&$called) {
|
||||
$called = true;
|
||||
$this->assertEquals('some-id', $queryId);
|
||||
|
||||
return '{f1}';
|
||||
});
|
||||
|
||||
@ -309,17 +365,16 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$this->assertTrue($called);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'f1' => 'f1'
|
||||
]
|
||||
'data' => ['f1' => 'f1'],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
// Make sure it allows returning document node:
|
||||
$called = false;
|
||||
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) {
|
||||
$this->config->setPersistentQueryLoader(function ($queryId, OperationParams $params) use (&$called) {
|
||||
$called = true;
|
||||
$this->assertEquals('some-id', $queryId);
|
||||
|
||||
return Parser::parse('{f1}');
|
||||
});
|
||||
$result = $this->executePersistedQuery('some-id');
|
||||
@ -334,7 +389,7 @@ class QueryExecutionTest extends ServerTestCase
|
||||
'Persistent query loader must return query string or instance of GraphQL\Language\AST\DocumentNode ' .
|
||||
'but got: {"err":"err"}'
|
||||
);
|
||||
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) {
|
||||
$this->config->setPersistentQueryLoader(function () {
|
||||
return ['err' => 'err'];
|
||||
});
|
||||
$this->executePersistedQuery('some-id');
|
||||
@ -342,56 +397,55 @@ class QueryExecutionTest extends ServerTestCase
|
||||
|
||||
public function testPersistedQueriesAreStillValidatedByDefault() : void
|
||||
{
|
||||
$this->config->setPersistentQueryLoader(function() {
|
||||
$this->config->setPersistentQueryLoader(function () {
|
||||
return '{invalid}';
|
||||
});
|
||||
$result = $this->executePersistedQuery('some-id');
|
||||
$result = $this->executePersistedQuery('some-id');
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'Cannot query field "invalid" on type "Query".',
|
||||
'locations' => [ ['line' => 1, 'column' => 2] ],
|
||||
'category' => 'graphql'
|
||||
]
|
||||
]
|
||||
'message' => 'Cannot query field "invalid" on type "Query".',
|
||||
'locations' => [['line' => 1, 'column' => 2]],
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
}
|
||||
|
||||
public function testAllowSkippingValidationForPersistedQueries() : void
|
||||
{
|
||||
$this->config
|
||||
->setPersistentQueryLoader(function($queryId) {
|
||||
->setPersistentQueryLoader(function ($queryId) {
|
||||
if ($queryId === 'some-id') {
|
||||
return '{invalid}';
|
||||
} else {
|
||||
return '{invalid2}';
|
||||
}
|
||||
|
||||
return '{invalid2}';
|
||||
})
|
||||
->setValidationRules(function(OperationParams $params) {
|
||||
->setValidationRules(function (OperationParams $params) {
|
||||
if ($params->queryId === 'some-id') {
|
||||
return [];
|
||||
} else {
|
||||
return DocumentValidator::allRules();
|
||||
}
|
||||
|
||||
return DocumentValidator::allRules();
|
||||
});
|
||||
|
||||
$result = $this->executePersistedQuery('some-id');
|
||||
$result = $this->executePersistedQuery('some-id');
|
||||
$expected = [
|
||||
'data' => []
|
||||
'data' => [],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
$result = $this->executePersistedQuery('some-other-id');
|
||||
$result = $this->executePersistedQuery('some-other-id');
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'Cannot query field "invalid2" on type "Query".',
|
||||
'locations' => [ ['line' => 1, 'column' => 2] ],
|
||||
'category' => 'graphql'
|
||||
]
|
||||
]
|
||||
'message' => 'Cannot query field "invalid2" on type "Query".',
|
||||
'locations' => [['line' => 1, 'column' => 2]],
|
||||
'category' => 'graphql',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
@ -400,7 +454,7 @@ class QueryExecutionTest extends ServerTestCase
|
||||
{
|
||||
$this->expectException(InvariantViolation::class);
|
||||
$this->expectExceptionMessage('Expecting validation rules to be array or callable returning array, but got: instance of stdClass');
|
||||
$this->config->setValidationRules(function(OperationParams $params) {
|
||||
$this->config->setValidationRules(function (OperationParams $params) {
|
||||
return new \stdClass();
|
||||
});
|
||||
$this->executeQuery('{f1}');
|
||||
@ -411,28 +465,24 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$this->config->setQueryBatching(true);
|
||||
|
||||
$batch = [
|
||||
['query' => '{invalid}'],
|
||||
['query' => '{f1,fieldWithSafeException}'],
|
||||
[
|
||||
'query' => '{invalid}'
|
||||
],
|
||||
[
|
||||
'query' => '{f1,fieldWithSafeException}'
|
||||
],
|
||||
[
|
||||
'query' => '
|
||||
'query' => '
|
||||
query ($a: String!, $b: String!) {
|
||||
a: fieldWithArg(arg: $a)
|
||||
b: fieldWithArg(arg: $b)
|
||||
}
|
||||
',
|
||||
'variables' => ['a' => 'a', 'b' => 'b'],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$result = $this->executeBatchedQuery($batch);
|
||||
|
||||
$expected = [
|
||||
[
|
||||
'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']]
|
||||
'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']],
|
||||
],
|
||||
[
|
||||
'data' => [
|
||||
@ -440,15 +490,15 @@ class QueryExecutionTest extends ServerTestCase
|
||||
'fieldWithSafeException' => null
|
||||
],
|
||||
'errors' => [
|
||||
['message' => 'This is the exception we want']
|
||||
]
|
||||
['message' => 'This is the exception we want'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'data' => [
|
||||
'a' => 'a',
|
||||
'b' => 'b'
|
||||
]
|
||||
]
|
||||
'b' => 'b',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected[0], $result[0]->toArray());
|
||||
@ -459,15 +509,9 @@ class QueryExecutionTest extends ServerTestCase
|
||||
public function testDeferredsAreSharedAmongAllBatchedQueries() : void
|
||||
{
|
||||
$batch = [
|
||||
[
|
||||
'query' => '{dfd(num: 1)}'
|
||||
],
|
||||
[
|
||||
'query' => '{dfd(num: 2)}'
|
||||
],
|
||||
[
|
||||
'query' => '{dfd(num: 3)}',
|
||||
]
|
||||
['query' => '{dfd(num: 1)}'],
|
||||
['query' => '{dfd(num: 2)}'],
|
||||
['query' => '{dfd(num: 3)}'],
|
||||
];
|
||||
|
||||
$calls = [];
|
||||
@ -476,13 +520,14 @@ class QueryExecutionTest extends ServerTestCase
|
||||
->setQueryBatching(true)
|
||||
->setRootValue('1')
|
||||
->setContext([
|
||||
'buffer' => function($num) use (&$calls) {
|
||||
$calls[] = "buffer: $num";
|
||||
'buffer' => function ($num) use (&$calls) {
|
||||
$calls[] = sprintf('buffer: %d', $num);
|
||||
},
|
||||
'load' => function ($num) use (&$calls) {
|
||||
$calls[] = sprintf('load: %d', $num);
|
||||
|
||||
return sprintf('loaded: %d', $num);
|
||||
},
|
||||
'load' => function($num) use (&$calls) {
|
||||
$calls[] = "load: $num";
|
||||
return "loaded: $num";
|
||||
}
|
||||
]);
|
||||
|
||||
$result = $this->executeBatchedQuery($batch);
|
||||
@ -499,19 +544,13 @@ class QueryExecutionTest extends ServerTestCase
|
||||
|
||||
$expected = [
|
||||
[
|
||||
'data' => [
|
||||
'dfd' => 'loaded: 1'
|
||||
]
|
||||
'data' => ['dfd' => 'loaded: 1'],
|
||||
],
|
||||
[
|
||||
'data' => [
|
||||
'dfd' => 'loaded: 2'
|
||||
]
|
||||
'data' => ['dfd' => 'loaded: 2'],
|
||||
],
|
||||
[
|
||||
'data' => [
|
||||
'dfd' => 'loaded: 3'
|
||||
]
|
||||
'data' => ['dfd' => 'loaded: 3'],
|
||||
],
|
||||
];
|
||||
|
||||
@ -522,7 +561,7 @@ class QueryExecutionTest extends ServerTestCase
|
||||
|
||||
public function testValidatesParamsBeforeExecution() : void
|
||||
{
|
||||
$op = OperationParams::create(['queryBad' => '{f1}']);
|
||||
$op = OperationParams::create(['queryBad' => '{f1}']);
|
||||
$helper = new Helper();
|
||||
$result = $helper->executeOperation($this->config, $op);
|
||||
$this->assertInstanceOf(ExecutionResult::class, $result);
|
||||
@ -546,10 +585,10 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$called = false;
|
||||
$params = $doc = $operationType = null;
|
||||
|
||||
$this->config->setContext(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
|
||||
$called = true;
|
||||
$params = $p;
|
||||
$doc = $d;
|
||||
$this->config->setContext(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
|
||||
$called = true;
|
||||
$params = $p;
|
||||
$doc = $d;
|
||||
$operationType = $o;
|
||||
});
|
||||
|
||||
@ -566,10 +605,10 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$called = false;
|
||||
$params = $doc = $operationType = null;
|
||||
|
||||
$this->config->setRootValue(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
|
||||
$called = true;
|
||||
$params = $p;
|
||||
$doc = $d;
|
||||
$this->config->setRootValue(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
|
||||
$called = true;
|
||||
$params = $p;
|
||||
$doc = $d;
|
||||
$operationType = $o;
|
||||
});
|
||||
|
||||
@ -584,20 +623,21 @@ class QueryExecutionTest extends ServerTestCase
|
||||
public function testAppliesErrorFormatter() : void
|
||||
{
|
||||
$called = false;
|
||||
$error = null;
|
||||
$this->config->setErrorFormatter(function($e) use (&$called, &$error) {
|
||||
$error = null;
|
||||
$this->config->setErrorFormatter(function ($e) use (&$called, &$error) {
|
||||
$called = true;
|
||||
$error = $e;
|
||||
$error = $e;
|
||||
|
||||
return ['test' => 'formatted'];
|
||||
});
|
||||
|
||||
$result = $this->executeQuery('{fieldWithSafeException}');
|
||||
$this->assertFalse($called);
|
||||
$formatted = $result->toArray();
|
||||
$expected = [
|
||||
$expected = [
|
||||
'errors' => [
|
||||
['test' => 'formatted']
|
||||
]
|
||||
['test' => 'formatted'],
|
||||
],
|
||||
];
|
||||
$this->assertTrue($called);
|
||||
$this->assertArraySubset($expected, $formatted);
|
||||
@ -605,28 +645,29 @@ class QueryExecutionTest extends ServerTestCase
|
||||
|
||||
// Assert debugging still works even with custom formatter
|
||||
$formatted = $result->toArray(Debug::INCLUDE_TRACE);
|
||||
$expected = [
|
||||
$expected = [
|
||||
'errors' => [
|
||||
[
|
||||
'test' => 'formatted',
|
||||
'trace' => []
|
||||
]
|
||||
]
|
||||
'test' => 'formatted',
|
||||
'trace' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertArraySubset($expected, $formatted);
|
||||
}
|
||||
|
||||
public function testAppliesErrorsHandler() : void
|
||||
{
|
||||
$called = false;
|
||||
$errors = null;
|
||||
$called = false;
|
||||
$errors = null;
|
||||
$formatter = null;
|
||||
$this->config->setErrorsHandler(function($e, $f) use (&$called, &$errors, &$formatter) {
|
||||
$called = true;
|
||||
$errors = $e;
|
||||
$this->config->setErrorsHandler(function ($e, $f) use (&$called, &$errors, &$formatter) {
|
||||
$called = true;
|
||||
$errors = $e;
|
||||
$formatter = $f;
|
||||
|
||||
return [
|
||||
['test' => 'handled']
|
||||
['test' => 'handled'],
|
||||
];
|
||||
});
|
||||
|
||||
@ -634,10 +675,10 @@ class QueryExecutionTest extends ServerTestCase
|
||||
|
||||
$this->assertFalse($called);
|
||||
$formatted = $result->toArray();
|
||||
$expected = [
|
||||
$expected = [
|
||||
'errors' => [
|
||||
['test' => 'handled']
|
||||
]
|
||||
['test' => 'handled'],
|
||||
],
|
||||
];
|
||||
$this->assertTrue($called);
|
||||
$this->assertArraySubset($expected, $formatted);
|
||||
@ -646,46 +687,4 @@ class QueryExecutionTest extends ServerTestCase
|
||||
$this->assertInternalType('callable', $formatter);
|
||||
$this->assertArraySubset($expected, $formatted);
|
||||
}
|
||||
|
||||
private function executePersistedQuery($queryId, $variables = null)
|
||||
{
|
||||
$op = OperationParams::create(['queryId' => $queryId, 'variables' => $variables]);
|
||||
$helper = new Helper();
|
||||
$result = $helper->executeOperation($this->config, $op);
|
||||
$this->assertInstanceOf(ExecutionResult::class, $result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function executeQuery($query, $variables = null, $readonly = false)
|
||||
{
|
||||
$op = OperationParams::create(['query' => $query, 'variables' => $variables], $readonly);
|
||||
$helper = new Helper();
|
||||
$result = $helper->executeOperation($this->config, $op);
|
||||
$this->assertInstanceOf(ExecutionResult::class, $result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function executeBatchedQuery(array $qs)
|
||||
{
|
||||
$batch = [];
|
||||
foreach ($qs as $params) {
|
||||
$batch[] = OperationParams::create($params);
|
||||
}
|
||||
$helper = new Helper();
|
||||
$result = $helper->executeBatch($this->config, $batch);
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(count($qs), $result);
|
||||
|
||||
foreach ($result as $index => $entry) {
|
||||
$this->assertInstanceOf(ExecutionResult::class, $entry, "Result at $index is not an instance of " . ExecutionResult::class);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function assertQueryResultEquals($expected, $query, $variables = null)
|
||||
{
|
||||
$result = $this->executeQuery($query, $variables);
|
||||
$this->assertArraySubset($expected, $result->toArray(true));
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Server\Helper;
|
||||
use GraphQL\Server\OperationParams;
|
||||
@ -9,15 +11,17 @@ use GraphQL\Server\RequestError;
|
||||
use GraphQL\Tests\Server\Psr7\PsrRequestStub;
|
||||
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
|
||||
class RequestParsingTest extends TestCase
|
||||
{
|
||||
public function testParsesGraphqlRequest() : void
|
||||
{
|
||||
$query = '{my query}';
|
||||
$query = '{my query}';
|
||||
$parsed = [
|
||||
'raw' => $this->parseRawRequest('application/graphql', $query),
|
||||
'psr' => $this->parsePsrRequest('application/graphql', $query)
|
||||
'psr' => $this->parsePsrRequest('application/graphql', $query),
|
||||
];
|
||||
|
||||
foreach ($parsed as $source => $parsedBody) {
|
||||
@ -26,20 +30,91 @@ class RequestParsingTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contentType
|
||||
* @param string $content
|
||||
*
|
||||
* @return OperationParams|OperationParams[]
|
||||
*/
|
||||
private function parseRawRequest($contentType, $content, string $method = 'POST')
|
||||
{
|
||||
$_SERVER['CONTENT_TYPE'] = $contentType;
|
||||
$_SERVER['REQUEST_METHOD'] = $method;
|
||||
|
||||
$helper = new Helper();
|
||||
|
||||
return $helper->parseHttpRequest(function () use ($content) {
|
||||
return $content;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contentType
|
||||
* @param string $content
|
||||
*
|
||||
* @return OperationParams|OperationParams[]
|
||||
*/
|
||||
private function parsePsrRequest($contentType, $content, string $method = 'POST')
|
||||
{
|
||||
$psrRequestBody = new PsrStreamStub();
|
||||
$psrRequestBody->content = $content;
|
||||
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->headers['content-type'] = [$contentType];
|
||||
$psrRequest->method = $method;
|
||||
$psrRequest->body = $psrRequestBody;
|
||||
|
||||
if ($contentType === 'application/json') {
|
||||
$parsedBody = json_decode($content, true);
|
||||
$parsedBody = $parsedBody === false ? null : $parsedBody;
|
||||
} else {
|
||||
$parsedBody = null;
|
||||
}
|
||||
|
||||
$psrRequest->parsedBody = $parsedBody;
|
||||
|
||||
$helper = new Helper();
|
||||
|
||||
return $helper->parsePsrRequest($psrRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OperationParams $params
|
||||
* @param string $query
|
||||
* @param string $queryId
|
||||
* @param mixed|null $variables
|
||||
* @param string $operation
|
||||
*/
|
||||
private function assertValidOperationParams(
|
||||
$params,
|
||||
$query,
|
||||
$queryId = null,
|
||||
$variables = null,
|
||||
$operation = null,
|
||||
$message = ''
|
||||
) {
|
||||
$this->assertInstanceOf(OperationParams::class, $params, $message);
|
||||
|
||||
$this->assertSame($query, $params->query, $message);
|
||||
$this->assertSame($queryId, $params->queryId, $message);
|
||||
$this->assertSame($variables, $params->variables, $message);
|
||||
$this->assertSame($operation, $params->operation, $message);
|
||||
}
|
||||
|
||||
public function testParsesUrlencodedRequest() : void
|
||||
{
|
||||
$query = '{my query}';
|
||||
$query = '{my query}';
|
||||
$variables = ['test' => 1, 'test2' => 2];
|
||||
$operation = 'op';
|
||||
|
||||
$post = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation
|
||||
$post = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation,
|
||||
];
|
||||
$parsed = [
|
||||
'raw' => $this->parseRawFormUrlencodedRequest($post),
|
||||
'psr' => $this->parsePsrFormUrlEncodedRequest($post)
|
||||
'psr' => $this->parsePsrFormUrlEncodedRequest($post),
|
||||
];
|
||||
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
@ -48,20 +123,53 @@ class RequestParsingTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $postValue
|
||||
* @return OperationParams|OperationParams[]
|
||||
*/
|
||||
private function parseRawFormUrlencodedRequest($postValue)
|
||||
{
|
||||
$_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_POST = $postValue;
|
||||
|
||||
$helper = new Helper();
|
||||
|
||||
return $helper->parseHttpRequest(function () {
|
||||
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $postValue
|
||||
* @return OperationParams[]|OperationParams
|
||||
*/
|
||||
private function parsePsrFormUrlEncodedRequest($postValue)
|
||||
{
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded'];
|
||||
$psrRequest->method = 'POST';
|
||||
$psrRequest->parsedBody = $postValue;
|
||||
|
||||
$helper = new Helper();
|
||||
|
||||
return $helper->parsePsrRequest($psrRequest);
|
||||
}
|
||||
|
||||
public function testParsesGetRequest() : void
|
||||
{
|
||||
$query = '{my query}';
|
||||
$query = '{my query}';
|
||||
$variables = ['test' => 1, 'test2' => 2];
|
||||
$operation = 'op';
|
||||
|
||||
$get = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation
|
||||
$get = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation,
|
||||
];
|
||||
$parsed = [
|
||||
'raw' => $this->parseRawGetRequest($get),
|
||||
'psr' => $this->parsePsrGetRequest($get)
|
||||
'psr' => $this->parsePsrGetRequest($get),
|
||||
];
|
||||
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
@ -70,20 +178,51 @@ class RequestParsingTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $getValue
|
||||
* @return OperationParams
|
||||
*/
|
||||
private function parseRawGetRequest($getValue)
|
||||
{
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$_GET = $getValue;
|
||||
|
||||
$helper = new Helper();
|
||||
|
||||
return $helper->parseHttpRequest(function () {
|
||||
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $getValue
|
||||
* @return OperationParams[]|OperationParams
|
||||
*/
|
||||
private function parsePsrGetRequest($getValue)
|
||||
{
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->method = 'GET';
|
||||
$psrRequest->queryParams = $getValue;
|
||||
|
||||
$helper = new Helper();
|
||||
|
||||
return $helper->parsePsrRequest($psrRequest);
|
||||
}
|
||||
|
||||
public function testParsesMultipartFormdataRequest() : void
|
||||
{
|
||||
$query = '{my query}';
|
||||
$query = '{my query}';
|
||||
$variables = ['test' => 1, 'test2' => 2];
|
||||
$operation = 'op';
|
||||
|
||||
$post = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation
|
||||
$post = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation,
|
||||
];
|
||||
$parsed = [
|
||||
'raw' => $this->parseRawMultipartFormdataRequest($post),
|
||||
'psr' => $this->parsePsrMultipartFormdataRequest($post)
|
||||
'psr' => $this->parsePsrMultipartFormdataRequest($post),
|
||||
];
|
||||
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
@ -92,20 +231,53 @@ class RequestParsingTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $postValue
|
||||
* @return OperationParams|OperationParams[]
|
||||
*/
|
||||
private function parseRawMultipartFormDataRequest($postValue)
|
||||
{
|
||||
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data; boundary=----FormBoundary';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_POST = $postValue;
|
||||
|
||||
$helper = new Helper();
|
||||
|
||||
return $helper->parseHttpRequest(function () {
|
||||
throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $postValue
|
||||
* @return OperationParams|OperationParams[]
|
||||
*/
|
||||
private function parsePsrMultipartFormDataRequest($postValue)
|
||||
{
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary'];
|
||||
$psrRequest->method = 'POST';
|
||||
$psrRequest->parsedBody = $postValue;
|
||||
|
||||
$helper = new Helper();
|
||||
|
||||
return $helper->parsePsrRequest($psrRequest);
|
||||
}
|
||||
|
||||
public function testParsesJSONRequest() : void
|
||||
{
|
||||
$query = '{my query}';
|
||||
$query = '{my query}';
|
||||
$variables = ['test' => 1, 'test2' => 2];
|
||||
$operation = 'op';
|
||||
|
||||
$body = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation
|
||||
$body = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation,
|
||||
];
|
||||
$parsed = [
|
||||
'raw' => $this->parseRawRequest('application/json', json_encode($body)),
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body))
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
|
||||
];
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
|
||||
@ -115,18 +287,18 @@ class RequestParsingTest extends TestCase
|
||||
|
||||
public function testParsesVariablesAsJSON() : void
|
||||
{
|
||||
$query = '{my query}';
|
||||
$query = '{my query}';
|
||||
$variables = ['test' => 1, 'test2' => 2];
|
||||
$operation = 'op';
|
||||
|
||||
$body = [
|
||||
'query' => $query,
|
||||
'variables' => json_encode($variables),
|
||||
'operationName' => $operation
|
||||
$body = [
|
||||
'query' => $query,
|
||||
'variables' => json_encode($variables),
|
||||
'operationName' => $operation,
|
||||
];
|
||||
$parsed = [
|
||||
'raw' => $this->parseRawRequest('application/json', json_encode($body)),
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body))
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
|
||||
];
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
|
||||
@ -136,14 +308,14 @@ class RequestParsingTest extends TestCase
|
||||
|
||||
public function testIgnoresInvalidVariablesJson() : void
|
||||
{
|
||||
$query = '{my query}';
|
||||
$query = '{my query}';
|
||||
$variables = '"some invalid json';
|
||||
$operation = 'op';
|
||||
|
||||
$body = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation
|
||||
$body = [
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation,
|
||||
];
|
||||
$parsed = [
|
||||
'raw' => $this->parseRawRequest('application/json', json_encode($body)),
|
||||
@ -157,27 +329,41 @@ class RequestParsingTest extends TestCase
|
||||
|
||||
public function testParsesBatchJSONRequest() : void
|
||||
{
|
||||
$body = [
|
||||
$body = [
|
||||
[
|
||||
'query' => '{my query}',
|
||||
'variables' => ['test' => 1, 'test2' => 2],
|
||||
'operationName' => 'op'
|
||||
'query' => '{my query}',
|
||||
'variables' => ['test' => 1, 'test2' => 2],
|
||||
'operationName' => 'op',
|
||||
],
|
||||
[
|
||||
'queryId' => 'my-query-id',
|
||||
'variables' => ['test' => 1, 'test2' => 2],
|
||||
'operationName' => 'op2'
|
||||
'queryId' => 'my-query-id',
|
||||
'variables' => ['test' => 1, 'test2' => 2],
|
||||
'operationName' => 'op2',
|
||||
],
|
||||
];
|
||||
$parsed = [
|
||||
'raw' => $this->parseRawRequest('application/json', json_encode($body)),
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body))
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
|
||||
];
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
$this->assertInternalType('array', $parsedBody, $method);
|
||||
$this->assertCount(2, $parsedBody, $method);
|
||||
$this->assertValidOperationParams($parsedBody[0], $body[0]['query'], null, $body[0]['variables'], $body[0]['operationName'], $method);
|
||||
$this->assertValidOperationParams($parsedBody[1], null, $body[1]['queryId'], $body[1]['variables'], $body[1]['operationName'], $method);
|
||||
$this->assertValidOperationParams(
|
||||
$parsedBody[0],
|
||||
$body[0]['query'],
|
||||
null,
|
||||
$body[0]['variables'],
|
||||
$body[0]['operationName'],
|
||||
$method
|
||||
);
|
||||
$this->assertValidOperationParams(
|
||||
$parsedBody[1],
|
||||
null,
|
||||
$body[1]['queryId'],
|
||||
$body[1]['variables'],
|
||||
$body[1]['operationName'],
|
||||
$method
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,8 +373,8 @@ class RequestParsingTest extends TestCase
|
||||
|
||||
$this->expectException(RequestError::class);
|
||||
$this->expectExceptionMessage('Could not parse JSON: Syntax error');
|
||||
$this->parseRawRequest('application/json', $body);
|
||||
}
|
||||
$this->parseRawRequest('application/json', $body);
|
||||
}
|
||||
|
||||
public function testFailsParsingInvalidRawJsonRequestPsr() : void
|
||||
{
|
||||
@ -196,7 +382,7 @@ class RequestParsingTest extends TestCase
|
||||
|
||||
$this->expectException(InvariantViolation::class);
|
||||
$this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "application/json" requests but got null');
|
||||
$this->parsePsrRequest('application/json', $body);
|
||||
$this->parsePsrRequest('application/json', $body);
|
||||
}
|
||||
|
||||
public function testFailsParsingNonPreParsedPsrRequest() : void
|
||||
@ -222,8 +408,8 @@ class RequestParsingTest extends TestCase
|
||||
|
||||
$this->expectException(RequestError::class);
|
||||
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
|
||||
$this->parseRawRequest('application/json', $body);
|
||||
}
|
||||
$this->parseRawRequest('application/json', $body);
|
||||
}
|
||||
|
||||
public function testFailsParsingNonArrayOrObjectJsonRequestPsr() : void
|
||||
{
|
||||
@ -231,13 +417,13 @@ class RequestParsingTest extends TestCase
|
||||
|
||||
$this->expectException(RequestError::class);
|
||||
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
|
||||
$this->parsePsrRequest('application/json', $body);
|
||||
}
|
||||
$this->parsePsrRequest('application/json', $body);
|
||||
}
|
||||
|
||||
public function testFailsParsingInvalidContentTypeRaw() : void
|
||||
{
|
||||
$contentType = 'not-supported-content-type';
|
||||
$body = 'test';
|
||||
$body = 'test';
|
||||
|
||||
$this->expectException(RequestError::class);
|
||||
$this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
|
||||
@ -247,194 +433,38 @@ class RequestParsingTest extends TestCase
|
||||
public function testFailsParsingInvalidContentTypePsr() : void
|
||||
{
|
||||
$contentType = 'not-supported-content-type';
|
||||
$body = 'test';
|
||||
$body = 'test';
|
||||
|
||||
$this->expectException(RequestError::class);
|
||||
$this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
|
||||
$this->parseRawRequest($contentType, $body);
|
||||
}
|
||||
$this->parseRawRequest($contentType, $body);
|
||||
}
|
||||
|
||||
public function testFailsWithMissingContentTypeRaw() : void
|
||||
{
|
||||
$this->expectException(RequestError::class);
|
||||
$this->expectExceptionMessage('Missing "Content-Type" header');
|
||||
$this->parseRawRequest(null, 'test');
|
||||
}
|
||||
$this->parseRawRequest(null, 'test');
|
||||
}
|
||||
|
||||
public function testFailsWithMissingContentTypePsr() : void
|
||||
{
|
||||
$this->expectException(RequestError::class);
|
||||
$this->expectExceptionMessage('Missing "Content-Type" header');
|
||||
$this->parsePsrRequest(null, 'test');
|
||||
$this->parsePsrRequest(null, 'test');
|
||||
}
|
||||
|
||||
public function testFailsOnMethodsOtherThanPostOrGetRaw() : void
|
||||
{
|
||||
$this->expectException(RequestError::class);
|
||||
$this->expectExceptionMessage('HTTP Method "PUT" is not supported');
|
||||
$this->parseRawRequest('application/json', json_encode([]), "PUT");
|
||||
$this->parseRawRequest('application/json', json_encode([]), 'PUT');
|
||||
}
|
||||
|
||||
public function testFailsOnMethodsOtherThanPostOrGetPsr() : void
|
||||
{
|
||||
$this->expectException(RequestError::class);
|
||||
$this->expectExceptionMessage('HTTP Method "PUT" is not supported');
|
||||
$this->parsePsrRequest('application/json', json_encode([]), "PUT");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contentType
|
||||
* @param string $content
|
||||
* @param $method
|
||||
*
|
||||
* @return OperationParams|OperationParams[]
|
||||
*/
|
||||
private function parseRawRequest($contentType, $content, $method = 'POST')
|
||||
{
|
||||
$_SERVER['CONTENT_TYPE'] = $contentType;
|
||||
$_SERVER['REQUEST_METHOD'] = $method;
|
||||
|
||||
$helper = new Helper();
|
||||
return $helper->parseHttpRequest(function() use ($content) {
|
||||
return $content;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contentType
|
||||
* @param string $content
|
||||
* @param $method
|
||||
*
|
||||
* @return OperationParams|OperationParams[]
|
||||
*/
|
||||
private function parsePsrRequest($contentType, $content, $method = 'POST')
|
||||
{
|
||||
$psrRequestBody = new PsrStreamStub();
|
||||
$psrRequestBody->content = $content;
|
||||
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->headers['content-type'] = [$contentType];
|
||||
$psrRequest->method = $method;
|
||||
$psrRequest->body = $psrRequestBody;
|
||||
|
||||
if ($contentType === 'application/json') {
|
||||
$parsedBody = json_decode($content, true);
|
||||
$parsedBody = $parsedBody === false ? null : $parsedBody;
|
||||
} else {
|
||||
$parsedBody = null;
|
||||
}
|
||||
|
||||
$psrRequest->parsedBody = $parsedBody;
|
||||
|
||||
$helper = new Helper();
|
||||
return $helper->parsePsrRequest($psrRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $postValue
|
||||
* @return OperationParams|OperationParams[]
|
||||
*/
|
||||
private function parseRawFormUrlencodedRequest($postValue)
|
||||
{
|
||||
$_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_POST = $postValue;
|
||||
|
||||
$helper = new Helper();
|
||||
return $helper->parseHttpRequest(function() {
|
||||
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $postValue
|
||||
* @return array|Helper
|
||||
*/
|
||||
private function parsePsrFormUrlEncodedRequest($postValue)
|
||||
{
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded'];
|
||||
$psrRequest->method = 'POST';
|
||||
$psrRequest->parsedBody = $postValue;
|
||||
|
||||
$helper = new Helper();
|
||||
return $helper->parsePsrRequest($psrRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $postValue
|
||||
* @return OperationParams|OperationParams[]
|
||||
*/
|
||||
private function parseRawMultipartFormDataRequest($postValue)
|
||||
{
|
||||
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data; boundary=----FormBoundary';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_POST = $postValue;
|
||||
|
||||
$helper = new Helper();
|
||||
return $helper->parseHttpRequest(function() {
|
||||
throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $postValue
|
||||
* @return array|Helper
|
||||
*/
|
||||
private function parsePsrMultipartFormDataRequest($postValue)
|
||||
{
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary'];
|
||||
$psrRequest->method = 'POST';
|
||||
$psrRequest->parsedBody = $postValue;
|
||||
|
||||
$helper = new Helper();
|
||||
return $helper->parsePsrRequest($psrRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $getValue
|
||||
* @return OperationParams
|
||||
*/
|
||||
private function parseRawGetRequest($getValue)
|
||||
{
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$_GET = $getValue;
|
||||
|
||||
$helper = new Helper();
|
||||
return $helper->parseHttpRequest(function() {
|
||||
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $getValue
|
||||
* @return array|Helper
|
||||
*/
|
||||
private function parsePsrGetRequest($getValue)
|
||||
{
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->method = 'GET';
|
||||
$psrRequest->queryParams = $getValue;
|
||||
|
||||
$helper = new Helper();
|
||||
return $helper->parsePsrRequest($psrRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OperationParams $params
|
||||
* @param string $query
|
||||
* @param string $queryId
|
||||
* @param array $variables
|
||||
* @param string $operation
|
||||
*/
|
||||
private function assertValidOperationParams($params, $query, $queryId = null, $variables = null, $operation = null, $message = '')
|
||||
{
|
||||
$this->assertInstanceOf(OperationParams::class, $params, $message);
|
||||
|
||||
$this->assertSame($query, $params->query, $message);
|
||||
$this->assertSame($queryId, $params->queryId, $message);
|
||||
$this->assertSame($variables, $params->variables, $message);
|
||||
$this->assertSame($operation, $params->operation, $message);
|
||||
$this->parsePsrRequest('application/json', json_encode([]), 'PUT');
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server;
|
||||
|
||||
use GraphQL\Server\Helper;
|
||||
@ -9,28 +12,35 @@ class RequestValidationTest extends TestCase
|
||||
{
|
||||
public function testSimpleRequestShouldValidate() : void
|
||||
{
|
||||
$query = '{my q}';
|
||||
$query = '{my q}';
|
||||
$variables = ['a' => 'b', 'c' => 'd'];
|
||||
$operation = 'op';
|
||||
|
||||
$parsedBody = OperationParams::create([
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation,
|
||||
]);
|
||||
|
||||
$this->assertValid($parsedBody);
|
||||
}
|
||||
|
||||
private function assertValid($parsedRequest)
|
||||
{
|
||||
$helper = new Helper();
|
||||
$errors = $helper->validateOperationParams($parsedRequest);
|
||||
$this->assertEmpty($errors, isset($errors[0]) ? $errors[0]->getMessage() : '');
|
||||
}
|
||||
|
||||
public function testRequestWithQueryIdShouldValidate() : void
|
||||
{
|
||||
$queryId = 'some-query-id';
|
||||
$queryId = 'some-query-id';
|
||||
$variables = ['a' => 'b', 'c' => 'd'];
|
||||
$operation = 'op';
|
||||
|
||||
$parsedBody = OperationParams::create([
|
||||
'queryId' => $queryId,
|
||||
'variables' => $variables,
|
||||
'queryId' => $queryId,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation,
|
||||
]);
|
||||
|
||||
@ -40,7 +50,7 @@ class RequestValidationTest extends TestCase
|
||||
public function testRequiresQueryOrQueryId() : void
|
||||
{
|
||||
$parsedBody = OperationParams::create([
|
||||
'variables' => ['foo' => 'bar'],
|
||||
'variables' => ['foo' => 'bar'],
|
||||
'operationName' => 'op',
|
||||
]);
|
||||
|
||||
@ -50,10 +60,21 @@ class RequestValidationTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
private function assertInputError($parsedRequest, $expectedMessage)
|
||||
{
|
||||
$helper = new Helper();
|
||||
$errors = $helper->validateOperationParams($parsedRequest);
|
||||
if (! empty($errors[0])) {
|
||||
$this->assertEquals($expectedMessage, $errors[0]->getMessage());
|
||||
} else {
|
||||
$this->fail('Expected error not returned');
|
||||
}
|
||||
}
|
||||
|
||||
public function testFailsWhenBothQueryAndQueryIdArePresent() : void
|
||||
{
|
||||
$parsedBody = OperationParams::create([
|
||||
'query' => '{my query}',
|
||||
'query' => '{my query}',
|
||||
'queryId' => 'my-query-id',
|
||||
]);
|
||||
|
||||
@ -66,7 +87,7 @@ class RequestValidationTest extends TestCase
|
||||
public function testFailsWhenQueryParameterIsNotString() : void
|
||||
{
|
||||
$parsedBody = OperationParams::create([
|
||||
'query' => ['t' => '{my query}']
|
||||
'query' => ['t' => '{my query}'],
|
||||
]);
|
||||
|
||||
$this->assertInputError(
|
||||
@ -78,7 +99,7 @@ class RequestValidationTest extends TestCase
|
||||
public function testFailsWhenQueryIdParameterIsNotString() : void
|
||||
{
|
||||
$parsedBody = OperationParams::create([
|
||||
'queryId' => ['t' => '{my query}']
|
||||
'queryId' => ['t' => '{my query}'],
|
||||
]);
|
||||
|
||||
$this->assertInputError(
|
||||
@ -90,8 +111,8 @@ class RequestValidationTest extends TestCase
|
||||
public function testFailsWhenOperationParameterIsNotString() : void
|
||||
{
|
||||
$parsedBody = OperationParams::create([
|
||||
'query' => '{my query}',
|
||||
'operationName' => []
|
||||
'query' => '{my query}',
|
||||
'operationName' => [],
|
||||
]);
|
||||
|
||||
$this->assertInputError(
|
||||
@ -105,17 +126,17 @@ class RequestValidationTest extends TestCase
|
||||
*/
|
||||
public function testIgnoresNullAndEmptyStringVariables() : void
|
||||
{
|
||||
$query = '{my q}';
|
||||
$query = '{my q}';
|
||||
$parsedBody = OperationParams::create([
|
||||
'query' => $query,
|
||||
'variables' => null
|
||||
'query' => $query,
|
||||
'variables' => null,
|
||||
]);
|
||||
$this->assertValid($parsedBody);
|
||||
|
||||
$variables = "";
|
||||
$variables = '';
|
||||
$parsedBody = OperationParams::create([
|
||||
'query' => $query,
|
||||
'variables' => $variables
|
||||
'query' => $query,
|
||||
'variables' => $variables,
|
||||
]);
|
||||
$this->assertValid($parsedBody);
|
||||
}
|
||||
@ -123,8 +144,8 @@ class RequestValidationTest extends TestCase
|
||||
public function testFailsWhenVariablesParameterIsNotObject() : void
|
||||
{
|
||||
$parsedBody = OperationParams::create([
|
||||
'query' => '{my query}',
|
||||
'variables' => 0
|
||||
'query' => '{my query}',
|
||||
'variables' => 0,
|
||||
]);
|
||||
|
||||
$this->assertInputError(
|
||||
@ -132,22 +153,4 @@ class RequestValidationTest extends TestCase
|
||||
'GraphQL Request parameter "variables" must be object or JSON string parsed to object, but got 0'
|
||||
);
|
||||
}
|
||||
|
||||
private function assertValid($parsedRequest)
|
||||
{
|
||||
$helper = new Helper();
|
||||
$errors = $helper->validateOperationParams($parsedRequest);
|
||||
$this->assertEmpty($errors, isset($errors[0]) ? $errors[0]->getMessage() : '');
|
||||
}
|
||||
|
||||
private function assertInputError($parsedRequest, $expectedMessage)
|
||||
{
|
||||
$helper = new Helper();
|
||||
$errors = $helper->validateOperationParams($parsedRequest);
|
||||
if (!empty($errors[0])) {
|
||||
$this->assertEquals($expectedMessage, $errors[0]->getMessage());
|
||||
} else {
|
||||
$this->fail('Expected error not returned');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Server\ServerConfig;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ServerConfigTest extends TestCase
|
||||
@ -14,17 +17,17 @@ class ServerConfigTest extends TestCase
|
||||
public function testDefaults() : void
|
||||
{
|
||||
$config = ServerConfig::create();
|
||||
$this->assertEquals(null, $config->getSchema());
|
||||
$this->assertEquals(null, $config->getContext());
|
||||
$this->assertEquals(null, $config->getRootValue());
|
||||
$this->assertEquals(null, $config->getErrorFormatter());
|
||||
$this->assertEquals(null, $config->getErrorsHandler());
|
||||
$this->assertEquals(null, $config->getPromiseAdapter());
|
||||
$this->assertEquals(null, $config->getValidationRules());
|
||||
$this->assertEquals(null, $config->getFieldResolver());
|
||||
$this->assertEquals(null, $config->getPersistentQueryLoader());
|
||||
$this->assertEquals(false, $config->getDebug());
|
||||
$this->assertEquals(false, $config->getQueryBatching());
|
||||
$this->assertNull($config->getSchema());
|
||||
$this->assertNull($config->getContext());
|
||||
$this->assertNull($config->getRootValue());
|
||||
$this->assertNull($config->getErrorFormatter());
|
||||
$this->assertNull($config->getErrorsHandler());
|
||||
$this->assertNull($config->getPromiseAdapter());
|
||||
$this->assertNull($config->getValidationRules());
|
||||
$this->assertNull($config->getFieldResolver());
|
||||
$this->assertNull($config->getPersistentQueryLoader());
|
||||
$this->assertFalse($config->getDebug());
|
||||
$this->assertFalse($config->getQueryBatching());
|
||||
}
|
||||
|
||||
public function testAllowsSettingSchema() : void
|
||||
@ -70,7 +73,8 @@ class ServerConfigTest extends TestCase
|
||||
{
|
||||
$config = ServerConfig::create();
|
||||
|
||||
$formatter = function() {};
|
||||
$formatter = function () {
|
||||
};
|
||||
$config->setErrorFormatter($formatter);
|
||||
$this->assertSame($formatter, $config->getErrorFormatter());
|
||||
|
||||
@ -83,7 +87,8 @@ class ServerConfigTest extends TestCase
|
||||
{
|
||||
$config = ServerConfig::create();
|
||||
|
||||
$handler = function() {};
|
||||
$handler = function () {
|
||||
};
|
||||
$config->setErrorsHandler($handler);
|
||||
$this->assertSame($handler, $config->getErrorsHandler());
|
||||
|
||||
@ -113,11 +118,17 @@ class ServerConfigTest extends TestCase
|
||||
$config->setValidationRules($rules);
|
||||
$this->assertSame($rules, $config->getValidationRules());
|
||||
|
||||
$rules = [function() {}];
|
||||
$rules = [function () {
|
||||
},
|
||||
];
|
||||
$config->setValidationRules($rules);
|
||||
$this->assertSame($rules, $config->getValidationRules());
|
||||
|
||||
$rules = function() {return [function() {}];};
|
||||
$rules = function () {
|
||||
return [function () {
|
||||
},
|
||||
];
|
||||
};
|
||||
$config->setValidationRules($rules);
|
||||
$this->assertSame($rules, $config->getValidationRules());
|
||||
}
|
||||
@ -126,7 +137,8 @@ class ServerConfigTest extends TestCase
|
||||
{
|
||||
$config = ServerConfig::create();
|
||||
|
||||
$resolver = function() {};
|
||||
$resolver = function () {
|
||||
};
|
||||
$config->setFieldResolver($resolver);
|
||||
$this->assertSame($resolver, $config->getFieldResolver());
|
||||
|
||||
@ -139,7 +151,8 @@ class ServerConfigTest extends TestCase
|
||||
{
|
||||
$config = ServerConfig::create();
|
||||
|
||||
$loader = function() {};
|
||||
$loader = function () {
|
||||
};
|
||||
$config->setPersistentQueryLoader($loader);
|
||||
$this->assertSame($loader, $config->getPersistentQueryLoader());
|
||||
|
||||
@ -153,27 +166,32 @@ class ServerConfigTest extends TestCase
|
||||
$config = ServerConfig::create();
|
||||
|
||||
$config->setDebug(true);
|
||||
$this->assertSame(true, $config->getDebug());
|
||||
$this->assertTrue($config->getDebug());
|
||||
|
||||
$config->setDebug(false);
|
||||
$this->assertSame(false, $config->getDebug());
|
||||
$this->assertFalse($config->getDebug());
|
||||
}
|
||||
|
||||
public function testAcceptsArray() : void
|
||||
{
|
||||
$arr = [
|
||||
'schema' => new \GraphQL\Type\Schema([
|
||||
'query' => new ObjectType(['name' => 't', 'fields' => ['a' => Type::string()]])
|
||||
'schema' => new Schema([
|
||||
'query' => new ObjectType(['name' => 't', 'fields' => ['a' => Type::string()]]),
|
||||
]),
|
||||
'context' => new \stdClass(),
|
||||
'rootValue' => new \stdClass(),
|
||||
'errorFormatter' => function() {},
|
||||
'promiseAdapter' => new SyncPromiseAdapter(),
|
||||
'validationRules' => [function() {}],
|
||||
'fieldResolver' => function() {},
|
||||
'persistentQueryLoader' => function() {},
|
||||
'debug' => true,
|
||||
'queryBatching' => true,
|
||||
'context' => new \stdClass(),
|
||||
'rootValue' => new \stdClass(),
|
||||
'errorFormatter' => function () {
|
||||
},
|
||||
'promiseAdapter' => new SyncPromiseAdapter(),
|
||||
'validationRules' => [function () {
|
||||
},
|
||||
],
|
||||
'fieldResolver' => function () {
|
||||
},
|
||||
'persistentQueryLoader' => function () {
|
||||
},
|
||||
'debug' => true,
|
||||
'queryBatching' => true,
|
||||
];
|
||||
|
||||
$config = ServerConfig::create($arr);
|
||||
@ -186,15 +204,13 @@ class ServerConfigTest extends TestCase
|
||||
$this->assertSame($arr['validationRules'], $config->getValidationRules());
|
||||
$this->assertSame($arr['fieldResolver'], $config->getFieldResolver());
|
||||
$this->assertSame($arr['persistentQueryLoader'], $config->getPersistentQueryLoader());
|
||||
$this->assertSame(true, $config->getDebug());
|
||||
$this->assertSame(true, $config->getQueryBatching());
|
||||
$this->assertTrue($config->getDebug());
|
||||
$this->assertTrue($config->getQueryBatching());
|
||||
}
|
||||
|
||||
public function testThrowsOnInvalidArrayKey() : void
|
||||
{
|
||||
$arr = [
|
||||
'missingKey' => 'value'
|
||||
];
|
||||
$arr = ['missingKey' => 'value'];
|
||||
|
||||
$this->expectException(InvariantViolation::class);
|
||||
$this->expectExceptionMessage('Unknown server config option "missingKey"');
|
||||
@ -204,7 +220,7 @@ class ServerConfigTest extends TestCase
|
||||
|
||||
public function testInvalidValidationRules() : void
|
||||
{
|
||||
$rules = new \stdClass();
|
||||
$rules = new \stdClass();
|
||||
$config = ServerConfig::create();
|
||||
|
||||
$this->expectException(InvariantViolation::class);
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Server;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server;
|
||||
|
||||
use GraphQL\Deferred;
|
||||
use GraphQL\Error\ClientAware;
|
||||
@ -9,31 +11,36 @@ use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function trigger_error;
|
||||
use const E_USER_DEPRECATED;
|
||||
use const E_USER_NOTICE;
|
||||
use const E_USER_WARNING;
|
||||
|
||||
abstract class ServerTestCase extends TestCase
|
||||
{
|
||||
protected function buildSchema()
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'f1' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function($root, $args, $context, $info) {
|
||||
'f1' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($root, $args, $context, $info) {
|
||||
return $info->fieldName;
|
||||
}
|
||||
},
|
||||
],
|
||||
'fieldWithPhpError' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function($root, $args, $context, $info) {
|
||||
'fieldWithPhpError' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($root, $args, $context, $info) {
|
||||
trigger_error('deprecated', E_USER_DEPRECATED);
|
||||
trigger_error('notice', E_USER_NOTICE);
|
||||
trigger_error('warning', E_USER_WARNING);
|
||||
$a = [];
|
||||
$a['test']; // should produce PHP notice
|
||||
|
||||
return $info->fieldName;
|
||||
}
|
||||
},
|
||||
],
|
||||
'fieldWithSafeException' => [
|
||||
'type' => Type::string(),
|
||||
@ -48,54 +55,56 @@ abstract class ServerTestCase extends TestCase
|
||||
}
|
||||
],
|
||||
'testContextAndRootValue' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function($root, $args, $context, $info) {
|
||||
'type' => Type::string(),
|
||||
'resolve' => function ($root, $args, $context, $info) {
|
||||
$context->testedRootValue = $root;
|
||||
|
||||
return $info->fieldName;
|
||||
}
|
||||
},
|
||||
],
|
||||
'fieldWithArg' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'fieldWithArg' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'arg' => [
|
||||
'type' => Type::nonNull(Type::string())
|
||||
'type' => Type::nonNull(Type::string()),
|
||||
],
|
||||
],
|
||||
'resolve' => function($root, $args) {
|
||||
'resolve' => function ($root, $args) {
|
||||
return $args['arg'];
|
||||
}
|
||||
},
|
||||
],
|
||||
'dfd' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'dfd' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'num' => [
|
||||
'type' => Type::nonNull(Type::int())
|
||||
'type' => Type::nonNull(Type::int()),
|
||||
],
|
||||
],
|
||||
'resolve' => function($root, $args, $context) {
|
||||
'resolve' => function ($root, $args, $context) {
|
||||
$context['buffer']($args['num']);
|
||||
|
||||
return new Deferred(function() use ($args, $context) {
|
||||
return new Deferred(function () use ($args, $context) {
|
||||
return $context['load']($args['num']);
|
||||
});
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
'mutation' => new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'name' => 'Mutation',
|
||||
'fields' => [
|
||||
'm1' => [
|
||||
'type' => new ObjectType([
|
||||
'name' => 'TestMutation',
|
||||
'name' => 'TestMutation',
|
||||
'fields' => [
|
||||
'result' => Type::string()
|
||||
]
|
||||
])
|
||||
]
|
||||
]
|
||||
])
|
||||
'result' => Type::string(),
|
||||
],
|
||||
]),
|
||||
],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Server;
|
||||
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
@ -6,72 +9,75 @@ use GraphQL\Server\Helper;
|
||||
use GraphQL\Server\ServerConfig;
|
||||
use GraphQL\Server\StandardServer;
|
||||
use GraphQL\Tests\Server\Psr7\PsrRequestStub;
|
||||
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
|
||||
use function json_encode;
|
||||
|
||||
class StandardServerTest extends ServerTestCase
|
||||
{
|
||||
/**
|
||||
* @var ServerConfig
|
||||
*/
|
||||
/** @var ServerConfig */
|
||||
private $config;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$schema = $this->buildSchema();
|
||||
$schema = $this->buildSchema();
|
||||
$this->config = ServerConfig::create()
|
||||
->setSchema($schema);
|
||||
}
|
||||
|
||||
public function testSimpleRequestExecutionWithOutsideParsing() : void
|
||||
{
|
||||
$body = json_encode([
|
||||
'query' => '{f1}'
|
||||
]);
|
||||
$body = json_encode(['query' => '{f1}']);
|
||||
|
||||
$parsedBody = $this->parseRawRequest('application/json', $body);
|
||||
$server = new StandardServer($this->config);
|
||||
$server = new StandardServer($this->config);
|
||||
|
||||
$result = $server->executeRequest($parsedBody);
|
||||
$result = $server->executeRequest($parsedBody);
|
||||
$expected = [
|
||||
'data' => [
|
||||
'f1' => 'f1',
|
||||
]
|
||||
'data' => ['f1' => 'f1'],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result->toArray(true));
|
||||
}
|
||||
|
||||
private function parseRawRequest($contentType, $content, $method = 'POST')
|
||||
{
|
||||
$_SERVER['CONTENT_TYPE'] = $contentType;
|
||||
$_SERVER['REQUEST_METHOD'] = $method;
|
||||
|
||||
$helper = new Helper();
|
||||
|
||||
return $helper->parseHttpRequest(function () use ($content) {
|
||||
return $content;
|
||||
});
|
||||
}
|
||||
|
||||
public function testSimplePsrRequestExecution() : void
|
||||
{
|
||||
$body = [
|
||||
'query' => '{f1}'
|
||||
];
|
||||
$body = ['query' => '{f1}'];
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'f1' => 'f1'
|
||||
]
|
||||
'data' => ['f1' => 'f1'],
|
||||
];
|
||||
|
||||
$request = $this->preparePsrRequest('application/json', $body);
|
||||
$this->assertPsrRequestEquals($expected, $request);
|
||||
}
|
||||
|
||||
public function testMultipleOperationPsrRequestExecution() : void
|
||||
private function preparePsrRequest($contentType, $parsedBody)
|
||||
{
|
||||
$body = [
|
||||
'query' => 'query firstOp {fieldWithPhpError} query secondOp {f1}',
|
||||
'operationName' => 'secondOp'
|
||||
];
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->headers['content-type'] = [$contentType];
|
||||
$psrRequest->method = 'POST';
|
||||
$psrRequest->parsedBody = $parsedBody;
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'f1' => 'f1'
|
||||
]
|
||||
];
|
||||
return $psrRequest;
|
||||
}
|
||||
|
||||
$request = $this->preparePsrRequest('application/json', $body);
|
||||
$this->assertPsrRequestEquals($expected, $request);
|
||||
private function assertPsrRequestEquals($expected, $request)
|
||||
{
|
||||
$result = $this->executePsrRequest($request);
|
||||
$this->assertArraySubset($expected, $result->toArray(true));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function executePsrRequest($psrRequest)
|
||||
@ -79,33 +85,22 @@ class StandardServerTest extends ServerTestCase
|
||||
$server = new StandardServer($this->config);
|
||||
$result = $server->executePsrRequest($psrRequest);
|
||||
$this->assertInstanceOf(ExecutionResult::class, $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function assertPsrRequestEquals($expected, $request)
|
||||
public function testMultipleOperationPsrRequestExecution() : void
|
||||
{
|
||||
$result = $this->executePsrRequest($request);
|
||||
$this->assertArraySubset($expected, $result->toArray(true));
|
||||
return $result;
|
||||
}
|
||||
$body = [
|
||||
'query' => 'query firstOp {fieldWithPhpError} query secondOp {f1}',
|
||||
'operationName' => 'secondOp',
|
||||
];
|
||||
|
||||
private function preparePsrRequest($contentType, $parsedBody)
|
||||
{
|
||||
$psrRequest = new PsrRequestStub();
|
||||
$psrRequest->headers['content-type'] = [$contentType];
|
||||
$psrRequest->method = 'POST';
|
||||
$psrRequest->parsedBody = $parsedBody;
|
||||
return $psrRequest;
|
||||
}
|
||||
$expected = [
|
||||
'data' => ['f1' => 'f1'],
|
||||
];
|
||||
|
||||
private function parseRawRequest($contentType, $content, $method = 'POST')
|
||||
{
|
||||
$_SERVER['CONTENT_TYPE'] = $contentType;
|
||||
$_SERVER['REQUEST_METHOD'] = $method;
|
||||
|
||||
$helper = new Helper();
|
||||
return $helper->parseHttpRequest(function() use ($content) {
|
||||
return $content;
|
||||
});
|
||||
$request = $this->preparePsrRequest('application/json', $body);
|
||||
$this->assertPsrRequestEquals($expected, $request);
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
use function array_map;
|
||||
|
||||
class StarWarsData
|
||||
{
|
||||
private static function luke()
|
||||
/**
|
||||
* Helper function to get a character by ID.
|
||||
*/
|
||||
public static function getCharacter($id)
|
||||
{
|
||||
return [
|
||||
'id' => '1000',
|
||||
'name' => 'Luke Skywalker',
|
||||
'friends' => ['1002', '1003', '2000', '2001'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'homePlanet' => 'Tatooine',
|
||||
];
|
||||
$humans = self::humans();
|
||||
$droids = self::droids();
|
||||
if (isset($humans[$id])) {
|
||||
return $humans[$id];
|
||||
}
|
||||
if (isset($droids[$id])) {
|
||||
return $droids[$id];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function vader()
|
||||
{
|
||||
return [
|
||||
'id' => '1001',
|
||||
'name' => 'Darth Vader',
|
||||
'friends' => ['1004'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'homePlanet' => 'Tatooine',
|
||||
];
|
||||
}
|
||||
|
||||
private static function han()
|
||||
{
|
||||
return [
|
||||
'id' => '1002',
|
||||
'name' => 'Han Solo',
|
||||
'friends' => ['1000', '1003', '2001'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
];
|
||||
}
|
||||
|
||||
private static function leia()
|
||||
{
|
||||
return [
|
||||
'id' => '1003',
|
||||
'name' => 'Leia Organa',
|
||||
'friends' => ['1000', '1002', '2000', '2001'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'homePlanet' => 'Alderaan',
|
||||
];
|
||||
}
|
||||
|
||||
private static function tarkin()
|
||||
{
|
||||
return [
|
||||
'id' => '1004',
|
||||
'name' => 'Wilhuff Tarkin',
|
||||
'friends' => ['1001'],
|
||||
'appearsIn' => [4],
|
||||
];
|
||||
}
|
||||
|
||||
static function humans()
|
||||
public static function humans()
|
||||
{
|
||||
return [
|
||||
'1000' => self::luke(),
|
||||
@ -67,13 +36,74 @@ class StarWarsData
|
||||
];
|
||||
}
|
||||
|
||||
private static function luke()
|
||||
{
|
||||
return [
|
||||
'id' => '1000',
|
||||
'name' => 'Luke Skywalker',
|
||||
'friends' => ['1002', '1003', '2000', '2001'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'homePlanet' => 'Tatooine',
|
||||
];
|
||||
}
|
||||
|
||||
private static function vader()
|
||||
{
|
||||
return [
|
||||
'id' => '1001',
|
||||
'name' => 'Darth Vader',
|
||||
'friends' => ['1004'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'homePlanet' => 'Tatooine',
|
||||
];
|
||||
}
|
||||
|
||||
private static function han()
|
||||
{
|
||||
return [
|
||||
'id' => '1002',
|
||||
'name' => 'Han Solo',
|
||||
'friends' => ['1000', '1003', '2001'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
];
|
||||
}
|
||||
|
||||
private static function leia()
|
||||
{
|
||||
return [
|
||||
'id' => '1003',
|
||||
'name' => 'Leia Organa',
|
||||
'friends' => ['1000', '1002', '2000', '2001'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'homePlanet' => 'Alderaan',
|
||||
];
|
||||
}
|
||||
|
||||
private static function tarkin()
|
||||
{
|
||||
return [
|
||||
'id' => '1004',
|
||||
'name' => 'Wilhuff Tarkin',
|
||||
'friends' => ['1001'],
|
||||
'appearsIn' => [4],
|
||||
];
|
||||
}
|
||||
|
||||
public static function droids()
|
||||
{
|
||||
return [
|
||||
'2000' => self::threepio(),
|
||||
'2001' => self::artoo(),
|
||||
];
|
||||
}
|
||||
|
||||
private static function threepio()
|
||||
{
|
||||
return [
|
||||
'id' => '2000',
|
||||
'name' => 'C-3PO',
|
||||
'friends' => ['1000', '1002', '1003', '2001'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'id' => '2000',
|
||||
'name' => 'C-3PO',
|
||||
'friends' => ['1000', '1002', '1003', '2001'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'primaryFunction' => 'Protocol',
|
||||
];
|
||||
}
|
||||
@ -82,81 +112,60 @@ class StarWarsData
|
||||
* We export artoo directly because the schema returns him
|
||||
* from a root field, and hence needs to reference him.
|
||||
*/
|
||||
static function artoo()
|
||||
private static function artoo()
|
||||
{
|
||||
return [
|
||||
|
||||
'id' => '2001',
|
||||
'name' => 'R2-D2',
|
||||
'friends' => ['1000', '1002', '1003'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'id' => '2001',
|
||||
'name' => 'R2-D2',
|
||||
'friends' => ['1000', '1002', '1003'],
|
||||
'appearsIn' => [4, 5, 6],
|
||||
'primaryFunction' => 'Astromech',
|
||||
];
|
||||
}
|
||||
|
||||
static function droids()
|
||||
{
|
||||
return [
|
||||
'2000' => self::threepio(),
|
||||
'2001' => self::artoo(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get a character by ID.
|
||||
*/
|
||||
static function getCharacter($id)
|
||||
{
|
||||
$humans = self::humans();
|
||||
$droids = self::droids();
|
||||
if (isset($humans[$id])) {
|
||||
return $humans[$id];
|
||||
}
|
||||
if (isset($droids[$id])) {
|
||||
return $droids[$id];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows us to query for a character's friends.
|
||||
*/
|
||||
static function getFriends($character)
|
||||
public static function getFriends($character)
|
||||
{
|
||||
return array_map([__CLASS__, 'getCharacter'], $character['friends']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $episode
|
||||
* @return array
|
||||
* @param int $episode
|
||||
* @return mixed[]
|
||||
*/
|
||||
static function getHero($episode)
|
||||
public static function getHero($episode)
|
||||
{
|
||||
if ($episode === 5) {
|
||||
// Luke is the hero of Episode V.
|
||||
return self::luke();
|
||||
}
|
||||
|
||||
// Artoo is the hero otherwise.
|
||||
return self::artoo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @param string $id
|
||||
* @return mixed|null
|
||||
*/
|
||||
static function getHuman($id)
|
||||
public static function getHuman($id)
|
||||
{
|
||||
$humans = self::humans();
|
||||
return isset($humans[$id]) ? $humans[$id] : null;
|
||||
|
||||
return $humans[$id] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @param string $id
|
||||
* @return mixed|null
|
||||
*/
|
||||
static function getDroid($id)
|
||||
public static function getDroid($id)
|
||||
{
|
||||
$droids = self::droids();
|
||||
return isset($droids[$id]) ? $droids[$id] : null;
|
||||
|
||||
return $droids[$id] ?? null;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
use GraphQL\GraphQL;
|
||||
@ -14,7 +17,7 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
*/
|
||||
public function testAllowsQueryingTheSchemaForTypes() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query IntrospectionTypeQuery {
|
||||
__schema {
|
||||
types {
|
||||
@ -44,18 +47,26 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
['name' => '__EnumValue'],
|
||||
['name' => '__Directive'],
|
||||
['name' => '__DirectiveLocation'],
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a query and the expected response.
|
||||
*/
|
||||
private function assertValidQuery($query, $expected) : void
|
||||
{
|
||||
$this->assertEquals(['data' => $expected], GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('Allows querying the schema for query type')
|
||||
*/
|
||||
public function testAllowsQueryingTheSchemaForQueryType() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query IntrospectionQueryTypeQuery {
|
||||
__schema {
|
||||
queryType {
|
||||
@ -66,10 +77,8 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
';
|
||||
$expected = [
|
||||
'__schema' => [
|
||||
'queryType' => [
|
||||
'name' => 'Query'
|
||||
],
|
||||
]
|
||||
'queryType' => ['name' => 'Query'],
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -79,7 +88,7 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
*/
|
||||
public function testAllowsQueryingTheSchemaForASpecificType() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query IntrospectionDroidTypeQuery {
|
||||
__type(name: "Droid") {
|
||||
name
|
||||
@ -87,9 +96,7 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
}
|
||||
';
|
||||
$expected = [
|
||||
'__type' => [
|
||||
'name' => 'Droid'
|
||||
]
|
||||
'__type' => ['name' => 'Droid'],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -99,7 +106,7 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
*/
|
||||
public function testAllowsQueryingForAnObjectKind() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query IntrospectionDroidKindQuery {
|
||||
__type(name: "Droid") {
|
||||
name
|
||||
@ -110,8 +117,8 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
$expected = [
|
||||
'__type' => [
|
||||
'name' => 'Droid',
|
||||
'kind' => 'OBJECT'
|
||||
]
|
||||
'kind' => 'OBJECT',
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -121,7 +128,7 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
*/
|
||||
public function testAllowsQueryingForInterfaceKind() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query IntrospectionCharacterKindQuery {
|
||||
__type(name: "Character") {
|
||||
name
|
||||
@ -132,8 +139,8 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
$expected = [
|
||||
'__type' => [
|
||||
'name' => 'Character',
|
||||
'kind' => 'INTERFACE'
|
||||
]
|
||||
'kind' => 'INTERFACE',
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -143,7 +150,7 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
*/
|
||||
public function testAllowsQueryingForObjectFields() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query IntrospectionDroidFieldsQuery {
|
||||
__type(name: "Droid") {
|
||||
name
|
||||
@ -159,52 +166,52 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
';
|
||||
$expected = [
|
||||
'__type' => [
|
||||
'name' => 'Droid',
|
||||
'name' => 'Droid',
|
||||
'fields' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'type' => [
|
||||
'name' => null,
|
||||
'kind' => 'NON_NULL'
|
||||
]
|
||||
'kind' => 'NON_NULL',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'name',
|
||||
'type' => [
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR'
|
||||
]
|
||||
'kind' => 'SCALAR',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'friends',
|
||||
'type' => [
|
||||
'name' => null,
|
||||
'kind' => 'LIST'
|
||||
]
|
||||
'kind' => 'LIST',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'appearsIn',
|
||||
'type' => [
|
||||
'name' => null,
|
||||
'kind' => 'LIST'
|
||||
]
|
||||
'kind' => 'LIST',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'secretBackstory',
|
||||
'type' => [
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR'
|
||||
]
|
||||
'kind' => 'SCALAR',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'primaryFunction',
|
||||
'type' => [
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
'kind' => 'SCALAR',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -214,7 +221,7 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
*/
|
||||
public function testAllowsQueryingTheSchemaForNestedObjectFields() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query IntrospectionDroidNestedFieldsQuery {
|
||||
__type(name: "Droid") {
|
||||
name
|
||||
@ -234,67 +241,67 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
';
|
||||
$expected = [
|
||||
'__type' => [
|
||||
'name' => 'Droid',
|
||||
'name' => 'Droid',
|
||||
'fields' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'type' => [
|
||||
'name' => null,
|
||||
'kind' => 'NON_NULL',
|
||||
'name' => null,
|
||||
'kind' => 'NON_NULL',
|
||||
'ofType' => [
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR'
|
||||
]
|
||||
]
|
||||
'kind' => 'SCALAR',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'name',
|
||||
'type' => [
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR',
|
||||
'ofType' => null
|
||||
]
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR',
|
||||
'ofType' => null,
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'friends',
|
||||
'type' => [
|
||||
'name' => null,
|
||||
'kind' => 'LIST',
|
||||
'name' => null,
|
||||
'kind' => 'LIST',
|
||||
'ofType' => [
|
||||
'name' => 'Character',
|
||||
'kind' => 'INTERFACE'
|
||||
]
|
||||
]
|
||||
'kind' => 'INTERFACE',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'appearsIn',
|
||||
'type' => [
|
||||
'name' => null,
|
||||
'kind' => 'LIST',
|
||||
'name' => null,
|
||||
'kind' => 'LIST',
|
||||
'ofType' => [
|
||||
'name' => 'Episode',
|
||||
'kind' => 'ENUM'
|
||||
]
|
||||
]
|
||||
'kind' => 'ENUM',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'secretBackstory',
|
||||
'type' => [
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR',
|
||||
'ofType' => null
|
||||
]
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR',
|
||||
'ofType' => null,
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'primaryFunction',
|
||||
'type' => [
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR',
|
||||
'ofType' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
'name' => 'String',
|
||||
'kind' => 'SCALAR',
|
||||
'ofType' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -329,7 +336,7 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
}
|
||||
';
|
||||
|
||||
$expected = array(
|
||||
$expected = [
|
||||
'__schema' => [
|
||||
'queryType' => [
|
||||
'fields' => [
|
||||
@ -337,13 +344,13 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
'name' => 'hero',
|
||||
'args' => [
|
||||
[
|
||||
'defaultValue' => NULL,
|
||||
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
|
||||
'name' => 'episode',
|
||||
'type' => [
|
||||
'kind' => 'ENUM',
|
||||
'name' => 'Episode',
|
||||
'ofType' => NULL,
|
||||
'defaultValue' => null,
|
||||
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
|
||||
'name' => 'episode',
|
||||
'type' => [
|
||||
'kind' => 'ENUM',
|
||||
'name' => 'Episode',
|
||||
'ofType' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -352,17 +359,17 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
'name' => 'human',
|
||||
'args' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'description' => 'id of the human',
|
||||
'type' => [
|
||||
'kind' => 'NON_NULL',
|
||||
'name' => NULL,
|
||||
'name' => 'id',
|
||||
'description' => 'id of the human',
|
||||
'type' => [
|
||||
'kind' => 'NON_NULL',
|
||||
'name' => null,
|
||||
'ofType' => [
|
||||
'kind' => 'SCALAR',
|
||||
'name' => 'String',
|
||||
],
|
||||
],
|
||||
'defaultValue' => NULL,
|
||||
'defaultValue' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -370,25 +377,25 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
'name' => 'droid',
|
||||
'args' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'description' => 'id of the droid',
|
||||
'type' => [
|
||||
'kind' => 'NON_NULL',
|
||||
'name' => NULL,
|
||||
'name' => 'id',
|
||||
'description' => 'id of the droid',
|
||||
'type' => [
|
||||
'kind' => 'NON_NULL',
|
||||
'name' => null,
|
||||
'ofType' =>
|
||||
[
|
||||
'kind' => 'SCALAR',
|
||||
'name' => 'String',
|
||||
],
|
||||
],
|
||||
'defaultValue' => NULL,
|
||||
'defaultValue' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
];
|
||||
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -398,7 +405,7 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
*/
|
||||
public function testAllowsQueryingTheSchemaForDocumentation() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query IntrospectionDroidDescriptionQuery {
|
||||
__type(name: "Droid") {
|
||||
name
|
||||
@ -408,18 +415,10 @@ class StarWarsIntrospectionTest extends TestCase
|
||||
';
|
||||
$expected = [
|
||||
'__type' => [
|
||||
'name' => 'Droid',
|
||||
'description' => 'A mechanical creature in the Star Wars universe.'
|
||||
]
|
||||
'name' => 'Droid',
|
||||
'description' => 'A mechanical creature in the Star Wars universe.',
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a query and the expected response.
|
||||
*/
|
||||
private function assertValidQuery($query, $expected)
|
||||
{
|
||||
$this->assertEquals(['data' => $expected], GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray());
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,20 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
use GraphQL\GraphQL;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class StarWarsQueryTest extends TestCase
|
||||
{
|
||||
// Star Wars Query Tests
|
||||
// Basic Queries
|
||||
|
||||
/**
|
||||
* @see it('Correctly identifies R2-D2 as the hero of the Star Wars Saga')
|
||||
*/
|
||||
public function testCorrectlyIdentifiesR2D2AsTheHeroOfTheStarWarsSaga() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query HeroNameQuery {
|
||||
hero {
|
||||
name
|
||||
@ -23,19 +22,30 @@ class StarWarsQueryTest extends TestCase
|
||||
}
|
||||
';
|
||||
$expected = [
|
||||
'hero' => [
|
||||
'name' => 'R2-D2'
|
||||
]
|
||||
'hero' => ['name' => 'R2-D2'],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a query and the expected response.
|
||||
*/
|
||||
private function assertValidQuery($query, $expected) : void
|
||||
{
|
||||
$this->assertEquals(
|
||||
['data' => $expected],
|
||||
GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
// Describe: Nested Queries
|
||||
|
||||
/**
|
||||
* @see it('Allows us to query for the ID and friends of R2-D2')
|
||||
*/
|
||||
public function testAllowsUsToQueryForTheIDAndFriendsOfR2D2() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query HeroNameAndFriendsQuery {
|
||||
hero {
|
||||
id
|
||||
@ -48,32 +58,26 @@ class StarWarsQueryTest extends TestCase
|
||||
';
|
||||
$expected = [
|
||||
'hero' => [
|
||||
'id' => '2001',
|
||||
'name' => 'R2-D2',
|
||||
'id' => '2001',
|
||||
'name' => 'R2-D2',
|
||||
'friends' => [
|
||||
[
|
||||
'name' => 'Luke Skywalker',
|
||||
],
|
||||
[
|
||||
'name' => 'Han Solo',
|
||||
],
|
||||
[
|
||||
'name' => 'Leia Organa',
|
||||
],
|
||||
]
|
||||
]
|
||||
['name' => 'Luke Skywalker'],
|
||||
['name' => 'Han Solo'],
|
||||
['name' => 'Leia Organa'],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
|
||||
// Describe: Nested Queries
|
||||
// Describe: Using IDs and query parameters to refetch objects
|
||||
|
||||
/**
|
||||
* @see it('Allows us to query for the friends of friends of R2-D2')
|
||||
*/
|
||||
public function testAllowsUsToQueryForTheFriendsOfFriendsOfR2D2() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query NestedQuery {
|
||||
hero {
|
||||
name
|
||||
@ -89,52 +93,50 @@ class StarWarsQueryTest extends TestCase
|
||||
';
|
||||
$expected = [
|
||||
'hero' => [
|
||||
'name' => 'R2-D2',
|
||||
'name' => 'R2-D2',
|
||||
'friends' => [
|
||||
[
|
||||
'name' => 'Luke Skywalker',
|
||||
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI',],
|
||||
'friends' => [
|
||||
['name' => 'Han Solo',],
|
||||
['name' => 'Leia Organa',],
|
||||
['name' => 'C-3PO',],
|
||||
['name' => 'R2-D2',],
|
||||
'name' => 'Luke Skywalker',
|
||||
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
||||
'friends' => [
|
||||
['name' => 'Han Solo'],
|
||||
['name' => 'Leia Organa'],
|
||||
['name' => 'C-3PO'],
|
||||
['name' => 'R2-D2'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Han Solo',
|
||||
'name' => 'Han Solo',
|
||||
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
||||
'friends' => [
|
||||
['name' => 'Luke Skywalker',],
|
||||
'friends' => [
|
||||
['name' => 'Luke Skywalker'],
|
||||
['name' => 'Leia Organa'],
|
||||
['name' => 'R2-D2',],
|
||||
]
|
||||
['name' => 'R2-D2'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Leia Organa',
|
||||
'name' => 'Leia Organa',
|
||||
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
||||
'friends' =>
|
||||
'friends' =>
|
||||
[
|
||||
['name' => 'Luke Skywalker',],
|
||||
['name' => 'Han Solo',],
|
||||
['name' => 'C-3PO',],
|
||||
['name' => 'R2-D2',],
|
||||
['name' => 'Luke Skywalker'],
|
||||
['name' => 'Han Solo'],
|
||||
['name' => 'C-3PO'],
|
||||
['name' => 'R2-D2'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
|
||||
// Describe: Using IDs and query parameters to refetch objects
|
||||
|
||||
/**
|
||||
* @see it('Using IDs and query parameters to refetch objects')
|
||||
*/
|
||||
public function testAllowsUsToQueryForLukeSkywalkerDirectlyUsingHisID() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query FetchLukeQuery {
|
||||
human(id: "1000") {
|
||||
name
|
||||
@ -142,9 +144,7 @@ class StarWarsQueryTest extends TestCase
|
||||
}
|
||||
';
|
||||
$expected = [
|
||||
'human' => [
|
||||
'name' => 'Luke Skywalker'
|
||||
]
|
||||
'human' => ['name' => 'Luke Skywalker'],
|
||||
];
|
||||
|
||||
$this->assertValidQuery($query, $expected);
|
||||
@ -155,44 +155,49 @@ class StarWarsQueryTest extends TestCase
|
||||
*/
|
||||
public function testGenericQueryToGetLukeSkywalkerById() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query FetchSomeIDQuery($someId: String!) {
|
||||
human(id: $someId) {
|
||||
name
|
||||
}
|
||||
}
|
||||
';
|
||||
$params = [
|
||||
'someId' => '1000'
|
||||
];
|
||||
$params = ['someId' => '1000'];
|
||||
$expected = [
|
||||
'human' => [
|
||||
'name' => 'Luke Skywalker'
|
||||
]
|
||||
'human' => ['name' => 'Luke Skywalker'],
|
||||
];
|
||||
|
||||
$this->assertValidQueryWithParams($query, $params, $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a query with params and the expected response.
|
||||
*/
|
||||
private function assertValidQueryWithParams($query, $params, $expected)
|
||||
{
|
||||
$this->assertEquals(
|
||||
['data' => $expected],
|
||||
GraphQL::executeQuery(StarWarsSchema::build(), $query, null, null, $params)->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
// Using aliases to change the key in the response
|
||||
|
||||
/**
|
||||
* @see it('Allows us to create a generic query, then use it to fetch Han Solo using his ID')
|
||||
*/
|
||||
public function testGenericQueryToGetHanSoloById() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query FetchSomeIDQuery($someId: String!) {
|
||||
human(id: $someId) {
|
||||
name
|
||||
}
|
||||
}
|
||||
';
|
||||
$params = [
|
||||
'someId' => '1002'
|
||||
];
|
||||
$params = ['someId' => '1002'];
|
||||
$expected = [
|
||||
'human' => [
|
||||
'name' => 'Han Solo'
|
||||
]
|
||||
'human' => ['name' => 'Han Solo'],
|
||||
];
|
||||
$this->assertValidQueryWithParams($query, $params, $expected);
|
||||
}
|
||||
@ -202,30 +207,26 @@ class StarWarsQueryTest extends TestCase
|
||||
*/
|
||||
public function testGenericQueryWithInvalidId() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query humanQuery($id: String!) {
|
||||
human(id: $id) {
|
||||
name
|
||||
}
|
||||
}
|
||||
';
|
||||
$params = [
|
||||
'id' => 'not a valid id'
|
||||
];
|
||||
$expected = [
|
||||
'human' => null
|
||||
];
|
||||
$params = ['id' => 'not a valid id'];
|
||||
$expected = ['human' => null];
|
||||
$this->assertValidQueryWithParams($query, $params, $expected);
|
||||
}
|
||||
|
||||
// Using aliases to change the key in the response
|
||||
// Uses fragments to express more complex queries
|
||||
|
||||
/**
|
||||
* @see it('Allows us to query for Luke, changing his key with an alias')
|
||||
*/
|
||||
function testLukeKeyAlias()
|
||||
public function testLukeKeyAlias() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query FetchLukeAliased {
|
||||
luke: human(id: "1000") {
|
||||
name
|
||||
@ -233,9 +234,7 @@ class StarWarsQueryTest extends TestCase
|
||||
}
|
||||
';
|
||||
$expected = [
|
||||
'luke' => [
|
||||
'name' => 'Luke Skywalker'
|
||||
],
|
||||
'luke' => ['name' => 'Luke Skywalker'],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -243,9 +242,9 @@ class StarWarsQueryTest extends TestCase
|
||||
/**
|
||||
* @see it('Allows us to query for both Luke and Leia, using two root fields and an alias')
|
||||
*/
|
||||
function testTwoRootKeysAsAnAlias()
|
||||
public function testTwoRootKeysAsAnAlias() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query FetchLukeAndLeiaAliased {
|
||||
luke: human(id: "1000") {
|
||||
name
|
||||
@ -256,24 +255,18 @@ class StarWarsQueryTest extends TestCase
|
||||
}
|
||||
';
|
||||
$expected = [
|
||||
'luke' => [
|
||||
'name' => 'Luke Skywalker'
|
||||
],
|
||||
'leia' => [
|
||||
'name' => 'Leia Organa'
|
||||
]
|
||||
'luke' => ['name' => 'Luke Skywalker'],
|
||||
'leia' => ['name' => 'Leia Organa'],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
|
||||
// Uses fragments to express more complex queries
|
||||
|
||||
/**
|
||||
* @see it('Allows us to query using duplicated content')
|
||||
*/
|
||||
function testQueryUsingDuplicatedContent()
|
||||
public function testQueryUsingDuplicatedContent() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query DuplicateFields {
|
||||
luke: human(id: "1000") {
|
||||
name
|
||||
@ -287,13 +280,13 @@ class StarWarsQueryTest extends TestCase
|
||||
';
|
||||
$expected = [
|
||||
'luke' => [
|
||||
'name' => 'Luke Skywalker',
|
||||
'homePlanet' => 'Tatooine'
|
||||
'name' => 'Luke Skywalker',
|
||||
'homePlanet' => 'Tatooine',
|
||||
],
|
||||
'leia' => [
|
||||
'name' => 'Leia Organa',
|
||||
'homePlanet' => 'Alderaan'
|
||||
]
|
||||
'name' => 'Leia Organa',
|
||||
'homePlanet' => 'Alderaan',
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -301,7 +294,7 @@ class StarWarsQueryTest extends TestCase
|
||||
/**
|
||||
* @see it('Allows us to use a fragment to avoid duplicating content')
|
||||
*/
|
||||
function testUsingFragment()
|
||||
public function testUsingFragment() : void
|
||||
{
|
||||
$query = '
|
||||
query UseFragment {
|
||||
@ -321,13 +314,13 @@ class StarWarsQueryTest extends TestCase
|
||||
|
||||
$expected = [
|
||||
'luke' => [
|
||||
'name' => 'Luke Skywalker',
|
||||
'homePlanet' => 'Tatooine'
|
||||
'name' => 'Luke Skywalker',
|
||||
'homePlanet' => 'Tatooine',
|
||||
],
|
||||
'leia' => [
|
||||
'name' => 'Leia Organa',
|
||||
'homePlanet' => 'Alderaan'
|
||||
]
|
||||
'name' => 'Leia Organa',
|
||||
'homePlanet' => 'Alderaan',
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
@ -337,7 +330,7 @@ class StarWarsQueryTest extends TestCase
|
||||
*/
|
||||
public function testVerifyThatR2D2IsADroid() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query CheckTypeOfR2 {
|
||||
hero {
|
||||
__typename
|
||||
@ -348,7 +341,7 @@ class StarWarsQueryTest extends TestCase
|
||||
$expected = [
|
||||
'hero' => [
|
||||
'__typename' => 'Droid',
|
||||
'name' => 'R2-D2'
|
||||
'name' => 'R2-D2',
|
||||
],
|
||||
];
|
||||
$this->assertValidQuery($query, $expected);
|
||||
@ -371,32 +364,10 @@ class StarWarsQueryTest extends TestCase
|
||||
$expected = [
|
||||
'hero' => [
|
||||
'__typename' => 'Human',
|
||||
'name' => 'Luke Skywalker'
|
||||
'name' => 'Luke Skywalker',
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertValidQuery($query, $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a query and the expected response.
|
||||
*/
|
||||
private function assertValidQuery($query, $expected)
|
||||
{
|
||||
$this->assertEquals(
|
||||
['data' => $expected],
|
||||
GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a query with params and the expected response.
|
||||
*/
|
||||
private function assertValidQueryWithParams($query, $params, $expected)
|
||||
{
|
||||
$this->assertEquals(
|
||||
['data' => $expected],
|
||||
GraphQL::executeQuery(StarWarsSchema::build(), $query, null, null, $params)->toArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
/**
|
||||
@ -11,13 +14,16 @@ namespace GraphQL\Tests;
|
||||
* NOTE: This may contain spoilers for the original Star
|
||||
* Wars trilogy.
|
||||
*/
|
||||
use GraphQL\Type\Schema;
|
||||
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use function array_intersect_key;
|
||||
use function array_map;
|
||||
|
||||
/**
|
||||
* Using our shorthand to describe type systems, the type system for our
|
||||
@ -56,10 +62,9 @@ use GraphQL\Type\Definition\Type;
|
||||
*
|
||||
* We begin by setting up our schema.
|
||||
*/
|
||||
|
||||
class StarWarsSchema
|
||||
{
|
||||
public static function build()
|
||||
public static function build() : Schema
|
||||
{
|
||||
/**
|
||||
* The original trilogy consists of three movies.
|
||||
@ -68,22 +73,22 @@ class StarWarsSchema
|
||||
* enum Episode { NEWHOPE, EMPIRE, JEDI }
|
||||
*/
|
||||
$episodeEnum = new EnumType([
|
||||
'name' => 'Episode',
|
||||
'name' => 'Episode',
|
||||
'description' => 'One of the films in the Star Wars Trilogy',
|
||||
'values' => [
|
||||
'values' => [
|
||||
'NEWHOPE' => [
|
||||
'value' => 4,
|
||||
'description' => 'Released in 1977.'
|
||||
'value' => 4,
|
||||
'description' => 'Released in 1977.',
|
||||
],
|
||||
'EMPIRE' => [
|
||||
'value' => 5,
|
||||
'description' => 'Released in 1980.'
|
||||
'EMPIRE' => [
|
||||
'value' => 5,
|
||||
'description' => 'Released in 1980.',
|
||||
],
|
||||
'JEDI' => [
|
||||
'value' => 6,
|
||||
'description' => 'Released in 1983.'
|
||||
'JEDI' => [
|
||||
'value' => 6,
|
||||
'description' => 'Released in 1983.',
|
||||
],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$humanType = null;
|
||||
@ -101,28 +106,28 @@ class StarWarsSchema
|
||||
* }
|
||||
*/
|
||||
$characterInterface = new InterfaceType([
|
||||
'name' => 'Character',
|
||||
'name' => 'Character',
|
||||
'description' => 'A character in the Star Wars Trilogy',
|
||||
'fields' => function() use (&$characterInterface, $episodeEnum) {
|
||||
'fields' => function () use (&$characterInterface, $episodeEnum) {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => Type::nonNull(Type::string()),
|
||||
'id' => [
|
||||
'type' => Type::nonNull(Type::string()),
|
||||
'description' => 'The id of the character.',
|
||||
],
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'description' => 'The name of the character.'
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'description' => 'The name of the character.',
|
||||
],
|
||||
'friends' => [
|
||||
'type' => Type::listOf($characterInterface),
|
||||
'friends' => [
|
||||
'type' => Type::listOf($characterInterface),
|
||||
'description' => 'The friends of the character, or an empty list if they have none.',
|
||||
],
|
||||
'appearsIn' => [
|
||||
'type' => Type::listOf($episodeEnum),
|
||||
'description' => 'Which movies they appear in.'
|
||||
'appearsIn' => [
|
||||
'type' => Type::listOf($episodeEnum),
|
||||
'description' => 'Which movies they appear in.',
|
||||
],
|
||||
'secretBackstory' => [
|
||||
'type' => Type::string(),
|
||||
'type' => Type::string(),
|
||||
'description' => 'All secrets about their past.',
|
||||
],
|
||||
];
|
||||
@ -145,26 +150,26 @@ class StarWarsSchema
|
||||
* }
|
||||
*/
|
||||
$humanType = new ObjectType([
|
||||
'name' => 'Human',
|
||||
'name' => 'Human',
|
||||
'description' => 'A humanoid creature in the Star Wars universe.',
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'type' => new NonNull(Type::string()),
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'type' => new NonNull(Type::string()),
|
||||
'description' => 'The id of the human.',
|
||||
],
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'description' => 'The name of the human.',
|
||||
],
|
||||
'friends' => [
|
||||
'type' => Type::listOf($characterInterface),
|
||||
'friends' => [
|
||||
'type' => Type::listOf($characterInterface),
|
||||
'description' => 'The friends of the human, or an empty list if they have none.',
|
||||
'resolve' => function ($human, $args, $context, ResolveInfo $info) {
|
||||
$fieldSelection = $info->getFieldSelection();
|
||||
'resolve' => function ($human, $args, $context, ResolveInfo $info) {
|
||||
$fieldSelection = $info->getFieldSelection();
|
||||
$fieldSelection['id'] = true;
|
||||
|
||||
$friends = array_map(
|
||||
function($friend) use ($fieldSelection) {
|
||||
function ($friend) use ($fieldSelection) {
|
||||
return array_intersect_key($friend, $fieldSelection);
|
||||
},
|
||||
StarWarsData::getFriends($human)
|
||||
@ -173,24 +178,24 @@ class StarWarsSchema
|
||||
return $friends;
|
||||
},
|
||||
],
|
||||
'appearsIn' => [
|
||||
'type' => Type::listOf($episodeEnum),
|
||||
'description' => 'Which movies they appear in.'
|
||||
'appearsIn' => [
|
||||
'type' => Type::listOf($episodeEnum),
|
||||
'description' => 'Which movies they appear in.',
|
||||
],
|
||||
'homePlanet' => [
|
||||
'type' => Type::string(),
|
||||
'description' => 'The home planet of the human, or null if unknown.'
|
||||
'homePlanet' => [
|
||||
'type' => Type::string(),
|
||||
'description' => 'The home planet of the human, or null if unknown.',
|
||||
],
|
||||
'secretBackstory' => [
|
||||
'type' => Type::string(),
|
||||
'type' => Type::string(),
|
||||
'description' => 'Where are they from and how they came to be who they are.',
|
||||
'resolve' => function() {
|
||||
'resolve' => function () {
|
||||
// This is to demonstrate error reporting
|
||||
throw new \Exception('secretBackstory is secret.');
|
||||
},
|
||||
],
|
||||
],
|
||||
'interfaces' => [$characterInterface]
|
||||
'interfaces' => [$characterInterface],
|
||||
]);
|
||||
|
||||
/**
|
||||
@ -207,42 +212,42 @@ class StarWarsSchema
|
||||
* }
|
||||
*/
|
||||
$droidType = new ObjectType([
|
||||
'name' => 'Droid',
|
||||
'name' => 'Droid',
|
||||
'description' => 'A mechanical creature in the Star Wars universe.',
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'type' => Type::nonNull(Type::string()),
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'type' => Type::nonNull(Type::string()),
|
||||
'description' => 'The id of the droid.',
|
||||
],
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'description' => 'The name of the droid.'
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'description' => 'The name of the droid.',
|
||||
],
|
||||
'friends' => [
|
||||
'type' => Type::listOf($characterInterface),
|
||||
'friends' => [
|
||||
'type' => Type::listOf($characterInterface),
|
||||
'description' => 'The friends of the droid, or an empty list if they have none.',
|
||||
'resolve' => function ($droid) {
|
||||
'resolve' => function ($droid) {
|
||||
return StarWarsData::getFriends($droid);
|
||||
},
|
||||
],
|
||||
'appearsIn' => [
|
||||
'type' => Type::listOf($episodeEnum),
|
||||
'description' => 'Which movies they appear in.'
|
||||
'appearsIn' => [
|
||||
'type' => Type::listOf($episodeEnum),
|
||||
'description' => 'Which movies they appear in.',
|
||||
],
|
||||
'secretBackstory' => [
|
||||
'type' => Type::string(),
|
||||
'type' => Type::string(),
|
||||
'description' => 'Construction date and the name of the designer.',
|
||||
'resolve' => function() {
|
||||
'resolve' => function () {
|
||||
// This is to demonstrate error reporting
|
||||
throw new \Exception('secretBackstory is secret.');
|
||||
},
|
||||
],
|
||||
'primaryFunction' => [
|
||||
'type' => Type::string(),
|
||||
'description' => 'The primary function of the droid.'
|
||||
]
|
||||
'type' => Type::string(),
|
||||
'description' => 'The primary function of the droid.',
|
||||
],
|
||||
],
|
||||
'interfaces' => [$characterInterface]
|
||||
'interfaces' => [$characterInterface],
|
||||
]);
|
||||
|
||||
/**
|
||||
@ -260,49 +265,51 @@ class StarWarsSchema
|
||||
*
|
||||
*/
|
||||
$queryType = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'hero' => [
|
||||
'type' => $characterInterface,
|
||||
'args' => [
|
||||
'hero' => [
|
||||
'type' => $characterInterface,
|
||||
'args' => [
|
||||
'episode' => [
|
||||
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
|
||||
'type' => $episodeEnum
|
||||
]
|
||||
'type' => $episodeEnum,
|
||||
],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
return StarWarsData::getHero(isset($args['episode']) ? $args['episode'] : null);
|
||||
return StarWarsData::getHero($args['episode'] ?? null);
|
||||
},
|
||||
],
|
||||
'human' => [
|
||||
'type' => $humanType,
|
||||
'args' => [
|
||||
'type' => $humanType,
|
||||
'args' => [
|
||||
'id' => [
|
||||
'name' => 'id',
|
||||
'name' => 'id',
|
||||
'description' => 'id of the human',
|
||||
'type' => Type::nonNull(Type::string())
|
||||
]
|
||||
'type' => Type::nonNull(Type::string()),
|
||||
],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
$humans = StarWarsData::humans();
|
||||
return isset($humans[$args['id']]) ? $humans[$args['id']] : null;
|
||||
}
|
||||
|
||||
return $humans[$args['id']] ?? null;
|
||||
},
|
||||
],
|
||||
'droid' => [
|
||||
'type' => $droidType,
|
||||
'args' => [
|
||||
'type' => $droidType,
|
||||
'args' => [
|
||||
'id' => [
|
||||
'name' => 'id',
|
||||
'name' => 'id',
|
||||
'description' => 'id of the droid',
|
||||
'type' => Type::nonNull(Type::string())
|
||||
]
|
||||
'type' => Type::nonNull(Type::string()),
|
||||
],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
$droids = StarWarsData::droids();
|
||||
return isset($droids[$args['id']]) ? $droids[$args['id']] : null;
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
return $droids[$args['id']] ?? null;
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return new Schema(['query' => $queryType]);
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
use GraphQL\Language\Parser;
|
||||
@ -15,7 +18,7 @@ class StarWarsValidationTest extends TestCase
|
||||
*/
|
||||
public function testValidatesAComplexButValidQuery() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query NestedQueryWithFragment {
|
||||
hero {
|
||||
...NameAndAppearances
|
||||
@ -37,12 +40,22 @@ class StarWarsValidationTest extends TestCase
|
||||
$this->assertEquals(true, empty($errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a query and the expected response.
|
||||
*/
|
||||
private function validationErrors($query)
|
||||
{
|
||||
$ast = Parser::parse($query);
|
||||
|
||||
return DocumentValidator::validate(StarWarsSchema::build(), $ast);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('Notes that non-existent fields are invalid')
|
||||
*/
|
||||
public function testThatNonExistentFieldsAreInvalid() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query HeroSpaceshipQuery {
|
||||
hero {
|
||||
favoriteSpaceship
|
||||
@ -73,7 +86,7 @@ class StarWarsValidationTest extends TestCase
|
||||
*/
|
||||
public function testDisallowsFieldsOnScalars() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query HeroFieldsOnScalarQuery {
|
||||
hero {
|
||||
name {
|
||||
@ -91,7 +104,7 @@ class StarWarsValidationTest extends TestCase
|
||||
*/
|
||||
public function testDisallowsObjectFieldsOnInterfaces() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query DroidFieldOnCharacter {
|
||||
hero {
|
||||
name
|
||||
@ -108,7 +121,7 @@ class StarWarsValidationTest extends TestCase
|
||||
*/
|
||||
public function testAllowsObjectFieldsInFragments() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query DroidFieldInFragment {
|
||||
hero {
|
||||
name
|
||||
@ -129,7 +142,7 @@ class StarWarsValidationTest extends TestCase
|
||||
*/
|
||||
public function testAllowsObjectFieldsInInlineFragments() : void
|
||||
{
|
||||
$query = '
|
||||
$query = '
|
||||
query DroidFieldInFragment {
|
||||
hero {
|
||||
name
|
||||
@ -142,13 +155,4 @@ class StarWarsValidationTest extends TestCase
|
||||
$errors = $this->validationErrors($query);
|
||||
$this->assertEquals(true, empty($errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a query and the expected response.
|
||||
*/
|
||||
private function validationErrors($query)
|
||||
{
|
||||
$ast = Parser::parse($query);
|
||||
return DocumentValidator::validate(StarWarsSchema::build(), $ast);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,68 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Type;
|
||||
|
||||
use ArrayObject;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Introspection;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function count;
|
||||
use function is_array;
|
||||
|
||||
class EnumTypeTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Schema
|
||||
*/
|
||||
/** @var Schema */
|
||||
private $schema;
|
||||
|
||||
/**
|
||||
* @var EnumType
|
||||
*/
|
||||
/** @var EnumType */
|
||||
private $ComplexEnum;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $Complex1;
|
||||
|
||||
/** @var ArrayObject */
|
||||
private $Complex2;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$ColorType = new EnumType([
|
||||
'name' => 'Color',
|
||||
'name' => 'Color',
|
||||
'values' => [
|
||||
'RED' => ['value' => 0],
|
||||
'RED' => ['value' => 0],
|
||||
'GREEN' => ['value' => 1],
|
||||
'BLUE' => ['value' => 2],
|
||||
]
|
||||
'BLUE' => ['value' => 2],
|
||||
],
|
||||
]);
|
||||
|
||||
$simpleEnum = new EnumType([
|
||||
'name' => 'SimpleEnum',
|
||||
'name' => 'SimpleEnum',
|
||||
'values' => [
|
||||
'ONE', 'TWO', 'THREE'
|
||||
]
|
||||
'ONE',
|
||||
'TWO',
|
||||
'THREE',
|
||||
],
|
||||
]);
|
||||
|
||||
$Complex1 = ['someRandomFunction' => function() {}];
|
||||
$Complex1 = [
|
||||
'someRandomFunction' => function () {
|
||||
},
|
||||
];
|
||||
$Complex2 = new \ArrayObject(['someRandomValue' => 123]);
|
||||
|
||||
$ComplexEnum = new EnumType([
|
||||
'name' => 'Complex',
|
||||
'name' => 'Complex',
|
||||
'values' => [
|
||||
'ONE' => ['value' => $Complex1],
|
||||
'TWO' => ['value' => $Complex2]
|
||||
]
|
||||
'TWO' => ['value' => $Complex2],
|
||||
],
|
||||
]);
|
||||
|
||||
$QueryType = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'colorEnum' => [
|
||||
'type' => $ColorType,
|
||||
'args' => [
|
||||
'fromEnum' => ['type' => $ColorType],
|
||||
'fromInt' => ['type' => Type::int()],
|
||||
'colorEnum' => [
|
||||
'type' => $ColorType,
|
||||
'args' => [
|
||||
'fromEnum' => ['type' => $ColorType],
|
||||
'fromInt' => ['type' => Type::int()],
|
||||
'fromString' => ['type' => Type::string()],
|
||||
],
|
||||
'resolve' => function ($value, $args) {
|
||||
@ -75,28 +84,28 @@ class EnumTypeTest extends TestCase
|
||||
if (isset($args['fromEnum'])) {
|
||||
return $args['fromEnum'];
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
'simpleEnum' => [
|
||||
'type' => $simpleEnum,
|
||||
'args' => [
|
||||
'fromName' => ['type' => Type::string()],
|
||||
'fromValue' => ['type' => Type::string()]
|
||||
'simpleEnum' => [
|
||||
'type' => $simpleEnum,
|
||||
'args' => [
|
||||
'fromName' => ['type' => Type::string()],
|
||||
'fromValue' => ['type' => Type::string()],
|
||||
],
|
||||
'resolve' => function($value, $args) {
|
||||
'resolve' => function ($value, $args) {
|
||||
if (isset($args['fromName'])) {
|
||||
return $args['fromName'];
|
||||
}
|
||||
if (isset($args['fromValue'])) {
|
||||
return $args['fromValue'];
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
'colorInt' => [
|
||||
'type' => Type::int(),
|
||||
'args' => [
|
||||
'colorInt' => [
|
||||
'type' => Type::int(),
|
||||
'args' => [
|
||||
'fromEnum' => ['type' => $ColorType],
|
||||
'fromInt' => ['type' => Type::int()],
|
||||
'fromInt' => ['type' => Type::int()],
|
||||
],
|
||||
'resolve' => function ($value, $args) {
|
||||
if (isset($args['fromInt'])) {
|
||||
@ -105,75 +114,76 @@ class EnumTypeTest extends TestCase
|
||||
if (isset($args['fromEnum'])) {
|
||||
return $args['fromEnum'];
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
'complexEnum' => [
|
||||
'type' => $ComplexEnum,
|
||||
'args' => [
|
||||
'fromEnum' => [
|
||||
'type' => $ComplexEnum,
|
||||
'type' => $ComplexEnum,
|
||||
'args' => [
|
||||
'fromEnum' => [
|
||||
'type' => $ComplexEnum,
|
||||
// Note: defaultValue is provided an *internal* representation for
|
||||
// Enums, rather than the string name.
|
||||
'defaultValue' => $Complex1
|
||||
'defaultValue' => $Complex1,
|
||||
],
|
||||
'provideGoodValue' => [
|
||||
'type' => Type::boolean(),
|
||||
],
|
||||
'provideBadValue' => [
|
||||
'type' => Type::boolean()
|
||||
]
|
||||
'provideBadValue' => [
|
||||
'type' => Type::boolean(),
|
||||
],
|
||||
],
|
||||
'resolve' => function($value, $args) use ($Complex1, $Complex2) {
|
||||
if (!empty($args['provideGoodValue'])) {
|
||||
'resolve' => function ($value, $args) use ($Complex2) {
|
||||
if (! empty($args['provideGoodValue'])) {
|
||||
// Note: this is one of the references of the internal values which
|
||||
// ComplexEnum allows.
|
||||
return $Complex2;
|
||||
}
|
||||
if (!empty($args['provideBadValue'])) {
|
||||
if (! empty($args['provideBadValue'])) {
|
||||
// Note: similar shape, but not the same *reference*
|
||||
// as Complex2 above. Enum internal values require === equality.
|
||||
return new \ArrayObject(['someRandomValue' => 123]);
|
||||
}
|
||||
|
||||
return $args['fromEnum'];
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$MutationType = new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'name' => 'Mutation',
|
||||
'fields' => [
|
||||
'favoriteEnum' => [
|
||||
'type' => $ColorType,
|
||||
'args' => ['color' => ['type' => $ColorType]],
|
||||
'type' => $ColorType,
|
||||
'args' => ['color' => ['type' => $ColorType]],
|
||||
'resolve' => function ($value, $args) {
|
||||
return isset($args['color']) ? $args['color'] : null;
|
||||
}
|
||||
]
|
||||
]
|
||||
return $args['color'] ?? null;
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$SubscriptionType = new ObjectType([
|
||||
'name' => 'Subscription',
|
||||
'name' => 'Subscription',
|
||||
'fields' => [
|
||||
'subscribeToEnum' => [
|
||||
'type' => $ColorType,
|
||||
'args' => ['color' => ['type' => $ColorType]],
|
||||
'type' => $ColorType,
|
||||
'args' => ['color' => ['type' => $ColorType]],
|
||||
'resolve' => function ($value, $args) {
|
||||
return isset($args['color']) ? $args['color'] : null;
|
||||
}
|
||||
]
|
||||
]
|
||||
return $args['color'] ?? null;
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->Complex1 = $Complex1;
|
||||
$this->Complex2 = $Complex2;
|
||||
$this->Complex1 = $Complex1;
|
||||
$this->Complex2 = $Complex2;
|
||||
$this->ComplexEnum = $ComplexEnum;
|
||||
|
||||
$this->schema = new Schema([
|
||||
'query' => $QueryType,
|
||||
'mutation' => $MutationType,
|
||||
'subscription' => $SubscriptionType
|
||||
'query' => $QueryType,
|
||||
'mutation' => $MutationType,
|
||||
'subscription' => $SubscriptionType,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -221,12 +231,34 @@ class EnumTypeTest extends TestCase
|
||||
'{ colorEnum(fromEnum: "GREEN") }',
|
||||
null,
|
||||
[
|
||||
'message' => "Expected type Color, found \"GREEN\"; Did you mean the enum value GREEN?",
|
||||
'locations' => [new SourceLocation(1, 23)]
|
||||
'message' => 'Expected type Color, found "GREEN"; Did you mean the enum value GREEN?',
|
||||
'locations' => [new SourceLocation(1, 23)],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function expectFailure($query, $vars, $err)
|
||||
{
|
||||
$result = GraphQL::executeQuery($this->schema, $query, null, null, $vars);
|
||||
$this->assertEquals(1, count($result->errors));
|
||||
|
||||
if (is_array($err)) {
|
||||
$this->assertEquals(
|
||||
$err['message'],
|
||||
$result->errors[0]->getMessage()
|
||||
);
|
||||
$this->assertEquals(
|
||||
$err['locations'],
|
||||
$result->errors[0]->getLocations()
|
||||
);
|
||||
} else {
|
||||
$this->assertEquals(
|
||||
$err,
|
||||
$result->errors[0]->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('does not accept valuesNotInTheEnum')
|
||||
*/
|
||||
@ -236,8 +268,8 @@ class EnumTypeTest extends TestCase
|
||||
'{ colorEnum(fromEnum: GREENISH) }',
|
||||
null,
|
||||
[
|
||||
'message' => "Expected type Color, found GREENISH; Did you mean the enum value GREEN?",
|
||||
'locations' => [new SourceLocation(1, 23)]
|
||||
'message' => 'Expected type Color, found GREENISH; Did you mean the enum value GREEN?',
|
||||
'locations' => [new SourceLocation(1, 23)],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -251,8 +283,8 @@ class EnumTypeTest extends TestCase
|
||||
'{ colorEnum(fromEnum: green) }',
|
||||
null,
|
||||
[
|
||||
'message' => "Expected type Color, found green; Did you mean the enum value GREEN?",
|
||||
'locations' => [new SourceLocation(1, 23)]
|
||||
'message' => 'Expected type Color, found green; Did you mean the enum value GREEN?',
|
||||
'locations' => [new SourceLocation(1, 23)],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -266,9 +298,9 @@ class EnumTypeTest extends TestCase
|
||||
'{ colorEnum(fromString: "GREEN") }',
|
||||
null,
|
||||
[
|
||||
'message' => 'Expected a value of type "Color" but received: GREEN',
|
||||
'message' => 'Expected a value of type "Color" but received: GREEN',
|
||||
'locations' => [new SourceLocation(1, 3)],
|
||||
'path' => ['colorEnum'],
|
||||
'path' => ['colorEnum'],
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -281,7 +313,7 @@ class EnumTypeTest extends TestCase
|
||||
$this->expectFailure(
|
||||
'{ colorEnum(fromEnum: 1) }',
|
||||
null,
|
||||
"Expected type Color, found 1."
|
||||
'Expected type Color, found 1.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -293,7 +325,7 @@ class EnumTypeTest extends TestCase
|
||||
$this->expectFailure(
|
||||
'{ colorEnum(fromInt: GREEN) }',
|
||||
null,
|
||||
"Expected type Int, found GREEN."
|
||||
'Expected type Int, found GREEN.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -381,7 +413,7 @@ class EnumTypeTest extends TestCase
|
||||
$this->expectFailure(
|
||||
'query test($color: Int!) { colorEnum(fromEnum: $color) }',
|
||||
['color' => 2],
|
||||
'Variable "$color" of type "Int!" used in position ' . 'expecting type "Color".'
|
||||
'Variable "$color" of type "Int!" used in position expecting type "Color".'
|
||||
);
|
||||
}
|
||||
|
||||
@ -392,10 +424,13 @@ class EnumTypeTest extends TestCase
|
||||
{
|
||||
$this->assertEquals(
|
||||
['data' => ['colorEnum' => 'RED', 'colorInt' => 0]],
|
||||
GraphQL::executeQuery($this->schema, "{
|
||||
GraphQL::executeQuery(
|
||||
$this->schema,
|
||||
'{
|
||||
colorEnum(fromEnum: RED)
|
||||
colorInt(fromEnum: RED)
|
||||
}")->toArray()
|
||||
}'
|
||||
)->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
@ -406,10 +441,13 @@ class EnumTypeTest extends TestCase
|
||||
{
|
||||
$this->assertEquals(
|
||||
['data' => ['colorEnum' => null, 'colorInt' => null]],
|
||||
GraphQL::executeQuery($this->schema, "{
|
||||
GraphQL::executeQuery(
|
||||
$this->schema,
|
||||
'{
|
||||
colorEnum
|
||||
colorInt
|
||||
}")->toArray()
|
||||
}'
|
||||
)->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
@ -419,7 +457,7 @@ class EnumTypeTest extends TestCase
|
||||
public function testPresentsGetValuesAPIForComplexEnums() : void
|
||||
{
|
||||
$ComplexEnum = $this->ComplexEnum;
|
||||
$values = $ComplexEnum->getValues();
|
||||
$values = $ComplexEnum->getValues();
|
||||
|
||||
$this->assertEquals(2, count($values));
|
||||
$this->assertEquals('ONE', $values[0]->name);
|
||||
@ -446,25 +484,29 @@ class EnumTypeTest extends TestCase
|
||||
*/
|
||||
public function testMayBeInternallyRepresentedWithComplexValues() : void
|
||||
{
|
||||
$result = GraphQL::executeQuery($this->schema, '{
|
||||
$result = GraphQL::executeQuery(
|
||||
$this->schema,
|
||||
'{
|
||||
first: complexEnum
|
||||
second: complexEnum(fromEnum: TWO)
|
||||
good: complexEnum(provideGoodValue: true)
|
||||
bad: complexEnum(provideBadValue: true)
|
||||
}')->toArray(true);
|
||||
}'
|
||||
)->toArray(true);
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'first' => 'ONE',
|
||||
'data' => [
|
||||
'first' => 'ONE',
|
||||
'second' => 'TWO',
|
||||
'good' => 'TWO',
|
||||
'bad' => null
|
||||
'good' => 'TWO',
|
||||
'bad' => null,
|
||||
],
|
||||
'errors' => [[
|
||||
'debugMessage' =>
|
||||
'Expected a value of type "Complex" but received: instance of ArrayObject',
|
||||
'locations' => [['line' => 5, 'column' => 9]]
|
||||
]]
|
||||
'locations' => [['line' => 5, 'column' => 9]],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertArraySubset($expected, $result);
|
||||
@ -489,35 +531,14 @@ class EnumTypeTest extends TestCase
|
||||
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'data' => ['first' => 'ONE', 'second' => 'TWO', 'third' => null],
|
||||
'data' => ['first' => 'ONE', 'second' => 'TWO', 'third' => null],
|
||||
'errors' => [[
|
||||
'debugMessage' => 'Expected a value of type "SimpleEnum" but received: WRONG',
|
||||
'locations' => [['line' => 4, 'column' => 13]]
|
||||
]]
|
||||
'locations' => [['line' => 4, 'column' => 13]],
|
||||
],
|
||||
],
|
||||
],
|
||||
GraphQL::executeQuery($this->schema, $q)->toArray(true)
|
||||
);
|
||||
}
|
||||
|
||||
private function expectFailure($query, $vars, $err)
|
||||
{
|
||||
$result = GraphQL::executeQuery($this->schema, $query, null, null, $vars);
|
||||
$this->assertEquals(1, count($result->errors));
|
||||
|
||||
if (is_array($err)) {
|
||||
$this->assertEquals(
|
||||
$err['message'],
|
||||
$result->errors[0]->getMessage()
|
||||
);
|
||||
$this->assertEquals(
|
||||
$err['locations'],
|
||||
$result->errors[0]->getLocations()
|
||||
);
|
||||
} else {
|
||||
$this->assertEquals(
|
||||
$err,
|
||||
$result->errors[0]->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Type;
|
||||
|
||||
class ObjectIdStub
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var int */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Type;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
@ -10,124 +13,84 @@ use GraphQL\Type\Definition\UnionType;
|
||||
use GraphQL\Type\EagerResolution;
|
||||
use GraphQL\Type\LazyResolution;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function lcfirst;
|
||||
|
||||
class ResolutionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $mutation;
|
||||
|
||||
/**
|
||||
* @var InterfaceType
|
||||
*/
|
||||
/** @var InterfaceType */
|
||||
private $node;
|
||||
|
||||
/**
|
||||
* @var InterfaceType
|
||||
*/
|
||||
/** @var InterfaceType */
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $blogStory;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
private $link;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $video;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $videoMetadata;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $comment;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $category;
|
||||
|
||||
/**
|
||||
* @var UnionType
|
||||
*/
|
||||
/** @var UnionType */
|
||||
private $mention;
|
||||
|
||||
/** @var ObjectType */
|
||||
private $postStoryMutation;
|
||||
|
||||
/** @var InputObjectType */
|
||||
private $postStoryMutationInput;
|
||||
|
||||
/** @var ObjectType */
|
||||
private $postCommentMutation;
|
||||
|
||||
/** @var InputObjectType */
|
||||
private $postCommentMutationInput;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->node = new InterfaceType([
|
||||
'name' => 'Node',
|
||||
'name' => 'Node',
|
||||
'fields' => [
|
||||
'id' => Type::string()
|
||||
]
|
||||
'id' => Type::string(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->content = new InterfaceType([
|
||||
'name' => 'Content',
|
||||
'fields' => function() {
|
||||
'name' => 'Content',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => $this->user,
|
||||
'comments' => Type::listOf($this->comment),
|
||||
'categories' => Type::listOf($this->category)
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => $this->user,
|
||||
'comments' => Type::listOf($this->comment),
|
||||
'categories' => Type::listOf($this->category),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->blogStory = new ObjectType([
|
||||
'name' => 'BlogStory',
|
||||
'name' => 'BlogStory',
|
||||
'interfaces' => [
|
||||
$this->node,
|
||||
$this->content
|
||||
$this->content,
|
||||
],
|
||||
'fields' => function() {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
$this->content->getField('title'),
|
||||
$this->content->getField('body'),
|
||||
$this->content->getField('author'),
|
||||
$this->content->getField('comments'),
|
||||
$this->content->getField('categories')
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$this->link = new ObjectType([
|
||||
'name' => 'Link',
|
||||
'interfaces' => [
|
||||
$this->node,
|
||||
$this->content
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
$this->content->getField('title'),
|
||||
@ -135,143 +98,166 @@ class ResolutionTest extends TestCase
|
||||
$this->content->getField('author'),
|
||||
$this->content->getField('comments'),
|
||||
$this->content->getField('categories'),
|
||||
'url' => Type::string()
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
new ObjectType([
|
||||
'name' => 'Link',
|
||||
'interfaces' => [
|
||||
$this->node,
|
||||
$this->content,
|
||||
],
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => $this->node->getField('id'),
|
||||
'title' => $this->content->getField('title'),
|
||||
'body' => $this->content->getField('body'),
|
||||
'author' => $this->content->getField('author'),
|
||||
'comments' => $this->content->getField('comments'),
|
||||
'categories' => $this->content->getField('categories'),
|
||||
'url' => Type::string(),
|
||||
];
|
||||
},
|
||||
|
||||
]);
|
||||
|
||||
$this->videoMetadata = new ObjectType([
|
||||
'name' => 'VideoMetadata',
|
||||
'fields' => [
|
||||
'lat' => Type::float(),
|
||||
'lng' => Type::float(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->video = new ObjectType([
|
||||
'name' => 'Video',
|
||||
'name' => 'Video',
|
||||
'interfaces' => [
|
||||
$this->node,
|
||||
$this->content
|
||||
$this->content,
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
$this->content->getField('title'),
|
||||
$this->content->getField('body'),
|
||||
$this->content->getField('author'),
|
||||
$this->content->getField('comments'),
|
||||
$this->content->getField('categories'),
|
||||
'streamUrl' => Type::string(),
|
||||
'id' => $this->node->getField('id'),
|
||||
'title' => $this->content->getField('title'),
|
||||
'body' => $this->content->getField('body'),
|
||||
'author' => $this->content->getField('author'),
|
||||
'comments' => $this->content->getField('comments'),
|
||||
'categories' => $this->content->getField('categories'),
|
||||
'streamUrl' => Type::string(),
|
||||
'downloadUrl' => Type::string(),
|
||||
'metadata' => $this->videoMetadata = new ObjectType([
|
||||
'name' => 'VideoMetadata',
|
||||
'fields' => [
|
||||
'lat' => Type::float(),
|
||||
'lng' => Type::float()
|
||||
]
|
||||
])
|
||||
'metadata' => $this->videoMetadata,
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->comment = new ObjectType([
|
||||
'name' => 'Comment',
|
||||
'name' => 'Comment',
|
||||
'interfaces' => [
|
||||
$this->node
|
||||
$this->node,
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
'author' => $this->user,
|
||||
'text' => Type::string(),
|
||||
'id' => $this->node->getField('id'),
|
||||
'author' => $this->user,
|
||||
'text' => Type::string(),
|
||||
'replies' => Type::listOf($this->comment),
|
||||
'parent' => $this->comment,
|
||||
'content' => $this->content
|
||||
'parent' => $this->comment,
|
||||
'content' => $this->content,
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->user = new ObjectType([
|
||||
'name' => 'User',
|
||||
'name' => 'User',
|
||||
'interfaces' => [
|
||||
$this->node
|
||||
$this->node,
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
'id' => $this->node->getField('id'),
|
||||
'name' => Type::string(),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->category = new ObjectType([
|
||||
'name' => 'Category',
|
||||
'name' => 'Category',
|
||||
'interfaces' => [
|
||||
$this->node
|
||||
$this->node,
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
'name' => Type::string()
|
||||
'id' => $this->node->getField('id'),
|
||||
'name' => Type::string(),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->mention = new UnionType([
|
||||
'name' => 'Mention',
|
||||
'name' => 'Mention',
|
||||
'types' => [
|
||||
$this->user,
|
||||
$this->category
|
||||
]
|
||||
$this->category,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'viewer' => $this->user,
|
||||
'viewer' => $this->user,
|
||||
'latestContent' => $this->content,
|
||||
'node' => $this->node,
|
||||
'mentions' => Type::listOf($this->mention)
|
||||
]
|
||||
'node' => $this->node,
|
||||
'mentions' => Type::listOf($this->mention),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->postStoryMutationInput = new InputObjectType([
|
||||
'name' => 'PostStoryMutationInput',
|
||||
'fields' => [
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => Type::id(),
|
||||
'category' => Type::id(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->mutation = new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'name' => 'Mutation',
|
||||
'fields' => [
|
||||
'postStory' => [
|
||||
'postStory' => [
|
||||
'type' => $this->postStoryMutation = new ObjectType([
|
||||
'name' => 'PostStoryMutation',
|
||||
'name' => 'PostStoryMutation',
|
||||
'fields' => [
|
||||
'story' => $this->blogStory
|
||||
]
|
||||
'story' => $this->blogStory,
|
||||
],
|
||||
]),
|
||||
'args' => [
|
||||
'input' => Type::nonNull($this->postStoryMutationInput = new InputObjectType([
|
||||
'name' => 'PostStoryMutationInput',
|
||||
'fields' => [
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => Type::id(),
|
||||
'category' => Type::id()
|
||||
]
|
||||
])),
|
||||
'clientRequestId' => Type::string()
|
||||
]
|
||||
'input' => Type::nonNull($this->postStoryMutationInput),
|
||||
'clientRequestId' => Type::string(),
|
||||
],
|
||||
],
|
||||
'postComment' => [
|
||||
'type' => $this->postCommentMutation = new ObjectType([
|
||||
'name' => 'PostCommentMutation',
|
||||
'name' => 'PostCommentMutation',
|
||||
'fields' => [
|
||||
'comment' => $this->comment
|
||||
]
|
||||
'comment' => $this->comment,
|
||||
],
|
||||
]),
|
||||
'args' => [
|
||||
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
|
||||
'name' => 'PostCommentMutationInput',
|
||||
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
|
||||
'name' => 'PostCommentMutationInput',
|
||||
'fields' => [
|
||||
'text' => Type::nonNull(Type::string()),
|
||||
'author' => Type::nonNull(Type::id()),
|
||||
'text' => Type::nonNull(Type::string()),
|
||||
'author' => Type::nonNull(Type::id()),
|
||||
'content' => Type::id(),
|
||||
'parent' => Type::id()
|
||||
]
|
||||
'parent' => Type::id(),
|
||||
],
|
||||
])),
|
||||
'clientRequestId' => Type::string()
|
||||
]
|
||||
]
|
||||
]
|
||||
'clientRequestId' => Type::string(),
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@ -279,29 +265,29 @@ class ResolutionTest extends TestCase
|
||||
{
|
||||
// Has internal types by default:
|
||||
$eagerTypeResolution = new EagerResolution([]);
|
||||
$expectedTypeMap = [
|
||||
'ID' => Type::id(),
|
||||
'String' => Type::string(),
|
||||
'Float' => Type::float(),
|
||||
'Int' => Type::int(),
|
||||
'Boolean' => Type::boolean()
|
||||
$expectedTypeMap = [
|
||||
'ID' => Type::id(),
|
||||
'String' => Type::string(),
|
||||
'Float' => Type::float(),
|
||||
'Int' => Type::int(),
|
||||
'Boolean' => Type::boolean(),
|
||||
];
|
||||
$this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap());
|
||||
|
||||
$expectedDescriptor = [
|
||||
'version' => '1.0',
|
||||
'typeMap' => [
|
||||
'ID' => 1,
|
||||
'String' => 1,
|
||||
'Float' => 1,
|
||||
'Int' => 1,
|
||||
'version' => '1.0',
|
||||
'typeMap' => [
|
||||
'ID' => 1,
|
||||
'String' => 1,
|
||||
'Float' => 1,
|
||||
'Int' => 1,
|
||||
'Boolean' => 1,
|
||||
],
|
||||
'possibleTypeMap' => []
|
||||
'possibleTypeMap' => [],
|
||||
];
|
||||
$this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor());
|
||||
|
||||
$this->assertSame(null, $eagerTypeResolution->resolveType('User'));
|
||||
$this->assertNull($eagerTypeResolution->resolveType('User'));
|
||||
$this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->node));
|
||||
$this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->content));
|
||||
$this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->mention));
|
||||
@ -321,72 +307,76 @@ class ResolutionTest extends TestCase
|
||||
$this->assertSame($this->postStoryMutation, $eagerTypeResolution->resolveType('PostStoryMutation'));
|
||||
$this->assertSame($this->postStoryMutationInput, $eagerTypeResolution->resolveType('PostStoryMutationInput'));
|
||||
$this->assertSame($this->postCommentMutation, $eagerTypeResolution->resolveType('PostCommentMutation'));
|
||||
$this->assertSame($this->postCommentMutationInput, $eagerTypeResolution->resolveType('PostCommentMutationInput'));
|
||||
$this->assertSame(
|
||||
$this->postCommentMutationInput,
|
||||
$eagerTypeResolution->resolveType('PostCommentMutationInput')
|
||||
);
|
||||
|
||||
$this->assertEquals([$this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->content));
|
||||
$this->assertEquals([$this->user, $this->comment, $this->category, $this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->node));
|
||||
$this->assertEquals(
|
||||
[$this->user, $this->comment, $this->category, $this->blogStory],
|
||||
$eagerTypeResolution->resolvePossibleTypes($this->node)
|
||||
);
|
||||
$this->assertEquals([$this->user, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->mention));
|
||||
|
||||
$expectedTypeMap = [
|
||||
'Query' => $this->query,
|
||||
'Mutation' => $this->mutation,
|
||||
'User' => $this->user,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'Comment' => $this->comment,
|
||||
'Mention' => $this->mention,
|
||||
'BlogStory' => $this->blogStory,
|
||||
'Category' => $this->category,
|
||||
'PostStoryMutationInput' => $this->postStoryMutationInput,
|
||||
'ID' => Type::id(),
|
||||
'PostStoryMutation' => $this->postStoryMutation,
|
||||
'Query' => $this->query,
|
||||
'Mutation' => $this->mutation,
|
||||
'User' => $this->user,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'Comment' => $this->comment,
|
||||
'Mention' => $this->mention,
|
||||
'BlogStory' => $this->blogStory,
|
||||
'Category' => $this->category,
|
||||
'PostStoryMutationInput' => $this->postStoryMutationInput,
|
||||
'ID' => Type::id(),
|
||||
'PostStoryMutation' => $this->postStoryMutation,
|
||||
'PostCommentMutationInput' => $this->postCommentMutationInput,
|
||||
'PostCommentMutation' => $this->postCommentMutation,
|
||||
'Float' => Type::float(),
|
||||
'Int' => Type::int(),
|
||||
'Boolean' => Type::boolean()
|
||||
'PostCommentMutation' => $this->postCommentMutation,
|
||||
'Float' => Type::float(),
|
||||
'Int' => Type::int(),
|
||||
'Boolean' => Type::boolean(),
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap());
|
||||
|
||||
$expectedDescriptor = [
|
||||
'version' => '1.0',
|
||||
'typeMap' => [
|
||||
'Query' => 1,
|
||||
'Mutation' => 1,
|
||||
'User' => 1,
|
||||
'Node' => 1,
|
||||
'String' => 1,
|
||||
'Content' => 1,
|
||||
'Comment' => 1,
|
||||
'Mention' => 1,
|
||||
'BlogStory' => 1,
|
||||
'Category' => 1,
|
||||
'PostStoryMutationInput' => 1,
|
||||
'ID' => 1,
|
||||
'PostStoryMutation' => 1,
|
||||
'version' => '1.0',
|
||||
'typeMap' => [
|
||||
'Query' => 1,
|
||||
'Mutation' => 1,
|
||||
'User' => 1,
|
||||
'Node' => 1,
|
||||
'String' => 1,
|
||||
'Content' => 1,
|
||||
'Comment' => 1,
|
||||
'Mention' => 1,
|
||||
'BlogStory' => 1,
|
||||
'Category' => 1,
|
||||
'PostStoryMutationInput' => 1,
|
||||
'ID' => 1,
|
||||
'PostStoryMutation' => 1,
|
||||
'PostCommentMutationInput' => 1,
|
||||
'PostCommentMutation' => 1,
|
||||
'Float' => 1,
|
||||
'Int' => 1,
|
||||
'Boolean' => 1
|
||||
'PostCommentMutation' => 1,
|
||||
'Float' => 1,
|
||||
'Int' => 1,
|
||||
'Boolean' => 1,
|
||||
],
|
||||
'possibleTypeMap' => [
|
||||
'Node' => [
|
||||
'User' => 1,
|
||||
'Comment' => 1,
|
||||
'Category' => 1,
|
||||
'BlogStory' => 1
|
||||
],
|
||||
'Content' => [
|
||||
'BlogStory' => 1
|
||||
'Node' => [
|
||||
'User' => 1,
|
||||
'Comment' => 1,
|
||||
'Category' => 1,
|
||||
'BlogStory' => 1,
|
||||
],
|
||||
'Content' => ['BlogStory' => 1],
|
||||
'Mention' => [
|
||||
'User' => 1,
|
||||
'Category' => 1
|
||||
]
|
||||
]
|
||||
'User' => 1,
|
||||
'Category' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor());
|
||||
@ -402,7 +392,10 @@ class ResolutionTest extends TestCase
|
||||
$this->assertEquals(null, $eagerTypeResolution->resolveType('VideoMetadata'));
|
||||
|
||||
$this->assertEquals([$this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->content));
|
||||
$this->assertEquals([$this->user, $this->comment, $this->category, $this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->node));
|
||||
$this->assertEquals(
|
||||
[$this->user, $this->comment, $this->category, $this->blogStory],
|
||||
$eagerTypeResolution->resolvePossibleTypes($this->node)
|
||||
);
|
||||
$this->assertEquals([$this->user, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->mention));
|
||||
|
||||
$eagerTypeResolution = new EagerResolution([null, $this->video, null]);
|
||||
@ -410,52 +403,53 @@ class ResolutionTest extends TestCase
|
||||
$this->assertEquals($this->video, $eagerTypeResolution->resolveType('Video'));
|
||||
|
||||
$this->assertEquals([$this->video], $eagerTypeResolution->resolvePossibleTypes($this->content));
|
||||
$this->assertEquals([$this->video, $this->user, $this->comment, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->node));
|
||||
$this->assertEquals(
|
||||
[$this->video, $this->user, $this->comment, $this->category],
|
||||
$eagerTypeResolution->resolvePossibleTypes($this->node)
|
||||
);
|
||||
$this->assertEquals([], $eagerTypeResolution->resolvePossibleTypes($this->mention));
|
||||
|
||||
$expectedTypeMap = [
|
||||
'Video' => $this->video,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'User' => $this->user,
|
||||
'Comment' => $this->comment,
|
||||
'Category' => $this->category,
|
||||
'Video' => $this->video,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'User' => $this->user,
|
||||
'Comment' => $this->comment,
|
||||
'Category' => $this->category,
|
||||
'VideoMetadata' => $this->videoMetadata,
|
||||
'Float' => Type::float(),
|
||||
'ID' => Type::id(),
|
||||
'Int' => Type::int(),
|
||||
'Boolean' => Type::boolean()
|
||||
'Float' => Type::float(),
|
||||
'ID' => Type::id(),
|
||||
'Int' => Type::int(),
|
||||
'Boolean' => Type::boolean(),
|
||||
];
|
||||
$this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap());
|
||||
|
||||
$expectedDescriptor = [
|
||||
'version' => '1.0',
|
||||
'typeMap' => [
|
||||
'Video' => 1,
|
||||
'Node' => 1,
|
||||
'String' => 1,
|
||||
'Content' => 1,
|
||||
'User' => 1,
|
||||
'Comment' => 1,
|
||||
'Category' => 1,
|
||||
'version' => '1.0',
|
||||
'typeMap' => [
|
||||
'Video' => 1,
|
||||
'Node' => 1,
|
||||
'String' => 1,
|
||||
'Content' => 1,
|
||||
'User' => 1,
|
||||
'Comment' => 1,
|
||||
'Category' => 1,
|
||||
'VideoMetadata' => 1,
|
||||
'Float' => 1,
|
||||
'ID' => 1,
|
||||
'Int' => 1,
|
||||
'Boolean' => 1
|
||||
'Float' => 1,
|
||||
'ID' => 1,
|
||||
'Int' => 1,
|
||||
'Boolean' => 1,
|
||||
],
|
||||
'possibleTypeMap' => [
|
||||
'Node' => [
|
||||
'Video' => 1,
|
||||
'User' => 1,
|
||||
'Comment' => 1,
|
||||
'Category' => 1
|
||||
'Node' => [
|
||||
'Video' => 1,
|
||||
'User' => 1,
|
||||
'Comment' => 1,
|
||||
'Category' => 1,
|
||||
],
|
||||
'Content' => [
|
||||
'Video' => 1
|
||||
]
|
||||
]
|
||||
'Content' => ['Video' => 1],
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor());
|
||||
}
|
||||
@ -463,11 +457,11 @@ class ResolutionTest extends TestCase
|
||||
public function testLazyResolutionFollowsEagerResolution() : void
|
||||
{
|
||||
// Lazy resolution should work the same way as eager resolution works, except that it should load types on demand
|
||||
$eager = new EagerResolution([]);
|
||||
$eager = new EagerResolution([]);
|
||||
$emptyDescriptor = $eager->getDescriptor();
|
||||
|
||||
$typeLoader = function($name) {
|
||||
throw new \Exception("This should be never called for empty descriptor");
|
||||
$typeLoader = function ($name) {
|
||||
throw new \Exception('This should be never called for empty descriptor');
|
||||
};
|
||||
|
||||
$lazy = new LazyResolution($emptyDescriptor, $typeLoader);
|
||||
@ -478,11 +472,12 @@ class ResolutionTest extends TestCase
|
||||
|
||||
$eager = new EagerResolution([$this->query, $this->mutation]);
|
||||
|
||||
$called = 0;
|
||||
$called = 0;
|
||||
$descriptor = $eager->getDescriptor();
|
||||
$typeLoader = function($name) use (&$called) {
|
||||
$typeLoader = function ($name) use (&$called) {
|
||||
$called++;
|
||||
$prop = lcfirst($name);
|
||||
|
||||
return $this->{$prop};
|
||||
};
|
||||
|
||||
@ -507,7 +502,10 @@ class ResolutionTest extends TestCase
|
||||
$this->assertSame($eager->resolveType('PostStoryMutation'), $lazy->resolveType('PostStoryMutation'));
|
||||
$this->assertSame($eager->resolveType('PostStoryMutationInput'), $lazy->resolveType('PostStoryMutationInput'));
|
||||
$this->assertSame($eager->resolveType('PostCommentMutation'), $lazy->resolveType('PostCommentMutation'));
|
||||
$this->assertSame($eager->resolveType('PostCommentMutationInput'), $lazy->resolveType('PostCommentMutationInput'));
|
||||
$this->assertSame(
|
||||
$eager->resolveType('PostCommentMutationInput'),
|
||||
$lazy->resolveType('PostCommentMutationInput')
|
||||
);
|
||||
$this->assertSame(13, $called);
|
||||
|
||||
$this->assertEquals($eager->resolvePossibleTypes($this->content), $lazy->resolvePossibleTypes($this->content));
|
||||
@ -515,8 +513,8 @@ class ResolutionTest extends TestCase
|
||||
$this->assertEquals($eager->resolvePossibleTypes($this->mention), $lazy->resolvePossibleTypes($this->mention));
|
||||
|
||||
$called = 0;
|
||||
$eager = new EagerResolution([$this->video]);
|
||||
$lazy = new LazyResolution($eager->getDescriptor(), $typeLoader);
|
||||
$eager = new EagerResolution([$this->video]);
|
||||
$lazy = new LazyResolution($eager->getDescriptor(), $typeLoader);
|
||||
|
||||
$this->assertEquals($eager->resolveType('VideoMetadata'), $lazy->resolveType('VideoMetadata'));
|
||||
$this->assertEquals($eager->resolveType('Video'), $lazy->resolveType('Video'));
|
||||
@ -527,40 +525,6 @@ class ResolutionTest extends TestCase
|
||||
$this->assertEquals($eager->resolvePossibleTypes($this->mention), $lazy->resolvePossibleTypes($this->mention));
|
||||
}
|
||||
|
||||
private function createLazy(){
|
||||
|
||||
$descriptor = [
|
||||
'version' => '1.0',
|
||||
'typeMap' => [
|
||||
'null' => 1,
|
||||
'int' => 1
|
||||
],
|
||||
'possibleTypeMap' => [
|
||||
'a' => [
|
||||
'null' => 1,
|
||||
],
|
||||
'b' => [
|
||||
'int' => 1
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$invalidTypeLoader = function($name) {
|
||||
switch ($name) {
|
||||
case 'null':
|
||||
return null;
|
||||
case 'int':
|
||||
return 7;
|
||||
}
|
||||
};
|
||||
|
||||
$lazy = new LazyResolution($descriptor, $invalidTypeLoader);
|
||||
$value = $lazy->resolveType('null');
|
||||
$this->assertEquals(null, $value);
|
||||
|
||||
return $lazy;
|
||||
}
|
||||
|
||||
public function testLazyThrowsOnInvalidLoadedType() : void
|
||||
{
|
||||
$lazy = $this->createLazy();
|
||||
@ -569,9 +533,39 @@ class ResolutionTest extends TestCase
|
||||
$lazy->resolveType('int');
|
||||
}
|
||||
|
||||
private function createLazy()
|
||||
{
|
||||
$descriptor = [
|
||||
'version' => '1.0',
|
||||
'typeMap' => [
|
||||
'null' => 1,
|
||||
'int' => 1,
|
||||
],
|
||||
'possibleTypeMap' => [
|
||||
'a' => ['null' => 1],
|
||||
'b' => ['int' => 1],
|
||||
],
|
||||
];
|
||||
|
||||
$invalidTypeLoader = function ($name) {
|
||||
switch ($name) {
|
||||
case 'null':
|
||||
return null;
|
||||
case 'int':
|
||||
return 7;
|
||||
}
|
||||
};
|
||||
|
||||
$lazy = new LazyResolution($descriptor, $invalidTypeLoader);
|
||||
$value = $lazy->resolveType('null');
|
||||
$this->assertEquals(null, $value);
|
||||
|
||||
return $lazy;
|
||||
}
|
||||
|
||||
public function testLazyThrowsOnInvalidLoadedPossibleType() : void
|
||||
{
|
||||
$tmp = new InterfaceType(['name' => 'a', 'fields' => []]);
|
||||
$tmp = new InterfaceType(['name' => 'a', 'fields' => []]);
|
||||
$lazy = $this->createLazy();
|
||||
$this->expectException(InvariantViolation::class);
|
||||
$this->expectExceptionMessage('Lazy Type Resolution Error: Implementation null of interface a is expected to be instance of ObjectType, but got NULL');
|
||||
@ -580,7 +574,7 @@ class ResolutionTest extends TestCase
|
||||
|
||||
public function testLazyThrowsOnInvalidLoadedPossibleTypeWithInteger() : void
|
||||
{
|
||||
$tmp = new InterfaceType(['name' => 'b', 'fields' => []]);
|
||||
$tmp = new InterfaceType(['name' => 'b', 'fields' => []]);
|
||||
$lazy = $this->createLazy();
|
||||
$this->expectException(InvariantViolation::class);
|
||||
$this->expectExceptionMessage('Lazy Type Resolution Error: Expecting GraphQL Type instance, but got integer');
|
||||
|
@ -1,11 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Type;
|
||||
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ResolveInfoTest extends TestCase
|
||||
@ -13,53 +16,56 @@ class ResolveInfoTest extends TestCase
|
||||
public function testFieldSelection() : void
|
||||
{
|
||||
$image = new ObjectType([
|
||||
'name' => 'Image',
|
||||
'name' => 'Image',
|
||||
'fields' => [
|
||||
'url' => ['type' => Type::string()],
|
||||
'width' => ['type' => Type::int()],
|
||||
'height' => ['type' => Type::int()]
|
||||
]
|
||||
'url' => ['type' => Type::string()],
|
||||
'width' => ['type' => Type::int()],
|
||||
'height' => ['type' => Type::int()],
|
||||
],
|
||||
]);
|
||||
|
||||
$article = null;
|
||||
|
||||
$author = new ObjectType([
|
||||
'name' => 'Author',
|
||||
'fields' => function() use ($image, &$article) {
|
||||
'name' => 'Author',
|
||||
'fields' => function () use ($image, &$article) {
|
||||
return [
|
||||
'id' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'pic' => [ 'type' => $image, 'args' => [
|
||||
'width' => ['type' => Type::int()],
|
||||
'height' => ['type' => Type::int()]
|
||||
]],
|
||||
'id' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'pic' => [
|
||||
'type' => $image,
|
||||
'args' => [
|
||||
'width' => ['type' => Type::int()],
|
||||
'height' => ['type' => Type::int()],
|
||||
],
|
||||
],
|
||||
'recentArticle' => ['type' => $article],
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$reply = new ObjectType([
|
||||
'name' => 'Reply',
|
||||
'name' => 'Reply',
|
||||
'fields' => [
|
||||
'author' => ['type' => $author],
|
||||
'body' => ['type' => Type::string()]
|
||||
]
|
||||
'body' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$article = new ObjectType([
|
||||
'name' => 'Article',
|
||||
'name' => 'Article',
|
||||
'fields' => [
|
||||
'id' => ['type' => Type::string()],
|
||||
'id' => ['type' => Type::string()],
|
||||
'isPublished' => ['type' => Type::boolean()],
|
||||
'author' => ['type' => $author],
|
||||
'title' => ['type' => Type::string()],
|
||||
'body' => ['type' => Type::string()],
|
||||
'image' => ['type' => $image],
|
||||
'replies' => ['type' => Type::listOf($reply)]
|
||||
]
|
||||
'author' => ['type' => $author],
|
||||
'title' => ['type' => Type::string()],
|
||||
'body' => ['type' => Type::string()],
|
||||
'image' => ['type' => $image],
|
||||
'replies' => ['type' => Type::listOf($reply)],
|
||||
],
|
||||
]);
|
||||
|
||||
$doc = '
|
||||
$doc = '
|
||||
query Test {
|
||||
article {
|
||||
author {
|
||||
@ -100,59 +106,70 @@ class ResolveInfoTest extends TestCase
|
||||
}
|
||||
';
|
||||
$expectedDefaultSelection = [
|
||||
'author' => true,
|
||||
'image' => true,
|
||||
'replies' => true
|
||||
'author' => true,
|
||||
'image' => true,
|
||||
'replies' => true,
|
||||
];
|
||||
$expectedDeepSelection = [
|
||||
'author' => [
|
||||
$expectedDeepSelection = [
|
||||
'author' => [
|
||||
'name' => true,
|
||||
'pic' => [
|
||||
'url' => true,
|
||||
'width' => true
|
||||
]
|
||||
'pic' => [
|
||||
'url' => true,
|
||||
'width' => true,
|
||||
],
|
||||
],
|
||||
'image' => [
|
||||
'width' => true,
|
||||
'image' => [
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'url' => true
|
||||
'url' => true,
|
||||
],
|
||||
'replies' => [
|
||||
'body' => true,
|
||||
'body' => true,
|
||||
'author' => [
|
||||
'id' => true,
|
||||
'name' => true,
|
||||
'pic' => [
|
||||
'url' => true,
|
||||
'width' => true,
|
||||
'height' => true
|
||||
'id' => true,
|
||||
'name' => true,
|
||||
'pic' => [
|
||||
'url' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
],
|
||||
'recentArticle' => [
|
||||
'id' => true,
|
||||
'id' => true,
|
||||
'title' => true,
|
||||
'body' => true
|
||||
]
|
||||
]
|
||||
]
|
||||
'body' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$hasCalled = false;
|
||||
$hasCalled = false;
|
||||
$actualDefaultSelection = null;
|
||||
$actualDeepSelection = null;
|
||||
$actualDeepSelection = null;
|
||||
|
||||
$blogQuery = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'article' => [
|
||||
'type' => $article,
|
||||
'resolve' => function($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$actualDefaultSelection, &$actualDeepSelection) {
|
||||
$hasCalled = true;
|
||||
'type' => $article,
|
||||
'resolve' => function (
|
||||
$value,
|
||||
$args,
|
||||
$context,
|
||||
ResolveInfo $info
|
||||
) use (
|
||||
&$hasCalled,
|
||||
&
|
||||
$actualDefaultSelection,
|
||||
&$actualDeepSelection
|
||||
) {
|
||||
$hasCalled = true;
|
||||
$actualDefaultSelection = $info->getFieldSelection();
|
||||
$actualDeepSelection = $info->getFieldSelection(5);
|
||||
$actualDeepSelection = $info->getFieldSelection(5);
|
||||
|
||||
return null;
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $blogQuery]);
|
||||
@ -167,50 +184,53 @@ class ResolveInfoTest extends TestCase
|
||||
public function testMergedFragmentsFieldSelection() : void
|
||||
{
|
||||
$image = new ObjectType([
|
||||
'name' => 'Image',
|
||||
'name' => 'Image',
|
||||
'fields' => [
|
||||
'url' => ['type' => Type::string()],
|
||||
'width' => ['type' => Type::int()],
|
||||
'height' => ['type' => Type::int()]
|
||||
]
|
||||
'url' => ['type' => Type::string()],
|
||||
'width' => ['type' => Type::int()],
|
||||
'height' => ['type' => Type::int()],
|
||||
],
|
||||
]);
|
||||
|
||||
$article = null;
|
||||
|
||||
$author = new ObjectType([
|
||||
'name' => 'Author',
|
||||
'fields' => function() use ($image, &$article) {
|
||||
'name' => 'Author',
|
||||
'fields' => function () use ($image, &$article) {
|
||||
return [
|
||||
'id' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'pic' => [ 'type' => $image, 'args' => [
|
||||
'width' => ['type' => Type::int()],
|
||||
'height' => ['type' => Type::int()]
|
||||
]],
|
||||
'id' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::string()],
|
||||
'pic' => [
|
||||
'type' => $image,
|
||||
'args' => [
|
||||
'width' => ['type' => Type::int()],
|
||||
'height' => ['type' => Type::int()],
|
||||
],
|
||||
],
|
||||
'recentArticle' => ['type' => $article],
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$reply = new ObjectType([
|
||||
'name' => 'Reply',
|
||||
'name' => 'Reply',
|
||||
'fields' => [
|
||||
'author' => ['type' => $author],
|
||||
'body' => ['type' => Type::string()]
|
||||
]
|
||||
'body' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$article = new ObjectType([
|
||||
'name' => 'Article',
|
||||
'name' => 'Article',
|
||||
'fields' => [
|
||||
'id' => ['type' => Type::string()],
|
||||
'id' => ['type' => Type::string()],
|
||||
'isPublished' => ['type' => Type::boolean()],
|
||||
'author' => ['type' => $author],
|
||||
'title' => ['type' => Type::string()],
|
||||
'body' => ['type' => Type::string()],
|
||||
'image' => ['type' => $image],
|
||||
'replies' => ['type' => Type::listOf($reply)]
|
||||
]
|
||||
'author' => ['type' => $author],
|
||||
'title' => ['type' => Type::string()],
|
||||
'body' => ['type' => Type::string()],
|
||||
'image' => ['type' => $image],
|
||||
'replies' => ['type' => Type::listOf($reply)],
|
||||
],
|
||||
]);
|
||||
|
||||
$doc = '
|
||||
@ -264,53 +284,63 @@ class ResolveInfoTest extends TestCase
|
||||
';
|
||||
|
||||
$expectedDeepSelection = [
|
||||
'author' => [
|
||||
'author' => [
|
||||
'name' => true,
|
||||
'pic' => [
|
||||
'url' => true,
|
||||
'width' => true
|
||||
]
|
||||
'pic' => [
|
||||
'url' => true,
|
||||
'width' => true,
|
||||
],
|
||||
],
|
||||
'image' => [
|
||||
'width' => true,
|
||||
'image' => [
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'url' => true
|
||||
'url' => true,
|
||||
],
|
||||
'replies' => [
|
||||
'body' => true, //this would be missing if not for the fix https://github.com/webonyx/graphql-php/pull/98
|
||||
'body' => true, //this would be missing if not for the fix https://github.com/webonyx/graphql-php/pull/98
|
||||
'author' => [
|
||||
'id' => true,
|
||||
'name' => true,
|
||||
'pic' => [
|
||||
'url' => true,
|
||||
'width' => true,
|
||||
'height' => true
|
||||
'id' => true,
|
||||
'name' => true,
|
||||
'pic' => [
|
||||
'url' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
],
|
||||
'recentArticle' => [
|
||||
'id' => true,
|
||||
'id' => true,
|
||||
'title' => true,
|
||||
'body' => true
|
||||
]
|
||||
]
|
||||
]
|
||||
'body' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$hasCalled = false;
|
||||
$hasCalled = false;
|
||||
$actualDefaultSelection = null;
|
||||
$actualDeepSelection = null;
|
||||
$actualDeepSelection = null;
|
||||
|
||||
$blogQuery = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'article' => [
|
||||
'type' => $article,
|
||||
'resolve' => function($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$actualDeepSelection) {
|
||||
$hasCalled = true;
|
||||
'type' => $article,
|
||||
'resolve' => function (
|
||||
$value,
|
||||
$args,
|
||||
$context,
|
||||
ResolveInfo $info
|
||||
) use (
|
||||
&$hasCalled,
|
||||
&
|
||||
$actualDeepSelection
|
||||
) {
|
||||
$hasCalled = true;
|
||||
$actualDeepSelection = $info->getFieldSelection(5);
|
||||
|
||||
return null;
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $blogQuery]);
|
||||
@ -320,6 +350,4 @@ class ResolveInfoTest extends TestCase
|
||||
$this->assertEquals(['data' => ['article' => null]], $result);
|
||||
$this->assertEquals($expectedDeepSelection, $actualDeepSelection);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Type;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
@ -8,7 +11,6 @@ use PHPUnit\Framework\TestCase;
|
||||
class ScalarSerializationTest extends TestCase
|
||||
{
|
||||
// Type System: Scalar coercion
|
||||
|
||||
/**
|
||||
* @see it('serializes output int')
|
||||
*/
|
||||
@ -42,7 +44,6 @@ class ScalarSerializationTest extends TestCase
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Int cannot represent non-integer value: 1.1');
|
||||
$intType->serialize(1.1);
|
||||
|
||||
}
|
||||
|
||||
public function testSerializesOutputIntCannotRepresentNegativeFloat() : void
|
||||
@ -51,7 +52,6 @@ class ScalarSerializationTest extends TestCase
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Int cannot represent non-integer value: -1.1');
|
||||
$intType->serialize(-1.1);
|
||||
|
||||
}
|
||||
|
||||
public function testSerializesOutputIntCannotRepresentNumericString() : void
|
||||
@ -60,7 +60,6 @@ class ScalarSerializationTest extends TestCase
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: Int cannot represent non-integer value: "-1.1"');
|
||||
$intType->serialize('Int cannot represent non-integer value: "-1.1"');
|
||||
|
||||
}
|
||||
|
||||
public function testSerializesOutputIntCannotRepresentBiggerThan32Bits() : void
|
||||
@ -71,7 +70,6 @@ class ScalarSerializationTest extends TestCase
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 9876504321');
|
||||
$intType->serialize(9876504321);
|
||||
|
||||
}
|
||||
|
||||
public function testSerializesOutputIntCannotRepresentLowerThan32Bits() : void
|
||||
@ -104,7 +102,6 @@ class ScalarSerializationTest extends TestCase
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: one');
|
||||
$intType->serialize('one');
|
||||
|
||||
}
|
||||
|
||||
public function testSerializesOutputIntCannotRepresentEmptyString() : void
|
||||
@ -189,14 +186,13 @@ class ScalarSerializationTest extends TestCase
|
||||
{
|
||||
$boolType = Type::boolean();
|
||||
|
||||
$this->assertSame(true, $boolType->serialize('string'));
|
||||
$this->assertSame(false, $boolType->serialize(''));
|
||||
$this->assertSame(true, $boolType->serialize('1'));
|
||||
$this->assertSame(true, $boolType->serialize(1));
|
||||
$this->assertSame(false, $boolType->serialize(0));
|
||||
$this->assertSame(true, $boolType->serialize(true));
|
||||
$this->assertSame(false, $boolType->serialize(false));
|
||||
|
||||
$this->assertTrue($boolType->serialize('string'));
|
||||
$this->assertFalse($boolType->serialize(''));
|
||||
$this->assertTrue($boolType->serialize('1'));
|
||||
$this->assertTrue($boolType->serialize(1));
|
||||
$this->assertFalse($boolType->serialize(0));
|
||||
$this->assertTrue($boolType->serialize(true));
|
||||
$this->assertFalse($boolType->serialize(false));
|
||||
// TODO: how should it behave on '0'?
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Type;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
@ -12,14 +15,19 @@ use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SchemaTest extends TestCase
|
||||
{
|
||||
/** @var InterfaceType */
|
||||
private $interfaceType;
|
||||
|
||||
/** @var ObjectType */
|
||||
private $implementingType;
|
||||
|
||||
/** @var InputObjectType */
|
||||
private $directiveInputType;
|
||||
|
||||
/** @var InputObjectType */
|
||||
private $wrappedDirectiveInputType;
|
||||
|
||||
/** @var Directive */
|
||||
private $directive;
|
||||
|
||||
/** @var Schema */
|
||||
@ -28,20 +36,25 @@ class SchemaTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
$this->interfaceType = new InterfaceType([
|
||||
'name' => 'Interface',
|
||||
'name' => 'Interface',
|
||||
'fields' => ['fieldName' => ['type' => Type::string()]],
|
||||
]);
|
||||
|
||||
$this->implementingType = new ObjectType([
|
||||
'name' => 'Object',
|
||||
'name' => 'Object',
|
||||
'interfaces' => [$this->interfaceType],
|
||||
'fields' => ['fieldName' => ['type' => Type::string(), 'resolve' => function () {
|
||||
return '';
|
||||
}]],
|
||||
'fields' => [
|
||||
'fieldName' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function () {
|
||||
return '';
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->directiveInputType = new InputObjectType([
|
||||
'name' => 'DirInput',
|
||||
'name' => 'DirInput',
|
||||
'fields' => [
|
||||
'field' => [
|
||||
'type' => Type::string(),
|
||||
@ -50,7 +63,7 @@ class SchemaTest extends TestCase
|
||||
]);
|
||||
|
||||
$this->wrappedDirectiveInputType = new InputObjectType([
|
||||
'name' => 'WrappedDirInput',
|
||||
'name' => 'WrappedDirInput',
|
||||
'fields' => [
|
||||
'field' => [
|
||||
'type' => Type::string(),
|
||||
@ -59,10 +72,10 @@ class SchemaTest extends TestCase
|
||||
]);
|
||||
|
||||
$this->directive = new Directive([
|
||||
'name' => 'dir',
|
||||
'name' => 'dir',
|
||||
'locations' => ['OBJECT'],
|
||||
'args' => [
|
||||
'arg' => [
|
||||
'args' => [
|
||||
'arg' => [
|
||||
'type' => $this->directiveInputType,
|
||||
],
|
||||
'argList' => [
|
||||
@ -72,11 +85,11 @@ class SchemaTest extends TestCase
|
||||
]);
|
||||
|
||||
$this->schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'getObject' => [
|
||||
'type' => $this->interfaceType,
|
||||
'type' => $this->interfaceType,
|
||||
'resolve' => function () {
|
||||
return [];
|
||||
},
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
21
tests/Type/TestClasses/MyCustomType.php
Normal file
21
tests/Type/TestClasses/MyCustomType.php
Normal 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);
|
||||
}
|
||||
}
|
24
tests/Type/TestClasses/OtherCustom.php
Normal file
24
tests/Type/TestClasses/OtherCustom.php
Normal 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);
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Type;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Type;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
@ -9,52 +11,35 @@ use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function lcfirst;
|
||||
|
||||
class TypeLoaderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $mutation;
|
||||
|
||||
/**
|
||||
* @var InterfaceType
|
||||
*/
|
||||
/** @var InterfaceType */
|
||||
private $node;
|
||||
|
||||
/**
|
||||
* @var InterfaceType
|
||||
*/
|
||||
/** @var InterfaceType */
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $blogStory;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $postStoryMutation;
|
||||
|
||||
/**
|
||||
* @var InputObjectType
|
||||
*/
|
||||
/** @var InputObjectType */
|
||||
private $postStoryMutationInput;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
/** @var callable */
|
||||
private $typeLoader;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var string[] */
|
||||
private $calls;
|
||||
|
||||
public function setUp()
|
||||
@ -62,36 +47,41 @@ class TypeLoaderTest extends TestCase
|
||||
$this->calls = [];
|
||||
|
||||
$this->node = new InterfaceType([
|
||||
'name' => 'Node',
|
||||
'fields' => function() {
|
||||
'name' => 'Node',
|
||||
'fields' => function () {
|
||||
$this->calls[] = 'Node.fields';
|
||||
|
||||
return [
|
||||
'id' => Type::string()
|
||||
'id' => Type::string(),
|
||||
];
|
||||
},
|
||||
'resolveType' => function() {}
|
||||
'resolveType' => function () {
|
||||
},
|
||||
]);
|
||||
|
||||
$this->content = new InterfaceType([
|
||||
'name' => 'Content',
|
||||
'fields' => function() {
|
||||
'name' => 'Content',
|
||||
'fields' => function () {
|
||||
$this->calls[] = 'Content.fields';
|
||||
|
||||
return [
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
];
|
||||
},
|
||||
'resolveType' => function() {}
|
||||
'resolveType' => function () {
|
||||
},
|
||||
]);
|
||||
|
||||
$this->blogStory = new ObjectType([
|
||||
'name' => 'BlogStory',
|
||||
'name' => 'BlogStory',
|
||||
'interfaces' => [
|
||||
$this->node,
|
||||
$this->content
|
||||
$this->content,
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
$this->calls[] = 'BlogStory.fields';
|
||||
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
$this->content->getField('title'),
|
||||
@ -101,53 +91,56 @@ class TypeLoaderTest extends TestCase
|
||||
]);
|
||||
|
||||
$this->query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => function() {
|
||||
'name' => 'Query',
|
||||
'fields' => function () {
|
||||
$this->calls[] = 'Query.fields';
|
||||
|
||||
return [
|
||||
'latestContent' => $this->content,
|
||||
'node' => $this->node,
|
||||
'node' => $this->node,
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->mutation = new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'fields' => function() {
|
||||
'name' => 'Mutation',
|
||||
'fields' => function () {
|
||||
$this->calls[] = 'Mutation.fields';
|
||||
|
||||
return [
|
||||
'postStory' => [
|
||||
'type' => $this->postStoryMutation,
|
||||
'args' => [
|
||||
'input' => Type::nonNull($this->postStoryMutationInput),
|
||||
'clientRequestId' => Type::string()
|
||||
]
|
||||
]
|
||||
'input' => Type::nonNull($this->postStoryMutationInput),
|
||||
'clientRequestId' => Type::string(),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->postStoryMutation = new ObjectType([
|
||||
'name' => 'PostStoryMutation',
|
||||
'name' => 'PostStoryMutation',
|
||||
'fields' => [
|
||||
'story' => $this->blogStory
|
||||
]
|
||||
'story' => $this->blogStory,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->postStoryMutationInput = new InputObjectType([
|
||||
'name' => 'PostStoryMutationInput',
|
||||
'name' => 'PostStoryMutationInput',
|
||||
'fields' => [
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => Type::id(),
|
||||
'category' => Type::id()
|
||||
]
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => Type::id(),
|
||||
'category' => Type::id(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->typeLoader = function($name) {
|
||||
$this->typeLoader = function ($name) {
|
||||
$this->calls[] = $name;
|
||||
$prop = lcfirst($name);
|
||||
return isset($this->{$prop}) ? $this->{$prop} : null;
|
||||
$prop = lcfirst($name);
|
||||
|
||||
return $this->{$prop} ?? null;
|
||||
};
|
||||
}
|
||||
|
||||
@ -155,11 +148,12 @@ class TypeLoaderTest extends TestCase
|
||||
{
|
||||
$this->expectNotToPerformAssertions();
|
||||
new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['a' => Type::string()]
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['a' => Type::string()],
|
||||
]),
|
||||
'typeLoader' => function() {}
|
||||
'typeLoader' => function () {
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@ -169,20 +163,20 @@ class TypeLoaderTest extends TestCase
|
||||
$this->expectExceptionMessage('Schema type loader must be callable if provided but got: []');
|
||||
|
||||
new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['a' => Type::string()]
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['a' => Type::string()],
|
||||
]),
|
||||
'typeLoader' => []
|
||||
'typeLoader' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testWorksWithoutTypeLoader() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->query,
|
||||
'query' => $this->query,
|
||||
'mutation' => $this->mutation,
|
||||
'types' => [$this->blogStory]
|
||||
'types' => [$this->blogStory],
|
||||
]);
|
||||
|
||||
$expected = [
|
||||
@ -203,12 +197,12 @@ class TypeLoaderTest extends TestCase
|
||||
$this->assertSame($this->postStoryMutationInput, $schema->getType('PostStoryMutationInput'));
|
||||
|
||||
$expectedTypeMap = [
|
||||
'Query' => $this->query,
|
||||
'Mutation' => $this->mutation,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'BlogStory' => $this->blogStory,
|
||||
'Query' => $this->query,
|
||||
'Mutation' => $this->mutation,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'BlogStory' => $this->blogStory,
|
||||
'PostStoryMutationInput' => $this->postStoryMutationInput,
|
||||
];
|
||||
|
||||
@ -218,9 +212,9 @@ class TypeLoaderTest extends TestCase
|
||||
public function testWorksWithTypeLoader() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->query,
|
||||
'mutation' => $this->mutation,
|
||||
'typeLoader' => $this->typeLoader
|
||||
'query' => $this->query,
|
||||
'mutation' => $this->mutation,
|
||||
'typeLoader' => $this->typeLoader,
|
||||
]);
|
||||
$this->assertEquals([], $this->calls);
|
||||
|
||||
@ -244,8 +238,8 @@ class TypeLoaderTest extends TestCase
|
||||
public function testOnlyCallsLoaderOnce() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->query,
|
||||
'typeLoader' => $this->typeLoader
|
||||
'query' => $this->query,
|
||||
'typeLoader' => $this->typeLoader,
|
||||
]);
|
||||
|
||||
$schema->getType('Node');
|
||||
@ -258,8 +252,9 @@ class TypeLoaderTest extends TestCase
|
||||
public function testFailsOnNonExistentType() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->query,
|
||||
'typeLoader' => function() {}
|
||||
'query' => $this->query,
|
||||
'typeLoader' => function () {
|
||||
},
|
||||
]);
|
||||
|
||||
$this->expectException(InvariantViolation::class);
|
||||
@ -271,10 +266,10 @@ class TypeLoaderTest extends TestCase
|
||||
public function testFailsOnNonType() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->query,
|
||||
'typeLoader' => function() {
|
||||
'query' => $this->query,
|
||||
'typeLoader' => function () {
|
||||
return new \stdClass();
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->expectException(InvariantViolation::class);
|
||||
@ -286,10 +281,10 @@ class TypeLoaderTest extends TestCase
|
||||
public function testFailsOnInvalidLoad() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->query,
|
||||
'typeLoader' => function() {
|
||||
'query' => $this->query,
|
||||
'typeLoader' => function () {
|
||||
return $this->content;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->expectException(InvariantViolation::class);
|
||||
@ -301,10 +296,10 @@ class TypeLoaderTest extends TestCase
|
||||
public function testPassesThroughAnExceptionInLoader() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->query,
|
||||
'typeLoader' => function() {
|
||||
throw new \Exception("This is the exception we are looking for");
|
||||
}
|
||||
'query' => $this->query,
|
||||
'typeLoader' => function () {
|
||||
throw new \Exception('This is the exception we are looking for');
|
||||
},
|
||||
]);
|
||||
|
||||
$this->expectException(\Throwable::class);
|
||||
@ -316,14 +311,14 @@ class TypeLoaderTest extends TestCase
|
||||
public function testReturnsIdenticalResults() : void
|
||||
{
|
||||
$withoutLoader = new Schema([
|
||||
'query' => $this->query,
|
||||
'mutation' => $this->mutation
|
||||
'query' => $this->query,
|
||||
'mutation' => $this->mutation,
|
||||
]);
|
||||
|
||||
$withLoader = new Schema([
|
||||
'query' => $this->query,
|
||||
'mutation' => $this->mutation,
|
||||
'typeLoader' => $this->typeLoader
|
||||
'query' => $this->query,
|
||||
'mutation' => $this->mutation,
|
||||
'typeLoader' => $this->typeLoader,
|
||||
]);
|
||||
|
||||
$this->assertSame($withoutLoader->getQueryType(), $withLoader->getQueryType());
|
||||
@ -335,9 +330,9 @@ class TypeLoaderTest extends TestCase
|
||||
public function testSkipsLoaderForInternalTypes() : void
|
||||
{
|
||||
$schema = new Schema([
|
||||
'query' => $this->query,
|
||||
'mutation' => $this->mutation,
|
||||
'typeLoader' => $this->typeLoader
|
||||
'query' => $this->query,
|
||||
'mutation' => $this->mutation,
|
||||
'typeLoader' => $this->typeLoader,
|
||||
]);
|
||||
|
||||
$type = $schema->getType('ID');
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
@ -9,7 +12,6 @@ use PHPUnit\Framework\TestCase;
|
||||
class AssertValidNameTest extends TestCase
|
||||
{
|
||||
// Describe: assertValidName()
|
||||
|
||||
/**
|
||||
* @see it('throws for use of leading double underscores')
|
||||
*/
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Language\AST\BooleanValueNode;
|
||||
@ -16,10 +19,12 @@ use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Utils\AST;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use stdClass;
|
||||
|
||||
class AstFromValueTest extends TestCase
|
||||
{
|
||||
// Describe: astFromValue
|
||||
/** @var stdClass */
|
||||
private $complexValue;
|
||||
|
||||
/**
|
||||
* @see it('converts boolean values to ASTs')
|
||||
@ -31,8 +36,14 @@ class AstFromValueTest extends TestCase
|
||||
$this->assertEquals(new NullValueNode([]), AST::astFromValue(null, Type::boolean()));
|
||||
$this->assertEquals(new BooleanValueNode(['value' => false]), AST::astFromValue(0, Type::boolean()));
|
||||
$this->assertEquals(new BooleanValueNode(['value' => true]), AST::astFromValue(1, Type::boolean()));
|
||||
$this->assertEquals(new BooleanValueNode(['value' => false]), AST::astFromValue(0, Type::nonNull(Type::boolean())));
|
||||
$this->assertEquals(null, AST::astFromValue(null, Type::nonNull(Type::boolean()))); // Note: null means that AST cannot
|
||||
$this->assertEquals(
|
||||
new BooleanValueNode(['value' => false]),
|
||||
AST::astFromValue(0, Type::nonNull(Type::boolean()))
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
AST::astFromValue(null, Type::nonNull(Type::boolean()))
|
||||
); // Note: null means that AST cannot
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,7 +70,10 @@ class AstFromValueTest extends TestCase
|
||||
{
|
||||
$this->expectException(\Throwable::class);
|
||||
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 1.0E+40');
|
||||
AST::astFromValue(1e40, Type::int()); // Note: js version will produce 1e+40, both values are valid GraphQL floats
|
||||
AST::astFromValue(
|
||||
1e40,
|
||||
Type::int()
|
||||
); // Note: js version will produce 1e+40, both values are valid GraphQL floats
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,7 +126,7 @@ class AstFromValueTest extends TestCase
|
||||
*/
|
||||
public function testDoesNotConvertsNonNullValuestoNullValue() : void
|
||||
{
|
||||
$this->assertSame(null, AST::astFromValue(null, Type::nonNull(Type::boolean())));
|
||||
$this->assertNull(AST::astFromValue(null, Type::nonNull(Type::boolean())));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,13 +135,41 @@ class AstFromValueTest extends TestCase
|
||||
public function testConvertsStringValuesToEnumASTsIfPossible() : void
|
||||
{
|
||||
$this->assertEquals(new EnumValueNode(['value' => 'HELLO']), AST::astFromValue('HELLO', $this->myEnum()));
|
||||
$this->assertEquals(new EnumValueNode(['value' => 'COMPLEX']), AST::astFromValue($this->complexValue(), $this->myEnum()));
|
||||
$this->assertEquals(
|
||||
new EnumValueNode(['value' => 'COMPLEX']),
|
||||
AST::astFromValue($this->complexValue(), $this->myEnum())
|
||||
);
|
||||
|
||||
// Note: case sensitive
|
||||
$this->assertEquals(null, AST::astFromValue('hello', $this->myEnum()));
|
||||
$this->assertNull(AST::astFromValue('hello', $this->myEnum()));
|
||||
|
||||
// Note: Not a valid enum value
|
||||
$this->assertEquals(null, AST::astFromValue('VALUE', $this->myEnum()));
|
||||
$this->assertNull(AST::astFromValue('VALUE', $this->myEnum()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EnumType
|
||||
*/
|
||||
private function myEnum()
|
||||
{
|
||||
return new EnumType([
|
||||
'name' => 'MyEnum',
|
||||
'values' => [
|
||||
'HELLO' => [],
|
||||
'GOODBYE' => [],
|
||||
'COMPLEX' => ['value' => $this->complexValue()],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function complexValue()
|
||||
{
|
||||
if (! $this->complexValue) {
|
||||
$this->complexValue = new \stdClass();
|
||||
$this->complexValue->someArbitrary = 'complexValue';
|
||||
}
|
||||
|
||||
return $this->complexValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,8 +180,8 @@ class AstFromValueTest extends TestCase
|
||||
$value1 = new ListValueNode([
|
||||
'values' => [
|
||||
new StringValueNode(['value' => 'FOO']),
|
||||
new StringValueNode(['value' => 'BAR'])
|
||||
]
|
||||
new StringValueNode(['value' => 'BAR']),
|
||||
],
|
||||
]);
|
||||
$this->assertEquals($value1, AST::astFromValue(['FOO', 'BAR'], Type::listOf(Type::string())));
|
||||
|
||||
@ -147,7 +189,7 @@ class AstFromValueTest extends TestCase
|
||||
'values' => [
|
||||
new EnumValueNode(['value' => 'HELLO']),
|
||||
new EnumValueNode(['value' => 'GOODBYE']),
|
||||
]
|
||||
],
|
||||
]);
|
||||
$this->assertEquals($value2, AST::astFromValue(['HELLO', 'GOODBYE'], Type::listOf($this->myEnum())));
|
||||
}
|
||||
@ -157,7 +199,10 @@ class AstFromValueTest extends TestCase
|
||||
*/
|
||||
public function testConvertsListSingletons() : void
|
||||
{
|
||||
$this->assertEquals(new StringValueNode(['value' => 'FOO']), AST::astFromValue('FOO', Type::listOf(Type::string())));
|
||||
$this->assertEquals(
|
||||
new StringValueNode(['value' => 'FOO']),
|
||||
AST::astFromValue('FOO', Type::listOf(Type::string()))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,18 +211,18 @@ class AstFromValueTest extends TestCase
|
||||
public function testConvertsInputObjects() : void
|
||||
{
|
||||
$inputObj = new InputObjectType([
|
||||
'name' => 'MyInputObj',
|
||||
'name' => 'MyInputObj',
|
||||
'fields' => [
|
||||
'foo' => Type::float(),
|
||||
'bar' => $this->myEnum()
|
||||
]
|
||||
'bar' => $this->myEnum(),
|
||||
],
|
||||
]);
|
||||
|
||||
$expected = new ObjectValueNode([
|
||||
'fields' => [
|
||||
$this->objectField('foo', new IntValueNode(['value' => '3'])),
|
||||
$this->objectField('bar', new EnumValueNode(['value' => 'HELLO']))
|
||||
]
|
||||
$this->objectField('bar', new EnumValueNode(['value' => 'HELLO'])),
|
||||
],
|
||||
]);
|
||||
|
||||
$data = ['foo' => 3, 'bar' => 'HELLO'];
|
||||
@ -185,62 +230,38 @@ class AstFromValueTest extends TestCase
|
||||
$this->assertEquals($expected, AST::astFromValue((object) $data, $inputObj));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return ObjectFieldNode
|
||||
*/
|
||||
private function objectField(string $name, $value)
|
||||
{
|
||||
return new ObjectFieldNode([
|
||||
'name' => new NameNode(['value' => $name]),
|
||||
'value' => $value,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('converts input objects with explicit nulls')
|
||||
*/
|
||||
public function testConvertsInputObjectsWithExplicitNulls() : void
|
||||
{
|
||||
$inputObj = new InputObjectType([
|
||||
'name' => 'MyInputObj',
|
||||
'name' => 'MyInputObj',
|
||||
'fields' => [
|
||||
'foo' => Type::float(),
|
||||
'bar' => $this->myEnum()
|
||||
]
|
||||
'bar' => $this->myEnum(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(new ObjectValueNode([
|
||||
$this->assertEquals(
|
||||
new ObjectValueNode([
|
||||
'fields' => [
|
||||
$this->objectField('foo', new NullValueNode([]))
|
||||
]
|
||||
]), AST::astFromValue(['foo' => null], $inputObj));
|
||||
}
|
||||
|
||||
private $complexValue;
|
||||
|
||||
private function complexValue()
|
||||
{
|
||||
if (!$this->complexValue) {
|
||||
$this->complexValue = new \stdClass();
|
||||
$this->complexValue->someArbitrary = 'complexValue';
|
||||
}
|
||||
return $this->complexValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EnumType
|
||||
*/
|
||||
private function myEnum()
|
||||
{
|
||||
return new EnumType([
|
||||
'name' => 'MyEnum',
|
||||
'values' => [
|
||||
'HELLO' => [],
|
||||
'GOODBYE' => [],
|
||||
'COMPLEX' => ['value' => $this->complexValue()]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $value
|
||||
* @return ObjectFieldNode
|
||||
*/
|
||||
private function objectField($name, $value)
|
||||
{
|
||||
return new ObjectFieldNode([
|
||||
'name' => new NameNode(['value' => $name]),
|
||||
'value' => $value
|
||||
]);
|
||||
$this->objectField('foo', new NullValueNode([])),
|
||||
],
|
||||
]),
|
||||
AST::astFromValue(['foo' => null], $inputObj)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Language\Parser;
|
||||
@ -8,14 +11,6 @@ use PHPUnit\Framework\TestCase;
|
||||
class AstFromValueUntypedTest extends TestCase
|
||||
{
|
||||
// Describe: valueFromASTUntyped
|
||||
|
||||
private function assertTestCase($valueText, $expected, array $variables = null) {
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
AST::valueFromASTUntyped(Parser::parseValue($valueText), $variables)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('parses simple values')
|
||||
*/
|
||||
@ -29,6 +24,17 @@ class AstFromValueUntypedTest extends TestCase
|
||||
$this->assertTestCase('abc123', 'abc123');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[]|null $variables
|
||||
*/
|
||||
private function assertTestCase($valueText, $expected, ?array $variables = null) : void
|
||||
{
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
AST::valueFromASTUntyped(Parser::parseValue($valueText), $variables)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('parses lists of values')
|
||||
*/
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
@ -8,24 +11,19 @@ use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Utils\BuildSchema;
|
||||
use GraphQL\Utils\SchemaPrinter;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
|
||||
class BuildSchemaTest extends TestCase
|
||||
{
|
||||
// Describe: Schema Builder
|
||||
|
||||
private function cycleOutput($body, $options = [])
|
||||
{
|
||||
$ast = Parser::parse($body);
|
||||
$schema = BuildSchema::buildAST($ast, null, $options);
|
||||
return "\n" . SchemaPrinter::doPrint($schema, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('can use built schema for limited execution')
|
||||
*/
|
||||
@ -46,16 +44,16 @@ class BuildSchemaTest extends TestCase
|
||||
*/
|
||||
public function testBuildSchemaDirectlyFromSource() : void
|
||||
{
|
||||
$schema = BuildSchema::build("
|
||||
$schema = BuildSchema::build('
|
||||
type Query {
|
||||
add(x: Int, y: Int): Int
|
||||
}
|
||||
");
|
||||
');
|
||||
|
||||
$root = [
|
||||
'add' => function ($root, $args) {
|
||||
return $args['x'] + $args['y'];
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
$result = GraphQL::executeQuery(
|
||||
@ -71,7 +69,7 @@ class BuildSchemaTest extends TestCase
|
||||
*/
|
||||
public function testSimpleType() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type HelloScalars {
|
||||
str: String
|
||||
int: Int
|
||||
@ -84,12 +82,20 @@ type HelloScalars {
|
||||
$this->assertEquals($output, $body);
|
||||
}
|
||||
|
||||
private function cycleOutput($body, $options = [])
|
||||
{
|
||||
$ast = Parser::parse($body);
|
||||
$schema = BuildSchema::buildAST($ast, null, $options);
|
||||
|
||||
return "\n" . SchemaPrinter::doPrint($schema, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('With directives')
|
||||
*/
|
||||
public function testWithDirectives() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
directive @foo(arg: Int) on FIELD
|
||||
|
||||
type Query {
|
||||
@ -105,7 +111,7 @@ type Query {
|
||||
*/
|
||||
public function testSupportsDescriptions() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
"""This is a directive"""
|
||||
directive @foo(
|
||||
"""It has an argument"""
|
||||
@ -137,7 +143,7 @@ type Query {
|
||||
*/
|
||||
public function testSupportsOptionForCommentDescriptions() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
# This is a directive
|
||||
directive @foo(
|
||||
# It has an argument
|
||||
@ -159,7 +165,7 @@ type Query {
|
||||
str: String
|
||||
}
|
||||
';
|
||||
$output = $this->cycleOutput($body, [ 'commentDescriptions' => true ]);
|
||||
$output = $this->cycleOutput($body, ['commentDescriptions' => true]);
|
||||
$this->assertEquals($body, $output);
|
||||
}
|
||||
|
||||
@ -168,7 +174,7 @@ type Query {
|
||||
*/
|
||||
public function testMaintainsSkipAndInclude() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type Query {
|
||||
str: String
|
||||
}
|
||||
@ -185,7 +191,7 @@ type Query {
|
||||
*/
|
||||
public function testOverridingDirectivesExcludesSpecified() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
directive @skip on FIELD
|
||||
directive @include on FIELD
|
||||
directive @deprecated on FIELD_DEFINITION
|
||||
@ -206,7 +212,7 @@ type Query {
|
||||
*/
|
||||
public function testAddingDirectivesMaintainsSkipAndInclude() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
directive @foo(arg: Int) on FIELD
|
||||
|
||||
type Query {
|
||||
@ -225,7 +231,7 @@ type Query {
|
||||
*/
|
||||
public function testTypeModifiers() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type HelloScalars {
|
||||
nonNullStr: String!
|
||||
listOfStrs: [String]
|
||||
@ -243,7 +249,7 @@ type HelloScalars {
|
||||
*/
|
||||
public function testRecursiveType() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type Query {
|
||||
str: String
|
||||
recurse: Query
|
||||
@ -258,7 +264,7 @@ type Query {
|
||||
*/
|
||||
public function testTwoTypesCircular() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
schema {
|
||||
query: TypeOne
|
||||
}
|
||||
@ -282,7 +288,7 @@ type TypeTwo {
|
||||
*/
|
||||
public function testSingleArgumentField() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type Query {
|
||||
str(int: Int): String
|
||||
floatToStr(float: Float): String
|
||||
@ -300,7 +306,7 @@ type Query {
|
||||
*/
|
||||
public function testSimpleTypeWithMultipleArguments() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type Query {
|
||||
str(int: Int, bool: Boolean): String
|
||||
}
|
||||
@ -314,7 +320,7 @@ type Query {
|
||||
*/
|
||||
public function testSimpleTypeWithInterface() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type Query implements WorldInterface {
|
||||
str: String
|
||||
}
|
||||
@ -332,7 +338,7 @@ interface WorldInterface {
|
||||
*/
|
||||
public function testSimpleOutputEnum() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
enum Hello {
|
||||
WORLD
|
||||
}
|
||||
@ -350,7 +356,7 @@ type Query {
|
||||
*/
|
||||
public function testSimpleInputEnum() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
enum Hello {
|
||||
WORLD
|
||||
}
|
||||
@ -368,7 +374,7 @@ type Query {
|
||||
*/
|
||||
public function testMultipleValueEnum() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
enum Hello {
|
||||
WO
|
||||
RLD
|
||||
@ -387,7 +393,7 @@ type Query {
|
||||
*/
|
||||
public function testSimpleUnion() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
union Hello = World
|
||||
|
||||
type Query {
|
||||
@ -407,7 +413,7 @@ type World {
|
||||
*/
|
||||
public function testMultipleUnion() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
union Hello = WorldOne | WorldTwo
|
||||
|
||||
type Query {
|
||||
@ -431,7 +437,7 @@ type WorldTwo {
|
||||
*/
|
||||
public function testSpecifyingUnionTypeUsingTypename() : void
|
||||
{
|
||||
$schema = BuildSchema::buildAST(Parser::parse('
|
||||
$schema = BuildSchema::buildAST(Parser::parse('
|
||||
type Query {
|
||||
fruits: [Fruit]
|
||||
}
|
||||
@ -446,7 +452,7 @@ type WorldTwo {
|
||||
length: Int
|
||||
}
|
||||
'));
|
||||
$query = '
|
||||
$query = '
|
||||
{
|
||||
fruits {
|
||||
... on Apple {
|
||||
@ -458,25 +464,25 @@ type WorldTwo {
|
||||
}
|
||||
}
|
||||
';
|
||||
$root = [
|
||||
$root = [
|
||||
'fruits' => [
|
||||
[
|
||||
'color' => 'green',
|
||||
'color' => 'green',
|
||||
'__typename' => 'Apple',
|
||||
],
|
||||
[
|
||||
'length' => 5,
|
||||
'length' => 5,
|
||||
'__typename' => 'Banana',
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
$expected = [
|
||||
'data' => [
|
||||
'fruits' => [
|
||||
['color' => 'green'],
|
||||
['length' => 5],
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = GraphQL::executeQuery($schema, $query, $root);
|
||||
@ -488,7 +494,7 @@ type WorldTwo {
|
||||
*/
|
||||
public function testSpecifyingInterfaceUsingTypename() : void
|
||||
{
|
||||
$schema = BuildSchema::buildAST(Parser::parse('
|
||||
$schema = BuildSchema::buildAST(Parser::parse('
|
||||
type Query {
|
||||
characters: [Character]
|
||||
}
|
||||
@ -507,7 +513,7 @@ type WorldTwo {
|
||||
primaryFunction: String
|
||||
}
|
||||
'));
|
||||
$query = '
|
||||
$query = '
|
||||
{
|
||||
characters {
|
||||
name
|
||||
@ -520,27 +526,27 @@ type WorldTwo {
|
||||
}
|
||||
}
|
||||
';
|
||||
$root = [
|
||||
$root = [
|
||||
'characters' => [
|
||||
[
|
||||
'name' => 'Han Solo',
|
||||
'name' => 'Han Solo',
|
||||
'totalCredits' => 10,
|
||||
'__typename' => 'Human',
|
||||
'__typename' => 'Human',
|
||||
],
|
||||
[
|
||||
'name' => 'R2-D2',
|
||||
'name' => 'R2-D2',
|
||||
'primaryFunction' => 'Astromech',
|
||||
'__typename' => 'Droid',
|
||||
]
|
||||
]
|
||||
'__typename' => 'Droid',
|
||||
],
|
||||
],
|
||||
];
|
||||
$expected = [
|
||||
'data' => [
|
||||
'characters' => [
|
||||
['name' => 'Han Solo', 'totalCredits' => 10],
|
||||
['name' => 'R2-D2', 'primaryFunction' => 'Astromech'],
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = GraphQL::executeQuery($schema, $query, $root);
|
||||
@ -552,7 +558,7 @@ type WorldTwo {
|
||||
*/
|
||||
public function testCustomScalar() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
scalar CustomScalar
|
||||
|
||||
type Query {
|
||||
@ -568,7 +574,7 @@ type Query {
|
||||
*/
|
||||
public function testInputObject() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
input Input {
|
||||
int: Int
|
||||
}
|
||||
@ -586,7 +592,7 @@ type Query {
|
||||
*/
|
||||
public function testSimpleArgumentFieldWithDefault() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type Query {
|
||||
str(int: Int = 2): String
|
||||
}
|
||||
@ -600,7 +606,7 @@ type Query {
|
||||
*/
|
||||
public function testCustomScalarArgumentFieldWithDefault() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
scalar CustomScalar
|
||||
|
||||
type Query {
|
||||
@ -616,7 +622,7 @@ type Query {
|
||||
*/
|
||||
public function testSimpleTypeWithMutation() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
schema {
|
||||
query: HelloScalars
|
||||
mutation: Mutation
|
||||
@ -641,7 +647,7 @@ type Mutation {
|
||||
*/
|
||||
public function testSimpleTypeWithSubscription() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
schema {
|
||||
query: HelloScalars
|
||||
subscription: Subscription
|
||||
@ -666,7 +672,7 @@ type Subscription {
|
||||
*/
|
||||
public function testUnreferencedTypeImplementingReferencedInterface() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type Concrete implements Iface {
|
||||
key: String
|
||||
}
|
||||
@ -688,7 +694,7 @@ type Query {
|
||||
*/
|
||||
public function testUnreferencedTypeImplementingReferencedUnion() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
type Concrete {
|
||||
key: String
|
||||
}
|
||||
@ -708,7 +714,7 @@ union Union = Concrete
|
||||
*/
|
||||
public function testSupportsDeprecated() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
enum MyEnum {
|
||||
VALUE
|
||||
OLD_VALUE @deprecated
|
||||
@ -724,7 +730,7 @@ type Query {
|
||||
$output = $this->cycleOutput($body);
|
||||
$this->assertEquals($output, $body);
|
||||
|
||||
$ast = Parser::parse($body);
|
||||
$ast = Parser::parse($body);
|
||||
$schema = BuildSchema::buildAST($ast);
|
||||
|
||||
/** @var EnumType $myEnum */
|
||||
@ -785,15 +791,15 @@ type Query {
|
||||
|
||||
directive @test(arg: TestScalar) on FIELD
|
||||
');
|
||||
$schema = BuildSchema::buildAST($schemaAST);
|
||||
$schema = BuildSchema::buildAST($schemaAST);
|
||||
/** @var ObjectType $query */
|
||||
$query = $schema->getType('Query');
|
||||
$testInput = $schema->getType('TestInput');
|
||||
$testEnum = $schema->getType('TestEnum');
|
||||
$testUnion = $schema->getType('TestUnion');
|
||||
$query = $schema->getType('Query');
|
||||
$testInput = $schema->getType('TestInput');
|
||||
$testEnum = $schema->getType('TestEnum');
|
||||
$testUnion = $schema->getType('TestUnion');
|
||||
$testInterface = $schema->getType('TestInterface');
|
||||
$testType = $schema->getType('TestType');
|
||||
$testScalar = $schema->getType('TestScalar');
|
||||
$testType = $schema->getType('TestType');
|
||||
$testScalar = $schema->getType('TestScalar');
|
||||
$testDirective = $schema->getDirective('test');
|
||||
|
||||
$restoredIDL = SchemaPrinter::doPrint(BuildSchema::build(
|
||||
@ -813,9 +819,15 @@ type Query {
|
||||
$testField = $query->getField('testField');
|
||||
$this->assertEquals('testField(testArg: TestInput): TestUnion', Printer::doPrint($testField->astNode));
|
||||
$this->assertEquals('testArg: TestInput', Printer::doPrint($testField->args[0]->astNode));
|
||||
$this->assertEquals('testInputField: TestEnum', Printer::doPrint($testInput->getField('testInputField')->astNode));
|
||||
$this->assertEquals(
|
||||
'testInputField: TestEnum',
|
||||
Printer::doPrint($testInput->getField('testInputField')->astNode)
|
||||
);
|
||||
$this->assertEquals('TEST_VALUE', Printer::doPrint($testEnum->getValue('TEST_VALUE')->astNode));
|
||||
$this->assertEquals('interfaceField: String', Printer::doPrint($testInterface->getField('interfaceField')->astNode));
|
||||
$this->assertEquals(
|
||||
'interfaceField: String',
|
||||
Printer::doPrint($testInterface->getField('interfaceField')->astNode)
|
||||
);
|
||||
$this->assertEquals('interfaceField: String', Printer::doPrint($testType->getField('interfaceField')->astNode));
|
||||
$this->assertEquals('arg: TestScalar', Printer::doPrint($testDirective->args[0]->astNode));
|
||||
}
|
||||
@ -893,7 +905,7 @@ type Hello {
|
||||
bar: Bar
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
BuildSchema::buildAST($doc);
|
||||
}
|
||||
|
||||
@ -918,7 +930,7 @@ type Yellow {
|
||||
isColor: Boolean
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
BuildSchema::buildAST($doc);
|
||||
}
|
||||
|
||||
@ -944,7 +956,7 @@ type Yellow {
|
||||
isColor: Boolean
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
BuildSchema::buildAST($doc);
|
||||
}
|
||||
|
||||
@ -970,7 +982,7 @@ type Yellow {
|
||||
isColor: Boolean
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
BuildSchema::buildAST($doc);
|
||||
}
|
||||
|
||||
@ -981,7 +993,7 @@ type Yellow {
|
||||
{
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Type "Bar" not found in document.');
|
||||
$body = '
|
||||
$body = '
|
||||
schema {
|
||||
query: Hello
|
||||
}
|
||||
@ -990,7 +1002,7 @@ type Hello {
|
||||
bar: Bar
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
$schema = BuildSchema::buildAST($doc);
|
||||
$schema->getTypeMap();
|
||||
}
|
||||
@ -1002,12 +1014,12 @@ type Hello {
|
||||
{
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Type "Bar" not found in document.');
|
||||
$body = '
|
||||
$body = '
|
||||
type Query implements Bar {
|
||||
field: String
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
$schema = BuildSchema::buildAST($doc);
|
||||
$schema->getTypeMap();
|
||||
}
|
||||
@ -1019,11 +1031,11 @@ type Query implements Bar {
|
||||
{
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Type "Bar" not found in document.');
|
||||
$body = '
|
||||
$body = '
|
||||
union TestUnion = Bar
|
||||
type Query { testUnion: TestUnion }
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
$schema = BuildSchema::buildAST($doc);
|
||||
$schema->getTypeMap();
|
||||
}
|
||||
@ -1044,7 +1056,7 @@ type Hello {
|
||||
str: String
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
BuildSchema::buildAST($doc);
|
||||
}
|
||||
|
||||
@ -1065,7 +1077,7 @@ type Hello {
|
||||
str: String
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
BuildSchema::buildAST($doc);
|
||||
}
|
||||
|
||||
@ -1091,7 +1103,7 @@ type Wat {
|
||||
str: String
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
BuildSchema::buildAST($doc);
|
||||
}
|
||||
|
||||
@ -1109,7 +1121,7 @@ schema {
|
||||
|
||||
query Foo { field }
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
BuildSchema::buildAST($doc);
|
||||
}
|
||||
|
||||
@ -1127,7 +1139,7 @@ schema {
|
||||
|
||||
fragment Foo on Type { field }
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
BuildSchema::buildAST($doc);
|
||||
}
|
||||
|
||||
@ -1149,7 +1161,7 @@ type Repeated {
|
||||
id: String
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Type "Repeated" was defined more than once.');
|
||||
@ -1179,14 +1191,15 @@ interface Hello {
|
||||
world: String
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
|
||||
$decorated = [];
|
||||
$calls = [];
|
||||
$calls = [];
|
||||
|
||||
$typeConfigDecorator = function($defaultConfig, $node, $allNodesMap) use (&$decorated, &$calls) {
|
||||
$typeConfigDecorator = function ($defaultConfig, $node, $allNodesMap) use (&$decorated, &$calls) {
|
||||
$decorated[] = $defaultConfig['name'];
|
||||
$calls[] = [$defaultConfig, $node, $allNodesMap];
|
||||
$calls[] = [$defaultConfig, $node, $allNodesMap];
|
||||
|
||||
return ['description' => 'My description of ' . $node->name->value] + $defaultConfig;
|
||||
};
|
||||
|
||||
@ -1204,19 +1217,21 @@ interface Hello {
|
||||
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
|
||||
$this->assertEquals('My description of Query', $schema->getType('Query')->description);
|
||||
|
||||
|
||||
list($defaultConfig, $node, $allNodesMap) = $calls[1];
|
||||
$this->assertInstanceOf(EnumTypeDefinitionNode::class, $node);
|
||||
$this->assertEquals('Color', $defaultConfig['name']);
|
||||
$enumValue = [
|
||||
'description' => '',
|
||||
'deprecationReason' => ''
|
||||
'description' => '',
|
||||
'deprecationReason' => '',
|
||||
];
|
||||
$this->assertArraySubset([
|
||||
'RED' => $enumValue,
|
||||
'GREEN' => $enumValue,
|
||||
'BLUE' => $enumValue,
|
||||
], $defaultConfig['values']);
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'RED' => $enumValue,
|
||||
'GREEN' => $enumValue,
|
||||
'BLUE' => $enumValue,
|
||||
],
|
||||
$defaultConfig['values']
|
||||
);
|
||||
$this->assertCount(4, $defaultConfig); // 3 + astNode
|
||||
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
|
||||
$this->assertEquals('My description of Color', $schema->getType('Color')->description);
|
||||
@ -1233,7 +1248,7 @@ interface Hello {
|
||||
|
||||
public function testCreatesTypesLazily() : void
|
||||
{
|
||||
$body = '
|
||||
$body = '
|
||||
schema {
|
||||
query: Query
|
||||
}
|
||||
@ -1258,11 +1273,12 @@ type World implements Hello {
|
||||
world: String
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$doc = Parser::parse($body);
|
||||
$created = [];
|
||||
|
||||
$typeConfigDecorator = function($config, $node) use (&$created) {
|
||||
$typeConfigDecorator = function ($config, $node) use (&$created) {
|
||||
$created[] = $node->name->value;
|
||||
|
||||
return $config;
|
||||
};
|
||||
|
||||
@ -1282,5 +1298,4 @@ type World implements Hello {
|
||||
$this->assertArrayHasKey('Hello', $types);
|
||||
$this->assertArrayHasKey('World', $types);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Executor\Values;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
@ -12,13 +13,16 @@ use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CoerceValueTest extends TestCase
|
||||
{
|
||||
/** @var EnumType */
|
||||
private $testEnum;
|
||||
|
||||
/** @var InputObjectType */
|
||||
private $testInputObject;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->testEnum = new EnumType([
|
||||
'name' => 'TestEnum',
|
||||
'name' => 'TestEnum',
|
||||
'values' => [
|
||||
'FOO' => 'InternalFoo',
|
||||
'BAR' => 123456789,
|
||||
@ -26,7 +30,7 @@ class CoerceValueTest extends TestCase
|
||||
]);
|
||||
|
||||
$this->testInputObject = new InputObjectType([
|
||||
'name' => 'TestInputObject',
|
||||
'name' => 'TestInputObject',
|
||||
'fields' => [
|
||||
'foo' => Type::nonNull(Type::int()),
|
||||
'bar' => Type::int(),
|
||||
@ -34,9 +38,9 @@ class CoerceValueTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
// Describe: coerceValue
|
||||
|
||||
/**
|
||||
* Describe: coerceValue
|
||||
*
|
||||
* @see it('coercing an array to GraphQLString produces an error')
|
||||
*/
|
||||
public function testCoercingAnArrayToGraphQLStringProducesAnError() : void
|
||||
@ -53,7 +57,17 @@ class CoerceValueTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
// Describe: for GraphQLInt
|
||||
/**
|
||||
* Describe: for GraphQLInt
|
||||
*/
|
||||
private function expectError($result, $expected)
|
||||
{
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertInternalType('array', $result['errors']);
|
||||
$this->assertCount(1, $result['errors']);
|
||||
$this->assertEquals($expected, $result['errors'][0]->getMessage());
|
||||
$this->assertEquals(Utils::undefined(), $result['value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('returns no error for int input')
|
||||
@ -64,6 +78,13 @@ class CoerceValueTest extends TestCase
|
||||
$this->expectNoErrors($result);
|
||||
}
|
||||
|
||||
private function expectNoErrors($result)
|
||||
{
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertNull($result['errors']);
|
||||
$this->assertNotEquals(Utils::undefined(), $result['value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('returns no error for negative int input')
|
||||
*/
|
||||
@ -115,6 +136,8 @@ class CoerceValueTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
// Describe: for GraphQLFloat
|
||||
|
||||
/**
|
||||
* @see it('returns a single error for char input')
|
||||
*/
|
||||
@ -139,8 +162,6 @@ class CoerceValueTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
// Describe: for GraphQLFloat
|
||||
|
||||
/**
|
||||
* @see it('returns no error for int input')
|
||||
*/
|
||||
@ -189,6 +210,8 @@ class CoerceValueTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
// DESCRIBE: for GraphQLEnum
|
||||
|
||||
/**
|
||||
* @see it('returns a single error for char input')
|
||||
*/
|
||||
@ -213,8 +236,6 @@ class CoerceValueTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
// DESCRIBE: for GraphQLEnum
|
||||
|
||||
/**
|
||||
* @see it('returns no error for a known enum name')
|
||||
*/
|
||||
@ -229,6 +250,8 @@ class CoerceValueTest extends TestCase
|
||||
$this->assertEquals(123456789, $barResult['value']);
|
||||
}
|
||||
|
||||
// DESCRIBE: for GraphQLInputObject
|
||||
|
||||
/**
|
||||
* @see it('results error for misspelled enum value')
|
||||
*/
|
||||
@ -250,8 +273,6 @@ class CoerceValueTest extends TestCase
|
||||
$this->expectError($result2, 'Expected type TestEnum.');
|
||||
}
|
||||
|
||||
// DESCRIBE: for GraphQLInputObject
|
||||
|
||||
/**
|
||||
* @see it('returns no error for a valid input')
|
||||
*/
|
||||
@ -277,7 +298,10 @@ class CoerceValueTest extends TestCase
|
||||
public function testReturnErrorForAnInvalidField() : void
|
||||
{
|
||||
$result = Value::coerceValue(['foo' => 'abc'], $this->testInputObject);
|
||||
$this->expectError($result, 'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc');
|
||||
$this->expectError(
|
||||
$result,
|
||||
'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -286,10 +310,13 @@ class CoerceValueTest extends TestCase
|
||||
public function testReturnsMultipleErrorsForMultipleInvalidFields() : void
|
||||
{
|
||||
$result = Value::coerceValue(['foo' => 'abc', 'bar' => 'def'], $this->testInputObject);
|
||||
$this->assertEquals([
|
||||
'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc',
|
||||
'Expected type Int at value.bar; Int cannot represent non 32-bit signed integer value: def',
|
||||
], $result['errors']);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc',
|
||||
'Expected type Int at value.bar; Int cannot represent non 32-bit signed integer value: def',
|
||||
],
|
||||
$result['errors']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -318,20 +345,4 @@ class CoerceValueTest extends TestCase
|
||||
$result = Value::coerceValue(['foo' => 123, 'bart' => 123], $this->testInputObject);
|
||||
$this->expectError($result, 'Field "bart" is not defined by type TestInputObject; did you mean bar?');
|
||||
}
|
||||
|
||||
private function expectNoErrors($result)
|
||||
{
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertNull($result['errors']);
|
||||
$this->assertNotEquals(Utils::undefined(), $result['value']);
|
||||
}
|
||||
|
||||
|
||||
private function expectError($result, $expected) {
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertInternalType('array', $result['errors']);
|
||||
$this->assertCount(1, $result['errors']);
|
||||
$this->assertEquals($expected, $result['errors'][0]->getMessage());
|
||||
$this->assertEquals(Utils::undefined(), $result['value']);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
<?php
|
||||
namespace Utils;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
@ -12,121 +15,74 @@ use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ExtractTypesTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $mutation;
|
||||
|
||||
/**
|
||||
* @var InterfaceType
|
||||
*/
|
||||
/** @var InterfaceType */
|
||||
private $node;
|
||||
|
||||
/**
|
||||
* @var InterfaceType
|
||||
*/
|
||||
/** @var InterfaceType */
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $blogStory;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
private $link;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
private $video;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
private $videoMetadata;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $comment;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
/** @var ObjectType */
|
||||
private $category;
|
||||
|
||||
/**
|
||||
* @var UnionType
|
||||
*/
|
||||
/** @var UnionType */
|
||||
private $mention;
|
||||
|
||||
/** @var ObjectType */
|
||||
private $postStoryMutation;
|
||||
|
||||
/** @var InputObjectType */
|
||||
private $postStoryMutationInput;
|
||||
|
||||
/** @var ObjectType */
|
||||
private $postCommentMutation;
|
||||
|
||||
/** @var InputObjectType */
|
||||
private $postCommentMutationInput;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->node = new InterfaceType([
|
||||
'name' => 'Node',
|
||||
'name' => 'Node',
|
||||
'fields' => [
|
||||
'id' => Type::string()
|
||||
]
|
||||
'id' => Type::string(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->content = new InterfaceType([
|
||||
'name' => 'Content',
|
||||
'fields' => function() {
|
||||
'name' => 'Content',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => $this->user,
|
||||
'comments' => Type::listOf($this->comment),
|
||||
'categories' => Type::listOf($this->category)
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => $this->user,
|
||||
'comments' => Type::listOf($this->comment),
|
||||
'categories' => Type::listOf($this->category),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->blogStory = new ObjectType([
|
||||
'name' => 'BlogStory',
|
||||
'name' => 'BlogStory',
|
||||
'interfaces' => [
|
||||
$this->node,
|
||||
$this->content
|
||||
$this->content,
|
||||
],
|
||||
'fields' => function() {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
$this->content->getField('title'),
|
||||
$this->content->getField('body'),
|
||||
$this->content->getField('author'),
|
||||
$this->content->getField('comments'),
|
||||
$this->content->getField('categories')
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$this->link = new ObjectType([
|
||||
'name' => 'Link',
|
||||
'interfaces' => [
|
||||
$this->node,
|
||||
$this->content
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
$this->content->getField('title'),
|
||||
@ -134,156 +90,176 @@ class ExtractTypesTest extends TestCase
|
||||
$this->content->getField('author'),
|
||||
$this->content->getField('comments'),
|
||||
$this->content->getField('categories'),
|
||||
'url' => Type::string()
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$this->video = new ObjectType([
|
||||
'name' => 'Video',
|
||||
new ObjectType([
|
||||
'name' => 'Link',
|
||||
'interfaces' => [
|
||||
$this->node,
|
||||
$this->content
|
||||
$this->content,
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
$this->content->getField('title'),
|
||||
$this->content->getField('body'),
|
||||
$this->content->getField('author'),
|
||||
$this->content->getField('comments'),
|
||||
$this->content->getField('categories'),
|
||||
'streamUrl' => Type::string(),
|
||||
'id' => $this->node->getField('id'),
|
||||
'title' => $this->content->getField('title'),
|
||||
'body' => $this->content->getField('body'),
|
||||
'author' => $this->content->getField('author'),
|
||||
'comments' => $this->content->getField('comments'),
|
||||
'categories' => $this->content->getField('categories'),
|
||||
'url' => Type::string(),
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
new ObjectType([
|
||||
'name' => 'Video',
|
||||
'interfaces' => [
|
||||
$this->node,
|
||||
$this->content,
|
||||
],
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => $this->node->getField('id'),
|
||||
'title' => $this->content->getField('title'),
|
||||
'body' => $this->content->getField('body'),
|
||||
'author' => $this->content->getField('author'),
|
||||
'comments' => $this->content->getField('comments'),
|
||||
'categories' => $this->content->getField('categories'),
|
||||
'streamUrl' => Type::string(),
|
||||
'downloadUrl' => Type::string(),
|
||||
'metadata' => $this->videoMetadata = new ObjectType([
|
||||
'name' => 'VideoMetadata',
|
||||
'metadata' => new ObjectType([
|
||||
'name' => 'VideoMetadata',
|
||||
'fields' => [
|
||||
'lat' => Type::float(),
|
||||
'lng' => Type::float()
|
||||
]
|
||||
])
|
||||
'lng' => Type::float(),
|
||||
],
|
||||
]),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->comment = new ObjectType([
|
||||
'name' => 'Comment',
|
||||
'name' => 'Comment',
|
||||
'interfaces' => [
|
||||
$this->node
|
||||
$this->node,
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
'author' => $this->user,
|
||||
'text' => Type::string(),
|
||||
'id' => $this->node->getField('id'),
|
||||
'author' => $this->user,
|
||||
'text' => Type::string(),
|
||||
'replies' => Type::listOf($this->comment),
|
||||
'parent' => $this->comment,
|
||||
'content' => $this->content
|
||||
'parent' => $this->comment,
|
||||
'content' => $this->content,
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->user = new ObjectType([
|
||||
'name' => 'User',
|
||||
'name' => 'User',
|
||||
'interfaces' => [
|
||||
$this->node
|
||||
$this->node,
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
'id' => $this->node->getField('id'),
|
||||
'name' => Type::string(),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->category = new ObjectType([
|
||||
'name' => 'Category',
|
||||
'name' => 'Category',
|
||||
'interfaces' => [
|
||||
$this->node
|
||||
$this->node,
|
||||
],
|
||||
'fields' => function() {
|
||||
'fields' => function () {
|
||||
return [
|
||||
$this->node->getField('id'),
|
||||
'name' => Type::string()
|
||||
'id' => $this->node->getField('id'),
|
||||
'name' => Type::string(),
|
||||
];
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$this->mention = new UnionType([
|
||||
'name' => 'Mention',
|
||||
'name' => 'Mention',
|
||||
'types' => [
|
||||
$this->user,
|
||||
$this->category
|
||||
]
|
||||
$this->category,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'viewer' => $this->user,
|
||||
'viewer' => $this->user,
|
||||
'latestContent' => $this->content,
|
||||
'node' => $this->node,
|
||||
'mentions' => Type::listOf($this->mention)
|
||||
]
|
||||
'node' => $this->node,
|
||||
'mentions' => Type::listOf($this->mention),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->postStoryMutationInput = new InputObjectType([
|
||||
'name' => 'PostStoryMutationInput',
|
||||
'fields' => [
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => Type::id(),
|
||||
'category' => Type::id(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->mutation = new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'name' => 'Mutation',
|
||||
'fields' => [
|
||||
'postStory' => [
|
||||
'postStory' => [
|
||||
'type' => $this->postStoryMutation = new ObjectType([
|
||||
'name' => 'PostStoryMutation',
|
||||
'name' => 'PostStoryMutation',
|
||||
'fields' => [
|
||||
'story' => $this->blogStory
|
||||
]
|
||||
'story' => $this->blogStory,
|
||||
],
|
||||
]),
|
||||
'args' => [
|
||||
'input' => Type::nonNull($this->postStoryMutationInput = new InputObjectType([
|
||||
'name' => 'PostStoryMutationInput',
|
||||
'fields' => [
|
||||
'title' => Type::string(),
|
||||
'body' => Type::string(),
|
||||
'author' => Type::id(),
|
||||
'category' => Type::id()
|
||||
]
|
||||
])),
|
||||
'clientRequestId' => Type::string()
|
||||
]
|
||||
'input' => Type::nonNull($this->postStoryMutationInput),
|
||||
'clientRequestId' => Type::string(),
|
||||
],
|
||||
],
|
||||
'postComment' => [
|
||||
'type' => $this->postCommentMutation = new ObjectType([
|
||||
'name' => 'PostCommentMutation',
|
||||
'name' => 'PostCommentMutation',
|
||||
'fields' => [
|
||||
'comment' => $this->comment
|
||||
]
|
||||
'comment' => $this->comment,
|
||||
],
|
||||
]),
|
||||
'args' => [
|
||||
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
|
||||
'name' => 'PostCommentMutationInput',
|
||||
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
|
||||
'name' => 'PostCommentMutationInput',
|
||||
'fields' => [
|
||||
'text' => Type::nonNull(Type::string()),
|
||||
'author' => Type::nonNull(Type::id()),
|
||||
'text' => Type::nonNull(Type::string()),
|
||||
'author' => Type::nonNull(Type::id()),
|
||||
'content' => Type::id(),
|
||||
'parent' => Type::id()
|
||||
]
|
||||
'parent' => Type::id(),
|
||||
],
|
||||
])),
|
||||
'clientRequestId' => Type::string()
|
||||
]
|
||||
]
|
||||
]
|
||||
'clientRequestId' => Type::string(),
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testExtractTypesFromQuery() : void
|
||||
{
|
||||
$expectedTypeMap = [
|
||||
'Query' => $this->query,
|
||||
'User' => $this->user,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'Comment' => $this->comment,
|
||||
'Mention' => $this->mention,
|
||||
'Query' => $this->query,
|
||||
'User' => $this->user,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'Comment' => $this->comment,
|
||||
'Mention' => $this->mention,
|
||||
'Category' => $this->category,
|
||||
];
|
||||
|
||||
@ -294,19 +270,19 @@ class ExtractTypesTest extends TestCase
|
||||
public function testExtractTypesFromMutation() : void
|
||||
{
|
||||
$expectedTypeMap = [
|
||||
'Mutation' => $this->mutation,
|
||||
'User' => $this->user,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'Comment' => $this->comment,
|
||||
'BlogStory' => $this->blogStory,
|
||||
'Category' => $this->category,
|
||||
'PostStoryMutationInput' => $this->postStoryMutationInput,
|
||||
'ID' => Type::id(),
|
||||
'PostStoryMutation' => $this->postStoryMutation,
|
||||
'Mutation' => $this->mutation,
|
||||
'User' => $this->user,
|
||||
'Node' => $this->node,
|
||||
'String' => Type::string(),
|
||||
'Content' => $this->content,
|
||||
'Comment' => $this->comment,
|
||||
'BlogStory' => $this->blogStory,
|
||||
'Category' => $this->category,
|
||||
'PostStoryMutationInput' => $this->postStoryMutationInput,
|
||||
'ID' => Type::id(),
|
||||
'PostStoryMutation' => $this->postStoryMutation,
|
||||
'PostCommentMutationInput' => $this->postCommentMutationInput,
|
||||
'PostCommentMutation' => $this->postCommentMutation,
|
||||
'PostCommentMutation' => $this->postCommentMutation,
|
||||
];
|
||||
|
||||
$actualTypeMap = TypeInfo::extractTypes($this->mutation);
|
||||
@ -316,16 +292,16 @@ class ExtractTypesTest extends TestCase
|
||||
public function testThrowsOnMultipleTypesWithSameName() : void
|
||||
{
|
||||
$otherUserType = new ObjectType([
|
||||
'name' => 'User',
|
||||
'fields' => ['a' => Type::string()]
|
||||
'name' => 'User',
|
||||
'fields' => ['a' => Type::string()],
|
||||
]);
|
||||
|
||||
$queryType = new ObjectType([
|
||||
'name' => 'Test',
|
||||
'name' => 'Test',
|
||||
'fields' => [
|
||||
'otherUser' => $otherUserType,
|
||||
'user' => $this->user
|
||||
]
|
||||
'user' => $this->user,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->expectException(InvariantViolation::class);
|
||||
|
@ -1,17 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Utils\Utils;
|
||||
use GraphQL\Validator\DocumentValidator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class IsValidLiteralValueTest extends TestCase
|
||||
{
|
||||
// DESCRIBE: isValidLiteralValue
|
||||
|
||||
/**
|
||||
* @see it('Returns no errors for a valid value')
|
||||
*/
|
||||
|
@ -1,16 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
|
||||
use GraphQL\Utils\Utils;
|
||||
use GraphQL\Utils\MixedStore;
|
||||
use GraphQL\Utils\Utils;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MixedStoreTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var MixedStore
|
||||
*/
|
||||
/** @var MixedStore */
|
||||
private $mixedStore;
|
||||
|
||||
public function setUp()
|
||||
@ -18,6 +18,13 @@ class MixedStoreTest extends TestCase
|
||||
$this->mixedStore = new MixedStore();
|
||||
}
|
||||
|
||||
public function testAcceptsNullKeys() : void
|
||||
{
|
||||
foreach ($this->getPossibleValues() as $value) {
|
||||
$this->assertAcceptsKeyValue(null, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPossibleValues()
|
||||
{
|
||||
return [
|
||||
@ -30,16 +37,38 @@ class MixedStoreTest extends TestCase
|
||||
'a',
|
||||
[],
|
||||
new \stdClass(),
|
||||
function() {},
|
||||
new MixedStore()
|
||||
function () {
|
||||
},
|
||||
new MixedStore(),
|
||||
];
|
||||
}
|
||||
|
||||
public function testAcceptsNullKeys() : void
|
||||
private function assertAcceptsKeyValue($key, $value)
|
||||
{
|
||||
foreach ($this->getPossibleValues() as $value) {
|
||||
$this->assertAcceptsKeyValue(null, $value);
|
||||
}
|
||||
$err = 'Failed assertion that MixedStore accepts key ' .
|
||||
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
|
||||
|
||||
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
|
||||
$this->mixedStore->offsetSet($key, $value);
|
||||
$this->assertTrue($this->mixedStore->offsetExists($key), $err);
|
||||
$this->assertSame($value, $this->mixedStore->offsetGet($key), $err);
|
||||
$this->mixedStore->offsetUnset($key);
|
||||
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
|
||||
$this->assertProvidesArrayAccess($key, $value);
|
||||
}
|
||||
|
||||
private function assertProvidesArrayAccess($key, $value)
|
||||
{
|
||||
$err = 'Failed assertion that MixedStore provides array access for key ' .
|
||||
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
|
||||
|
||||
$this->assertFalse(isset($this->mixedStore[$key]), $err);
|
||||
$this->mixedStore[$key] = $value;
|
||||
$this->assertTrue(isset($this->mixedStore[$key]), $err);
|
||||
$this->assertEquals(! empty($value), ! empty($this->mixedStore[$key]), $err);
|
||||
$this->assertSame($value, $this->mixedStore[$key], $err);
|
||||
unset($this->mixedStore[$key]);
|
||||
$this->assertFalse(isset($this->mixedStore[$key]), $err);
|
||||
}
|
||||
|
||||
public function testAcceptsBoolKeys() : void
|
||||
@ -93,35 +122,11 @@ class MixedStoreTest extends TestCase
|
||||
foreach ($this->getPossibleValues() as $value) {
|
||||
$this->assertAcceptsKeyValue(new \stdClass(), $value);
|
||||
$this->assertAcceptsKeyValue(new MixedStore(), $value);
|
||||
$this->assertAcceptsKeyValue(function() {}, $value);
|
||||
$this->assertAcceptsKeyValue(
|
||||
function () {
|
||||
},
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertAcceptsKeyValue($key, $value)
|
||||
{
|
||||
$err = 'Failed assertion that MixedStore accepts key ' .
|
||||
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
|
||||
|
||||
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
|
||||
$this->mixedStore->offsetSet($key, $value);
|
||||
$this->assertTrue($this->mixedStore->offsetExists($key), $err);
|
||||
$this->assertSame($value, $this->mixedStore->offsetGet($key), $err);
|
||||
$this->mixedStore->offsetUnset($key);
|
||||
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
|
||||
$this->assertProvidesArrayAccess($key, $value);
|
||||
}
|
||||
|
||||
private function assertProvidesArrayAccess($key, $value)
|
||||
{
|
||||
$err = 'Failed assertion that MixedStore provides array access for key ' .
|
||||
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
|
||||
|
||||
$this->assertFalse(isset($this->mixedStore[$key]), $err);
|
||||
$this->mixedStore[$key] = $value;
|
||||
$this->assertTrue(isset($this->mixedStore[$key]), $err);
|
||||
$this->assertEquals(!empty($value), !empty($this->mixedStore[$key]), $err);
|
||||
$this->assertSame($value, $this->mixedStore[$key], $err);
|
||||
unset($this->mixedStore[$key]);
|
||||
$this->assertFalse(isset($this->mixedStore[$key]), $err);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Executor\Values;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Utils\Utils;
|
||||
use GraphQL\Utils\Value;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class QuotedOrListTest extends TestCase
|
||||
{
|
||||
// DESCRIBE: quotedOrList
|
||||
|
||||
/**
|
||||
* @see it('Does not accept an empty list')
|
||||
*/
|
||||
|
@ -1,16 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Language\DirectiveLocation;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\CustomScalarType;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Utils\BuildSchema;
|
||||
use GraphQL\Utils\SchemaPrinter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@ -19,37 +22,40 @@ class SchemaPrinterTest extends TestCase
|
||||
{
|
||||
// Describe: Type System Printer
|
||||
|
||||
private function printForTest($schema)
|
||||
{
|
||||
$schemaText = SchemaPrinter::doPrint($schema);
|
||||
$this->assertEquals($schemaText, SchemaPrinter::doPrint(BuildSchema::build($schemaText)));
|
||||
return "\n" . $schemaText;
|
||||
}
|
||||
|
||||
private function printSingleFieldSchema($fieldConfig)
|
||||
{
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'singleField' => $fieldConfig
|
||||
]
|
||||
]);
|
||||
return $this->printForTest(new Schema(['query' => $query]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('Prints String Field')
|
||||
*/
|
||||
public function testPrintsStringField() : void
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string()
|
||||
'type' => Type::string(),
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField: String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
private function printSingleFieldSchema($fieldConfig)
|
||||
{
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['singleField' => $fieldConfig],
|
||||
]);
|
||||
|
||||
return $this->printForTest(new Schema(['query' => $query]));
|
||||
}
|
||||
|
||||
private function printForTest($schema)
|
||||
{
|
||||
$schemaText = SchemaPrinter::doPrint($schema);
|
||||
$this->assertEquals($schemaText, SchemaPrinter::doPrint(BuildSchema::build($schemaText)));
|
||||
|
||||
return "\n" . $schemaText;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,13 +64,16 @@ type Query {
|
||||
public function testPrintArrayStringField() : void
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::listOf(Type::string())
|
||||
'type' => Type::listOf(Type::string()),
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField: [String]
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,13 +82,16 @@ type Query {
|
||||
public function testPrintNonNullStringField() : void
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::nonNull(Type::string())
|
||||
'type' => Type::nonNull(Type::string()),
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField: String!
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,13 +100,16 @@ type Query {
|
||||
public function testPrintNonNullArrayStringField() : void
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::nonNull(Type::listOf(Type::string()))
|
||||
'type' => Type::nonNull(Type::listOf(Type::string())),
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField: [String]!
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,13 +118,16 @@ type Query {
|
||||
public function testPrintArrayNonNullStringField() : void
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::listOf(Type::nonNull(Type::string()))
|
||||
'type' => Type::listOf(Type::nonNull(Type::string())),
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField: [String!]
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,13 +136,16 @@ type Query {
|
||||
public function testPrintNonNullArrayNonNullStringField() : void
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))
|
||||
'type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string()))),
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField: [String!]!
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,18 +154,19 @@ type Query {
|
||||
public function testPrintObjectField() : void
|
||||
{
|
||||
$fooType = new ObjectType([
|
||||
'name' => 'Foo',
|
||||
'fields' => ['str' => ['type' => Type::string()]]
|
||||
'name' => 'Foo',
|
||||
'fields' => ['str' => ['type' => Type::string()]],
|
||||
]);
|
||||
|
||||
$root = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['foo' => ['type' => $fooType]]
|
||||
'name' => 'Query',
|
||||
'fields' => ['foo' => ['type' => $fooType]],
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $root]);
|
||||
$output = $this->printForTest($schema);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Foo {
|
||||
str: String
|
||||
}
|
||||
@ -152,7 +174,9 @@ type Foo {
|
||||
type Query {
|
||||
foo: Foo
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,13 +186,16 @@ type Query {
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'args' => ['argOne' => ['type' => Type::int()]]
|
||||
'args' => ['argOne' => ['type' => Type::int()]],
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField(argOne: Int): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,13 +205,16 @@ type Query {
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => 2]]
|
||||
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => 2]],
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField(argOne: Int = 2): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,11 +226,14 @@ type Query {
|
||||
'type' => Type::string(),
|
||||
'args' => ['argOne' => ['type' => Type::string(), 'defaultValue' => "tes\t de\fault"]],
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField(argOne: String = "tes\t de\fault"): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -210,13 +243,16 @@ type Query {
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => null]]
|
||||
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => null]],
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField(argOne: Int = null): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -226,13 +262,16 @@ type Query {
|
||||
{
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'args' => ['argOne' => ['type' => Type::nonNull(Type::int())]]
|
||||
'args' => ['argOne' => ['type' => Type::nonNull(Type::int())]],
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField(argOne: Int!): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -244,14 +283,17 @@ type Query {
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'argOne' => ['type' => Type::int()],
|
||||
'argTwo' => ['type' => Type::string()]
|
||||
]
|
||||
'argTwo' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField(argOne: Int, argTwo: String): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,16 +304,19 @@ type Query {
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'argOne' => ['type' => Type::int(), 'defaultValue' => 1],
|
||||
'argTwo' => ['type' => Type::string()],
|
||||
'argThree' => ['type' => Type::boolean()]
|
||||
]
|
||||
'argOne' => ['type' => Type::int(), 'defaultValue' => 1],
|
||||
'argTwo' => ['type' => Type::string()],
|
||||
'argThree' => ['type' => Type::boolean()],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField(argOne: Int = 1, argTwo: String, argThree: Boolean): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -282,16 +327,19 @@ type Query {
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'argOne' => ['type' => Type::int()],
|
||||
'argTwo' => ['type' => Type::string(), 'defaultValue' => 'foo'],
|
||||
'argThree' => ['type' => Type::boolean()]
|
||||
]
|
||||
'argOne' => ['type' => Type::int()],
|
||||
'argTwo' => ['type' => Type::string(), 'defaultValue' => 'foo'],
|
||||
'argThree' => ['type' => Type::boolean()],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField(argOne: Int, argTwo: String = "foo", argThree: Boolean): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -302,16 +350,19 @@ type Query {
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'argOne' => ['type' => Type::int()],
|
||||
'argTwo' => ['type' => Type::string()],
|
||||
'argThree' => ['type' => Type::boolean(), 'defaultValue' => false]
|
||||
]
|
||||
'argOne' => ['type' => Type::int()],
|
||||
'argTwo' => ['type' => Type::string()],
|
||||
'argThree' => ['type' => Type::boolean(), 'defaultValue' => false],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
singleField(argOne: Int, argTwo: String, argThree: Boolean = false): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -320,14 +371,12 @@ type Query {
|
||||
public function testPrintsCustomQueryRootType() : void
|
||||
{
|
||||
$customQueryType = new ObjectType([
|
||||
'name' => 'CustomQueryType',
|
||||
'name' => 'CustomQueryType',
|
||||
'fields' => ['bar' => ['type' => Type::string()]],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $customQueryType,
|
||||
]);
|
||||
$output = $this->printForTest($schema);
|
||||
$schema = new Schema(['query' => $customQueryType]);
|
||||
$output = $this->printForTest($schema);
|
||||
$expected = '
|
||||
schema {
|
||||
query: CustomQueryType
|
||||
@ -346,27 +395,28 @@ type CustomQueryType {
|
||||
public function testPrintInterface() : void
|
||||
{
|
||||
$fooType = new InterfaceType([
|
||||
'name' => 'Foo',
|
||||
'fields' => ['str' => ['type' => Type::string()]]
|
||||
'name' => 'Foo',
|
||||
'fields' => ['str' => ['type' => Type::string()]],
|
||||
]);
|
||||
|
||||
$barType = new ObjectType([
|
||||
'name' => 'Bar',
|
||||
'fields' => ['str' => ['type' => Type::string()]],
|
||||
'interfaces' => [$fooType]
|
||||
'name' => 'Bar',
|
||||
'fields' => ['str' => ['type' => Type::string()]],
|
||||
'interfaces' => [$fooType],
|
||||
]);
|
||||
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['bar' => ['type' => $barType]]
|
||||
'name' => 'Query',
|
||||
'fields' => ['bar' => ['type' => $barType]],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $query,
|
||||
'types' => [$barType]
|
||||
'types' => [$barType],
|
||||
]);
|
||||
$output = $this->printForTest($schema);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Bar implements Foo {
|
||||
str: String
|
||||
}
|
||||
@ -378,7 +428,9 @@ interface Foo {
|
||||
type Query {
|
||||
bar: Bar
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -387,35 +439,36 @@ type Query {
|
||||
public function testPrintMultipleInterface() : void
|
||||
{
|
||||
$fooType = new InterfaceType([
|
||||
'name' => 'Foo',
|
||||
'fields' => ['str' => ['type' => Type::string()]]
|
||||
'name' => 'Foo',
|
||||
'fields' => ['str' => ['type' => Type::string()]],
|
||||
]);
|
||||
|
||||
$baazType = new InterfaceType([
|
||||
'name' => 'Baaz',
|
||||
'fields' => ['int' => ['type' => Type::int()]]
|
||||
'name' => 'Baaz',
|
||||
'fields' => ['int' => ['type' => Type::int()]],
|
||||
]);
|
||||
|
||||
$barType = new ObjectType([
|
||||
'name' => 'Bar',
|
||||
'fields' => [
|
||||
'name' => 'Bar',
|
||||
'fields' => [
|
||||
'str' => ['type' => Type::string()],
|
||||
'int' => ['type' => Type::int()]
|
||||
'int' => ['type' => Type::int()],
|
||||
],
|
||||
'interfaces' => [$fooType, $baazType]
|
||||
'interfaces' => [$fooType, $baazType],
|
||||
]);
|
||||
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['bar' => ['type' => $barType]]
|
||||
'name' => 'Query',
|
||||
'fields' => ['bar' => ['type' => $barType]],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $query,
|
||||
'types' => [$barType]
|
||||
'types' => [$barType],
|
||||
]);
|
||||
$output = $this->printForTest($schema);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
interface Baaz {
|
||||
int: Int
|
||||
}
|
||||
@ -432,7 +485,9 @@ interface Foo {
|
||||
type Query {
|
||||
bar: Bar
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -441,36 +496,37 @@ type Query {
|
||||
public function testPrintUnions() : void
|
||||
{
|
||||
$fooType = new ObjectType([
|
||||
'name' => 'Foo',
|
||||
'fields' => ['bool' => ['type' => Type::boolean()]]
|
||||
'name' => 'Foo',
|
||||
'fields' => ['bool' => ['type' => Type::boolean()]],
|
||||
]);
|
||||
|
||||
$barType = new ObjectType([
|
||||
'name' => 'Bar',
|
||||
'fields' => ['str' => ['type' => Type::string()]]
|
||||
'name' => 'Bar',
|
||||
'fields' => ['str' => ['type' => Type::string()]],
|
||||
]);
|
||||
|
||||
$singleUnion = new UnionType([
|
||||
'name' => 'SingleUnion',
|
||||
'types' => [$fooType]
|
||||
'name' => 'SingleUnion',
|
||||
'types' => [$fooType],
|
||||
]);
|
||||
|
||||
$multipleUnion = new UnionType([
|
||||
'name' => 'MultipleUnion',
|
||||
'types' => [$fooType, $barType]
|
||||
'name' => 'MultipleUnion',
|
||||
'types' => [$fooType, $barType],
|
||||
]);
|
||||
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'single' => ['type' => $singleUnion],
|
||||
'multiple' => ['type' => $multipleUnion]
|
||||
]
|
||||
'single' => ['type' => $singleUnion],
|
||||
'multiple' => ['type' => $multipleUnion],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $query]);
|
||||
$output = $this->printForTest($schema);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Bar {
|
||||
str: String
|
||||
}
|
||||
@ -487,7 +543,9 @@ type Query {
|
||||
}
|
||||
|
||||
union SingleUnion = Foo
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -496,23 +554,24 @@ union SingleUnion = Foo
|
||||
public function testInputType() : void
|
||||
{
|
||||
$inputType = new InputObjectType([
|
||||
'name' => 'InputType',
|
||||
'fields' => ['int' => ['type' => Type::int()]]
|
||||
'name' => 'InputType',
|
||||
'fields' => ['int' => ['type' => Type::int()]],
|
||||
]);
|
||||
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'str' => [
|
||||
'type' => Type::string(),
|
||||
'args' => ['argOne' => ['type' => $inputType]]
|
||||
]
|
||||
]
|
||||
'args' => ['argOne' => ['type' => $inputType]],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $query]);
|
||||
$output = $this->printForTest($schema);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
input InputType {
|
||||
int: Int
|
||||
}
|
||||
@ -520,7 +579,9 @@ input InputType {
|
||||
type Query {
|
||||
str(argOne: InputType): String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -529,28 +590,31 @@ type Query {
|
||||
public function testCustomScalar() : void
|
||||
{
|
||||
$oddType = new CustomScalarType([
|
||||
'name' => 'Odd',
|
||||
'serialize' => function($value) {
|
||||
'name' => 'Odd',
|
||||
'serialize' => function ($value) {
|
||||
return $value % 2 === 1 ? $value : null;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'odd' => ['type' => $oddType]
|
||||
]
|
||||
'odd' => ['type' => $oddType],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $query]);
|
||||
$output = $this->printForTest($schema);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
scalar Odd
|
||||
|
||||
type Query {
|
||||
odd: Odd
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -559,24 +623,25 @@ type Query {
|
||||
public function testEnum() : void
|
||||
{
|
||||
$RGBType = new EnumType([
|
||||
'name' => 'RGB',
|
||||
'name' => 'RGB',
|
||||
'values' => [
|
||||
'RED' => ['value' => 0],
|
||||
'RED' => ['value' => 0],
|
||||
'GREEN' => ['value' => 1],
|
||||
'BLUE' => ['value' => 2]
|
||||
]
|
||||
'BLUE' => ['value' => 2],
|
||||
],
|
||||
]);
|
||||
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'rgb' => ['type' => $RGBType]
|
||||
]
|
||||
'rgb' => ['type' => $RGBType],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $query]);
|
||||
$output = $this->printForTest($schema);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
rgb: RGB
|
||||
}
|
||||
@ -586,7 +651,9 @@ enum RGB {
|
||||
GREEN
|
||||
BLUE
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -595,32 +662,35 @@ enum RGB {
|
||||
public function testPrintsCustomDirectives() : void
|
||||
{
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'field' => ['type' => Type::string()],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$customDirectives = new Directive([
|
||||
'name' => 'customDirective',
|
||||
'name' => 'customDirective',
|
||||
'locations' => [
|
||||
DirectiveLocation::FIELD
|
||||
]
|
||||
DirectiveLocation::FIELD,
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $query,
|
||||
'query' => $query,
|
||||
'directives' => [$customDirectives],
|
||||
]);
|
||||
|
||||
$output = $this->printForTest($schema);
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
directive @customDirective on FIELD
|
||||
|
||||
type Query {
|
||||
field: String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -629,19 +699,22 @@ type Query {
|
||||
public function testOneLinePrintsAShortDescription() : void
|
||||
{
|
||||
$description = 'This field is awesome';
|
||||
$output = $this->printSingleFieldSchema([
|
||||
"type" => Type::string(),
|
||||
"description" => $description
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'description' => $description,
|
||||
]);
|
||||
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
"""This field is awesome"""
|
||||
singleField: String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
|
||||
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
|
||||
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
|
||||
$recreatedField = $recreatedRoot->getFields()['singleField'];
|
||||
$this->assertEquals($description, $recreatedField->description);
|
||||
}
|
||||
@ -652,21 +725,24 @@ type Query {
|
||||
public function testDoesNotOneLinePrintADescriptionThatEndsWithAQuote() : void
|
||||
{
|
||||
$description = 'This field is "awesome"';
|
||||
$output = $this->printSingleFieldSchema([
|
||||
"type" => Type::string(),
|
||||
"description" => $description
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'description' => $description,
|
||||
]);
|
||||
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
"""
|
||||
This field is "awesome"
|
||||
"""
|
||||
singleField: String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
|
||||
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
|
||||
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
|
||||
$recreatedField = $recreatedRoot->getFields()['singleField'];
|
||||
$this->assertEquals($description, $recreatedField->description);
|
||||
}
|
||||
@ -677,20 +753,23 @@ type Query {
|
||||
public function testPReservesLeadingSpacesWhenPrintingADescription() : void
|
||||
{
|
||||
$description = ' This field is "awesome"';
|
||||
$output = $this->printSingleFieldSchema([
|
||||
"type" => Type::string(),
|
||||
"description" => $description
|
||||
$output = $this->printSingleFieldSchema([
|
||||
'type' => Type::string(),
|
||||
'description' => $description,
|
||||
]);
|
||||
|
||||
$this->assertEquals('
|
||||
$this->assertEquals(
|
||||
'
|
||||
type Query {
|
||||
""" This field is "awesome"
|
||||
"""
|
||||
singleField: String
|
||||
}
|
||||
', $output);
|
||||
',
|
||||
$output
|
||||
);
|
||||
|
||||
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
|
||||
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
|
||||
$recreatedField = $recreatedRoot->getFields()['singleField'];
|
||||
$this->assertEquals($description, $recreatedField->description);
|
||||
}
|
||||
@ -701,14 +780,14 @@ type Query {
|
||||
public function testPrintIntrospectionSchema() : void
|
||||
{
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'onlyField' => ['type' => Type::string()]
|
||||
]
|
||||
'onlyField' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $query]);
|
||||
$output = SchemaPrinter::printIntrosepctionSchema($schema);
|
||||
$schema = new Schema(['query' => $query]);
|
||||
$output = SchemaPrinter::printIntrosepctionSchema($schema);
|
||||
$introspectionSchema = <<<'EOT'
|
||||
"""
|
||||
Directs the executor to include this field or fragment only when the `if` argument is true.
|
||||
@ -946,16 +1025,17 @@ EOT;
|
||||
public function testPrintIntrospectionSchemaWithCommentDescriptions() : void
|
||||
{
|
||||
$query = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'onlyField' => ['type' => Type::string()]
|
||||
]
|
||||
'onlyField' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $query]);
|
||||
$output = SchemaPrinter::printIntrosepctionSchema($schema, [
|
||||
'commentDescriptions' => true
|
||||
]);
|
||||
$schema = new Schema(['query' => $query]);
|
||||
$output = SchemaPrinter::printIntrosepctionSchema(
|
||||
$schema,
|
||||
['commentDescriptions' => true]
|
||||
);
|
||||
$introspectionSchema = <<<'EOT'
|
||||
# Directs the executor to include this field or fragment only when the `if` argument is true.
|
||||
directive @include(
|
||||
|
@ -1,16 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Utils;
|
||||
|
||||
use GraphQL\Executor\Values;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Utils\Utils;
|
||||
use GraphQL\Utils\Value;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SuggestionListTest extends TestCase
|
||||
{
|
||||
// DESCRIBE: suggestionList
|
||||
|
||||
/**
|
||||
* @see it('Returns results when input is empty')
|
||||
*/
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user