graphql-php/tests/Executor/ExecutorTest.php

1199 lines
37 KiB
PHP
Raw Normal View History

2015-07-15 20:05:46 +03:00
<?php
2018-09-01 18:07:06 +03:00
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
2016-10-18 16:25:32 +03:00
2016-12-03 00:23:21 +03:00
use GraphQL\Deferred;
use GraphQL\Error\UserError;
2016-04-09 10:36:53 +03:00
use GraphQL\Executor\Executor;
2015-07-15 20:05:46 +03:00
use GraphQL\Language\Parser;
2018-09-01 18:07:06 +03:00
use GraphQL\Tests\Executor\TestClasses\NotSpecial;
use GraphQL\Tests\Executor\TestClasses\Special;
2016-10-18 16:25:32 +03:00
use GraphQL\Type\Definition\InputObjectType;
2015-08-31 22:44:03 +03:00
use GraphQL\Type\Definition\InterfaceType;
2015-07-15 20:05:46 +03:00
use GraphQL\Type\Definition\ObjectType;
2015-08-31 22:44:03 +03:00
use GraphQL\Type\Definition\ResolveInfo;
2015-07-15 20:05:46 +03:00
use GraphQL\Type\Definition\Type;
2018-09-01 18:07:06 +03:00
use GraphQL\Type\Schema;
2018-07-29 18:43:10 +03:00
use PHPUnit\Framework\TestCase;
2018-09-01 18:07:06 +03:00
use function array_keys;
use function count;
use function json_encode;
2015-07-15 20:05:46 +03:00
2018-07-29 18:43:10 +03:00
class ExecutorTest extends TestCase
2015-07-15 20:05:46 +03:00
{
2016-11-27 01:51:42 +03:00
public function tearDown()
{
Executor::setPromiseAdapter(null);
}
2015-07-15 20:05:46 +03:00
// Execute: Handles basic execution tasks
2016-10-18 16:25:32 +03:00
/**
* @see it('executes arbitrary code')
2016-10-18 16:25:32 +03:00
*/
public function testExecutesArbitraryCode() : void
2015-07-15 20:05:46 +03:00
{
$deepData = null;
2018-09-01 18:07:06 +03:00
$data = null;
2016-11-27 01:51:42 +03:00
$promiseData = function () use (&$data) {
2016-12-03 00:23:21 +03:00
return new Deferred(function () use (&$data) {
return $data;
2016-11-27 01:51:42 +03:00
});
};
2015-07-15 20:05:46 +03:00
$data = [
2018-09-01 18:07:06 +03:00
'a' => function () {
return 'Apple';
},
'b' => function () {
return 'Banana';
},
'c' => function () {
return 'Cookie';
},
'd' => function () {
return 'Donut';
},
'e' => function () {
return 'Egg';
},
'f' => 'Fish',
'pic' => function ($size = 50) {
2015-07-15 20:05:46 +03:00
return 'Pic of size: ' . $size;
},
2018-09-01 18:07:06 +03:00
'promise' => function () use ($promiseData) {
2016-11-27 01:51:42 +03:00
return $promiseData();
2015-07-15 20:05:46 +03:00
},
2018-09-01 18:07:06 +03:00
'deep' => function () use (&$deepData) {
2015-07-15 20:05:46 +03:00
return $deepData;
2018-09-01 18:07:06 +03:00
},
2015-07-15 20:05:46 +03:00
];
2018-09-01 18:07:06 +03:00
// Required for that & reference above
2015-07-15 20:05:46 +03:00
$deepData = [
2018-09-01 18:07:06 +03:00
'a' => function () {
return 'Already Been Done';
},
'b' => function () {
return 'Boring';
},
'c' => function () {
2015-07-15 20:05:46 +03:00
return ['Contrived', null, 'Confusing'];
},
2016-11-27 01:51:42 +03:00
'deeper' => function () use (&$data) {
2015-07-15 20:05:46 +03:00
return [$data, null, $data];
2018-09-01 18:07:06 +03:00
},
2015-07-15 20:05:46 +03:00
];
$doc = '
query Example($size: Int) {
a,
b,
x: c
...c
f
...on DataType {
pic(size: $size)
promise {
a
}
}
deep {
a
b
c
deeper {
a
b
}
}
}
fragment c on DataType {
d
e
}
';
2018-09-01 18:07:06 +03:00
$ast = Parser::parse($doc);
2015-07-15 20:05:46 +03:00
$expected = [
'data' => [
2018-09-01 18:07:06 +03:00
'a' => 'Apple',
'b' => 'Banana',
'x' => 'Cookie',
'd' => 'Donut',
'e' => 'Egg',
'f' => 'Fish',
'pic' => 'Pic of size: 100',
'promise' => ['a' => 'Apple'],
'deep' => [
'a' => 'Already Been Done',
'b' => 'Boring',
'c' => ['Contrived', null, 'Confusing'],
2015-07-15 20:05:46 +03:00
'deeper' => [
2018-09-01 18:07:06 +03:00
['a' => 'Apple', 'b' => 'Banana'],
2015-07-15 20:05:46 +03:00
null,
2018-09-01 18:07:06 +03:00
['a' => 'Apple', 'b' => 'Banana'],
],
],
],
2015-07-15 20:05:46 +03:00
];
$deepDataType = null;
2018-09-01 18:07:06 +03:00
$dataType = new ObjectType([
'name' => 'DataType',
'fields' => function () use (&$dataType, &$deepDataType) {
return [
2018-09-01 18:07:06 +03:00
'a' => ['type' => Type::string()],
'b' => ['type' => Type::string()],
'c' => ['type' => Type::string()],
'd' => ['type' => Type::string()],
'e' => ['type' => Type::string()],
'f' => ['type' => Type::string()],
'pic' => [
'args' => ['size' => ['type' => Type::int()]],
'type' => Type::string(),
'resolve' => function ($obj, $args) {
return $obj['pic']($args['size']);
2018-09-01 18:07:06 +03:00
},
],
'promise' => ['type' => $dataType],
2018-09-01 18:07:06 +03:00
'deep' => ['type' => $deepDataType],
];
2018-09-01 18:07:06 +03:00
},
2015-07-15 20:05:46 +03:00
]);
2018-09-01 18:07:06 +03:00
// Required for that & reference above
2015-07-15 20:05:46 +03:00
$deepDataType = new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'DeepDataType',
2015-07-15 20:05:46 +03:00
'fields' => [
2018-09-01 18:07:06 +03:00
'a' => ['type' => Type::string()],
'b' => ['type' => Type::string()],
'c' => ['type' => Type::listOf(Type::string())],
'deeper' => ['type' => Type::listOf($dataType)],
],
2015-07-15 20:05:46 +03:00
]);
2018-09-01 18:07:06 +03:00
$schema = new Schema(['query' => $dataType]);
2015-07-15 20:05:46 +03:00
2018-09-01 18:07:06 +03:00
$this->assertEquals(
$expected,
Executor::execute($schema, $ast, $data, null, ['size' => 100], 'Example')->toArray()
);
2015-07-15 20:05:46 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('merges parallel fragments')
2016-10-18 16:25:32 +03:00
*/
public function testMergesParallelFragments() : void
2015-07-15 20:05:46 +03:00
{
$ast = Parser::parse('
{ a, ...FragOne, ...FragTwo }
fragment FragOne on Type {
b
deep { b, deeper: deep { b } }
}
fragment FragTwo on Type {
c
deep { c, deeper: deep { c } }
}
');
$Type = new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => function () use (&$Type) {
return [
2018-09-01 18:07:06 +03:00
'a' => [
'type' => Type::string(),
'resolve' => function () {
return 'Apple';
},
],
'b' => [
'type' => Type::string(),
'resolve' => function () {
return 'Banana';
},
],
'c' => [
'type' => Type::string(),
'resolve' => function () {
return 'Cherry';
},
],
'deep' => [
2018-09-01 18:07:06 +03:00
'type' => $Type,
'resolve' => function () {
return [];
2018-09-01 18:07:06 +03:00
},
],
];
2018-09-01 18:07:06 +03:00
},
2015-07-15 20:05:46 +03:00
]);
2018-09-01 18:07:06 +03:00
$schema = new Schema(['query' => $Type]);
2015-07-15 20:05:46 +03:00
$expected = [
'data' => [
2018-09-01 18:07:06 +03:00
'a' => 'Apple',
'b' => 'Banana',
'c' => 'Cherry',
2015-07-15 20:05:46 +03:00
'deep' => [
2018-09-01 18:07:06 +03:00
'b' => 'Banana',
'c' => 'Cherry',
2015-07-15 20:05:46 +03:00
'deeper' => [
'b' => 'Banana',
2018-09-01 18:07:06 +03:00
'c' => 'Cherry',
],
],
],
2015-07-15 20:05:46 +03:00
];
$this->assertEquals($expected, Executor::execute($schema, $ast)->toArray());
2015-07-15 20:05:46 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('provides info about current execution state')
2016-10-18 16:25:32 +03:00
*/
public function testProvidesInfoAboutCurrentExecutionState() : void
2016-10-18 16:25:32 +03:00
{
$ast = Parser::parse('query ($var: String) { result: test }');
/** @var ResolveInfo $info */
2018-09-01 18:07:06 +03:00
$info = null;
2016-10-18 16:25:32 +03:00
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Test',
2016-10-18 16:25:32 +03:00
'fields' => [
'test' => [
2018-09-01 18:07:06 +03:00
'type' => Type::string(),
'resolve' => function ($val, $args, $ctx, $_info) use (&$info) {
2016-10-18 16:25:32 +03:00
$info = $_info;
2018-09-01 18:07:06 +03:00
},
],
],
]),
2016-10-18 16:25:32 +03:00
]);
2018-09-01 18:07:06 +03:00
$rootValue = ['root' => 'val'];
Executor::execute($schema, $ast, $rootValue, null, ['var' => '123']);
$this->assertEquals(
[
'fieldName',
'fieldNodes',
'returnType',
'parentType',
'path',
'schema',
'fragments',
'rootValue',
'operation',
'variableValues',
],
array_keys((array) $info)
);
2016-10-18 16:25:32 +03:00
$this->assertEquals('test', $info->fieldName);
$this->assertEquals(1, count($info->fieldNodes));
$this->assertSame($ast->definitions[0]->selectionSet->selections[0], $info->fieldNodes[0]);
2016-10-18 16:25:32 +03:00
$this->assertSame(Type::string(), $info->returnType);
$this->assertSame($schema->getQueryType(), $info->parentType);
$this->assertEquals(['result'], $info->path);
$this->assertSame($schema, $info->schema);
$this->assertSame($rootValue, $info->rootValue);
$this->assertEquals($ast->definitions[0], $info->operation);
$this->assertEquals(['var' => '123'], $info->variableValues);
}
/**
* @see it('threads root value context correctly')
2016-10-18 16:25:32 +03:00
*/
public function testThreadsContextCorrectly() : void
2015-07-15 20:05:46 +03:00
{
// threads context correctly
2015-07-15 20:05:46 +03:00
$doc = 'query Example { a }';
$gotHere = false;
2018-09-01 18:07:06 +03:00
$data = ['contextThing' => 'thing'];
2015-07-15 20:05:46 +03:00
2018-09-01 18:07:06 +03:00
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => [
'a' => [
2018-09-01 18:07:06 +03:00
'type' => Type::string(),
'resolve' => function ($context) use (&$gotHere) {
$this->assertEquals('thing', $context['contextThing']);
$gotHere = true;
2018-09-01 18:07:06 +03:00
},
],
],
]),
]);
2015-07-15 20:05:46 +03:00
Executor::execute($schema, $ast, $data, null, [], 'Example');
2015-07-15 20:05:46 +03:00
$this->assertEquals(true, $gotHere);
}
2016-10-18 16:25:32 +03:00
/**
* @see it('correctly threads arguments')
2016-10-18 16:25:32 +03:00
*/
public function testCorrectlyThreadsArguments() : void
2015-07-15 20:05:46 +03:00
{
$doc = '
query Example {
b(numArg: 123, stringArg: "foo")
}
';
$gotHere = false;
$docAst = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => [
'b' => [
2018-09-01 18:07:06 +03:00
'args' => [
'numArg' => ['type' => Type::int()],
'stringArg' => ['type' => Type::string()],
],
2018-09-01 18:07:06 +03:00
'type' => Type::string(),
'resolve' => function ($_, $args) use (&$gotHere) {
$this->assertEquals(123, $args['numArg']);
$this->assertEquals('foo', $args['stringArg']);
$gotHere = true;
2018-09-01 18:07:06 +03:00
},
],
],
]),
]);
Executor::execute($schema, $docAst, null, null, [], 'Example');
2015-07-15 20:05:46 +03:00
$this->assertSame($gotHere, true);
}
2016-10-18 16:25:32 +03:00
/**
* @see it('nulls out error subtrees')
2016-10-18 16:25:32 +03:00
*/
public function testNullsOutErrorSubtrees() : void
2015-07-15 20:05:46 +03:00
{
$doc = '{
2016-11-27 01:51:42 +03:00
sync
syncError
syncRawError
syncReturnError
syncReturnErrorList
async
asyncReject
asyncRawReject
asyncEmptyReject
2015-07-15 20:05:46 +03:00
asyncError
2016-11-27 01:51:42 +03:00
asyncRawError
asyncReturnError
2015-07-15 20:05:46 +03:00
}';
$data = [
2018-09-01 18:07:06 +03:00
'sync' => function () {
2015-07-15 20:05:46 +03:00
return 'sync';
},
2018-09-01 18:07:06 +03:00
'syncError' => function () {
throw new UserError('Error getting syncError');
},
2018-09-01 18:07:06 +03:00
'syncRawError' => function () {
throw new UserError('Error getting syncRawError');
2015-07-15 20:05:46 +03:00
},
2016-11-27 01:51:42 +03:00
// inherited from JS reference implementation, but make no sense in this PHP impl
// leaving it just to simplify migrations from newer js versions
2018-09-01 18:07:06 +03:00
'syncReturnError' => function () {
return new UserError('Error getting syncReturnError');
2016-11-27 01:51:42 +03:00
},
'syncReturnErrorList' => function () {
return [
'sync0',
new UserError('Error getting syncReturnErrorList1'),
2016-11-27 01:51:42 +03:00
'sync2',
2018-09-01 18:07:06 +03:00
new UserError('Error getting syncReturnErrorList3'),
2016-11-27 01:51:42 +03:00
];
},
2018-09-01 18:07:06 +03:00
'async' => function () {
return new Deferred(function () {
return 'async';
});
2015-07-15 20:05:46 +03:00
},
2018-09-01 18:07:06 +03:00
'asyncReject' => function () {
return new Deferred(function () {
throw new UserError('Error getting asyncReject');
});
2016-11-27 01:51:42 +03:00
},
2018-09-01 18:07:06 +03:00
'asyncRawReject' => function () {
return new Deferred(function () {
throw new UserError('Error getting asyncRawReject');
2016-12-03 00:23:21 +03:00
});
2016-11-27 01:51:42 +03:00
},
2018-09-01 18:07:06 +03:00
'asyncEmptyReject' => function () {
return new Deferred(function () {
throw new UserError();
2016-12-03 00:23:21 +03:00
});
2015-07-15 20:05:46 +03:00
},
2018-09-01 18:07:06 +03:00
'asyncError' => function () {
return new Deferred(function () {
throw new UserError('Error getting asyncError');
2016-12-03 00:23:21 +03:00
});
2016-11-27 01:51:42 +03:00
},
// inherited from JS reference implementation, but make no sense in this PHP impl
// leaving it just to simplify migrations from newer js versions
2018-09-01 18:07:06 +03:00
'asyncRawError' => function () {
return new Deferred(function () {
throw new UserError('Error getting asyncRawError');
2016-12-03 00:23:21 +03:00
});
2016-11-27 01:51:42 +03:00
},
2018-09-01 18:07:06 +03:00
'asyncReturnError' => function () {
return new Deferred(function () {
throw new UserError('Error getting asyncReturnError');
2016-12-03 00:23:21 +03:00
});
2016-11-27 01:51:42 +03:00
},
2015-07-15 20:05:46 +03:00
];
$docAst = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => [
2018-09-01 18:07:06 +03:00
'sync' => ['type' => Type::string()],
'syncError' => ['type' => Type::string()],
'syncRawError' => ['type' => Type::string()],
'syncReturnError' => ['type' => Type::string()],
2016-11-27 01:51:42 +03:00
'syncReturnErrorList' => ['type' => Type::listOf(Type::string())],
2018-09-01 18:07:06 +03:00
'async' => ['type' => Type::string()],
'asyncReject' => ['type' => Type::string()],
'asyncRawReject' => ['type' => Type::string()],
'asyncEmptyReject' => ['type' => Type::string()],
'asyncError' => ['type' => Type::string()],
'asyncRawError' => ['type' => Type::string()],
'asyncReturnError' => ['type' => Type::string()],
],
]),
]);
2015-07-15 20:05:46 +03:00
$expected = [
2018-09-01 18:07:06 +03:00
'data' => [
'sync' => 'sync',
'syncError' => null,
'syncRawError' => null,
'syncReturnError' => null,
2016-11-27 01:51:42 +03:00
'syncReturnErrorList' => ['sync0', null, 'sync2', null],
2018-09-01 18:07:06 +03:00
'async' => 'async',
'asyncReject' => null,
'asyncRawReject' => null,
'asyncEmptyReject' => null,
'asyncError' => null,
'asyncRawError' => null,
'asyncReturnError' => null,
2015-07-15 20:05:46 +03:00
],
'errors' => [
2016-11-27 01:51:42 +03:00
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting syncError',
2016-11-27 01:51:42 +03:00
'locations' => [['line' => 3, 'column' => 7]],
2018-09-01 18:07:06 +03:00
'path' => ['syncError'],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting syncRawError',
'locations' => [['line' => 4, 'column' => 7]],
'path' => ['syncRawError'],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting syncReturnError',
2016-11-27 01:51:42 +03:00
'locations' => [['line' => 5, 'column' => 7]],
2018-09-01 18:07:06 +03:00
'path' => ['syncReturnError'],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting syncReturnErrorList1',
2016-11-27 01:51:42 +03:00
'locations' => [['line' => 6, 'column' => 7]],
2018-09-01 18:07:06 +03:00
'path' => ['syncReturnErrorList', 1],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting syncReturnErrorList3',
2016-11-27 01:51:42 +03:00
'locations' => [['line' => 6, 'column' => 7]],
2018-09-01 18:07:06 +03:00
'path' => ['syncReturnErrorList', 3],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting asyncReject',
2016-11-27 01:51:42 +03:00
'locations' => [['line' => 8, 'column' => 7]],
2018-09-01 18:07:06 +03:00
'path' => ['asyncReject'],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting asyncRawReject',
2016-11-27 01:51:42 +03:00
'locations' => [['line' => 9, 'column' => 7]],
2018-09-01 18:07:06 +03:00
'path' => ['asyncRawReject'],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'An unknown error occurred.',
2016-11-27 01:51:42 +03:00
'locations' => [['line' => 10, 'column' => 7]],
2018-09-01 18:07:06 +03:00
'path' => ['asyncEmptyReject'],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting asyncError',
2016-11-27 01:51:42 +03:00
'locations' => [['line' => 11, 'column' => 7]],
2018-09-01 18:07:06 +03:00
'path' => ['asyncError'],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting asyncRawError',
'locations' => [['line' => 12, 'column' => 7]],
'path' => ['asyncRawError'],
2016-11-27 01:51:42 +03:00
],
[
2018-09-01 18:07:06 +03:00
'message' => 'Error getting asyncReturnError',
2016-11-27 01:51:42 +03:00
'locations' => [['line' => 13, 'column' => 7]],
2018-09-01 18:07:06 +03:00
'path' => ['asyncReturnError'],
2016-11-27 01:51:42 +03:00
],
2018-09-01 18:07:06 +03:00
],
2015-07-15 20:05:46 +03:00
];
2016-12-03 00:23:21 +03:00
$result = Executor::execute($schema, $docAst, $data)->toArray();
2015-07-15 20:05:46 +03:00
$this->assertArraySubset($expected, $result);
2015-07-15 20:05:46 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('uses the inline operation if no operation name is provided')
2016-10-18 16:25:32 +03:00
*/
public function testUsesTheInlineOperationIfNoOperationIsProvided() : void
2015-07-15 20:05:46 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = '{ a }';
$data = ['a' => 'b'];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
2018-09-01 18:07:06 +03:00
],
]),
]);
2015-07-15 20:05:46 +03:00
$ex = Executor::execute($schema, $ast, $data);
2015-07-15 20:05:46 +03:00
$this->assertEquals(['data' => ['a' => 'b']], $ex->toArray());
2015-07-15 20:05:46 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('uses the only operation if no operation name is provided')
2016-10-18 16:25:32 +03:00
*/
public function testUsesTheOnlyOperationIfNoOperationIsProvided() : void
2015-07-15 20:05:46 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = 'query Example { a }';
$data = ['a' => 'b'];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => [
2018-09-01 18:07:06 +03:00
'a' => ['type' => Type::string()],
],
]),
]);
2015-07-15 20:05:46 +03:00
$ex = Executor::execute($schema, $ast, $data);
$this->assertEquals(['data' => ['a' => 'b']], $ex->toArray());
2015-07-15 20:05:46 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('uses the named operation if operation name is provided')
2016-10-18 16:25:32 +03:00
*/
public function testUsesTheNamedOperationIfOperationNameIsProvided() : void
2016-10-18 16:25:32 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = 'query Example { first: a } query OtherExample { second: a }';
$data = ['a' => 'b'];
$ast = Parser::parse($doc);
2016-10-18 16:25:32 +03:00
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
2016-10-18 16:25:32 +03:00
'fields' => [
2018-09-01 18:07:06 +03:00
'a' => ['type' => Type::string()],
],
]),
2016-10-18 16:25:32 +03:00
]);
$result = Executor::execute($schema, $ast, $data, null, null, 'OtherExample');
$this->assertEquals(['data' => ['second' => 'b']], $result->toArray());
}
/**
* @see it('provides error if no operation is provided')
2016-10-18 16:25:32 +03:00
*/
public function testProvidesErrorIfNoOperationIsProvided() : void
2016-10-18 16:25:32 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = 'fragment Example on Type { a }';
$data = ['a' => 'b'];
$ast = Parser::parse($doc);
2016-10-18 16:25:32 +03:00
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
2016-10-18 16:25:32 +03:00
'fields' => [
2018-09-01 18:07:06 +03:00
'a' => ['type' => Type::string()],
],
]),
2016-10-18 16:25:32 +03:00
]);
2018-09-01 18:07:06 +03:00
$result = Executor::execute($schema, $ast, $data);
$expected = [
'errors' => [
2018-09-01 18:07:06 +03:00
['message' => 'Must provide an operation.'],
],
];
$this->assertArraySubset($expected, $result->toArray());
2016-10-18 16:25:32 +03:00
}
/**
* @see it('errors if no op name is provided with multiple operations')
2016-10-18 16:25:32 +03:00
*/
public function testErrorsIfNoOperationIsProvidedWithMultipleOperations() : void
2015-07-15 20:05:46 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = 'query Example { a } query OtherExample { a }';
$data = ['a' => 'b'];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
2018-09-01 18:07:06 +03:00
],
]),
]);
2015-07-15 20:05:46 +03:00
$result = Executor::execute($schema, $ast, $data);
$expected = [
'errors' => [
2018-09-01 18:07:06 +03:00
['message' => 'Must provide operation name if query contains multiple operations.'],
],
];
$this->assertArraySubset($expected, $result->toArray());
2015-07-15 20:05:46 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('errors if unknown operation name is provided')
2016-10-18 16:25:32 +03:00
*/
public function testErrorsIfUnknownOperationNameIsProvided() : void
2016-10-18 16:25:32 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = 'query Example { a } query OtherExample { a }';
$ast = Parser::parse($doc);
2016-10-18 16:25:32 +03:00
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
2016-10-18 16:25:32 +03:00
'fields' => [
'a' => ['type' => Type::string()],
2018-09-01 18:07:06 +03:00
],
]),
2016-10-18 16:25:32 +03:00
]);
$result = Executor::execute(
$schema,
$ast,
null,
null,
null,
'UnknownExample'
);
$expected = [
'errors' => [
2018-09-01 18:07:06 +03:00
['message' => 'Unknown operation named "UnknownExample".'],
],
];
$this->assertArraySubset($expected, $result->toArray());
2016-10-18 16:25:32 +03:00
}
/**
* @see it('uses the query schema for queries')
2016-10-18 16:25:32 +03:00
*/
public function testUsesTheQuerySchemaForQueries() : void
2015-07-15 20:05:46 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = 'query Q { a } mutation M { c }';
$data = ['a' => 'b', 'c' => 'd'];
$ast = Parser::parse($doc);
$schema = new Schema([
2018-09-01 18:07:06 +03:00
'query' => new ObjectType([
'name' => 'Q',
2015-07-15 20:05:46 +03:00
'fields' => [
'a' => ['type' => Type::string()],
2018-09-01 18:07:06 +03:00
],
2015-07-15 20:05:46 +03:00
]),
'mutation' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'M',
2015-07-15 20:05:46 +03:00
'fields' => [
'c' => ['type' => Type::string()],
2018-09-01 18:07:06 +03:00
],
]),
]);
2015-07-15 20:05:46 +03:00
$queryResult = Executor::execute($schema, $ast, $data, null, [], 'Q');
$this->assertEquals(['data' => ['a' => 'b']], $queryResult->toArray());
2015-07-15 20:05:46 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('uses the mutation schema for mutations')
2016-10-18 16:25:32 +03:00
*/
public function testUsesTheMutationSchemaForMutations() : void
2015-07-15 20:05:46 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = 'query Q { a } mutation M { c }';
$data = ['a' => 'b', 'c' => 'd'];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Q',
2015-07-15 20:05:46 +03:00
'fields' => [
'a' => ['type' => Type::string()],
2018-09-01 18:07:06 +03:00
],
2015-07-15 20:05:46 +03:00
]),
'mutation' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'M',
2015-07-15 20:05:46 +03:00
'fields' => [
2018-09-01 18:07:06 +03:00
'c' => ['type' => Type::string()],
],
]),
]);
$mutationResult = Executor::execute($schema, $ast, $data, null, [], 'M');
$this->assertEquals(['data' => ['c' => 'd']], $mutationResult->toArray());
2015-07-15 20:05:46 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('uses the subscription schema for subscriptions')
2016-10-18 16:25:32 +03:00
*/
public function testUsesTheSubscriptionSchemaForSubscriptions() : void
2016-10-18 16:25:32 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = 'query Q { a } subscription S { a }';
$data = ['a' => 'b', 'c' => 'd'];
$ast = Parser::parse($doc);
2016-10-18 16:25:32 +03:00
$schema = new Schema([
2018-09-01 18:07:06 +03:00
'query' => new ObjectType([
'name' => 'Q',
2016-10-18 16:25:32 +03:00
'fields' => [
2018-09-01 18:07:06 +03:00
'a' => ['type' => Type::string()],
],
2016-10-18 16:25:32 +03:00
]),
'subscription' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'S',
2016-10-18 16:25:32 +03:00
'fields' => [
2018-09-01 18:07:06 +03:00
'a' => ['type' => Type::string()],
],
]),
2016-10-18 16:25:32 +03:00
]);
$subscriptionResult = Executor::execute($schema, $ast, $data, null, [], 'S');
$this->assertEquals(['data' => ['a' => 'b']], $subscriptionResult->toArray());
}
public function testCorrectFieldOrderingDespiteExecutionOrder() : void
2016-11-27 01:51:42 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = '{
2016-11-27 01:51:42 +03:00
a,
b,
c,
d,
e
}';
$data = [
'a' => function () {
return 'a';
},
'b' => function () {
2018-09-01 18:07:06 +03:00
return new Deferred(function () {
return 'b';
});
2016-11-27 01:51:42 +03:00
},
'c' => function () {
return 'c';
},
'd' => function () {
2018-09-01 18:07:06 +03:00
return new Deferred(function () {
return 'd';
});
2016-11-27 01:51:42 +03:00
},
'e' => function () {
return 'e';
},
];
$ast = Parser::parse($doc);
$queryType = new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'DeepDataType',
2016-11-27 01:51:42 +03:00
'fields' => [
2018-09-01 18:07:06 +03:00
'a' => ['type' => Type::string()],
'b' => ['type' => Type::string()],
'c' => ['type' => Type::string()],
'd' => ['type' => Type::string()],
'e' => ['type' => Type::string()],
],
2016-11-27 01:51:42 +03:00
]);
2018-09-01 18:07:06 +03:00
$schema = new Schema(['query' => $queryType]);
2016-11-27 01:51:42 +03:00
$expected = [
'data' => [
'a' => 'a',
'b' => 'b',
'c' => 'c',
'd' => 'd',
'e' => 'e',
2018-09-01 18:07:06 +03:00
],
2016-11-27 01:51:42 +03:00
];
2016-12-03 00:23:21 +03:00
$this->assertEquals($expected, Executor::execute($schema, $ast, $data)->toArray());
2016-11-27 01:51:42 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('Avoids recursion')
2016-10-18 16:25:32 +03:00
*/
public function testAvoidsRecursion() : void
2015-07-15 20:05:46 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = '
2015-07-15 20:05:46 +03:00
query Q {
a
...Frag
...Frag
}
fragment Frag on DataType {
a,
...Frag
}
';
2018-09-01 18:07:06 +03:00
$data = ['a' => 'b'];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
2018-09-01 18:07:06 +03:00
],
]),
]);
2015-07-15 20:05:46 +03:00
$queryResult = Executor::execute($schema, $ast, $data, null, [], 'Q');
$this->assertEquals(['data' => ['a' => 'b']], $queryResult->toArray());
2015-07-15 20:05:46 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('does not include illegal fields in output')
2016-10-18 16:25:32 +03:00
*/
public function testDoesNotIncludeIllegalFieldsInOutput() : void
2015-07-15 20:05:46 +03:00
{
2018-09-01 18:07:06 +03:00
$doc = 'mutation M {
2015-07-15 20:05:46 +03:00
thisIsIllegalDontIncludeMe
}';
2018-09-01 18:07:06 +03:00
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Q',
2015-07-15 20:05:46 +03:00
'fields' => [
'a' => ['type' => Type::string()],
2018-09-01 18:07:06 +03:00
],
2015-07-15 20:05:46 +03:00
]),
'mutation' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'M',
2015-07-15 20:05:46 +03:00
'fields' => [
'c' => ['type' => Type::string()],
2018-09-01 18:07:06 +03:00
],
]),
]);
$mutationResult = Executor::execute($schema, $ast);
$this->assertEquals(['data' => []], $mutationResult->toArray());
}
2016-10-18 16:25:32 +03:00
/**
* @see it('does not include arguments that were not set')
2016-10-18 16:25:32 +03:00
*/
public function testDoesNotIncludeArgumentsThatWereNotSet() : void
{
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => [
'field' => [
2018-09-01 18:07:06 +03:00
'type' => Type::string(),
'resolve' => function ($data, $args) {
return $args ? json_encode($args) : '';
},
'args' => [
'a' => ['type' => Type::boolean()],
'b' => ['type' => Type::boolean()],
'c' => ['type' => Type::boolean()],
'd' => ['type' => Type::int()],
2018-09-01 18:07:06 +03:00
'e' => ['type' => Type::int()],
],
],
],
]),
]);
2018-09-01 18:07:06 +03:00
$query = Parser::parse('{ field(a: true, c: false, e: 0) }');
$result = Executor::execute($schema, $query);
$expected = [
2018-09-01 18:07:06 +03:00
'data' => ['field' => '{"a":true,"c":false,"e":0}'],
];
$this->assertEquals($expected, $result->toArray());
2015-08-31 22:44:03 +03:00
}
2016-10-18 16:25:32 +03:00
/**
* @see it('fails when an isTypeOf check is not met')
2016-10-18 16:25:32 +03:00
*/
public function testFailsWhenAnIsTypeOfCheckIsNotMet() : void
2016-10-18 16:25:32 +03:00
{
$SpecialType = new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'SpecialType',
'isTypeOf' => function ($obj) {
2016-10-18 16:25:32 +03:00
return $obj instanceof Special;
},
2018-09-01 18:07:06 +03:00
'fields' => [
'value' => ['type' => Type::string()],
],
2016-10-18 16:25:32 +03:00
]);
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Query',
2016-10-18 16:25:32 +03:00
'fields' => [
'specials' => [
2018-09-01 18:07:06 +03:00
'type' => Type::listOf($SpecialType),
'resolve' => function ($rootValue) {
2016-10-18 16:25:32 +03:00
return $rootValue['specials'];
2018-09-01 18:07:06 +03:00
},
],
],
]),
2016-10-18 16:25:32 +03:00
]);
2018-09-01 18:07:06 +03:00
$query = Parser::parse('{ specials { value } }');
$value = [
'specials' => [new Special('foo'), new NotSpecial('bar')],
2016-10-18 16:25:32 +03:00
];
$result = Executor::execute($schema, $query, $value);
2018-09-01 18:07:06 +03:00
$this->assertEquals(
[
'specials' => [
['value' => 'foo'],
null,
],
],
$result->data
);
2016-10-18 16:25:32 +03:00
$this->assertEquals(1, count($result->errors));
2018-09-01 18:07:06 +03:00
$this->assertEquals(
[
'message' => 'Expected value of type "SpecialType" but got: instance of GraphQL\Tests\Executor\TestClasses\NotSpecial.',
'locations' => [['line' => 1, 'column' => 3]],
'path' => ['specials', 1],
],
$result->errors[0]->toSerializableArray()
);
2016-10-18 16:25:32 +03:00
}
/**
* @see it('executes ignoring invalid non-executable definitions')
2016-10-18 16:25:32 +03:00
*/
public function testExecutesIgnoringInvalidNonExecutableDefinitions() : void
2016-10-18 16:25:32 +03:00
{
$query = Parser::parse('
{ foo }
type Query { bar: String }
2016-10-18 16:25:32 +03:00
');
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Query',
2016-10-18 16:25:32 +03:00
'fields' => [
2018-09-01 18:07:06 +03:00
'foo' => ['type' => Type::string()],
],
]),
2016-10-18 16:25:32 +03:00
]);
$result = Executor::execute($schema, $query);
$expected = [
2018-09-01 18:07:06 +03:00
'data' => ['foo' => null],
];
$this->assertArraySubset($expected, $result->toArray());
2016-10-18 16:25:32 +03:00
}
/**
* @see it('uses a custom field resolver')
*/
public function testUsesACustomFieldResolver() : void
{
$query = Parser::parse('{ foo }');
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Query',
'fields' => [
2018-09-01 18:07:06 +03:00
'foo' => ['type' => Type::string()],
],
]),
]);
// For the purposes of test, just return the name of the field!
$customResolver = function ($source, $args, $context, ResolveInfo $info) {
return $info->fieldName;
};
$result = Executor::execute(
$schema,
$query,
null,
null,
null,
null,
$customResolver
);
$expected = [
2018-09-01 18:07:06 +03:00
'data' => ['foo' => 'foo'],
];
$this->assertEquals($expected, $result->toArray());
}
public function testSubstitutesArgumentWithDefaultValue() : void
{
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Type',
'fields' => [
'field' => [
2018-09-01 18:07:06 +03:00
'type' => Type::string(),
'resolve' => function ($data, $args) {
return $args ? json_encode($args) : '';
},
'args' => [
'a' => ['type' => Type::boolean(), 'defaultValue' => 1],
'b' => ['type' => Type::boolean(), 'defaultValue' => null],
'c' => ['type' => Type::boolean(), 'defaultValue' => 0],
'd' => ['type' => Type::int(), 'defaultValue' => false],
'e' => ['type' => Type::int(), 'defaultValue' => '0'],
'f' => ['type' => Type::int(), 'defaultValue' => 'some-string'],
2016-10-18 16:25:32 +03:00
'g' => ['type' => Type::boolean()],
2018-09-01 18:07:06 +03:00
'h' => [
'type' => new InputObjectType([
'name' => 'ComplexType',
'fields' => [
'a' => ['type' => Type::int()],
'b' => ['type' => Type::string()],
],
]), 'defaultValue' => ['a' => 1, 'b' => 'test'],
],
],
],
],
]),
]);
2018-09-01 18:07:06 +03:00
$query = Parser::parse('{ field }');
$result = Executor::execute($schema, $query);
$expected = [
2018-09-01 18:07:06 +03:00
'data' => ['field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"}}'],
];
$this->assertEquals($expected, $result->toArray());
}
/**
* @see https://github.com/webonyx/graphql-php/issues/59
*/
public function testSerializesToEmptyObjectVsEmptyArray() : void
{
$iface = null;
$a = new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'A',
'fields' => [
'id' => Type::id(),
],
2018-09-01 18:07:06 +03:00
'interfaces' => function () use (&$iface) {
return [$iface];
2018-09-01 18:07:06 +03:00
},
]);
$b = new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'B',
'fields' => [
'id' => Type::id(),
],
2018-09-01 18:07:06 +03:00
'interfaces' => function () use (&$iface) {
return [$iface];
2018-09-01 18:07:06 +03:00
},
]);
$iface = new InterfaceType([
2018-09-01 18:07:06 +03:00
'name' => 'Iface',
'fields' => [
'id' => Type::id(),
],
2018-09-01 18:07:06 +03:00
'resolveType' => function ($v) use ($a, $b) {
return $v['type'] === 'A' ? $a : $b;
2018-09-01 18:07:06 +03:00
},
]);
$schema = new Schema([
'query' => new ObjectType([
2018-09-01 18:07:06 +03:00
'name' => 'Query',
'fields' => [
2018-09-01 18:07:06 +03:00
'ab' => Type::listOf($iface),
],
]),
2018-09-01 18:07:06 +03:00
'types' => [$a, $b],
]);
$data = [
'ab' => [
['id' => 1, 'type' => 'A'],
['id' => 2, 'type' => 'A'],
['id' => 3, 'type' => 'B'],
2018-09-01 18:07:06 +03:00
['id' => 4, 'type' => 'B'],
],
];
$query = Parser::parse('
{
ab {
... on A{
id
}
}
}
');
$result = Executor::execute($schema, $query, $data, null);
2018-09-01 18:07:06 +03:00
$this->assertEquals(
[
'data' => [
'ab' => [
['id' => '1'],
['id' => '2'],
new \stdClass(),
new \stdClass(),
],
],
],
$result->toArray()
);
}
2015-07-15 20:05:46 +03:00
}