mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 14:26:08 +03:00
Split HTTP server execution to canonical replaceable steps: parsing, validation, execution with separate tests for each step
This commit is contained in:
parent
f8c3195e54
commit
0e2ac57515
@ -37,7 +37,7 @@ class Helper
|
|||||||
if (!$doc instanceof DocumentNode) {
|
if (!$doc instanceof DocumentNode) {
|
||||||
$doc = Parser::parse($doc);
|
$doc = Parser::parse($doc);
|
||||||
}
|
}
|
||||||
if (!$op->allowsMutation() && AST::isMutation($op->operation, $doc)) {
|
if ($op->isReadOnly() && AST::isMutation($op->operation, $doc)) {
|
||||||
throw new UserError("Cannot execute mutation in read-only context");
|
throw new UserError("Cannot execute mutation in read-only context");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,47 +133,35 @@ class Helper
|
|||||||
return $validationRules;
|
return $validationRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses HTTP request and returns GraphQL QueryParams contained in this request.
|
* Parses HTTP request and returns GraphQL QueryParams contained in this request.
|
||||||
* For batched requests it returns an array of QueryParams.
|
* For batched requests it returns an array of QueryParams.
|
||||||
*
|
*
|
||||||
* @return OperationParams|OperationParams[]
|
* This function doesn't check validity of these params.
|
||||||
*/
|
|
||||||
public function parseHttpRequest()
|
|
||||||
{
|
|
||||||
list ($parsedBody, $isReadonly) = $this->parseRawBody();
|
|
||||||
return $this->toOperationParams($parsedBody, $isReadonly);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts parsed body and readonly flag from HTTP request
|
|
||||||
*
|
*
|
||||||
* If $readRawBodyFn argument is not provided - will attempt to read raw request body from php://input stream
|
* If $readRawBodyFn argument is not provided - will attempt to read raw request body from php://input stream
|
||||||
*
|
*
|
||||||
* @param callable|null $readRawBodyFn
|
* @param callable|null $readRawBodyFn
|
||||||
* @return array
|
* @return OperationParams|OperationParams[]
|
||||||
*/
|
*/
|
||||||
public function parseRawBody(callable $readRawBodyFn = null)
|
public function parseHttpRequest(callable $readRawBodyFn = null)
|
||||||
{
|
{
|
||||||
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null;
|
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null;
|
||||||
|
|
||||||
if ($method === 'GET') {
|
if ($method === 'GET') {
|
||||||
$isReadonly = true;
|
|
||||||
$request = array_change_key_case($_GET);
|
$request = array_change_key_case($_GET);
|
||||||
|
|
||||||
if (isset($request['query']) || isset($request['queryid']) || isset($request['documentid'])) {
|
if (isset($request['query']) || isset($request['queryid']) || isset($request['documentid'])) {
|
||||||
$body = $_GET;
|
$result = OperationParams::create($_GET, true);
|
||||||
} else {
|
} else {
|
||||||
throw new UserError('Cannot execute GET request without "query" or "queryId" parameter');
|
throw new UserError('Cannot execute GET request without "query" or "queryId" parameter');
|
||||||
}
|
}
|
||||||
} else if ($method === 'POST') {
|
} else if ($method === 'POST') {
|
||||||
$isReadonly = false;
|
|
||||||
$contentType = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : null;
|
$contentType = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : null;
|
||||||
|
|
||||||
if (stripos($contentType, 'application/graphql') !== false) {
|
if (stripos($contentType, 'application/graphql') !== false) {
|
||||||
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
|
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
|
||||||
$body = ['query' => $rawBody ?: ''];
|
$result = OperationParams::create(['query' => $rawBody ?: '']);
|
||||||
} else if (stripos($contentType, 'application/json') !== false) {
|
} else if (stripos($contentType, 'application/json') !== false) {
|
||||||
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
|
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
|
||||||
$body = json_decode($rawBody ?: '', true);
|
$body = json_decode($rawBody ?: '', true);
|
||||||
@ -187,8 +175,17 @@ class Helper
|
|||||||
Utils::printSafeJson($body)
|
Utils::printSafeJson($body)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (isset($body[0])) {
|
||||||
|
$result = [];
|
||||||
|
foreach ($body as $index => $entry) {
|
||||||
|
$op = OperationParams::create($entry, true);
|
||||||
|
$result[] = $op;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$result = OperationParams::create($body);
|
||||||
|
}
|
||||||
} else if (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
|
} else if (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
|
||||||
$body = $_POST;
|
$result = OperationParams::create($_POST);
|
||||||
} else if (null === $contentType) {
|
} else if (null === $contentType) {
|
||||||
throw new UserError('Missing "Content-Type" header');
|
throw new UserError('Missing "Content-Type" header');
|
||||||
} else {
|
} else {
|
||||||
@ -197,41 +194,6 @@ class Helper
|
|||||||
} else {
|
} else {
|
||||||
throw new UserError('HTTP Method "' . $method . '" is not supported', 405);
|
throw new UserError('HTTP Method "' . $method . '" is not supported', 405);
|
||||||
}
|
}
|
||||||
return [
|
|
||||||
$body,
|
|
||||||
$isReadonly
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts parsed body to OperationParams (or list of OperationParams for batched request)
|
|
||||||
*
|
|
||||||
* @param $parsedBody
|
|
||||||
* @param $isReadonly
|
|
||||||
* @return OperationParams|OperationParams[]
|
|
||||||
*/
|
|
||||||
public function toOperationParams($parsedBody, $isReadonly)
|
|
||||||
{
|
|
||||||
$assertValid = function (OperationParams $opParams, $queryNum = null) {
|
|
||||||
$errors = $opParams->validate();
|
|
||||||
if (!empty($errors[0])) {
|
|
||||||
$err = $queryNum ? "Error in query #$queryNum: {$errors[0]}" : $errors[0];
|
|
||||||
throw new UserError($err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isset($parsedBody[0])) {
|
|
||||||
// Batched query
|
|
||||||
$result = [];
|
|
||||||
foreach ($parsedBody as $index => $entry) {
|
|
||||||
$op = OperationParams::create($entry, $isReadonly);
|
|
||||||
$assertValid($op, $index);
|
|
||||||
$result[] = $op;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$result = OperationParams::create($parsedBody, $isReadonly);
|
|
||||||
$assertValid($result);
|
|
||||||
}
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,46 +205,81 @@ class Helper
|
|||||||
return file_get_contents('php://input');
|
return file_get_contents('php://input');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks validity of operation params and returns array of errors (empty array when params are valid)
|
||||||
|
*
|
||||||
|
* @param OperationParams $params
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validateOperationParams(OperationParams $params)
|
||||||
|
{
|
||||||
|
$errors = [];
|
||||||
|
if (!$params->query && !$params->queryId) {
|
||||||
|
$errors[] = 'GraphQL Request must include at least one of those two parameters: "query" or "queryId"';
|
||||||
|
}
|
||||||
|
if ($params->query && $params->queryId) {
|
||||||
|
$errors[] = 'GraphQL Request parameters "query" and "queryId" are mutually exclusive';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($params->query !== null && (!is_string($params->query) || empty($params->query))) {
|
||||||
|
$errors[] = 'GraphQL Request parameter "query" must be string, but got ' .
|
||||||
|
Utils::printSafeJson($params->query);
|
||||||
|
}
|
||||||
|
if ($params->queryId !== null && (!is_string($params->queryId) || empty($params->queryId))) {
|
||||||
|
$errors[] = 'GraphQL Request parameter "queryId" must be string, but got ' .
|
||||||
|
Utils::printSafeJson($params->queryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($params->operation !== null && (!is_string($params->operation) || empty($params->operation))) {
|
||||||
|
$errors[] = 'GraphQL Request parameter "operation" must be string, but got ' .
|
||||||
|
Utils::printSafeJson($params->operation);
|
||||||
|
}
|
||||||
|
if ($params->variables !== null && (!is_array($params->variables) || isset($params->variables[0]))) {
|
||||||
|
$errors[] = 'GraphQL Request parameter "variables" must be object, but got ' .
|
||||||
|
Utils::printSafeJson($params->variables);
|
||||||
|
}
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assertion to check that parsed body is valid instance of OperationParams (or array of instances)
|
* Assertion to check that parsed body is valid instance of OperationParams (or array of instances)
|
||||||
*
|
*
|
||||||
* @param $method
|
* @param OperationParams|OperationParams[] $parsedBody
|
||||||
* @param $parsedBody
|
* @throws InvariantViolation
|
||||||
|
* @throws UserError
|
||||||
*/
|
*/
|
||||||
public function assertBodyIsParsedProperly($method, $parsedBody)
|
public function assertValidRequest($parsedBody)
|
||||||
{
|
{
|
||||||
if (is_array($parsedBody)) {
|
if (is_array($parsedBody)) {
|
||||||
foreach ($parsedBody as $index => $entry) {
|
foreach ($parsedBody as $index => $entry) {
|
||||||
if (!$entry instanceof OperationParams) {
|
if (!$entry instanceof OperationParams) {
|
||||||
throw new InvariantViolation(sprintf(
|
throw new InvariantViolation(sprintf(
|
||||||
'%s expects instance of %s or array of instances. Got invalid array where entry at position %d is %s',
|
'GraphQL Server: Parsed http request must be an instance of %s or array of such instances, '.
|
||||||
$method,
|
'but got invalid array where entry at position %d is %s',
|
||||||
OperationParams::class,
|
OperationParams::class,
|
||||||
$index,
|
$index,
|
||||||
Utils::printSafe($entry)
|
Utils::printSafe($entry)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
$errors = $entry->validate();
|
|
||||||
|
$errors = $this->validateOperationParams($entry);
|
||||||
|
|
||||||
if (!empty($errors[0])) {
|
if (!empty($errors[0])) {
|
||||||
$err = $index ? "Error in query #$index: {$errors[0]}" : $errors[0];
|
$err = $index ? "Error in query #$index: {$errors[0]}" : $errors[0];
|
||||||
throw new InvariantViolation($err);
|
throw new UserError($err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if ($parsedBody instanceof OperationParams) {
|
||||||
|
$errors = $this->validateOperationParams($parsedBody);
|
||||||
if ($parsedBody instanceof OperationParams) {
|
|
||||||
$errors = $parsedBody->validate();
|
|
||||||
if (!empty($errors[0])) {
|
if (!empty($errors[0])) {
|
||||||
throw new InvariantViolation($errors[0]);
|
throw new UserError($errors[0]);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new InvariantViolation(sprintf(
|
||||||
|
'GraphQL Server: Parsed http request must be an instance of %s or array of such instances, but got %s',
|
||||||
|
OperationParams::class,
|
||||||
|
Utils::printSafe($parsedBody)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvariantViolation(sprintf(
|
|
||||||
'%s expects instance of %s or array of instances, but got %s',
|
|
||||||
$method,
|
|
||||||
OperationParams::class,
|
|
||||||
Utils::printSafe($parsedBody)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,17 +39,17 @@ class OperationParams
|
|||||||
/**
|
/**
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
private $allowsMutations;
|
private $readOnly;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance from given array
|
* Creates an instance from given array
|
||||||
*
|
*
|
||||||
* @param array $params
|
* @param array $params
|
||||||
* @param bool $allowsMutations
|
* @param bool $readonly
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function create(array $params, $allowsMutations = true)
|
public static function create(array $params, $readonly = false)
|
||||||
{
|
{
|
||||||
$instance = new static();
|
$instance = new static();
|
||||||
$instance->originalInput = $params;
|
$instance->originalInput = $params;
|
||||||
@ -68,7 +68,7 @@ class OperationParams
|
|||||||
$instance->queryId = $params['queryid'] ?: $params['documentid'];
|
$instance->queryId = $params['queryid'] ?: $params['documentid'];
|
||||||
$instance->operation = $params['operation'];
|
$instance->operation = $params['operation'];
|
||||||
$instance->variables = $params['variables'];
|
$instance->variables = $params['variables'];
|
||||||
$instance->allowsMutations = (bool) $allowsMutations;
|
$instance->readOnly = (bool) $readonly;
|
||||||
|
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
@ -76,36 +76,6 @@ class OperationParams
|
|||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function validate()
|
|
||||||
{
|
|
||||||
$errors = [];
|
|
||||||
if (!$this->query && !$this->queryId) {
|
|
||||||
$errors[] = 'GraphQL Request must include at least one of those two parameters: "query" or "queryId"';
|
|
||||||
}
|
|
||||||
if ($this->query && $this->queryId) {
|
|
||||||
$errors[] = 'GraphQL Request parameters "query" and "queryId" are mutually exclusive';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->query !== null && (!is_string($this->query) || empty($this->query))) {
|
|
||||||
$errors[] = 'GraphQL Request parameter "query" must be string, but got ' .
|
|
||||||
Utils::printSafeJson($this->query);
|
|
||||||
}
|
|
||||||
if ($this->queryId !== null && (!is_string($this->queryId) || empty($this->queryId))) {
|
|
||||||
$errors[] = 'GraphQL Request parameter "queryId" must be string, but got ' .
|
|
||||||
Utils::printSafeJson($this->queryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->operation !== null && (!is_string($this->operation) || empty($this->operation))) {
|
|
||||||
$errors[] = 'GraphQL Request parameter "operation" must be string, but got ' .
|
|
||||||
Utils::printSafeJson($this->operation);
|
|
||||||
}
|
|
||||||
if ($this->variables !== null && (!is_array($this->variables) || isset($this->variables[0]))) {
|
|
||||||
$errors[] = 'GraphQL Request parameter "variables" must be object, but got ' .
|
|
||||||
Utils::printSafeJson($this->variables);
|
|
||||||
}
|
|
||||||
return $errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOriginalInput()
|
public function getOriginalInput()
|
||||||
{
|
{
|
||||||
return $this->originalInput;
|
return $this->originalInput;
|
||||||
@ -114,8 +84,8 @@ class OperationParams
|
|||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function allowsMutation()
|
public function isReadOnly()
|
||||||
{
|
{
|
||||||
return $this->allowsMutations;
|
return $this->readOnly;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,9 @@ class StandardServer
|
|||||||
public function executeRequest($parsedBody = null)
|
public function executeRequest($parsedBody = null)
|
||||||
{
|
{
|
||||||
if (null !== $parsedBody) {
|
if (null !== $parsedBody) {
|
||||||
$this->helper->assertBodyIsParsedProperly(__METHOD__, $parsedBody);
|
|
||||||
} else {
|
|
||||||
$parsedBody = $this->helper->parseHttpRequest();
|
$parsedBody = $this->helper->parseHttpRequest();
|
||||||
}
|
}
|
||||||
|
$this->helper->assertValidRequest($parsedBody);
|
||||||
|
|
||||||
$batched = is_array($parsedBody);
|
$batched = is_array($parsedBody);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Tests\Server;
|
namespace GraphQL\Tests\Server;
|
||||||
|
|
||||||
|
use GraphQL\Error\InvariantViolation;
|
||||||
use GraphQL\Error\UserError;
|
use GraphQL\Error\UserError;
|
||||||
use GraphQL\Server\Helper;
|
use GraphQL\Server\Helper;
|
||||||
use GraphQL\Server\OperationParams;
|
use GraphQL\Server\OperationParams;
|
||||||
@ -10,16 +11,16 @@ use GraphQL\Server\OperationParams;
|
|||||||
*/
|
*/
|
||||||
class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
public function testParsesSimpleGraphqlRequest()
|
public function testParsesGraphqlRequest()
|
||||||
{
|
{
|
||||||
$query = '{my query}';
|
$query = '{my query}';
|
||||||
list ($parsedBody, $isReadonly) = $this->parseRawRequest('application/graphql', $query);
|
$parsedBody = $this->parseRawRequest('application/graphql', $query);
|
||||||
|
|
||||||
$this->assertSame(['query' => $query], $parsedBody);
|
$this->assertValidOperationParams($parsedBody, $query);
|
||||||
$this->assertFalse($isReadonly);
|
$this->assertFalse($parsedBody->isReadOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParsesSimpleUrlencodedRequest()
|
public function testParsesUrlencodedRequest()
|
||||||
{
|
{
|
||||||
$query = '{my query}';
|
$query = '{my query}';
|
||||||
$variables = ['test' => 1, 'test2' => 2];
|
$variables = ['test' => 1, 'test2' => 2];
|
||||||
@ -31,12 +32,12 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
|||||||
'operation' => $operation
|
'operation' => $operation
|
||||||
];
|
];
|
||||||
|
|
||||||
list ($parsedBody, $isReadonly) = $this->parseFormUrlencodedRequest($post);
|
$parsedBody = $this->parseFormUrlencodedRequest($post);
|
||||||
$this->assertSame($post, $parsedBody);
|
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation);
|
||||||
$this->assertFalse($isReadonly);
|
$this->assertFalse($parsedBody->isReadOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParsesSimpleGETRequest()
|
public function testParsesGetRequest()
|
||||||
{
|
{
|
||||||
$query = '{my query}';
|
$query = '{my query}';
|
||||||
$variables = ['test' => 1, 'test2' => 2];
|
$variables = ['test' => 1, 'test2' => 2];
|
||||||
@ -48,12 +49,12 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
|||||||
'operation' => $operation
|
'operation' => $operation
|
||||||
];
|
];
|
||||||
|
|
||||||
list ($parsedBody, $isReadonly) = $this->parseGetRequest($get);
|
$parsedBody = $this->parseGetRequest($get);
|
||||||
$this->assertSame($get, $parsedBody);
|
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation);
|
||||||
$this->assertTrue($isReadonly);
|
$this->assertTrue($parsedBody->isReadonly());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParsesSimpleJSONRequest()
|
public function testParsesJSONRequest()
|
||||||
{
|
{
|
||||||
$query = '{my query}';
|
$query = '{my query}';
|
||||||
$variables = ['test' => 1, 'test2' => 2];
|
$variables = ['test' => 1, 'test2' => 2];
|
||||||
@ -64,10 +65,9 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
|||||||
'variables' => $variables,
|
'variables' => $variables,
|
||||||
'operation' => $operation
|
'operation' => $operation
|
||||||
];
|
];
|
||||||
|
$parsedBody = $this->parseRawRequest('application/json', json_encode($body));
|
||||||
list ($parsedBody, $isReadonly) = $this->parseRawRequest('application/json', json_encode($body));
|
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation);
|
||||||
$this->assertEquals($body, $parsedBody);
|
$this->assertFalse($parsedBody->isReadOnly());
|
||||||
$this->assertFalse($isReadonly);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParsesBatchJSONRequest()
|
public function testParsesBatchJSONRequest()
|
||||||
@ -85,9 +85,16 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
list ($parsedBody, $isReadonly) = $this->parseRawRequest('application/json', json_encode($body));
|
$parsedBody = $this->parseRawRequest('application/json', json_encode($body));
|
||||||
$this->assertEquals($body, $parsedBody);
|
$this->assertInternalType('array', $parsedBody);
|
||||||
$this->assertFalse($isReadonly);
|
$this->assertCount(2, $parsedBody);
|
||||||
|
|
||||||
|
$this->assertValidOperationParams($parsedBody[0], $body[0]['query'], null, $body[0]['variables'], $body[0]['operation']);
|
||||||
|
$this->assertValidOperationParams($parsedBody[1], null, $body[1]['queryId'], $body[1]['variables'], $body[1]['operation']);
|
||||||
|
|
||||||
|
// Batched queries must be read-only (do not allow batched mutations)
|
||||||
|
$this->assertTrue($parsedBody[0]->isReadOnly());
|
||||||
|
$this->assertTrue($parsedBody[1]->isReadOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailsParsingInvalidJsonRequest()
|
public function testFailsParsingInvalidJsonRequest()
|
||||||
@ -130,162 +137,6 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->parseRawRequest(null, 'test', "PUT");
|
$this->parseRawRequest(null, 'test', "PUT");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSimpleRequestShouldPass()
|
|
||||||
{
|
|
||||||
$query = '{my q}';
|
|
||||||
$variables = ['a' => 'b', 'c' => 'd'];
|
|
||||||
$operation = 'op';
|
|
||||||
|
|
||||||
$parsedBody = [
|
|
||||||
'query' => $query,
|
|
||||||
'variables' => $variables,
|
|
||||||
'operation' => $operation,
|
|
||||||
];
|
|
||||||
|
|
||||||
$helper = new Helper();
|
|
||||||
$params = $helper->toOperationParams($parsedBody, false);
|
|
||||||
$this->assertValidOperationParams($params, $query, null, $variables, $operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRequestWithQueryIdShouldPass()
|
|
||||||
{
|
|
||||||
$queryId = 'some-query-id';
|
|
||||||
$variables = ['a' => 'b', 'c' => 'd'];
|
|
||||||
$operation = 'op';
|
|
||||||
|
|
||||||
$parsedBody = [
|
|
||||||
'queryId' => $queryId,
|
|
||||||
'variables' => $variables,
|
|
||||||
'operation' => $operation,
|
|
||||||
];
|
|
||||||
|
|
||||||
$helper = new Helper();
|
|
||||||
$params = $helper->toOperationParams($parsedBody, false);
|
|
||||||
$this->assertValidOperationParams($params, null, $queryId, $variables, $operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testProducesCorrectOperationParamsForBatchRequest()
|
|
||||||
{
|
|
||||||
$query = '{my q}';
|
|
||||||
$queryId = 'some-query-id';
|
|
||||||
$variables = ['a' => 'b', 'c' => 'd'];
|
|
||||||
$operation = 'op';
|
|
||||||
|
|
||||||
$parsedBody = [
|
|
||||||
[
|
|
||||||
'query' => $query,
|
|
||||||
'variables' => $variables,
|
|
||||||
'operation' => $operation,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'queryId' => $queryId,
|
|
||||||
'variables' => [],
|
|
||||||
'operation' => null
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
$helper = new Helper();
|
|
||||||
$params = $helper->toOperationParams($parsedBody, false);
|
|
||||||
|
|
||||||
$this->assertTrue(is_array($params));
|
|
||||||
$this->assertValidOperationParams($params[0], $query, null, $variables, $operation);
|
|
||||||
$this->assertValidOperationParams($params[1], null, $queryId, [], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRequiresQueryOrQueryId()
|
|
||||||
{
|
|
||||||
$parsedBody = [
|
|
||||||
'variables' => ['foo' => 'bar'],
|
|
||||||
'operation' => 'op',
|
|
||||||
];
|
|
||||||
|
|
||||||
$helper = new Helper();
|
|
||||||
|
|
||||||
$this->setExpectedException(
|
|
||||||
UserError::class,
|
|
||||||
'GraphQL Request must include at least one of those two parameters: "query" or "queryId"'
|
|
||||||
);
|
|
||||||
$helper->toOperationParams($parsedBody, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFailsWhenBothQueryAndQueryIdArePresent()
|
|
||||||
{
|
|
||||||
$parsedBody = [
|
|
||||||
'query' => '{my query}',
|
|
||||||
'queryId' => 'my-query-id',
|
|
||||||
];
|
|
||||||
|
|
||||||
$helper = new Helper();
|
|
||||||
|
|
||||||
$this->setExpectedException(
|
|
||||||
UserError::class,
|
|
||||||
'GraphQL Request parameters "query" and "queryId" are mutually exclusive'
|
|
||||||
);
|
|
||||||
$helper->toOperationParams($parsedBody, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFailsWhenQueryParameterIsNotString()
|
|
||||||
{
|
|
||||||
$parsedBody = [
|
|
||||||
'query' => ['t' => '{my query}']
|
|
||||||
];
|
|
||||||
|
|
||||||
$helper = new Helper();
|
|
||||||
|
|
||||||
$this->setExpectedException(
|
|
||||||
UserError::class,
|
|
||||||
'GraphQL Request parameter "query" must be string, but got object with first key: "t"'
|
|
||||||
);
|
|
||||||
$helper->toOperationParams($parsedBody, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFailsWhenQueryIdParameterIsNotString()
|
|
||||||
{
|
|
||||||
$parsedBody = [
|
|
||||||
'queryId' => ['t' => '{my query}']
|
|
||||||
];
|
|
||||||
|
|
||||||
$helper = new Helper();
|
|
||||||
|
|
||||||
$this->setExpectedException(
|
|
||||||
UserError::class,
|
|
||||||
'GraphQL Request parameter "queryId" must be string, but got object with first key: "t"'
|
|
||||||
);
|
|
||||||
$helper->toOperationParams($parsedBody, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFailsWhenOperationParameterIsNotString()
|
|
||||||
{
|
|
||||||
$parsedBody = [
|
|
||||||
'query' => '{my query}',
|
|
||||||
'operation' => []
|
|
||||||
];
|
|
||||||
|
|
||||||
$helper = new Helper();
|
|
||||||
|
|
||||||
$this->setExpectedException(
|
|
||||||
UserError::class,
|
|
||||||
'GraphQL Request parameter "operation" must be string, but got array(0)'
|
|
||||||
);
|
|
||||||
$helper->toOperationParams($parsedBody, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFailsWhenVariablesParameterIsNotObject()
|
|
||||||
{
|
|
||||||
$parsedBody = [
|
|
||||||
'query' => '{my query}',
|
|
||||||
'variables' => 'test'
|
|
||||||
];
|
|
||||||
|
|
||||||
$helper = new Helper();
|
|
||||||
|
|
||||||
$this->setExpectedException(
|
|
||||||
UserError::class,
|
|
||||||
'GraphQL Request parameter "variables" must be object, but got "test"'
|
|
||||||
);
|
|
||||||
$helper->toOperationParams($parsedBody, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $contentType
|
* @param string $contentType
|
||||||
* @param string $content
|
* @param string $content
|
||||||
@ -299,7 +150,7 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
|||||||
$_SERVER['REQUEST_METHOD'] = $method;
|
$_SERVER['REQUEST_METHOD'] = $method;
|
||||||
|
|
||||||
$helper = new Helper();
|
$helper = new Helper();
|
||||||
return $helper->parseRawBody(function() use ($content) {
|
return $helper->parseHttpRequest(function() use ($content) {
|
||||||
return $content;
|
return $content;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -315,12 +166,14 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
|||||||
$_POST = $postValue;
|
$_POST = $postValue;
|
||||||
|
|
||||||
$helper = new Helper();
|
$helper = new Helper();
|
||||||
return $helper->parseRawBody();
|
return $helper->parseHttpRequest(function() {
|
||||||
|
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $getValue
|
* @param $getValue
|
||||||
* @return array
|
* @return OperationParams
|
||||||
*/
|
*/
|
||||||
private function parseGetRequest($getValue)
|
private function parseGetRequest($getValue)
|
||||||
{
|
{
|
||||||
@ -328,7 +181,9 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
|||||||
$_GET = $getValue;
|
$_GET = $getValue;
|
||||||
|
|
||||||
$helper = new Helper();
|
$helper = new Helper();
|
||||||
return $helper->parseRawBody();
|
return $helper->parseHttpRequest(function() {
|
||||||
|
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
179
tests/Server/RequestValidationTest.php
Normal file
179
tests/Server/RequestValidationTest.php
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Server;
|
||||||
|
|
||||||
|
use GraphQL\Error\InvariantViolation;
|
||||||
|
use GraphQL\Error\UserError;
|
||||||
|
use GraphQL\Server\Helper;
|
||||||
|
use GraphQL\Server\OperationParams;
|
||||||
|
|
||||||
|
class RequestValidationTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testSimpleRequestShouldValidate()
|
||||||
|
{
|
||||||
|
$query = '{my q}';
|
||||||
|
$variables = ['a' => 'b', 'c' => 'd'];
|
||||||
|
$operation = 'op';
|
||||||
|
|
||||||
|
$parsedBody = OperationParams::create([
|
||||||
|
'query' => $query,
|
||||||
|
'variables' => $variables,
|
||||||
|
'operation' => $operation,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertValid($parsedBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRequestWithQueryIdShouldValidate()
|
||||||
|
{
|
||||||
|
$queryId = 'some-query-id';
|
||||||
|
$variables = ['a' => 'b', 'c' => 'd'];
|
||||||
|
$operation = 'op';
|
||||||
|
|
||||||
|
$parsedBody = OperationParams::create([
|
||||||
|
'queryId' => $queryId,
|
||||||
|
'variables' => $variables,
|
||||||
|
'operation' => $operation,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertValid($parsedBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBatchRequestShouldValidate()
|
||||||
|
{
|
||||||
|
$query = '{my q}';
|
||||||
|
$queryId = 'some-query-id';
|
||||||
|
$variables = ['a' => 'b', 'c' => 'd'];
|
||||||
|
$operation = 'op';
|
||||||
|
|
||||||
|
$parsedBody = [
|
||||||
|
OperationParams::create([
|
||||||
|
'query' => $query,
|
||||||
|
'variables' => $variables,
|
||||||
|
'operation' => $operation,
|
||||||
|
]),
|
||||||
|
OperationParams::create([
|
||||||
|
'queryId' => $queryId,
|
||||||
|
'variables' => [],
|
||||||
|
'operation' => null
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertValid($parsedBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testThrowsOnInvalidRequest()
|
||||||
|
{
|
||||||
|
$parsedBody = 'str';
|
||||||
|
$this->assertInternalError(
|
||||||
|
$parsedBody,
|
||||||
|
'GraphQL Server: Parsed http request must be an instance of GraphQL\Server\OperationParams or array of such instances, but got "str"'
|
||||||
|
);
|
||||||
|
|
||||||
|
$parsedBody = ['str'];
|
||||||
|
$this->assertInternalError(
|
||||||
|
$parsedBody,
|
||||||
|
'GraphQL Server: Parsed http request must be an instance of GraphQL\Server\OperationParams or array of such instances, but got invalid array where entry at position 0 is "str"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRequiresQueryOrQueryId()
|
||||||
|
{
|
||||||
|
$parsedBody = OperationParams::create([
|
||||||
|
'variables' => ['foo' => 'bar'],
|
||||||
|
'operation' => 'op',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertInputError(
|
||||||
|
$parsedBody,
|
||||||
|
'GraphQL Request must include at least one of those two parameters: "query" or "queryId"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsWhenBothQueryAndQueryIdArePresent()
|
||||||
|
{
|
||||||
|
$parsedBody = OperationParams::create([
|
||||||
|
'query' => '{my query}',
|
||||||
|
'queryId' => 'my-query-id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertInputError(
|
||||||
|
$parsedBody,
|
||||||
|
'GraphQL Request parameters "query" and "queryId" are mutually exclusive'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsWhenQueryParameterIsNotString()
|
||||||
|
{
|
||||||
|
$parsedBody = OperationParams::create([
|
||||||
|
'query' => ['t' => '{my query}']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertInputError(
|
||||||
|
$parsedBody,
|
||||||
|
'GraphQL Request parameter "query" must be string, but got object with first key: "t"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsWhenQueryIdParameterIsNotString()
|
||||||
|
{
|
||||||
|
$parsedBody = OperationParams::create([
|
||||||
|
'queryId' => ['t' => '{my query}']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertInputError(
|
||||||
|
$parsedBody,
|
||||||
|
'GraphQL Request parameter "queryId" must be string, but got object with first key: "t"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsWhenOperationParameterIsNotString()
|
||||||
|
{
|
||||||
|
$parsedBody = OperationParams::create([
|
||||||
|
'query' => '{my query}',
|
||||||
|
'operation' => []
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertInputError(
|
||||||
|
$parsedBody,
|
||||||
|
'GraphQL Request parameter "operation" must be string, but got array(0)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsWhenVariablesParameterIsNotObject()
|
||||||
|
{
|
||||||
|
$parsedBody = OperationParams::create([
|
||||||
|
'query' => '{my query}',
|
||||||
|
'variables' => 'test'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertInputError($parsedBody, 'GraphQL Request parameter "variables" must be object, but got "test"');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertValid($parsedRequest)
|
||||||
|
{
|
||||||
|
$helper = new Helper();
|
||||||
|
$helper->assertValidRequest($parsedRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertInputError($parsedRequest, $expectedMessage)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$helper = new Helper();
|
||||||
|
$helper->assertValidRequest($parsedRequest);
|
||||||
|
$this->fail('Expected exception not thrown');
|
||||||
|
} catch (UserError $e) {
|
||||||
|
$this->assertEquals($expectedMessage, $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertInternalError($parsedRequest, $expectedMessage)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$helper = new Helper();
|
||||||
|
$helper->assertValidRequest($parsedRequest);
|
||||||
|
$this->fail('Expected exception not thrown');
|
||||||
|
} catch (InvariantViolation $e) {
|
||||||
|
$this->assertEquals($expectedMessage, $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user