$this->parseRawRequest('application/graphql', $query), 'psr' => $this->parsePsrRequest('application/graphql', $query), ]; foreach ($parsed as $source => $parsedBody) { self::assertValidOperationParams($parsedBody, $query, null, null, null, null, $source); self::assertFalse($parsedBody->isReadOnly(), $source); } } /** * @param string $contentType * @param string $content * * @return OperationParams|OperationParams[] */ private function parseRawRequest($contentType, $content, string $method = 'POST') { $_SERVER['CONTENT_TYPE'] = $contentType; $_SERVER['REQUEST_METHOD'] = $method; $helper = new Helper(); return $helper->parseHttpRequest(static function () use ($content) { return $content; }); } /** * @param string $contentType * @param string $content * * @return OperationParams|OperationParams[] */ private function parsePsrRequest($contentType, $content, string $method = 'POST') { $psrRequestBody = new PsrStreamStub(); $psrRequestBody->content = $content; $psrRequest = new PsrRequestStub(); $psrRequest->headers['content-type'] = [$contentType]; $psrRequest->method = $method; $psrRequest->body = $psrRequestBody; if ($contentType === 'application/json') { $parsedBody = json_decode($content, true); $parsedBody = $parsedBody === false ? null : $parsedBody; } else { $parsedBody = null; } $psrRequest->parsedBody = $parsedBody; $helper = new Helper(); return $helper->parsePsrRequest($psrRequest); } /** * @param OperationParams $params * @param string $query * @param string $queryId * @param mixed|null $variables * @param string $operation */ private static function assertValidOperationParams( $params, $query, $queryId = null, $variables = null, $operation = null, $extensions = null, $message = '' ) { self::assertInstanceOf(OperationParams::class, $params, $message); self::assertSame($query, $params->query, $message); self::assertSame($queryId, $params->queryId, $message); self::assertSame($variables, $params->variables, $message); self::assertSame($operation, $params->operation, $message); self::assertSame($extensions, $params->extensions, $message); } public function testParsesUrlencodedRequest() : void { $query = '{my query}'; $variables = ['test' => 1, 'test2' => 2]; $operation = 'op'; $post = [ 'query' => $query, 'variables' => $variables, 'operationName' => $operation, ]; $parsed = [ 'raw' => $this->parseRawFormUrlencodedRequest($post), 'psr' => $this->parsePsrFormUrlEncodedRequest($post), ]; foreach ($parsed as $method => $parsedBody) { self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } /** * @param mixed[] $postValue * * @return OperationParams|OperationParams[] */ private function parseRawFormUrlencodedRequest($postValue) { $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; $_SERVER['REQUEST_METHOD'] = 'POST'; $_POST = $postValue; $helper = new Helper(); return $helper->parseHttpRequest(static function () { throw new InvariantViolation("Shouldn't read from php://input for urlencoded request"); }); } /** * @param mixed[] $postValue * * @return OperationParams[]|OperationParams */ private function parsePsrFormUrlEncodedRequest($postValue) { $psrRequest = new PsrRequestStub(); $psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded']; $psrRequest->method = 'POST'; $psrRequest->parsedBody = $postValue; $helper = new Helper(); return $helper->parsePsrRequest($psrRequest); } public function testParsesGetRequest() : void { $query = '{my query}'; $variables = ['test' => 1, 'test2' => 2]; $operation = 'op'; $get = [ 'query' => $query, 'variables' => $variables, 'operationName' => $operation, ]; $parsed = [ 'raw' => $this->parseRawGetRequest($get), 'psr' => $this->parsePsrGetRequest($get), ]; foreach ($parsed as $method => $parsedBody) { self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertTrue($parsedBody->isReadonly(), $method); } } /** * @param mixed[] $getValue * * @return OperationParams */ private function parseRawGetRequest($getValue) { $_SERVER['REQUEST_METHOD'] = 'GET'; $_GET = $getValue; $helper = new Helper(); return $helper->parseHttpRequest(static function () { throw new InvariantViolation("Shouldn't read from php://input for urlencoded request"); }); } /** * @param mixed[] $getValue * * @return OperationParams[]|OperationParams */ private function parsePsrGetRequest($getValue) { $psrRequest = new PsrRequestStub(); $psrRequest->method = 'GET'; $psrRequest->queryParams = $getValue; $helper = new Helper(); return $helper->parsePsrRequest($psrRequest); } public function testParsesMultipartFormdataRequest() : void { $query = '{my query}'; $variables = ['test' => 1, 'test2' => 2]; $operation = 'op'; $post = [ 'query' => $query, 'variables' => $variables, 'operationName' => $operation, ]; $parsed = [ 'raw' => $this->parseRawMultipartFormDataRequest($post), 'psr' => $this->parsePsrMultipartFormDataRequest($post), ]; foreach ($parsed as $method => $parsedBody) { self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } /** * @param mixed[] $postValue * * @return OperationParams|OperationParams[] */ private function parseRawMultipartFormDataRequest($postValue) { $_SERVER['CONTENT_TYPE'] = 'multipart/form-data; boundary=----FormBoundary'; $_SERVER['REQUEST_METHOD'] = 'POST'; $_POST = $postValue; $helper = new Helper(); return $helper->parseHttpRequest(static function () { throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request"); }); } /** * @param mixed[] $postValue * * @return OperationParams|OperationParams[] */ private function parsePsrMultipartFormDataRequest($postValue) { $psrRequest = new PsrRequestStub(); $psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary']; $psrRequest->method = 'POST'; $psrRequest->parsedBody = $postValue; $helper = new Helper(); return $helper->parsePsrRequest($psrRequest); } public function testParsesJSONRequest() : void { $query = '{my query}'; $variables = ['test' => 1, 'test2' => 2]; $operation = 'op'; $body = [ 'query' => $query, 'variables' => $variables, 'operationName' => $operation, ]; $parsed = [ 'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } public function testParsesVariablesAsJSON() : void { $query = '{my query}'; $variables = ['test' => 1, 'test2' => 2]; $operation = 'op'; $body = [ 'query' => $query, 'variables' => json_encode($variables), 'operationName' => $operation, ]; $parsed = [ 'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } public function testIgnoresInvalidVariablesJson() : void { $query = '{my query}'; $variables = '"some invalid json'; $operation = 'op'; $body = [ 'query' => $query, 'variables' => $variables, 'operationName' => $operation, ]; $parsed = [ 'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } public function testParsesApolloPersistedQueryJSONRequest() : void { $queryId = 'my-query-id'; $extensions = ['persistedQuery' => ['sha256Hash' => $queryId]]; $variables = ['test' => 1, 'test2' => 2]; $operation = 'op'; $body = [ 'extensions' => $extensions, 'variables' => $variables, 'operationName' => $operation, ]; $parsed = [ 'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $extensions, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } public function testParsesBatchJSONRequest() : void { $body = [ [ 'query' => '{my query}', 'variables' => ['test' => 1, 'test2' => 2], 'operationName' => 'op', ], [ 'queryId' => 'my-query-id', 'variables' => ['test' => 1, 'test2' => 2], 'operationName' => 'op2', ], ]; $parsed = [ 'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { self::assertInternalType('array', $parsedBody, $method); self::assertCount(2, $parsedBody, $method); self::assertValidOperationParams( $parsedBody[0], $body[0]['query'], null, $body[0]['variables'], $body[0]['operationName'], null, $method ); self::assertValidOperationParams( $parsedBody[1], null, $body[1]['queryId'], $body[1]['variables'], $body[1]['operationName'], null, $method ); } } public function testFailsParsingInvalidRawJsonRequestRaw() : void { $body = 'not really{} a json'; $this->expectException(RequestError::class); $this->expectExceptionMessage('Could not parse JSON: Syntax error'); $this->parseRawRequest('application/json', $body); } public function testFailsParsingInvalidRawJsonRequestPsr() : void { $body = 'not really{} a json'; $this->expectException(InvariantViolation::class); $this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "application/json" requests but got null'); $this->parsePsrRequest('application/json', $body); } public function testFailsParsingNonPreParsedPsrRequest() : void { try { $this->parsePsrRequest('application/json', json_encode(null)); self::fail('Expected exception not thrown'); } catch (InvariantViolation $e) { // Expecting parsing exception to be thrown somewhere else: self::assertEquals( 'PSR-7 request is expected to provide parsed body for "application/json" requests but got null', $e->getMessage() ); } } /** * There is no equivalent for psr request, because it should throw */ public function testFailsParsingNonArrayOrObjectJsonRequestRaw() : void { $body = '"str"'; $this->expectException(RequestError::class); $this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"'); $this->parseRawRequest('application/json', $body); } public function testFailsParsingNonArrayOrObjectJsonRequestPsr() : void { $body = '"str"'; $this->expectException(RequestError::class); $this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"'); $this->parsePsrRequest('application/json', $body); } public function testFailsParsingInvalidContentTypeRaw() : void { $contentType = 'not-supported-content-type'; $body = 'test'; $this->expectException(RequestError::class); $this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"'); $this->parseRawRequest($contentType, $body); } public function testFailsParsingInvalidContentTypePsr() : void { $contentType = 'not-supported-content-type'; $body = 'test'; $this->expectException(RequestError::class); $this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"'); $this->parseRawRequest($contentType, $body); } public function testFailsWithMissingContentTypeRaw() : void { $this->expectException(RequestError::class); $this->expectExceptionMessage('Missing "Content-Type" header'); $this->parseRawRequest(null, 'test'); } public function testFailsWithMissingContentTypePsr() : void { $this->expectException(RequestError::class); $this->expectExceptionMessage('Missing "Content-Type" header'); $this->parsePsrRequest(null, 'test'); } public function testFailsOnMethodsOtherThanPostOrGetRaw() : void { $this->expectException(RequestError::class); $this->expectExceptionMessage('HTTP Method "PUT" is not supported'); $this->parseRawRequest('application/json', json_encode([]), 'PUT'); } public function testFailsOnMethodsOtherThanPostOrGetPsr() : void { $this->expectException(RequestError::class); $this->expectExceptionMessage('HTTP Method "PUT" is not supported'); $this->parsePsrRequest('application/json', json_encode([]), 'PUT'); } }