/** * @it evaluates mutations correctly in the presense of a failed mutation */ public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() { $doc = 'mutation M { first: immediatelyChangeTheNumber(newNumber: 1) { theNumber }, second: promiseToChangeTheNumber(newNumber: 2) { theNumber }, third: failToChangeTheNumber(newNumber: 3) { theNumber } fourth: promiseToChangeTheNumber(newNumber: 4) { theNumber }, fifth: immediatelyChangeTheNumber(newNumber: 5) { theNumber } sixth: promiseAndFailToChangeTheNumber(newNumber: 6) { theNumber } }'; $ast = Parser::parse($doc); $mutationResult = Executor::execute($this->schema(), $ast, new Root(6)); $expected = ['data' => ['first' => ['theNumber' => 1], 'second' => ['theNumber' => 2], 'third' => null, 'fourth' => ['theNumber' => 4], 'fifth' => ['theNumber' => 5], 'sixth' => null], 'errors' => [FormattedError::create('Cannot change the number', [new SourceLocation(8, 7)]), FormattedError::create('Cannot change the number', [new SourceLocation(17, 7)])]]; $this->assertArraySubset($expected, self::awaitPromise($mutationResult)); }
/** * @it output types are invalid */ public function testOutputTypesAreInvalid() { $this->expectFailsRule(new VariablesAreInputTypes(), ' query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) { field(a: $a, b: $b, c: $c) } ', [FormattedError::create(VariablesAreInputTypes::nonInputTypeOnVarMessage('a', 'Dog'), [new SourceLocation(2, 21)]), FormattedError::create(VariablesAreInputTypes::nonInputTypeOnVarMessage('b', '[[CatOrDog!]]!'), [new SourceLocation(2, 30)]), FormattedError::create(VariablesAreInputTypes::nonInputTypeOnVarMessage('c', 'Pet'), [new SourceLocation(2, 50)])]); }
/** * @describe [T!]! */ public function testHandlesNonNullListOfNonNulls() { $type = Type::nonNull(Type::listOf(Type::nonNull(Type::int()))); // Contains values $this->check($type, [1, 2], ['data' => ['nest' => ['test' => [1, 2]]]]); // Contains null $this->check($type, [1, null, 2], ['data' => ['nest' => null], 'errors' => [FormattedError::create('Cannot return null for non-nullable field DataType.test.', [new SourceLocation(1, 10)])]]); // Returns null $this->check($type, null, ['data' => ['nest' => null], 'errors' => [FormattedError::create('Cannot return null for non-nullable field DataType.test.', [new SourceLocation(1, 10)])]]); }
private function unknownType($typeName, $line, $column) { return FormattedError::create(KnownTypeNames::unknownTypeMessage($typeName), [new SourceLocation($line, $column)]); }
private function duplicateField($name, $l1, $c1, $l2, $c2) { return FormattedError::create(UniqueInputFieldNames::duplicateInputFieldMessage($name), [new SourceLocation($l1, $c1), new SourceLocation($l2, $c2)]); }
/** * @it nulls out error subtrees */ public function testNullsOutErrorSubtrees() { $doc = '{ sync, syncError, syncRawError, async, asyncReject, asyncError }'; $data = ['sync' => function () { return 'sync'; }, 'syncError' => function () { throw new \Exception('Error getting syncError'); }, 'syncRawError' => function () { throw new \Exception('Error getting syncRawError'); }, 'async' => function () { return 'async'; }, 'asyncReject' => function () { throw new \Exception('Error getting asyncReject'); }, 'asyncError' => function () { throw new \Exception('Error getting asyncError'); }]; $docAst = Parser::parse($doc); $schema = new Schema(['query' => new ObjectType(['name' => 'Type', 'fields' => ['sync' => ['type' => Type::string()], 'syncError' => ['type' => Type::string()], 'syncRawError' => ['type' => Type::string()], 'async' => ['type' => Type::string()], 'asyncReject' => ['type' => Type::string()], 'asyncError' => ['type' => Type::string()]]])]); $expected = ['data' => ['sync' => 'sync', 'syncError' => null, 'syncRawError' => null, 'async' => 'async', 'asyncReject' => null, 'asyncError' => null], 'errors' => [FormattedError::create('Error getting syncError', [new SourceLocation(3, 7)]), FormattedError::create('Error getting syncRawError', [new SourceLocation(4, 7)]), FormattedError::create('Error getting asyncReject', [new SourceLocation(6, 7)]), FormattedError::create('Error getting asyncError', [new SourceLocation(7, 7)])]]; $result = Executor::execute($schema, $docAst, $data); $this->assertArraySubset($expected, $result->toArray()); }
private function error($fragName, $typeName, $line, $column) { return FormattedError::create(FragmentsOnCompositeTypes::fragmentOnNonCompositeErrorMessage($fragName, $typeName), [new SourceLocation($line, $column)]); }
private function undefinedField($field, $type, $suggestions, $line, $column) { return FormattedError::create(FieldsOnCorrectType::undefinedFieldMessage($field, $type, $suggestions), [new SourceLocation($line, $column)]); }
private function missingObjSubselection($field, $type, $line, $column) { return FormattedError::create(ScalarLeafs::requiredSubselectionMessage($field, $type), [new SourceLocation($line, $column)]); }
/** * @it String => Boolean! in directive */ public function testStringXBooleanNonNullInDirective() { // String => Boolean! in directive $this->expectFailsRule(new VariablesInAllowedPosition(), ' query Query($stringVar: String) { dog @include(if: $stringVar) } ', [FormattedError::create(VariablesInAllowedPosition::badVarPosMessage('stringVar', 'String', 'Boolean!'), [new SourceLocation(2, 19), new SourceLocation(3, 26)])]); }
private function unusedFrag($fragName, $line, $column) { return FormattedError::create(NoUnusedFragments::unusedFragMessage($fragName), [new SourceLocation($line, $column)]); }
private function undefVar($varName, $line, $column, $opName = null, $l2 = null, $c2 = null) { $locs = [new SourceLocation($line, $column)]; if ($l2 && $c2) { $locs[] = new SourceLocation($l2, $c2); } return FormattedError::create(NoUndefinedVariables::undefinedVarMessage($varName, $opName), $locs); }
/** * @it compares deep types including list */ public function testComparesDeepTypesIncludingList() { $this->expectFailsRuleWithSchema($this->getTestSchema(), new OverlappingFieldsCanBeMerged(), ' { connection { ...edgeID edges { node { id: name } } } } fragment edgeID on Connection { edges { node { id } } } ', [FormattedError::create(OverlappingFieldsCanBeMerged::fieldsConflictMessage('edges', [['node', [['id', 'id and name are different fields']]]]), [new SourceLocation(14, 11), new SourceLocation(15, 13), new SourceLocation(16, 15), new SourceLocation(5, 13), new SourceLocation(6, 15), new SourceLocation(7, 17)])]); }
private function missingDirectiveArg($directiveName, $argName, $typeName, $line, $column) { return FormattedError::create(ProvidedNonNullArguments::missingDirectiveArgMessage($directiveName, $argName, $typeName), [new SourceLocation($line, $column)]); }
private function undefFrag($fragName, $line, $column) { return FormattedError::create(KnownFragmentNames::unknownFragmentMessage($fragName), [new SourceLocation($line, $column)]); }
$appContext->rootUrl = 'http://localhost:8080'; $appContext->request = $_REQUEST; // Parse incoming query and variables if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) { $raw = file_get_contents('php://input') ?: ''; $data = json_decode($raw, true); } else { $data = $_REQUEST; } $data += ['query' => null, 'variables' => null]; if (null === $data['query']) { $data['query'] = '{hello}'; } // GraphQL schema to be passed to query executor: $schema = new Schema(['query' => Types::query()]); $result = GraphQL::execute($schema, $data['query'], null, $appContext, (array) $data['variables']); // Add reported PHP errors to result (if any) if (!empty($_GET['debug']) && !empty($phpErrors)) { $result['extensions']['phpErrors'] = array_map(['GraphQL\\Error\\FormattedError', 'createFromPHPError'], $phpErrors); } $httpStatus = 200; } catch (\Exception $error) { $httpStatus = 500; if (!empty($_GET['debug'])) { $result['extensions']['exception'] = FormattedError::createFromException($error); } else { $result['errors'] = [FormattedError::create('Unexpected Error')]; } } header('Content-Type: application/json', true, $httpStatus); echo json_encode($result);
function badValue($argName, $typeName, $value, $line, $column, $errors = null) { $realErrors = !$errors ? ["Expected type \"{$typeName}\", found {$value}."] : $errors; return FormattedError::create(ArgumentsOfCorrectType::badValueMessage($argName, $typeName, $value, $realErrors), [new SourceLocation($line, $column)]); }
private function duplicateVariable($name, $l1, $c1, $l2, $c2) { return FormattedError::create(UniqueVariableNames::duplicateVariableMessage($name), [new SourceLocation($l1, $c1), new SourceLocation($l2, $c2)]); }
/** * @it resolveType on Union yields useful error */ public function testResolveTypeOnUnionYieldsUsefulError() { $HumanType = new ObjectType(['name' => 'Human', 'fields' => ['name' => ['type' => Type::string()]]]); $DogType = new ObjectType(['name' => 'Dog', 'fields' => ['name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()]]]); $CatType = new ObjectType(['name' => 'Cat', 'fields' => ['name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()]]]); $PetType = new UnionType(['name' => 'Pet', 'resolveType' => function ($obj) use($DogType, $CatType, $HumanType) { if ($obj instanceof Dog) { return $DogType; } if ($obj instanceof Cat) { return $CatType; } if ($obj instanceof Human) { return $HumanType; } }, 'types' => [$DogType, $CatType]]); $schema = new Schema(['query' => new ObjectType(['name' => 'Query', 'fields' => ['pets' => ['type' => Type::listOf($PetType), 'resolve' => function () { return [new Dog('Odie', true), new Cat('Garfield', false), new Human('Jon')]; }]]])]); $query = '{ pets { ... on Dog { name woofs } ... on Cat { name meows } } }'; $result = GraphQL::execute($schema, $query); $expected = ['data' => ['pets' => [['name' => 'Odie', 'woofs' => true], ['name' => 'Garfield', 'meows' => false], null]], 'errors' => [FormattedError::create('Runtime Object type "Human" is not a possible type for "Pet".', [new SourceLocation(2, 11)])]]; $this->assertEquals($expected, $result); }
private function cycleError($fargment, $spreadNames, $line, $column) { return FormattedError::create(NoFragmentCycles::cycleErrorMessage($fargment, $spreadNames), [new SourceLocation($line, $column)]); }
private function anonNotAlone($line, $column) { return FormattedError::create(LoneAnonymousOperation::anonOperationNotAloneMessage(), [new SourceLocation($line, $column)]); }
public function testNullsTheTopLevelIfAsyncNonNullableFieldResolvesNull() { $doc = ' query Q { nonNullPromise } '; $ast = Parser::parse($doc); $expected = ['data' => null, 'errors' => [FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(2, 17)])]]; Executor::setPromiseAdapter(new ReactPromiseAdapter()); $this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')); }
private function badValue($varName, $typeName, $val, $line, $column, $errors = null) { $realErrors = !$errors ? ["Expected type \"{$typeName}\", found {$val}."] : $errors; return FormattedError::create(DefaultValuesOfCorrectType::badValueForDefaultArgMessage($varName, $typeName, $val, $realErrors), [new SourceLocation($line, $column)]); }
/** * @describe [T!]! */ public function testHandlesNonNullListOfNonNullsWithArrayPromise() { // Contains values $this->checkHandlesNonNullListOfNonNulls([\React\Promise\resolve(1), \React\Promise\resolve(2)], ['data' => ['nest' => ['test' => [1, 2]]]]); // Contains null $this->checkHandlesNonNullListOfNonNulls([\React\Promise\resolve(1), \React\Promise\resolve(null), \React\Promise\resolve(2)], ['data' => ['nest' => null], 'errors' => [FormattedError::create('Cannot return null for non-nullable field DataType.test.', [new SourceLocation(1, 10)])]]); // Contains reject $this->checkHandlesNonNullListOfNonNulls(function () { return [\React\Promise\resolve(1), \React\Promise\reject(new \Exception('bad')), \React\Promise\resolve(2)]; }, ['data' => ['nest' => null], 'errors' => [['message' => 'bad', 'locations' => [['line' => 1, 'column' => 10]], 'path' => ['nest', 'test']]]]); }
private function errorAnon($parentType, $fragType, $line, $column) { return FormattedError::create(PossibleFragmentSpreads::typeIncompatibleAnonSpreadMessage($parentType, $fragType), [new SourceLocation($line, $column)]); }
public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() { // nulls the top level if sync non-nullable field returns null $doc = ' query Q { nonNullSync } '; $expected = ['errors' => [FormattedError::create('Cannot return null for non-nullable field DataType.nonNullSync.', [new SourceLocation(2, 17)])]]; $this->assertArraySubset($expected, Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray()); }
/** * @it does not allow unknown types to be used as values */ public function testDoesNotAllowUnknownTypesToBeUsedAsValues() { $doc = ' query q($input: UnknownType!) { fieldWithObjectInput(input: $input) } '; $ast = Parser::parse($doc); $vars = ['input' => 'whoknows']; try { Executor::execute($this->schema(), $ast, null, null, $vars); $this->fail('Expected exception not thrown'); } catch (Error $error) { $expected = FormattedError::create('Variable "$input" expected value of type "UnknownType!" which ' . 'cannot be used as an input type.', [new SourceLocation(2, 17)]); $this->assertEquals($expected, Error::formatError($error)); } }
private function duplicateArg($argName, $l1, $c1, $l2, $c2) { return FormattedError::create(UniqueArgumentNames::duplicateArgMessage($argName), [new SourceLocation($l1, $c1), new SourceLocation($l2, $c2)]); }
/** * @it fails as expected on the __type root field without an arg */ public function testFailsAsExpectedOnThe__typeRootFieldWithoutAnArg() { $TestType = new ObjectType(['name' => 'TestType', 'fields' => ['testField' => ['type' => Type::string()]]]); $schema = new Schema(['query' => $TestType]); $request = ' { __type { name } } '; $expected = ['errors' => [FormattedError::create(ProvidedNonNullArguments::missingFieldArgMessage('__type', 'name', 'String!'), [new SourceLocation(3, 9)])]]; $this->assertEquals($expected, GraphQL::execute($schema, $request)); }
private function unknownDirectiveArg($argName, $directiveName, $line, $column) { return FormattedError::create(KnownArgumentNames::unknownDirectiveArgMessage($argName, $directiveName), [new SourceLocation($line, $column)]); }