2017-02-19 22:26:56 +03:00
|
|
|
<?php
|
|
|
|
namespace GraphQL\Tests\Utils;
|
|
|
|
|
|
|
|
use GraphQL\GraphQL;
|
2017-07-28 13:55:25 +03:00
|
|
|
use GraphQL\Language\AST\EnumTypeDefinitionNode;
|
|
|
|
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
|
|
|
|
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
|
|
|
|
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
|
|
|
|
use GraphQL\Language\AST\TypeNode;
|
2017-02-19 22:26:56 +03:00
|
|
|
use GraphQL\Language\Parser;
|
|
|
|
use GraphQL\Utils\BuildSchema;
|
|
|
|
use GraphQL\Utils\SchemaPrinter;
|
|
|
|
|
|
|
|
use GraphQL\Type\Definition\Directive;
|
|
|
|
use GraphQL\Type\Definition\EnumValueDefinition;
|
|
|
|
|
|
|
|
class BuildSchemaTest extends \PHPUnit_Framework_TestCase
|
|
|
|
{
|
|
|
|
// Describe: Schema Builder
|
|
|
|
|
|
|
|
private function cycleOutput($body)
|
|
|
|
{
|
|
|
|
$ast = Parser::parse($body);
|
|
|
|
$schema = BuildSchema::buildAST($ast);
|
|
|
|
return "\n" . SchemaPrinter::doPrint($schema);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it can use built schema for limited execution
|
|
|
|
*/
|
|
|
|
public function testUseBuiltSchemaForLimitedExecution()
|
|
|
|
{
|
|
|
|
$schema = BuildSchema::buildAST(Parser::parse('
|
|
|
|
schema { query: Query }
|
|
|
|
type Query {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
'));
|
|
|
|
|
|
|
|
$result = GraphQL::execute($schema, '{ str }', ['str' => 123]);
|
|
|
|
$this->assertEquals($result['data'], ['str' => 123]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it can build a schema directly from the source
|
|
|
|
*/
|
|
|
|
public function testBuildSchemaDirectlyFromSource()
|
|
|
|
{
|
|
|
|
$schema = BuildSchema::build("
|
|
|
|
schema { query: Query }
|
|
|
|
type Query {
|
|
|
|
add(x: Int, y: Int): Int
|
|
|
|
}
|
|
|
|
");
|
|
|
|
|
|
|
|
$result = GraphQL::execute(
|
|
|
|
$schema,
|
|
|
|
'{ add(x: 34, y: 55) }',
|
|
|
|
[
|
|
|
|
'add' => function ($root, $args) {
|
|
|
|
return $args['x'] + $args['y'];
|
|
|
|
}
|
|
|
|
]
|
|
|
|
);
|
|
|
|
$this->assertEquals($result, ['data' => ['add' => 89]]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Simple Type
|
|
|
|
*/
|
|
|
|
public function testSimpleType()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: HelloScalars
|
|
|
|
}
|
|
|
|
|
|
|
|
type HelloScalars {
|
2017-07-05 15:45:02 +03:00
|
|
|
str: String
|
|
|
|
int: Int
|
2017-02-19 22:26:56 +03:00
|
|
|
float: Float
|
|
|
|
id: ID
|
2017-07-05 15:45:02 +03:00
|
|
|
bool: Boolean
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it With directives
|
|
|
|
*/
|
|
|
|
public function testWithDirectives()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
directive @foo(arg: Int) on FIELD
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Supports descriptions
|
|
|
|
*/
|
|
|
|
public function testSupportsDescriptions()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
# This is a directive
|
|
|
|
directive @foo(
|
|
|
|
# It has an argument
|
|
|
|
arg: Int
|
|
|
|
) on FIELD
|
|
|
|
|
|
|
|
# With an enum
|
|
|
|
enum Color {
|
|
|
|
RED
|
|
|
|
|
|
|
|
# Not a creative color
|
|
|
|
GREEN
|
|
|
|
BLUE
|
|
|
|
}
|
|
|
|
|
|
|
|
# What a great type
|
|
|
|
type Hello {
|
|
|
|
# And a field to boot
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
2017-07-28 13:55:25 +03:00
|
|
|
$this->assertEquals($body, $output);
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Maintains @skip & @include
|
|
|
|
*/
|
|
|
|
public function testMaintainsSkipAndInclude()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$schema = BuildSchema::buildAST(Parser::parse($body));
|
|
|
|
$this->assertEquals(count($schema->getDirectives()), 3);
|
|
|
|
$this->assertEquals($schema->getDirective('skip'), Directive::skipDirective());
|
|
|
|
$this->assertEquals($schema->getDirective('include'), Directive::includeDirective());
|
|
|
|
$this->assertEquals($schema->getDirective('deprecated'), Directive::deprecatedDirective());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Overriding directives excludes specified
|
|
|
|
*/
|
|
|
|
public function testOverridingDirectivesExcludesSpecified()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
directive @skip on FIELD
|
|
|
|
directive @include on FIELD
|
|
|
|
directive @deprecated on FIELD_DEFINITION
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$schema = BuildSchema::buildAST(Parser::parse($body));
|
|
|
|
$this->assertEquals(count($schema->getDirectives()), 3);
|
|
|
|
$this->assertNotEquals($schema->getDirective('skip'), Directive::skipDirective());
|
|
|
|
$this->assertNotEquals($schema->getDirective('include'), Directive::includeDirective());
|
|
|
|
$this->assertNotEquals($schema->getDirective('deprecated'), Directive::deprecatedDirective());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Type modifiers
|
|
|
|
*/
|
|
|
|
public function testTypeModifiers()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: HelloScalars
|
|
|
|
}
|
|
|
|
|
|
|
|
type HelloScalars {
|
2017-07-05 15:45:02 +03:00
|
|
|
nonNullStr: String!
|
2017-07-05 14:33:16 +03:00
|
|
|
listOfStrs: [String]
|
2017-07-05 15:45:02 +03:00
|
|
|
listOfNonNullStrs: [String!]
|
2017-07-05 14:33:16 +03:00
|
|
|
nonNullListOfStrs: [String]!
|
2017-07-05 15:45:02 +03:00
|
|
|
nonNullListOfNonNullStrs: [String!]!
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Recursive type
|
|
|
|
*/
|
|
|
|
public function testRecursiveType()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Recurse
|
|
|
|
}
|
|
|
|
|
|
|
|
type Recurse {
|
2017-07-05 14:33:16 +03:00
|
|
|
str: String
|
2017-07-05 15:45:02 +03:00
|
|
|
recurse: Recurse
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Two types circular
|
|
|
|
*/
|
|
|
|
public function testTwoTypesCircular()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: TypeOne
|
|
|
|
}
|
|
|
|
|
|
|
|
type TypeOne {
|
|
|
|
str: String
|
|
|
|
typeTwo: TypeTwo
|
|
|
|
}
|
|
|
|
|
|
|
|
type TypeTwo {
|
|
|
|
str: String
|
|
|
|
typeOne: TypeOne
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Single argument field
|
|
|
|
*/
|
|
|
|
public function testSingleArgumentField()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
2017-07-05 15:45:02 +03:00
|
|
|
str(int: Int): String
|
2017-02-19 22:26:56 +03:00
|
|
|
floatToStr(float: Float): String
|
|
|
|
idToStr(id: ID): String
|
2017-07-05 15:45:02 +03:00
|
|
|
booleanToStr(bool: Boolean): String
|
2017-02-19 22:26:56 +03:00
|
|
|
strToStr(bool: String): String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Simple type with multiple arguments
|
|
|
|
*/
|
|
|
|
public function testSimpleTypeWithMultipleArguments()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
str(int: Int, bool: Boolean): String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Simple type with interface
|
|
|
|
*/
|
|
|
|
public function testSimpleTypeWithInterface()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello implements WorldInterface {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
|
|
|
|
interface WorldInterface {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Simple output enum
|
|
|
|
*/
|
|
|
|
public function testSimpleOutputEnum()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: OutputEnumRoot
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Hello {
|
|
|
|
WORLD
|
|
|
|
}
|
|
|
|
|
|
|
|
type OutputEnumRoot {
|
|
|
|
hello: Hello
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Multiple value enum
|
|
|
|
*/
|
|
|
|
public function testMultipleValueEnum()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: OutputEnumRoot
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Hello {
|
|
|
|
WO
|
|
|
|
RLD
|
|
|
|
}
|
|
|
|
|
|
|
|
type OutputEnumRoot {
|
|
|
|
hello: Hello
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Simple Union
|
|
|
|
*/
|
|
|
|
public function testSimpleUnion()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Root
|
|
|
|
}
|
|
|
|
|
|
|
|
union Hello = World
|
|
|
|
|
|
|
|
type Root {
|
|
|
|
hello: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type World {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Multiple Union
|
|
|
|
*/
|
|
|
|
public function testMultipleUnion()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Root
|
|
|
|
}
|
|
|
|
|
|
|
|
union Hello = WorldOne | WorldTwo
|
|
|
|
|
|
|
|
type Root {
|
|
|
|
hello: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type WorldOne {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
|
|
|
|
type WorldTwo {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it CustomScalar
|
|
|
|
*/
|
|
|
|
public function testCustomScalar()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Root
|
|
|
|
}
|
|
|
|
|
|
|
|
scalar CustomScalar
|
|
|
|
|
|
|
|
type Root {
|
|
|
|
customScalar: CustomScalar
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it CustomScalar
|
|
|
|
*/
|
|
|
|
public function testInputObject()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Root
|
|
|
|
}
|
|
|
|
|
|
|
|
input Input {
|
|
|
|
int: Int
|
|
|
|
}
|
|
|
|
|
|
|
|
type Root {
|
|
|
|
field(in: Input): String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Simple argument field with default
|
|
|
|
*/
|
|
|
|
public function testSimpleArgumentFieldWithDefault()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
str(int: Int = 2): String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Simple type with mutation
|
|
|
|
*/
|
|
|
|
public function testSimpleTypeWithMutation()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: HelloScalars
|
|
|
|
mutation: Mutation
|
|
|
|
}
|
|
|
|
|
|
|
|
type HelloScalars {
|
2017-07-05 14:33:16 +03:00
|
|
|
str: String
|
2017-07-05 15:45:02 +03:00
|
|
|
int: Int
|
|
|
|
bool: Boolean
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type Mutation {
|
|
|
|
addHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Simple type with subscription
|
|
|
|
*/
|
|
|
|
public function testSimpleTypeWithSubscription()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: HelloScalars
|
|
|
|
subscription: Subscription
|
|
|
|
}
|
|
|
|
|
|
|
|
type HelloScalars {
|
2017-07-05 14:33:16 +03:00
|
|
|
str: String
|
2017-07-05 15:45:02 +03:00
|
|
|
int: Int
|
|
|
|
bool: Boolean
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type Subscription {
|
|
|
|
subscribeHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Unreferenced type implementing referenced interface
|
|
|
|
*/
|
|
|
|
public function testUnreferencedTypeImplementingReferencedInterface()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
type Concrete implements Iface {
|
|
|
|
key: String
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Iface {
|
|
|
|
key: String
|
|
|
|
}
|
|
|
|
|
|
|
|
type Query {
|
|
|
|
iface: Iface
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Unreferenced type implementing referenced union
|
|
|
|
*/
|
|
|
|
public function testUnreferencedTypeImplementingReferencedUnion()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
type Concrete {
|
|
|
|
key: String
|
|
|
|
}
|
|
|
|
|
|
|
|
type Query {
|
|
|
|
union: Union
|
|
|
|
}
|
|
|
|
|
|
|
|
union Union = Concrete
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Supports @deprecated
|
|
|
|
*/
|
|
|
|
public function testSupportsDeprecated()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
enum MyEnum {
|
|
|
|
VALUE
|
|
|
|
OLD_VALUE @deprecated
|
|
|
|
OTHER_VALUE @deprecated(reason: "Terrible reasons")
|
|
|
|
}
|
|
|
|
|
|
|
|
type Query {
|
|
|
|
field1: String @deprecated
|
|
|
|
field2: Int @deprecated(reason: "Because I said so")
|
2017-07-05 15:45:02 +03:00
|
|
|
enum: MyEnum
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
';
|
|
|
|
$output = $this->cycleOutput($body);
|
|
|
|
$this->assertEquals($output, $body);
|
|
|
|
|
|
|
|
$ast = Parser::parse($body);
|
|
|
|
$schema = BuildSchema::buildAST($ast);
|
|
|
|
|
|
|
|
$this->assertEquals($schema->getType('MyEnum')->getValues(), [
|
|
|
|
new EnumValueDefinition([
|
|
|
|
'name' => 'VALUE',
|
|
|
|
'description' => '',
|
|
|
|
'deprecationReason' => null,
|
|
|
|
'value' => 'VALUE'
|
|
|
|
]),
|
|
|
|
new EnumValueDefinition([
|
|
|
|
'name' => 'OLD_VALUE',
|
|
|
|
'description' => '',
|
|
|
|
'deprecationReason' => 'No longer supported',
|
|
|
|
'value' => 'OLD_VALUE'
|
|
|
|
]),
|
|
|
|
new EnumValueDefinition([
|
|
|
|
'name' => 'OTHER_VALUE',
|
|
|
|
'description' => '',
|
|
|
|
'deprecationReason' => 'Terrible reasons',
|
|
|
|
'value' => 'OTHER_VALUE'
|
|
|
|
])
|
|
|
|
]);
|
|
|
|
|
|
|
|
$rootFields = $schema->getType('Query')->getFields();
|
|
|
|
$this->assertEquals($rootFields['field1']->isDeprecated(), true);
|
|
|
|
$this->assertEquals($rootFields['field1']->deprecationReason, 'No longer supported');
|
|
|
|
|
|
|
|
$this->assertEquals($rootFields['field2']->isDeprecated(), true);
|
|
|
|
$this->assertEquals($rootFields['field2']->deprecationReason, 'Because I said so');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Describe: Failures
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Requires a schema definition or Query type
|
|
|
|
*/
|
|
|
|
public function testRequiresSchemaDefinitionOrQueryType()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide schema definition with query type or a type named Query.');
|
|
|
|
$body = '
|
|
|
|
type Hello {
|
|
|
|
bar: Bar
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Allows only a single schema definition
|
|
|
|
*/
|
|
|
|
public function testAllowsOnlySingleSchemaDefinition()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide only one schema definition.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
bar: Bar
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Requires a query type
|
|
|
|
*/
|
|
|
|
public function testRequiresQueryType()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide schema definition with query type or a type named Query.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
mutation: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
bar: Bar
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Allows only a single query type
|
|
|
|
*/
|
|
|
|
public function testAllowsOnlySingleQueryType()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide only one query type in schema.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
query: Yellow
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
bar: Bar
|
|
|
|
}
|
|
|
|
|
|
|
|
type Yellow {
|
|
|
|
isColor: Boolean
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Allows only a single mutation type
|
|
|
|
*/
|
|
|
|
public function testAllowsOnlySingleMutationType()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide only one mutation type in schema.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
mutation: Hello
|
|
|
|
mutation: Yellow
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
bar: Bar
|
|
|
|
}
|
|
|
|
|
|
|
|
type Yellow {
|
|
|
|
isColor: Boolean
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Allows only a single subscription type
|
|
|
|
*/
|
|
|
|
public function testAllowsOnlySingleSubscriptionType()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide only one subscription type in schema.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
subscription: Hello
|
|
|
|
subscription: Yellow
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
bar: Bar
|
|
|
|
}
|
|
|
|
|
|
|
|
type Yellow {
|
|
|
|
isColor: Boolean
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Unknown type referenced
|
|
|
|
*/
|
|
|
|
public function testUnknownTypeReferenced()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Type "Bar" not found in document.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
bar: Bar
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
2017-07-28 13:55:25 +03:00
|
|
|
$schema = BuildSchema::buildAST($doc);
|
|
|
|
$schema->getTypeMap();
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Unknown type in interface list
|
|
|
|
*/
|
|
|
|
public function testUnknownTypeInInterfaceList()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Type "Bar" not found in document.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello implements Bar { }
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
2017-07-28 13:55:25 +03:00
|
|
|
$schema = BuildSchema::buildAST($doc);
|
|
|
|
$schema->getTypeMap();
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Unknown type in union list
|
|
|
|
*/
|
|
|
|
public function testUnknownTypeInUnionList()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Type "Bar" not found in document.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
union TestUnion = Bar
|
|
|
|
type Hello { testUnion: TestUnion }
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
2017-07-28 13:55:25 +03:00
|
|
|
$schema = BuildSchema::buildAST($doc);
|
|
|
|
$schema->getTypeMap();
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Unknown query type
|
|
|
|
*/
|
|
|
|
public function testUnknownQueryType()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified query type "Wat" not found in document.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Wat
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Unknown mutation type
|
|
|
|
*/
|
|
|
|
public function testUnknownMutationType()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified mutation type "Wat" not found in document.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
mutation: Wat
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Unknown subscription type
|
|
|
|
*/
|
|
|
|
public function testUnknownSubscriptionType()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified subscription type "Awesome" not found in document.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Hello
|
|
|
|
mutation: Wat
|
|
|
|
subscription: Awesome
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hello {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
|
|
|
|
type Wat {
|
|
|
|
str: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Does not consider operation names
|
|
|
|
*/
|
|
|
|
public function testDoesNotConsiderOperationNames()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified query type "Foo" not found in document.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Foo
|
|
|
|
}
|
|
|
|
|
|
|
|
query Foo { field }
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Does not consider fragment names
|
|
|
|
*/
|
|
|
|
public function testDoesNotConsiderFragmentNames()
|
|
|
|
{
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified query type "Foo" not found in document.');
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Foo
|
|
|
|
}
|
|
|
|
|
|
|
|
fragment Foo on Type { field }
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
2017-07-04 09:59:46 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @it Forbids duplicate type definitions
|
|
|
|
*/
|
|
|
|
public function testForbidsDuplicateTypeDefinitions()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Repeated
|
|
|
|
}
|
|
|
|
|
|
|
|
type Repeated {
|
|
|
|
id: Int
|
|
|
|
}
|
|
|
|
|
|
|
|
type Repeated {
|
|
|
|
id: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
|
|
|
|
$this->setExpectedException('GraphQL\Error\Error', 'Type "Repeated" was defined more than once.');
|
|
|
|
BuildSchema::buildAST($doc);
|
|
|
|
}
|
2017-07-28 13:55:25 +03:00
|
|
|
|
|
|
|
public function testSupportsTypeConfigDecorator()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Query
|
|
|
|
}
|
|
|
|
|
|
|
|
type Query {
|
|
|
|
str: String
|
|
|
|
color: Color
|
|
|
|
hello: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Color {
|
|
|
|
RED
|
|
|
|
GREEN
|
|
|
|
BLUE
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Hello {
|
|
|
|
world: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
|
|
|
|
$decorated = [];
|
|
|
|
$calls = [];
|
|
|
|
|
|
|
|
$typeConfigDecorator = function($node, $defaultConfig, $allNodesMap) use (&$decorated, &$calls) {
|
|
|
|
$decorated[] = $node->name->value;
|
|
|
|
$calls[] = [$node, $defaultConfig, $allNodesMap];
|
|
|
|
return ['description' => 'My description of ' . $node->name->value] + $defaultConfig;
|
|
|
|
};
|
|
|
|
|
|
|
|
$schema = BuildSchema::buildAST($doc, $typeConfigDecorator);
|
|
|
|
$schema->getTypeMap();
|
|
|
|
$this->assertEquals(['Query', 'Color', 'Hello'], $decorated);
|
|
|
|
|
|
|
|
list($node, $defaultConfig, $allNodesMap) = $calls[0];
|
|
|
|
$this->assertInstanceOf(ObjectTypeDefinitionNode::class, $node);
|
|
|
|
$this->assertEquals('Query', $defaultConfig['name']);
|
|
|
|
$this->assertInstanceOf(\Closure::class, $defaultConfig['fields']);
|
|
|
|
$this->assertInstanceOf(\Closure::class, $defaultConfig['interfaces']);
|
|
|
|
$this->assertArrayHasKey('description', $defaultConfig);
|
|
|
|
$this->assertCount(4, $defaultConfig);
|
|
|
|
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
|
|
|
|
$this->assertEquals('My description of Query', $schema->getType('Query')->description);
|
|
|
|
|
|
|
|
|
|
|
|
list($node, $defaultConfig, $allNodesMap) = $calls[1];
|
|
|
|
$this->assertInstanceOf(EnumTypeDefinitionNode::class, $node);
|
|
|
|
$this->assertEquals('Color', $defaultConfig['name']);
|
|
|
|
$enumValue = [
|
|
|
|
'description' => '',
|
|
|
|
'deprecationReason' => ''
|
|
|
|
];
|
|
|
|
$this->assertEquals([
|
|
|
|
'RED' => $enumValue,
|
|
|
|
'GREEN' => $enumValue,
|
|
|
|
'BLUE' => $enumValue,
|
|
|
|
], $defaultConfig['values']);
|
|
|
|
$this->assertCount(3, $defaultConfig);
|
|
|
|
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
|
|
|
|
$this->assertEquals('My description of Color', $schema->getType('Color')->description);
|
|
|
|
|
|
|
|
list($node, $defaultConfig, $allNodesMap) = $calls[2];
|
|
|
|
$this->assertInstanceOf(InterfaceTypeDefinitionNode::class, $node);
|
|
|
|
$this->assertEquals('Hello', $defaultConfig['name']);
|
|
|
|
$this->assertInstanceOf(\Closure::class, $defaultConfig['fields']);
|
|
|
|
$this->assertInstanceOf(\Closure::class, $defaultConfig['resolveType']);
|
|
|
|
$this->assertArrayHasKey('description', $defaultConfig);
|
|
|
|
$this->assertCount(4, $defaultConfig);
|
|
|
|
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
|
|
|
|
$this->assertEquals('My description of Hello', $schema->getType('Hello')->description);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCreatesTypesLazily()
|
|
|
|
{
|
|
|
|
$body = '
|
|
|
|
schema {
|
|
|
|
query: Query
|
|
|
|
}
|
|
|
|
|
|
|
|
type Query {
|
|
|
|
str: String
|
|
|
|
color: Color
|
|
|
|
hello: Hello
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Color {
|
|
|
|
RED
|
|
|
|
GREEN
|
|
|
|
BLUE
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Hello {
|
|
|
|
world: String
|
|
|
|
}
|
|
|
|
|
|
|
|
type World implements Hello {
|
|
|
|
world: String
|
|
|
|
}
|
|
|
|
';
|
|
|
|
$doc = Parser::parse($body);
|
|
|
|
$created = [];
|
|
|
|
|
|
|
|
$typeConfigDecorator = function($node, $config) use (&$created) {
|
|
|
|
$created[] = $node->name->value;
|
|
|
|
return $config;
|
|
|
|
};
|
|
|
|
|
|
|
|
$schema = BuildSchema::buildAST($doc, $typeConfigDecorator);
|
|
|
|
$this->assertEquals(['Query'], $created);
|
|
|
|
|
|
|
|
$schema->getType('Color');
|
|
|
|
$this->assertEquals(['Query', 'Color'], $created);
|
|
|
|
|
|
|
|
$schema->getType('Hello');
|
|
|
|
$this->assertEquals(['Query', 'Color', 'Hello'], $created);
|
|
|
|
|
|
|
|
$types = $schema->getTypeMap();
|
|
|
|
$this->assertEquals(['Query', 'Color', 'Hello', 'World'], $created);
|
|
|
|
$this->assertArrayHasKey('Query', $types);
|
|
|
|
$this->assertArrayHasKey('Color', $types);
|
|
|
|
$this->assertArrayHasKey('Hello', $types);
|
|
|
|
$this->assertArrayHasKey('World', $types);
|
|
|
|
}
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|