graphql-php/tests/Executor/ExecutorTest.php

1158 lines
35 KiB
PHP
Raw Normal View History

2015-07-15 20:05:46 +03:00
<?php
2016-04-09 10:36:53 +03:00
namespace GraphQL\Tests\Executor;
2015-07-15 20:05:46 +03:00
2016-10-18 16:25:32 +03:00
require_once __DIR__ . '/TestClasses.php';
2016-12-03 00:23:21 +03:00
use GraphQL\Deferred;
use GraphQL\Error\Error;
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;
use GraphQL\Type\Schema;
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-07-29 18:43:10 +03:00
use PHPUnit\Framework\TestCase;
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;
2016-11-27 01:51:42 +03:00
$data = null;
$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 = [
'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) {
return 'Pic of size: ' . $size;
},
2016-11-27 01:51:42 +03:00
'promise' => function() use ($promiseData) {
return $promiseData();
2015-07-15 20:05:46 +03:00
},
'deep' => function () use (&$deepData) {
return $deepData;
}
];
$deepData = [
'a' => function () { return 'Already Been Done'; },
'b' => function () { return 'Boring'; },
'c' => function () {
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];
}
];
$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
}
';
$ast = Parser::parse($doc);
$expected = [
'data' => [
'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' ],
'deeper' => [
[ 'a' => 'Apple', 'b' => 'Banana' ],
null,
[ 'a' => 'Apple', 'b' => 'Banana' ]
]
]
]
];
$deepDataType = null;
$dataType = new ObjectType([
'name' => 'DataType',
'fields' => function() use (&$dataType, &$deepDataType) {
return [
'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']);
}
],
'promise' => ['type' => $dataType],
'deep' => ['type' => $deepDataType],
];
}
2015-07-15 20:05:46 +03:00
]);
$deepDataType = new ObjectType([
'name' => 'DeepDataType',
'fields' => [
'a' => [ 'type' => Type::string() ],
'b' => [ 'type' => Type::string() ],
'c' => [ 'type' => Type::listOf(Type::string()) ],
'deeper' => [ 'type' => Type::listOf($dataType) ]
]
]);
$schema = new Schema(['query' => $dataType]);
2015-07-15 20:05:46 +03:00
2016-12-03 00:23:21 +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([
'name' => 'Type',
'fields' => function() use (&$Type) {
return [
'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' => [
'type' => $Type,
'resolve' => function () {
return [];
}
]
];
}
2015-07-15 20:05:46 +03:00
]);
$schema = new Schema(['query' => $Type]);
2015-07-15 20:05:46 +03:00
$expected = [
'data' => [
'a' => 'Apple',
'b' => 'Banana',
'c' => 'Cherry',
'deep' => [
'b' => 'Banana',
'c' => 'Cherry',
'deeper' => [
'b' => 'Banana',
'c' => 'Cherry'
]
]
]
];
$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 */
$info = null;
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Test',
'fields' => [
'test' => [
'type' => Type::string(),
'resolve' => function($val, $args, $ctx, $_info) use (&$info) {
$info = $_info;
}
]
]
])
]);
$rootValue = [ 'root' => 'val' ];
Update query variable coercion to meet the rules outlined in the specification. The framework currently coerces query variables similar to the way it treats output values, which means it attempts to coerce the value into the field's corresponding data type regardless of the received value. According to items 3f and 3g in section 6.1.2 (http://facebook.github.io/graphql/#sec-Validating-Requests) of Facebook's GraphQL specification query variables should be coerced according to their type's input coercion rules laid out in section 3.1.1 (http://facebook.github.io/graphql/#sec-Scalars). If the value can not be coerced into the correct type according the the input coercion rules for the type a query error should be thrown. This ensures that client provided query variables were of the correct format and will be a valid format and type by the time they are passed into an implementing resolver. This patch fixes the above issue by updating the way query variables are sanitized during the process of parsing the query. It directly follows the rules for scalar input coercion laid out by the specification and throws query errors when a value that cannot be coerced to the correct type is given. Tests for isValidPHPValue will also be updated to ensure that it is doing the correct type checks on Values::isValidPHPValue for the given type and value provided. A new test case will also be added to test Values::getVariableValues and make sure it is also enforcing the scalar input coercion rules and throwing errors for invalid values.
2017-09-18 18:49:05 +03:00
Executor::execute($schema, $ast, $rootValue, null, [ 'var' => '123' ]);
2016-10-18 16:25:32 +03:00
$this->assertEquals([
'fieldName',
'fieldNodes',
2016-10-18 16:25:32 +03:00
'returnType',
'parentType',
'path',
'schema',
'fragments',
'rootValue',
'operation',
'variableValues',
], array_keys((array) $info));
$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;
$data = [
'contextThing' => 'thing',
];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Type',
'fields' => [
'a' => [
'type' => Type::string(),
'resolve' => function ($context) use ($doc, &$gotHere) {
$this->assertEquals('thing', $context['contextThing']);
$gotHere = true;
}
]
2015-07-15 20:05:46 +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([
'name' => 'Type',
'fields' => [
'b' => [
'args' => [
'numArg' => ['type' => Type::int()],
'stringArg' => ['type' => Type::string()]
],
'type' => Type::string(),
'resolve' => function ($_, $args) use (&$gotHere) {
$this->assertEquals(123, $args['numArg']);
$this->assertEquals('foo', $args['stringArg']);
$gotHere = true;
}
]
2015-07-15 20:05:46 +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 = [
'sync' => function () {
return 'sync';
},
'syncError' => function () {
throw new UserError('Error getting syncError');
},
'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
'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',
new UserError('Error getting syncReturnErrorList3')
2016-11-27 01:51:42 +03:00
];
},
2015-07-15 20:05:46 +03:00
'async' => function() {
2016-12-03 00:23:21 +03:00
return new Deferred(function() { return 'async'; });
2015-07-15 20:05:46 +03:00
},
'asyncReject' => function() {
return new Deferred(function() { throw new UserError('Error getting asyncReject'); });
2016-11-27 01:51:42 +03:00
},
'asyncRawReject' => function () {
2016-12-03 00:23:21 +03:00
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
},
'asyncEmptyReject' => function () {
2016-12-03 00:23:21 +03:00
return new Deferred(function() {
throw new UserError();
2016-12-03 00:23:21 +03:00
});
2015-07-15 20:05:46 +03:00
},
'asyncError' => function() {
2016-12-03 00:23:21 +03:00
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
'asyncRawError' => function() {
2016-12-03 00:23:21 +03:00
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
},
'asyncReturnError' => function() {
2016-12-03 00:23:21 +03:00
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([
'name' => 'Type',
'fields' => [
'sync' => ['type' => Type::string()],
'syncError' => ['type' => Type::string()],
2016-11-27 01:51:42 +03:00
'syncRawError' => ['type' => Type::string()],
'syncReturnError' => ['type' => Type::string()],
'syncReturnErrorList' => ['type' => Type::listOf(Type::string())],
'async' => ['type' => Type::string()],
'asyncReject' => ['type' => Type::string() ],
2016-11-27 01:51:42 +03:00
'asyncRawReject' => ['type' => Type::string() ],
'asyncEmptyReject' => ['type' => Type::string() ],
'asyncError' => ['type' => Type::string()],
2016-11-27 01:51:42 +03:00
'asyncRawError' => ['type' => Type::string()],
'asyncReturnError' => ['type' => Type::string()],
]
])
]);
2015-07-15 20:05:46 +03:00
$expected = [
'data' => [
'sync' => 'sync',
'syncError' => null,
'syncRawError' => null,
2016-11-27 01:51:42 +03:00
'syncReturnError' => null,
'syncReturnErrorList' => ['sync0', null, 'sync2', null],
2015-07-15 20:05:46 +03:00
'async' => 'async',
'asyncReject' => null,
2016-11-27 01:51:42 +03:00
'asyncRawReject' => null,
'asyncEmptyReject' => null,
2015-07-15 20:05:46 +03:00
'asyncError' => null,
2016-11-27 01:51:42 +03:00
'asyncRawError' => null,
'asyncReturnError' => null,
2015-07-15 20:05:46 +03:00
],
'errors' => [
2016-11-27 01:51:42 +03:00
[
'message' => 'Error getting syncError',
'locations' => [['line' => 3, 'column' => 7]],
'path' => ['syncError']
],
[
'message' => 'Error getting syncRawError',
'locations' => [ [ 'line' => 4, 'column' => 7 ] ],
'path'=> [ 'syncRawError' ]
],
[
'message' => 'Error getting syncReturnError',
'locations' => [['line' => 5, 'column' => 7]],
'path' => ['syncReturnError']
],
[
'message' => 'Error getting syncReturnErrorList1',
'locations' => [['line' => 6, 'column' => 7]],
'path' => ['syncReturnErrorList', 1]
],
[
'message' => 'Error getting syncReturnErrorList3',
'locations' => [['line' => 6, 'column' => 7]],
'path' => ['syncReturnErrorList', 3]
],
[
'message' => 'Error getting asyncReject',
'locations' => [['line' => 8, 'column' => 7]],
'path' => ['asyncReject']
],
[
'message' => 'Error getting asyncRawReject',
'locations' => [['line' => 9, 'column' => 7]],
'path' => ['asyncRawReject']
],
[
'message' => 'An unknown error occurred.',
'locations' => [['line' => 10, 'column' => 7]],
'path' => ['asyncEmptyReject']
],
[
'message' => 'Error getting asyncError',
'locations' => [['line' => 11, 'column' => 7]],
'path' => ['asyncError']
],
[
'message' => 'Error getting asyncRawError',
'locations' => [ [ 'line' => 12, 'column' => 7 ] ],
'path' => [ 'asyncRawError' ]
],
[
'message' => 'Error getting asyncReturnError',
'locations' => [['line' => 13, 'column' => 7]],
'path' => ['asyncReturnError']
],
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
{
$doc = '{ a }';
$data = ['a' => 'b'];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
]);
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
{
$doc = 'query Example { a }';
$data = [ 'a' => 'b' ];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Type',
'fields' => [
'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
{
$doc = 'query Example { first: a } query OtherExample { second: a }';
$data = [ 'a' => 'b' ];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Type',
'fields' => [
'a' => [ 'type' => Type::string() ],
]
])
]);
$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
{
$doc = 'fragment Example on Type { a }';
$data = [ 'a' => 'b' ];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Type',
'fields' => [
'a' => [ 'type' => Type::string() ],
]
])
]);
$result = Executor::execute($schema, $ast, $data);
$expected = [
'errors' => [
[
'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
{
$doc = 'query Example { a } query OtherExample { a }';
$data = ['a' => 'b'];
2015-07-15 20:05:46 +03:00
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
]);
2015-07-15 20:05:46 +03:00
$result = Executor::execute($schema, $ast, $data);
$expected = [
'errors' => [
[
'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
{
$doc = 'query Example { a } query OtherExample { a }';
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
2016-10-18 16:25:32 +03:00
]
])
]);
$result = Executor::execute(
$schema,
$ast,
null,
null,
null,
'UnknownExample'
);
$expected = [
'errors' => [
[
'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
{
$doc = 'query Q { a } mutation M { c }';
$data = ['a' => 'b', 'c' => 'd'];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2015-07-15 20:05:46 +03:00
'name' => 'Q',
'fields' => [
'a' => ['type' => Type::string()],
]
]),
'mutation' => new ObjectType([
2015-07-15 20:05:46 +03:00
'name' => 'M',
'fields' => [
'c' => ['type' => Type::string()],
]
])
]);
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
{
$doc = 'query Q { a } mutation M { c }';
$data = [ 'a' => 'b', 'c' => 'd' ];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2015-07-15 20:05:46 +03:00
'name' => 'Q',
'fields' => [
'a' => ['type' => Type::string()],
]
]),
'mutation' => new ObjectType([
2015-07-15 20:05:46 +03:00
'name' => 'M',
'fields' => [
'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
{
$doc = 'query Q { a } subscription S { a }';
$data = [ 'a' => 'b', 'c' => 'd' ];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Q',
'fields' => [
'a' => [ 'type' => Type::string() ],
]
]),
'subscription' => new ObjectType([
'name' => 'S',
'fields' => [
'a' => [ 'type' => Type::string() ],
]
])
]);
$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
{
$doc = '{
a,
b,
c,
d,
e
}';
$data = [
'a' => function () {
return 'a';
},
'b' => function () {
2016-12-03 00:23:21 +03:00
return new Deferred(function () { return 'b'; });
2016-11-27 01:51:42 +03:00
},
'c' => function () {
return 'c';
},
'd' => function () {
2016-12-03 00:23:21 +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([
'name' => 'DeepDataType',
'fields' => [
'a' => [ 'type' => Type::string() ],
'b' => [ 'type' => Type::string() ],
'c' => [ 'type' => Type::string() ],
'd' => [ 'type' => Type::string() ],
'e' => [ 'type' => Type::string() ],
]
]);
$schema = new Schema(['query' => $queryType]);
$expected = [
'data' => [
'a' => 'a',
'b' => 'b',
'c' => 'c',
'd' => 'd',
'e' => 'e',
]
];
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
{
$doc = '
query Q {
a
...Frag
...Frag
}
fragment Frag on DataType {
a,
...Frag
}
';
$data = ['a' => 'b'];
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Type',
'fields' => [
'a' => ['type' => Type::string()],
]
])
]);
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
{
$doc = 'mutation M {
thisIsIllegalDontIncludeMe
}';
$ast = Parser::parse($doc);
$schema = new Schema([
'query' => new ObjectType([
2015-07-15 20:05:46 +03:00
'name' => 'Q',
'fields' => [
'a' => ['type' => Type::string()],
]
]),
'mutation' => new ObjectType([
2015-07-15 20:05:46 +03:00
'name' => 'M',
'fields' => [
'c' => ['type' => Type::string()],
]
])
]);
$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([
'name' => 'Type',
'fields' => [
'field' => [
'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()],
'e' => ['type' => Type::int()]
]
]
]
])
]);
$query = Parser::parse('{ field(a: true, c: false, e: 0) }');
$result = Executor::execute($schema, $query);
$expected = [
'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([
'name' => 'SpecialType',
'isTypeOf' => function($obj) {
return $obj instanceof Special;
},
'fields' => [
'value' => ['type' => Type::string()]
]
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'specials' => [
'type' => Type::listOf($SpecialType),
'resolve' => function($rootValue) {
return $rootValue['specials'];
}
]
]
])
]);
$query = Parser::parse('{ specials { value } }');
$value = [
'specials' => [ new Special('foo'), new NotSpecial('bar') ]
];
$result = Executor::execute($schema, $query, $value);
$this->assertEquals([
'specials' => [
['value' => 'foo'],
null
]
], $result->data);
$this->assertEquals(1, count($result->errors));
$this->assertEquals([
'message' => 'Expected value of type "SpecialType" but got: instance of GraphQL\Tests\Executor\NotSpecial.',
'locations' => [['line' => 1, 'column' => 3]],
'path' => ['specials', 1]
2016-10-18 16:25:32 +03:00
], $result->errors[0]->toSerializableArray());
}
/**
* @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([
'name' => 'Query',
'fields' => [
'foo' => ['type' => Type::string()]
2016-10-18 16:25:32 +03:00
]
])
]);
$result = Executor::execute($schema, $query);
$expected = [
'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([
'name' => 'Query',
'fields' => [
'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 = [
'data' => ['foo' => 'foo']
];
$this->assertEquals($expected, $result->toArray());
}
public function testSubstitutesArgumentWithDefaultValue() : void
{
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Type',
'fields' => [
'field' => [
'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()],
'h' => ['type' => new InputObjectType([
'name' => 'ComplexType',
'fields' => [
'a' => ['type' => Type::int()],
'b' => ['type' => Type::string()]
]
]), 'defaultValue' => ['a' => 1, 'b' => 'test']]
]
]
]
])
]);
$query = Parser::parse('{ field }');
$result = Executor::execute($schema, $query);
$expected = [
'data' => [
2016-11-18 19:59:28 +03:00
'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([
'name' => 'A',
'fields' => [
'id' => Type::id()
],
'interfaces' => function() use (&$iface) {
return [$iface];
}
]);
$b = new ObjectType([
'name' => 'B',
'fields' => [
'id' => Type::id()
],
'interfaces' => function() use (&$iface) {
return [$iface];
}
]);
$iface = new InterfaceType([
'name' => 'Iface',
'fields' => [
'id' => Type::id()
],
'resolveType' => function($v) use ($a, $b) {
return $v['type'] === 'A' ? $a : $b;
}
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'ab' => Type::listOf($iface)
]
]),
'types' => [$a, $b]
]);
$data = [
'ab' => [
['id' => 1, 'type' => 'A'],
['id' => 2, 'type' => 'A'],
['id' => 3, 'type' => 'B'],
['id' => 4, 'type' => 'B']
]
];
$query = Parser::parse('
{
ab {
... on A{
id
}
}
}
');
$result = Executor::execute($schema, $query, $data, null);
$this->assertEquals([
'data' => [
'ab' => [
['id' => '1'],
['id' => '2'],
new \stdClass(),
new \stdClass()
]
]
], $result->toArray());
}
2015-07-15 20:05:46 +03:00
}