mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 22:36:02 +03:00
06c6c4bd97
This moves validation out of GraphQLSchema's constructor (but not yet from other type constructors), which is responsible for root type validation and interface implementation checking. Reduces time to construct GraphQLSchema significantly, shifting the time to validation. This also allows for much looser rules within the schema builders, which implicitly validate while trying to adhere to flow types. Instead we use any casts to loosen the rules to defer that to validation where errors can be richer. This also loosens the rule that a schema can only be constructed if it has a query type, moving that to validation as well. That makes flow typing slightly less nice, but allows for incremental schema building which is valuable ref: graphql/graphql-js#1124
1144 lines
23 KiB
PHP
1144 lines
23 KiB
PHP
<?php
|
|
namespace GraphQL\Tests\Utils;
|
|
|
|
use GraphQL\GraphQL;
|
|
use GraphQL\Language\AST\EnumTypeDefinitionNode;
|
|
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
|
|
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
|
|
use GraphQL\Language\Parser;
|
|
use GraphQL\Language\Printer;
|
|
use GraphQL\Type\Definition\EnumType;
|
|
use GraphQL\Type\Definition\ObjectType;
|
|
use GraphQL\Utils\BuildSchema;
|
|
use GraphQL\Utils\SchemaPrinter;
|
|
use GraphQL\Type\Definition\Directive;
|
|
|
|
class BuildSchemaTest extends \PHPUnit_Framework_TestCase
|
|
{
|
|
// Describe: Schema Builder
|
|
|
|
private function cycleOutput($body, $options = [])
|
|
{
|
|
$ast = Parser::parse($body);
|
|
$schema = BuildSchema::buildAST($ast, $options);
|
|
return "\n" . SchemaPrinter::doPrint($schema, $options);
|
|
}
|
|
|
|
/**
|
|
* @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::executeQuery($schema, '{ str }', ['str' => 123]);
|
|
$this->assertEquals(['str' => 123], $result->toArray(true)['data']);
|
|
}
|
|
|
|
/**
|
|
* @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::executeQuery(
|
|
$schema,
|
|
'{ add(x: 34, y: 55) }',
|
|
[
|
|
'add' => function ($root, $args) {
|
|
return $args['x'] + $args['y'];
|
|
}
|
|
]
|
|
);
|
|
$this->assertEquals(['data' => ['add' => 89]], $result->toArray(true));
|
|
}
|
|
|
|
/**
|
|
* @it Simple Type
|
|
*/
|
|
public function testSimpleType()
|
|
{
|
|
$body = '
|
|
schema {
|
|
query: HelloScalars
|
|
}
|
|
|
|
type HelloScalars {
|
|
str: String
|
|
int: Int
|
|
float: Float
|
|
id: ID
|
|
bool: Boolean
|
|
}
|
|
';
|
|
$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);
|
|
$this->assertEquals($body, $output);
|
|
}
|
|
|
|
/**
|
|
* @it Supports descriptions
|
|
*/
|
|
public function testSupportsOptionForCommentDescriptions()
|
|
{
|
|
$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, [ 'commentDescriptions' => true ]);
|
|
$this->assertEquals($body, $output);
|
|
}
|
|
|
|
/**
|
|
* @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 {
|
|
nonNullStr: String!
|
|
listOfStrs: [String]
|
|
listOfNonNullStrs: [String!]
|
|
nonNullListOfStrs: [String]!
|
|
nonNullListOfNonNullStrs: [String!]!
|
|
}
|
|
';
|
|
$output = $this->cycleOutput($body);
|
|
$this->assertEquals($output, $body);
|
|
}
|
|
|
|
/**
|
|
* @it Recursive type
|
|
*/
|
|
public function testRecursiveType()
|
|
{
|
|
$body = '
|
|
schema {
|
|
query: Recurse
|
|
}
|
|
|
|
type Recurse {
|
|
str: String
|
|
recurse: Recurse
|
|
}
|
|
';
|
|
$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 {
|
|
str(int: Int): String
|
|
floatToStr(float: Float): String
|
|
idToStr(id: ID): String
|
|
booleanToStr(bool: Boolean): String
|
|
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 Specifying Union type using __typename
|
|
*/
|
|
public function testSpecifyingUnionTypeUsingTypename()
|
|
{
|
|
$schema = BuildSchema::buildAST(Parser::parse('
|
|
schema {
|
|
query: Root
|
|
}
|
|
|
|
type Root {
|
|
fruits: [Fruit]
|
|
}
|
|
|
|
union Fruit = Apple | Banana
|
|
|
|
type Apple {
|
|
color: String
|
|
}
|
|
|
|
type Banana {
|
|
length: Int
|
|
}
|
|
'));
|
|
$query = '
|
|
{
|
|
fruits {
|
|
... on Apple {
|
|
color
|
|
}
|
|
... on Banana {
|
|
length
|
|
}
|
|
}
|
|
}
|
|
';
|
|
$root = [
|
|
'fruits' => [
|
|
[
|
|
'color' => 'green',
|
|
'__typename' => 'Apple',
|
|
],
|
|
[
|
|
'length' => 5,
|
|
'__typename' => 'Banana',
|
|
]
|
|
]
|
|
];
|
|
$expected = [
|
|
'data' => [
|
|
'fruits' => [
|
|
['color' => 'green'],
|
|
['length' => 5],
|
|
]
|
|
]
|
|
];
|
|
|
|
$result = GraphQL::executeQuery($schema, $query, $root);
|
|
$this->assertEquals($expected, $result->toArray(true));
|
|
}
|
|
|
|
/**
|
|
* @it Specifying Interface type using __typename
|
|
*/
|
|
public function testSpecifyingInterfaceUsingTypename()
|
|
{
|
|
$schema = BuildSchema::buildAST(Parser::parse('
|
|
schema {
|
|
query: Root
|
|
}
|
|
|
|
type Root {
|
|
characters: [Character]
|
|
}
|
|
|
|
interface Character {
|
|
name: String!
|
|
}
|
|
|
|
type Human implements Character {
|
|
name: String!
|
|
totalCredits: Int
|
|
}
|
|
|
|
type Droid implements Character {
|
|
name: String!
|
|
primaryFunction: String
|
|
}
|
|
'));
|
|
$query = '
|
|
{
|
|
characters {
|
|
name
|
|
... on Human {
|
|
totalCredits
|
|
}
|
|
... on Droid {
|
|
primaryFunction
|
|
}
|
|
}
|
|
}
|
|
';
|
|
$root = [
|
|
'characters' => [
|
|
[
|
|
'name' => 'Han Solo',
|
|
'totalCredits' => 10,
|
|
'__typename' => 'Human',
|
|
],
|
|
[
|
|
'name' => 'R2-D2',
|
|
'primaryFunction' => 'Astromech',
|
|
'__typename' => 'Droid',
|
|
]
|
|
]
|
|
];
|
|
$expected = [
|
|
'data' => [
|
|
'characters' => [
|
|
['name' => 'Han Solo', 'totalCredits' => 10],
|
|
['name' => 'R2-D2', 'primaryFunction' => 'Astromech'],
|
|
]
|
|
]
|
|
];
|
|
|
|
$result = GraphQL::executeQuery($schema, $query, $root);
|
|
$this->assertEquals($expected, $result->toArray(true));
|
|
}
|
|
|
|
/**
|
|
* @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 Custom scalar argument field with default
|
|
*/
|
|
public function testCustomScalarArgumentFieldWithDefault()
|
|
{
|
|
$body = '
|
|
schema {
|
|
query: Hello
|
|
}
|
|
|
|
scalar CustomScalar
|
|
|
|
type Hello {
|
|
str(int: CustomScalar = 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 {
|
|
str: String
|
|
int: Int
|
|
bool: Boolean
|
|
}
|
|
|
|
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 {
|
|
str: String
|
|
int: Int
|
|
bool: Boolean
|
|
}
|
|
|
|
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")
|
|
enum: MyEnum
|
|
}
|
|
';
|
|
$output = $this->cycleOutput($body);
|
|
$this->assertEquals($output, $body);
|
|
|
|
$ast = Parser::parse($body);
|
|
$schema = BuildSchema::buildAST($ast);
|
|
|
|
/** @var EnumType $myEnum */
|
|
$myEnum = $schema->getType('MyEnum');
|
|
|
|
$value = $myEnum->getValue('VALUE');
|
|
$this->assertFalse($value->isDeprecated());
|
|
|
|
$oldValue = $myEnum->getValue('OLD_VALUE');
|
|
$this->assertTrue($oldValue->isDeprecated());
|
|
$this->assertEquals('No longer supported', $oldValue->deprecationReason);
|
|
|
|
$otherValue = $myEnum->getValue('OTHER_VALUE');
|
|
$this->assertTrue($otherValue->isDeprecated());
|
|
$this->assertEquals('Terrible reasons', $otherValue->deprecationReason);
|
|
|
|
$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');
|
|
}
|
|
|
|
/**
|
|
* @it Correctly assign AST nodes
|
|
*/
|
|
public function testCorrectlyAssignASTNodes()
|
|
{
|
|
|
|
$schema = BuildSchema::build('
|
|
schema {
|
|
query: Query
|
|
}
|
|
|
|
type Query {
|
|
testField(testArg: TestInput): TestUnion
|
|
}
|
|
|
|
input TestInput {
|
|
testInputField: TestEnum
|
|
}
|
|
|
|
enum TestEnum {
|
|
TEST_VALUE
|
|
}
|
|
|
|
union TestUnion = TestType
|
|
|
|
interface TestInterface {
|
|
interfaceField: String
|
|
}
|
|
|
|
type TestType implements TestInterface {
|
|
interfaceField: String
|
|
}
|
|
|
|
directive @test(arg: Int) on FIELD
|
|
');
|
|
/** @var ObjectType $query */
|
|
$query = $schema->getType('Query');
|
|
$testInput = $schema->getType('TestInput');
|
|
$testEnum = $schema->getType('TestEnum');
|
|
$testUnion = $schema->getType('TestUnion');
|
|
$testInterface = $schema->getType('TestInterface');
|
|
$testType = $schema->getType('TestType');
|
|
$testDirective = $schema->getDirective('test');
|
|
|
|
$restoredIDL = SchemaPrinter::doPrint(BuildSchema::build(
|
|
Printer::doPrint($schema->getAstNode()) . "\n" .
|
|
Printer::doPrint($query->astNode) . "\n" .
|
|
Printer::doPrint($testInput->astNode) . "\n" .
|
|
Printer::doPrint($testEnum->astNode) . "\n" .
|
|
Printer::doPrint($testUnion->astNode) . "\n" .
|
|
Printer::doPrint($testInterface->astNode) . "\n" .
|
|
Printer::doPrint($testType->astNode) . "\n" .
|
|
Printer::doPrint($testDirective->astNode)
|
|
));
|
|
|
|
$this->assertEquals($restoredIDL, SchemaPrinter::doPrint($schema));
|
|
|
|
$testField = $query->getField('testField');
|
|
$this->assertEquals('testField(testArg: TestInput): TestUnion', Printer::doPrint($testField->astNode));
|
|
$this->assertEquals('testArg: TestInput', Printer::doPrint($testField->args[0]->astNode));
|
|
$this->assertEquals('testInputField: TestEnum', Printer::doPrint($testInput->getField('testInputField')->astNode));
|
|
$this->assertEquals('TEST_VALUE', Printer::doPrint($testEnum->getValue('TEST_VALUE')->astNode));
|
|
$this->assertEquals('interfaceField: String', Printer::doPrint($testInterface->getField('interfaceField')->astNode));
|
|
$this->assertEquals('interfaceField: String', Printer::doPrint($testType->getField('interfaceField')->astNode));
|
|
$this->assertEquals('arg: Int', Printer::doPrint($testDirective->args[0]->astNode));
|
|
}
|
|
|
|
// Describe: Failures
|
|
|
|
/**
|
|
* @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 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);
|
|
$schema = BuildSchema::buildAST($doc);
|
|
$schema->getTypeMap();
|
|
}
|
|
|
|
/**
|
|
* @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 {
|
|
field: String
|
|
}
|
|
';
|
|
$doc = Parser::parse($body);
|
|
$schema = BuildSchema::buildAST($doc);
|
|
$schema->getTypeMap();
|
|
}
|
|
|
|
/**
|
|
* @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);
|
|
$schema = BuildSchema::buildAST($doc);
|
|
$schema->getTypeMap();
|
|
}
|
|
|
|
/**
|
|
* @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);
|
|
}
|
|
|
|
/**
|
|
* @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);
|
|
}
|
|
}
|