diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..26c5802 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,11 @@ +name: "Continuous Integration" + +on: + pull_request: + push: + branches: + tags: + +jobs: + ci: + uses: laminas/workflow-continuous-integration/.github/workflows/continuous-integration.yml@1.x diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml deleted file mode 100644 index 999e2ae..0000000 --- a/.github/workflows/php.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: PHP Composer - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - matrix: - php: [7.3, 7.4, 8.0] - - steps: - - uses: actions/checkout@v2 - - - name: Validate composer.json and composer.lock - run: composer validate --strict - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" - # Docs: https://getcomposer.org/doc/articles/scripts.md - - - name: Run test suite - run: composer run test - env: - AWS_ACCESS_KEY_ID: key - AWS_SECRET_ACCESS_KEY: secret - AWS_SESSION_TOKEN: token diff --git a/.laminas-ci.json b/.laminas-ci.json new file mode 100644 index 0000000..089ade8 --- /dev/null +++ b/.laminas-ci.json @@ -0,0 +1,13 @@ +{ + "additional_checks": [ + { + "name": "PHPStan", + "job": { + "php": "*", + "dependencies": "*", + "command": "vendor/bin/phpstan analyse" + } + } + ], + "stablePHP": "8.0" +} diff --git a/composer.json b/composer.json index 947eeb7..fd71755 100644 --- a/composer.json +++ b/composer.json @@ -29,22 +29,17 @@ } }, "require": { - "php": "^7.1 || ^8.0", + "php": "^8.2", "ext-json": "*", - "psr/http-message": "^1.0 || ^2.0 ", + "psr/http-message": "^2.0 ", "psr/http-client": "^1.0", - "guzzlehttp/guzzle": "^6.3|^7.0.1" + "guzzlehttp/guzzle": "^7.0.1" }, "require-dev": { - "phpunit/phpunit": "^7.5|^8.0|^9.0", - "codacy/coverage": "^1.4", - "aws/aws-sdk-php": "^3.186" - }, - "conflict": { - "guzzlehttp/psr7": "< 1.7.0" - }, - "scripts": { - "test": "phpunit tests/ --whitelist src/ --coverage-clover build/coverage/xml" + "aws/aws-sdk-php": "^3.186", + "phpstan/phpstan": "^1.11.4", + "squizlabs/php_codesniffer": "3.10.1", + "phpunit/phpunit": "^10.5.20" }, "suggest": { "aws/aws-sdk-php": "Move this package to require section to use AWS IAM authorization", diff --git a/examples/mutation_example.php b/examples/mutation_example.php index ed55af3..ae8b5cc 100644 --- a/examples/mutation_example.php +++ b/examples/mutation_example.php @@ -44,4 +44,4 @@ // Reformat the results to an array and get the results of part of the array $results->reformatResults(true); -print_r($results->getData()['pokemon']); \ No newline at end of file +print_r($results->getData()['pokemon']); diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..73969c8 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,11 @@ + + + + PHPCS configuration file. + src + + */vendor/* + + + + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..c77d90f --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 8 + paths: + - src + treatPhpDocTypesAsCertain: false diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..35bea45 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + + + src + + + diff --git a/src/Auth/AuthInterface.php b/src/Auth/AuthInterface.php index f2e5637..cd9c48d 100644 --- a/src/Auth/AuthInterface.php +++ b/src/Auth/AuthInterface.php @@ -3,13 +3,10 @@ namespace GraphQL\Auth; use GuzzleHttp\Psr7\Request; +use Psr\Http\Message\RequestInterface; interface AuthInterface { - /** - * @param Request $request - * @param array $options - * @return Request - */ - public function run(Request $request, array $options = []): Request; + /** @param array $options */ + public function run(Request $request, array $options = []): RequestInterface; } diff --git a/src/Auth/AwsIamAuth.php b/src/Auth/AwsIamAuth.php deleted file mode 100644 index 2f638a0..0000000 --- a/src/Auth/AwsIamAuth.php +++ /dev/null @@ -1,62 +0,0 @@ -getSignature($region)->signRequest( - $request, $this->getCredentials(), - self::SERVICE_NAME - ); - } - - /** - * @param string $region - * @return SignatureV4 - */ - protected function getSignature(string $region): SignatureV4 - { - return new SignatureV4(self::SERVICE_NAME, $region); - } - - /** - * @return Credentials - */ - protected function getCredentials(): Credentials - { - $provider = CredentialProvider::defaultProvider(); - return $provider()->wait(); - } -} diff --git a/src/Client.php b/src/Client.php index 9d8d2dd..030adeb 100644 --- a/src/Client.php +++ b/src/Client.php @@ -3,7 +3,6 @@ namespace GraphQL; use GraphQL\Auth\AuthInterface; -use GraphQL\Auth\HeaderAuth; use GraphQL\Exception\QueryError; use GraphQL\Exception\MethodNotSupportedException; use GraphQL\QueryBuilder\QueryBuilderInterface; @@ -12,141 +11,98 @@ use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Utils; use Psr\Http\Client\ClientInterface; -use TypeError; -/** - * Class Client - * - * @package GraphQL - */ class Client { - /** - * @var string - */ - protected $endpointUrl; + protected ClientInterface $httpClient; - /** - * @var ClientInterface - */ - protected $httpClient; + /** @var array> */ + protected array $httpHeaders; - /** - * @var array - */ - protected $httpHeaders; - - /** - * @var array - */ - protected $options; - - /** - * @var string - */ - protected $requestMethod; - - /** - * @var AuthInterface - */ - protected $auth; + /** @var array */ + protected array $options; /** * Client constructor. * - * @param string $endpointUrl - * @param array $authorizationHeaders - * @param array $httpOptions - * @param ClientInterface|null $httpClient - * @param string $requestMethod - * @param AuthInterface|null $auth + * @param array> $authorizationHeaders + * @param array $httpOptions */ public function __construct( - string $endpointUrl, + protected string $endpointUrl, array $authorizationHeaders = [], array $httpOptions = [], - ClientInterface $httpClient = null, - string $requestMethod = 'POST', - AuthInterface $auth = null + ?ClientInterface $httpClient = null, + protected string $requestMethod = 'POST', + protected ?AuthInterface $auth = null ) { - $headers = array_merge( + $this->httpHeaders = array_merge( $authorizationHeaders, $httpOptions['headers'] ?? [], ['Content-Type' => 'application/json'] ); - /** - * All headers will be set on the request objects explicitly, - * Guzzle doesn't have to care about them at this point, so to avoid any conflicts - * we are removing the headers from the options - */ - unset($httpOptions['headers']); - $this->options = $httpOptions; - if ($auth) { - $this->auth = $auth; - } - $this->endpointUrl = $endpointUrl; - $this->httpClient = $httpClient ?? new GuzzleAdapter(new \GuzzleHttp\Client($httpOptions)); - $this->httpHeaders = $headers; + $this->options = array_filter( + $httpOptions, + fn($k) => $k !== 'headers', + ARRAY_FILTER_USE_KEY, + ); + + $this->httpClient = $httpClient ?? + new GuzzleAdapter(new \GuzzleHttp\Client($httpOptions)); + if ($requestMethod !== 'POST') { throw new MethodNotSupportedException($requestMethod); } - $this->requestMethod = $requestMethod; } /** - * @param Query|QueryBuilderInterface $query - * @param bool $resultsAsArray - * @param array $variables - * - * @return Results + * @param array $variables * @throws QueryError */ - public function runQuery($query, bool $resultsAsArray = false, array $variables = []): Results - { + public function runQuery( + Query|QueryBuilderInterface $query, + bool $resultsAsArray = false, + array $variables = [] + ): Results { if ($query instanceof QueryBuilderInterface) { $query = $query->getQuery(); } - if (!$query instanceof Query) { - throw new TypeError('Client::runQuery accepts the first argument of type Query or QueryBuilderInterface'); - } - return $this->runRawQuery((string) $query, $resultsAsArray, $variables); } /** - * @param string $queryString - * @param bool $resultsAsArray - * @param array $variables - * @param - * - * @return Results + * @param array $variables * @throws QueryError */ - public function runRawQuery(string $queryString, $resultsAsArray = false, array $variables = []): Results - { + public function runRawQuery( + string $queryString, + bool $resultsAsArray = false, + array $variables = [] + ): Results { $request = new Request($this->requestMethod, $this->endpointUrl); - foreach($this->httpHeaders as $header => $value) { + foreach ($this->httpHeaders as $header => $value) { $request = $request->withHeader($header, $value); } // Convert empty variables array to empty json object - if (empty($variables)) $variables = (object) null; + if (empty($variables)) { + $variables = (object) null; + } // Set query in the request body $bodyArray = ['query' => (string) $queryString, 'variables' => $variables]; $request = $request->withBody(Utils::streamFor(json_encode($bodyArray))); - if ($this->auth) { + if (isset($this->auth)) { $request = $this->auth->run($request, $this->options); } // Send api request and get response try { $response = $this->httpClient->sendRequest($request); - } - catch (ClientException $exception) { + } catch (ClientException $exception) { $response = $exception->getResponse(); // If exception thrown by client is "400 Bad Request ", then it can be treated as a successful API request diff --git a/src/Exception/ArgumentException.php b/src/Exception/ArgumentException.php index 6ffe816..994179e 100644 --- a/src/Exception/ArgumentException.php +++ b/src/Exception/ArgumentException.php @@ -11,8 +11,8 @@ */ class ArgumentException extends InvalidArgumentException { - public function __construct($message = "") + public function __construct(string $message = '') { parent::__construct($message); } -} \ No newline at end of file +} diff --git a/src/Exception/AwsRegionNotSetException.php b/src/Exception/AwsRegionNotSetException.php deleted file mode 100644 index 5d396aa..0000000 --- a/src/Exception/AwsRegionNotSetException.php +++ /dev/null @@ -1,18 +0,0 @@ - */ + protected array $errorDetails; + + /** @var array */ protected $data; - /** - * @var array - */ + + /** @var array */ protected $errors; - /** - * QueryError constructor. - * - * @param array $errorDetails - */ - public function __construct($errorDetails) + /** @param array $errorDetails */ + public function __construct(array $errorDetails) { + $this->errors = $errorDetails['errors']; $this->errorDetails = $errorDetails['errors'][0]; + $this->data = []; if (!empty($errorDetails['data'])) { $this->data = $errorDetails['data']; } - $this->errors = $errorDetails['errors']; parent::__construct($this->errorDetails['message']); } - /** - * @return array - */ - public function getErrorDetails() + /** @return array */ + public function getErrorDetails(): array { return $this->errorDetails; } - /** - * @return array - */ - public function getData() + /** @return array */ + public function getData(): array { return $this->data; } - /** - * @return array - */ - public function getErrors() + /** @return array */ + public function getErrors(): array { return $this->errors; } diff --git a/src/FieldTrait.php b/src/FieldTrait.php index cec27b9..5dce1b0 100644 --- a/src/FieldTrait.php +++ b/src/FieldTrait.php @@ -3,72 +3,72 @@ namespace GraphQL; use GraphQL\Exception\InvalidSelectionException; +use GraphQL\QueryBuilder\QueryBuilderInterface; trait FieldTrait { /** * Stores the selection set desired to get from the query, can include nested queries * - * @var array + * @var array */ - protected $selectionSet; + protected array $selectionSet; /** - * @param array $selectionSet - * - * @return $this + * @param array $selectionSet * @throws InvalidSelectionException */ - public function setSelectionSet(array $selectionSet) + public function setSelectionSet(array $selectionSet): self { - $nonStringsFields = array_filter($selectionSet, function($element) { - return !is_string($element) && !$element instanceof Query && !$element instanceof InlineFragment; - }); - if (!empty($nonStringsFields)) { - throw new InvalidSelectionException( - 'One or more of the selection fields provided is not of type string or Query' - ); + $selectionSet = array_map( + fn ($s) => $s instanceof QueryBuilderInterface ? + $s->getQuery() : + $s, + $selectionSet, + ); + + + foreach ($selectionSet as $selection) { + if ( + !is_string($selection) && + !$selection instanceof Query && + !$selection instanceof InlineFragment + ) { + throw new InvalidSelectionException(sprintf( + 'Can only set a selection from one of the following: %s', + implode(', ', [ + InlineFragment::class, + Query::class, + QueryBuilderInterface::class, + 'string', + ]), + )); + } } $this->selectionSet = $selectionSet; - return $this; } - /** - * @return string - */ protected function constructSelectionSet(): string { if (empty($this->selectionSet)) { return ''; - } - - $attributesString = " {" . PHP_EOL; - $first = true; - foreach ($this->selectionSet as $attribute) { - - // Append empty line at the beginning if it's not the first item on the list - if ($first) { - $first = false; - } else { - $attributesString .= PHP_EOL; - } - - // If query is included in attributes set as a nested query - if ($attribute instanceof Query) { - $attribute->setAsNested(); - } - - // Append attribute to returned attributes list - $attributesString .= $attribute; } - $attributesString .= PHP_EOL . "}"; - return $attributesString; + return sprintf(' { %s }', implode(' ', array_map( + function ($selection) { + if ($selection instanceof Query) { + $selection->setAsNested(); + } + return $selection; + }, + $this->selectionSet, + ))); } - public function getSelectionSet() + /** @return array */ + public function getSelectionSet(): array { return $this->selectionSet; } diff --git a/src/InlineFragment.php b/src/InlineFragment.php index e41f242..8b67fa0 100644 --- a/src/InlineFragment.php +++ b/src/InlineFragment.php @@ -3,49 +3,21 @@ namespace GraphQL; use GraphQL\QueryBuilder\QueryBuilderInterface; +use Stringable; -/** - * Class InlineFragment - * - * @package GraphQL - */ -class InlineFragment extends NestableObject +class InlineFragment implements Stringable { use FieldTrait; - /** - * Stores the format for the inline fragment format - * - * @var string - */ protected const FORMAT = '... on %s%s'; - /** - * @var string - */ - protected $typeName; - - /** - * @var QueryBuilderInterface|null - */ - protected $queryBuilder; - - /** - * InlineFragment constructor. - * - * @param string $typeName - * @param QueryBuilderInterface|null $queryBuilder - */ - public function __construct(string $typeName, ?QueryBuilderInterface $queryBuilder = null) - { - $this->typeName = $typeName; - $this->queryBuilder = $queryBuilder; + public function __construct( + protected string $typeName, + protected ?QueryBuilderInterface $queryBuilder = null, + ) { } - /** - * - */ - public function __toString() + public function __toString(): string { if ($this->queryBuilder !== null) { $this->setSelectionSet($this->queryBuilder->getQuery()->getSelectionSet()); @@ -53,14 +25,4 @@ public function __toString() return sprintf(static::FORMAT, $this->typeName, $this->constructSelectionSet()); } - - /** - * @codeCoverageIgnore - * - * @return mixed|void - */ - protected function setAsNested() - { - // TODO: Remove this method, it's purely tech debt - } } diff --git a/src/Mutation.php b/src/Mutation.php index fc07282..e879643 100644 --- a/src/Mutation.php +++ b/src/Mutation.php @@ -2,17 +2,10 @@ namespace GraphQL; -/** - * Class Mutation - * - * @package GraphQL - */ class Mutation extends Query { /** * Stores the name of the type of the operation to be executed on the GraphQL server - * - * @var string */ - protected const OPERATION_TYPE = 'mutation'; -} \ No newline at end of file + protected const OPERATION_TYPE = OperationType::Mutation->value; +} diff --git a/src/NestableObject.php b/src/NestableObject.php deleted file mode 100644 index 0b3e980..0000000 --- a/src/NestableObject.php +++ /dev/null @@ -1,19 +0,0 @@ -value; - /** - * Stores the object alias - * - * @var string - */ - protected $alias; + /** The name of the operation to be run on the server */ + protected string $operationName = ''; /** - * Stores the list of variables to be used in the query + * The list of variables to be used in the query * - * @var array|Variable[] + * @var Variable[] */ - protected $variables; + protected array $variables; /** - * Stores the list of arguments used when querying data + * The list of arguments used when querying data * - * @var array + * @var array|scalar|Stringable|BackedEnum> */ - protected $arguments; + protected array $arguments = []; /** - * Private member that's not accessible from outside the class, used internally to deduce if query is nested or not + * Private member that's not accessible from outside the class, + * used internally to deduce if query is nested or not * * @var bool */ - protected $isNested; + protected bool $isNested = false; /** * GQLQueryBuilder constructor. * - * @param string $fieldName if no value is provided for the field name an empty query object is assumed + * @param string $fieldName if no value is provided, empty query object is assumed * @param string $alias the alias to use for the query if required */ - public function __construct(string $fieldName = '', string $alias = '') - { - $this->fieldName = $fieldName; - $this->alias = $alias; - $this->operationName = ''; - $this->variables = []; - $this->arguments = []; - $this->selectionSet = []; - $this->isNested = false; + public function __construct( + protected string $fieldName = '', + protected string $alias = '' + ) { } - /** - * @return string - */ public function getFieldName(): string { return $this->fieldName; } - /** - * @param string $alias - * - * @return Query - */ - public function setAlias(string $alias) + public function setAlias(string $alias): Query { $this->alias = $alias; return $this; } - /** - * @param string $operationName - * - * @return Query - */ - public function setOperationName(string $operationName) + public function setOperationName(string $operationName): Query { if (!empty($operationName)) { $this->operationName = " $operationName"; @@ -126,18 +82,15 @@ public function setOperationName(string $operationName) return $this; } - /** - * @param array $variables - * - * @return Query - */ - public function setVariables(array $variables) + /** @param Variable[] $variables */ + public function setVariables(array $variables): Query { - $nonVarElements = array_filter($variables, function($e) { - return !$e instanceof Variable; - }); - if (count($nonVarElements) > 0) { - throw new InvalidVariableException('At least one of the elements of the variables array provided is not an instance of GraphQL\\Variable'); + foreach ($variables as $variable) { + if (!$variable instanceof Variable) { + throw new InvalidVariableException( + 'All variables must be an instance of GraphQL\\Variable' + ); + } } $this->variables = $variables; @@ -146,24 +99,20 @@ public function setVariables(array $variables) } /** - * Throwing exception when setting the arguments if they are incorrect because we can't throw an exception during - * the execution of __ToString(), it's a fatal error in PHP - * - * @param array $arguments - * - * @return Query - * @throws ArgumentException + * @param array|Stringable|BackedEnum> $arguments + * @throws ArgumentException for invalid arguments */ public function setArguments(array $arguments): Query { - // If one of the arguments does not have a name provided, throw an exception - $nonStringArgs = array_filter(array_keys($arguments), function($element) { - return !is_string($element); - }); - if (!empty($nonStringArgs)) { - throw new ArgumentException( - 'One or more of the arguments provided for creating the query does not have a key, which represents argument name' - ); + foreach ($arguments as $name => $argument) { + if (!is_string($name)) { + throw new ArgumentException( + 'All query arguments require string keys,' . + 'these represent the argument name' + ); + } + + $this->validateArgument($argument); } $this->arguments = $arguments; @@ -171,125 +120,97 @@ public function setArguments(array $arguments): Query return $this; } - /** - * @return array - */ + private function validateArgument(mixed $value): void + { + if (is_array($value)) { + foreach ($value as $item) { + $this->validateArgument($item); + } + + return; + } + + if ( + is_null($value) + || is_scalar($value) + || $value instanceof Stringable + || $value instanceof BackedEnum + ) { + return; + } + + throw new ArgumentException(sprintf( + '%s cannot be supported', + gettype($value), + )); + } + + /** @return array|Stringable|BackedEnum> */ public function getArguments(): array { return $this->arguments; } - /** - * @return string - */ protected function constructVariables(): string { if (empty($this->variables)) { return ''; } - $varsString = '('; - $first = true; - foreach ($this->variables as $variable) { - - // Append space at the beginning if it's not the first item on the list - if ($first) { - $first = false; - } else { - $varsString .= ' '; - } - - // Append variable string value to the variables string - $varsString .= (string) $variable; - } - $varsString .= ')'; - - return $varsString; + return sprintf('( %s )', implode(' ', $this->variables)); } - /** - * @return string - */ protected function constructArguments(): string { - // Return empty string if list is empty if (empty($this->arguments)) { return ''; } - // Construct arguments string if list not empty - $constraintsString = '('; - $first = true; + $formattedArguments = []; foreach ($this->arguments as $name => $value) { + $formattedArguments[] = sprintf('%s: %s', $name, is_array($value) ? + StringLiteralFormatter::formatArrayForGQLQuery($value) : + StringLiteralFormatter::formatValueForRHS($value)); + } - // Append space at the beginning if it's not the first item on the list - if ($first) { - $first = false; - } else { - $constraintsString .= ' '; - } + return sprintf('(%s)', implode(' ', $formattedArguments)); + } - // Convert argument values to graphql string literal equivalent - if (is_scalar($value) || $value === null) { - // Convert scalar value to its literal in graphql - $value = StringLiteralFormatter::formatValueForRHS($value); - } elseif (is_array($value)) { - // Convert PHP array to its array representation in graphql arguments - $value = StringLiteralFormatter::formatArrayForGQLQuery($value); - } - // TODO: Handle cases where a non-string-convertible object is added to the arguments - $constraintsString .= $name . ': ' . $value; - } - $constraintsString .= ')'; + protected function generateFieldName(): string + { + return empty($this->alias) ? $this->fieldName : sprintf('%s: %s', $this->alias, $this->fieldName); + } - return $constraintsString; + protected function generateSignature(): string + { + return sprintf( + '%s%s%s', + static::OPERATION_TYPE, + $this->operationName, + $this->constructVariables() + ); } - /** - * @return string - */ - public function __toString() + public function setAsNested(): void + { + $this->isNested = true; + } + + public function __toString(): string { - $queryFormat = static::QUERY_FORMAT; + $queryFormat = self::QUERY_FORMAT; $selectionSetString = $this->constructSelectionSet(); if (!$this->isNested) { $queryFormat = $this->generateSignature(); if ($this->fieldName === '') { - return $queryFormat . $selectionSetString; } else { - $queryFormat = $this->generateSignature() . " {" . PHP_EOL . static::QUERY_FORMAT . PHP_EOL . "}"; + $queryFormat = $this->generateSignature() . ' { ' . static::QUERY_FORMAT . ' }'; } } $argumentsString = $this->constructArguments(); return sprintf($queryFormat, $this->generateFieldName(), $argumentsString, $selectionSetString); } - - /** - * @return string - */ - protected function generateFieldName(): string - { - return empty($this->alias) ? $this->fieldName : sprintf('%s: %s', $this->alias, $this->fieldName); - } - - /** - * @return string - */ - protected function generateSignature(): string - { - $signatureFormat = '%s%s%s'; - - return sprintf($signatureFormat, static::OPERATION_TYPE, $this->operationName, $this->constructVariables()); - } - - /** - * - */ - protected function setAsNested() - { - $this->isNested = true; - } } diff --git a/src/QueryBuilder/AbstractQueryBuilder.php b/src/QueryBuilder/AbstractQueryBuilder.php index 4fca5a1..3c6dd3c 100644 --- a/src/QueryBuilder/AbstractQueryBuilder.php +++ b/src/QueryBuilder/AbstractQueryBuilder.php @@ -2,67 +2,38 @@ namespace GraphQL\QueryBuilder; +use BackedEnum; use GraphQL\InlineFragment; use GraphQL\Query; use GraphQL\RawObject; use GraphQL\Variable; +use Stringable; -/** - * Class AbstractQueryBuilder - * - * @package GraphQL - */ abstract class AbstractQueryBuilder implements QueryBuilderInterface { - /** - * @var Query - */ - protected $query; + protected Query $query; - /** - * @var array|Variable[] - */ - private $variables; + /** @var Variable[] */ + private array $variables = []; - /** - * @var array - */ - private $selectionSet; + /** @var array */ + private array $selectionSet = []; - /** - * @var array - */ - private $argumentsList; + /** @var array|Stringable|BackedEnum> */ + private array $argumentsList = []; - /** - * QueryBuilder constructor. - * - * @param string $queryObject - * @param string $alias - */ public function __construct(string $queryObject = '', string $alias = '') { - $this->query = new Query($queryObject, $alias); - $this->variables = []; - $this->selectionSet = []; - $this->argumentsList = []; + $this->query = new Query($queryObject, $alias); } - /** - * @param string $alias - * - * @return $this - */ - public function setAlias(string $alias) + public function setAlias(string $alias): self { $this->query->setAlias($alias); return $this; } - /** - * @return Query - */ public function getQuery(): Query { // Convert nested query builders to query objects @@ -79,51 +50,41 @@ public function getQuery(): Query return $this->query; } - /** - * @param string|QueryBuilderInterface|InlineFragment|Query $selectedField - * - * @return $this - */ - protected function selectField($selectedField) - { - if ( - is_string($selectedField) - || $selectedField instanceof QueryBuilderInterface - || $selectedField instanceof Query - || $selectedField instanceof InlineFragment - ) { - $this->selectionSet[] = $selectedField; - } + protected function selectField( + InlineFragment|Query|QueryBuilderInterface|string $selectedField, + ): AbstractQueryBuilder { + $this->selectionSet[] = $selectedField; return $this; } /** - * @param $argumentName - * @param $argumentValue - * - * @return $this + * @param null|array|scalar|BackedEnum|Stringable $value */ - protected function setArgument(string $argumentName, $argumentValue) - { - if (is_scalar($argumentValue) || is_array($argumentValue) || $argumentValue instanceof RawObject) { - $this->argumentsList[$argumentName] = $argumentValue; + protected function setArgument( + string $name, + null|bool|float|int|string|array|Stringable|BackedEnum $value + ): self { + if (!is_null($value)) { + $this->argumentsList[$name] = $value; } return $this; } - /** - * @param string $name - * @param string $type - * @param bool $isRequired - * @param null $defaultValue - * - * @return $this - */ - protected function setVariable(string $name, string $type, bool $isRequired = false, $defaultValue = null) - { - $this->variables[] = new Variable($name, $type, $isRequired, $defaultValue); + /** @param null|array|scalar|BackedEnum|Stringable $defaultValue */ + protected function setVariable( + string $name, + string $type, + bool $isRequired = false, + null|bool|float|int|string|array|Stringable|BackedEnum $defaultValue = null, + ): AbstractQueryBuilder { + $this->variables[] = new Variable( + $name, + $type, + $isRequired, + $defaultValue + ); return $this; } diff --git a/src/QueryBuilder/MutationBuilder.php b/src/QueryBuilder/MutationBuilder.php index 2c506c1..9a33715 100644 --- a/src/QueryBuilder/MutationBuilder.php +++ b/src/QueryBuilder/MutationBuilder.php @@ -6,26 +6,15 @@ class MutationBuilder extends QueryBuilder { - /** - * MutationBuilder constructor. - * - * @param string $queryObject - * @param string $alias - */ public function __construct(string $queryObject = '', string $alias = '') { parent::__construct($queryObject, $alias); $this->query = new Mutation($queryObject, $alias); } - /** - * Synonymous method to getQuery(), it just return a Mutation type instead of Query type creating a neater - * interface when using interfaces - * - * @return Mutation - */ public function getMutation(): Mutation { + assert($this->getQuery() instanceof Mutation); return $this->getQuery(); } -} \ No newline at end of file +} diff --git a/src/QueryBuilder/QueryBuilder.php b/src/QueryBuilder/QueryBuilder.php index 94ccf65..e9a7dac 100644 --- a/src/QueryBuilder/QueryBuilder.php +++ b/src/QueryBuilder/QueryBuilder.php @@ -2,52 +2,34 @@ namespace GraphQL\QueryBuilder; +use BackedEnum; +use GraphQL\InlineFragment; use GraphQL\Query; +use Stringable; -/** - * Class QueryBuilder - * - * @package GraphQL - */ class QueryBuilder extends AbstractQueryBuilder { - /** - * Changing method visibility to public - * - * @param Query|QueryBuilder|string $selectedField - * - * @return $this - */ - public function selectField($selectedField) - { + public function selectField( + InlineFragment|Query|QueryBuilderInterface|string $selectedField, + ): AbstractQueryBuilder { return parent::selectField($selectedField); } - /** - * Changing method visibility to public - * - * @param string $argumentName - * @param $argumentValue - * - * @return $this - */ - public function setArgument(string $argumentName, $argumentValue) - { - return parent::setArgument($argumentName, $argumentValue); + /** @param null|scalar|array|BackedEnum|Stringable $value */ + public function setArgument( + string $name, + null|bool|float|int|string|array|BackedEnum|Stringable $value, + ): AbstractQueryBuilder { + return parent::setArgument($name, $value); } - /** - * Changing method visibility to public - * - * @param string $name - * @param string $type - * @param bool $isRequired - * @param null $defaultValue - * - * @return $this - */ - public function setVariable(string $name, string $type, bool $isRequired = false, $defaultValue = null) - { + /** @param null|array|scalar|BackedEnum|Stringable $defaultValue */ + public function setVariable( + string $name, + string $type, + bool $isRequired = false, + null|bool|float|int|string|array|Stringable|BackedEnum $defaultValue = null, + ): AbstractQueryBuilder { return parent::setVariable($name, $type, $isRequired, $defaultValue); } } diff --git a/src/QueryBuilder/QueryBuilderInterface.php b/src/QueryBuilder/QueryBuilderInterface.php index d4a9745..81f094b 100644 --- a/src/QueryBuilder/QueryBuilderInterface.php +++ b/src/QueryBuilder/QueryBuilderInterface.php @@ -4,15 +4,7 @@ use GraphQL\Query; -/** - * Interface QueryBuilderInterface - * - * @package GraphQL\QueryBuilder - */ interface QueryBuilderInterface { - /** - * @return Query - */ - function getQuery(): Query; -} \ No newline at end of file + public function getQuery(): Query; +} diff --git a/src/RawObject.php b/src/RawObject.php index 8d35af4..71dd87a 100644 --- a/src/RawObject.php +++ b/src/RawObject.php @@ -2,33 +2,17 @@ namespace GraphQL; -/** - * Class RawObject - * - * @package GraphQL - */ -class RawObject -{ - /** - * @var string - */ - protected $objectString; +use Stringable; - /** - * JsonObject constructor. - * - * @param string $objectString - */ - public function __construct(string $objectString) - { - $this->objectString = $objectString; +final readonly class RawObject implements Stringable +{ + public function __construct( + protected string $json + ) { } - /** - * @return mixed - */ - public function __toString() + public function __toString(): string { - return $this->objectString; + return $this->json; } -} \ No newline at end of file +} diff --git a/src/Results.php b/src/Results.php index 4bdfc43..f0f8238 100644 --- a/src/Results.php +++ b/src/Results.php @@ -5,101 +5,71 @@ use GraphQL\Exception\QueryError; use Psr\Http\Message\ResponseInterface; -/** - * Class Result - * - * @package GraphQl - */ class Results { - /** - * @var string - */ - protected $responseBody; - - /** - * @var ResponseInterface - */ - protected $responseObject; + protected string $responseBody; - /** - * @var array|object - */ - protected $results; + /** @var array|object */ + protected null|array|object $results; /** * Result constructor. * * Receives json response from GraphQL api response and parses it as associative array or nested object accordingly - * - * @param ResponseInterface $response - * @param bool $asArray - * * @throws QueryError */ - public function __construct(ResponseInterface $response, $asArray = false) - { - $this->responseObject = $response; - $this->responseBody = (string) $this->responseObject->getBody(); - $this->results = json_decode($this->responseBody, $asArray); + public function __construct( + protected ResponseInterface $response, + bool $asArray = false + ) { + $this->responseBody = (string) $this->response->getBody(); + $this->results = json_decode($this->responseBody, $asArray); - // Check if any errors exist, and throw exception if they do - if ($asArray) $containsErrors = array_key_exists('errors', $this->results); - else $containsErrors = isset($this->results->errors); + $containsErrors = is_array($this->results) ? + isset($this->results['errors']) : + isset($this->results->errors); if ($containsErrors) { - - // Reformat results to an array and use it to initialize exception object $this->reformatResults(true); + assert(is_array($this->results)); throw new QueryError($this->results); } } - /** - * @param bool $asArray - */ public function reformatResults(bool $asArray): void { - $this->results = json_decode($this->responseBody, (bool) $asArray); + $this->results = json_decode($this->responseBody, $asArray); } /** * Returns only parsed data objects in the requested format * - * @return array|object + * @return array|object */ public function getData() { - if (is_array($this->results)) { - return $this->results['data']; - } - - return $this->results->data; + return is_array($this->results) ? + $this->results['data'] ?? [] : + $this->results->data ?? []; } /** * Returns entire parsed results in the requested format * - * @return array|object + * @return null|array|object */ - public function getResults() + public function getResults(): null|array|object { return $this->results; } - /** - * @return string - */ - public function getResponseBody() + public function getResponseBody(): string { return $this->responseBody; } - /** - * @return ResponseInterface - */ - public function getResponseObject() + public function getResponseObject(): ResponseInterface { - return $this->responseObject; + return $this->response; } -} \ No newline at end of file +} diff --git a/src/Util/GuzzleAdapter.php b/src/Util/GuzzleAdapter.php index 9ee3ced..add32a4 100644 --- a/src/Util/GuzzleAdapter.php +++ b/src/Util/GuzzleAdapter.php @@ -10,27 +10,12 @@ class GuzzleAdapter implements Client\ClientInterface { - /** - * @var ClientInterface - */ - private $client; - - /** - * GuzzleAdapter constructor. - * - * @param ClientInterface $client - */ - public function __construct(ClientInterface $client) - { - $this->client = $client; + public function __construct( + private ClientInterface $client + ) { } - /** - * @param RequestInterface $request - * - * @return ResponseInterface - * @throws GuzzleException - */ + /** @throws GuzzleException */ public function sendRequest(RequestInterface $request): ResponseInterface { /** diff --git a/src/Util/StringLiteralFormatter.php b/src/Util/StringLiteralFormatter.php index 2d78a5f..01ef759 100644 --- a/src/Util/StringLiteralFormatter.php +++ b/src/Util/StringLiteralFormatter.php @@ -2,14 +2,12 @@ namespace GraphQL\Util; -/** - * Class StringLiteralFormatter - * - * @package GraphQL\Util - */ +use BackedEnum; +use Stringable; + class StringLiteralFormatter { - const ESCAPE_SEQUENCES = [ + private const ESCAPE_SEQUENCES = [ '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004', '\\u0005', '\\u0006', '\\u0007', '\\b', '\\t', '\\n', '\\u000B', '\\f', '\\r', '\\u000E', '\\u000F', '\\u0010', '\\u0011', '\\u0012', '\\u0013', '\\u0014', '\\u0015', '\\u0016', '\\u0017', @@ -32,96 +30,76 @@ class StringLiteralFormatter '\\u0098', '\\u0099', '\\u009A', '\\u009B', '\\u009C', '\\u009D', '\\u009E', '\\u009F', ]; - /** - * Converts the value provided to the equivalent RHS value to be put in a file declaration - * - * @param string|int|float|bool $value - * - * @return string - */ - public static function formatValueForRHS($value): string - { - if (is_string($value)) { - if (!static::isVariable($value)) { - if (strpos($value, "\n") !== false) { - $value = '"""' . $value . '"""'; - } else { - $value = preg_replace_callback('/[\x00-\x1f\x22\x5c\x7f-\x9f]/u', function(array $matches) { - $str = $matches[0]; - return self::ESCAPE_SEQUENCES[ord($str[0])]; - }, $value); - $value = "\"$value\""; - } - } - } elseif (is_bool($value)) { - if ($value) { - $value = 'true'; - } else { - $value = 'false'; - } - } elseif ($value === null) { - $value = 'null'; - } else { - $value = (string) $value; + public static function formatValueForRHS( + null|bool|float|int|string|BackedEnum|Stringable $value + ): string { + if (is_null($value)) { + return 'null'; + } + + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + if ( + is_float($value) + || is_int($value) + || $value instanceof Stringable + ) { + return (string) $value; + } + + if ($value instanceof BackedEnum) { + return (string) $value->value; } - return $value; + if (self::isVariable($value)) { + return $value; + } + + if (str_contains($value, "\n")) { + return sprintf('"""%s"""', $value); + } + + $value = preg_replace_callback( + '/[\x00-\x1f\x22\x5c\x7f-\x9f]/u', + function (array $matches) { + $str = $matches[0]; + return self::ESCAPE_SEQUENCES[ord($str[0])]; + }, + $value + ); + + return sprintf('"%s"', $value); } - /** - * Treat string value as variable if it matches variable regex - * - * @param string $value - * - * @return bool - */ - private static function isVariable(string $value): bool { - return preg_match('/^\$[_A-Za-z][_0-9A-Za-z]*$/', $value); + private static function isVariable(string $value): bool + { + return preg_match('/^\$[_A-Za-z][_0-9A-Za-z]*$/', $value) === 1; } - /** - * @param array $array - * - * @return string - */ + /** @param array $array */ public static function formatArrayForGQLQuery(array $array): string { - $arrString = '['; - $first = true; - foreach ($array as $element) { - if ($first) { - $first = false; - } else { - $arrString .= ', '; - } - $arrString .= StringLiteralFormatter::formatValueForRHS($element); - } - $arrString .= ']'; - - return $arrString; + return sprintf('[%s]', implode(', ', array_map( + fn ($p) => is_array($p) ? + self::formatArrayForGQLQuery($p) : + self::formatValueForRHS($p), + $array, + ))); } - /** - * @param string $stringValue - * - * @return string - */ public static function formatUpperCamelCase(string $stringValue): string { - if (strpos($stringValue, '_') === false) { + if (!str_contains($stringValue, '_')) { return ucfirst($stringValue); } return str_replace('_', '', ucwords($stringValue, '_')); } - /** - * @param string $stringValue - * - * @return string - */ public static function formatLowerCamelCase(string $stringValue): string { - return lcfirst(static::formatUpperCamelCase($stringValue)); + return lcfirst(self::formatUpperCamelCase($stringValue)); } } diff --git a/src/Variable.php b/src/Variable.php index b137ce3..6b24cc8 100644 --- a/src/Variable.php +++ b/src/Variable.php @@ -4,61 +4,31 @@ use GraphQL\Util\StringLiteralFormatter; -/** - * Class Variable - * - * @package GraphQL - */ -class Variable +final readonly class Variable implements \Stringable { - /** - * @var string - */ - protected $name; - - /** - * @var string - */ - protected $type; - - /** - * @var bool - */ - protected $required; - - /** - * @var null|string|int|float|bool - */ - protected $defaultValue; - - /** - * Variable constructor. - * - * @param string $name - * @param string $type - * @param bool $isRequired - * @param null $defaultValue - */ - public function __construct(string $name, string $type, bool $isRequired = false, $defaultValue = null) - { - $this->name = $name; - $this->type = $type; - $this->required = $isRequired; - $this->defaultValue = $defaultValue; + public function __construct( + public string $name, + public string $type, + public bool $nonNullable = false, + public mixed $defaultValue = null + ) { } - /** - * @return string - */ public function __toString(): string { - $varString = "\$$this->name: $this->type"; - if ($this->required) { - $varString .= '!'; - } elseif (!empty($this->defaultValue)) { - $varString .= '=' . StringLiteralFormatter::formatValueForRHS($this->defaultValue); + $varString = sprintf( + '$%s: %s%s', + $this->name, + $this->type, + $this->nonNullable ? '!' : '', + ); + + if (!isset($this->defaultValue)) { + return $varString; } - return $varString; + return sprintf('%s=%s', $varString, is_array($this->defaultValue) ? + StringLiteralFormatter::formatArrayForGQLQuery($this->defaultValue) : + StringLiteralFormatter::formatValueForRHS($this->defaultValue)); } -} \ No newline at end of file +} diff --git a/tests/Auth/AwsIamAuthTest.php b/tests/Auth/AwsIamAuthTest.php deleted file mode 100644 index 2719714..0000000 --- a/tests/Auth/AwsIamAuthTest.php +++ /dev/null @@ -1,49 +0,0 @@ -auth = new AwsIamAuth(); - } - - /** - * @covers \GraphQL\Auth\AwsIamAuth::run - * @covers \GraphQL\Exception\AwsRegionNotSetException::__construct - */ - public function testRunMissingRegion() - { - $this->expectException(AwsRegionNotSetException::class); - $request = new Request('POST', ''); - $this->auth->run($request, []); - } - - /** - * @covers \GraphQL\Auth\AwsIamAuth::run - * @covers \GraphQL\Auth\AwsIamAuth::getSignature - * @covers \GraphQL\Auth\AwsIamAuth::getCredentials - */ - public function testRunSuccess() - { - $request = $this->auth->run( - new Request('POST', ''), - ['aws_region' => 'us-east-1'] - ); - $headers = $request->getHeaders(); - $this->assertArrayHasKey('X-Amz-Date', $headers); - $this->assertArrayHasKey('X-Amz-Security-Token', $headers); - $this->assertArrayHasKey('Authorization', $headers); - } -} diff --git a/tests/ClientTest.php b/tests/ClientTest.php deleted file mode 100644 index a2d52e3..0000000 --- a/tests/ClientTest.php +++ /dev/null @@ -1,272 +0,0 @@ -mockHandler = new MockHandler(); - $handler = HandlerStack::create($this->mockHandler); - $this->client = new Client('', [], ['handler' => $handler]); - } - - /** - * @covers \GraphQL\Client::__construct - * @covers \GraphQL\Client::runRawQuery - * @covers \GraphQL\Util\GuzzleAdapter::__construct - * @covers \GraphQL\Util\GuzzleAdapter::sendRequest - */ - public function testConstructClient() - { - $mockHandler = new MockHandler(); - $handler = HandlerStack::create($mockHandler); - $container = []; - $history = Middleware::history($container); - $handler->push($history); - - $mockHandler->append(new Response(200)); - $mockHandler->append(new Response(200)); - $mockHandler->append(new Response(200)); - $mockHandler->append(new Response(200)); - $mockHandler->append(new Response(200)); - - $client = new Client('', [], ['handler' => $handler]); - $client->runRawQuery('query_string'); - - $client = new Client('', ['Authorization' => 'Basic xyz'], ['handler' => $handler]); - $client->runRawQuery('query_string'); - - $client = new Client('', [], ['handler' => $handler]); - $client->runRawQuery('query_string', false, ['name' => 'val']); - - $client = new Client('', ['Authorization' => 'Basic xyz'], ['handler' => $handler, 'headers' => [ 'Authorization' => 'Basic zyx', 'User-Agent' => 'test' ]]); - $client->runRawQuery('query_string'); - - /** @var Request $firstRequest */ - $firstRequest = $container[0]['request']; - $this->assertEquals('{"query":"query_string","variables":{}}', (string) $firstRequest->getBody()); - $this->assertSame('POST', $firstRequest->getMethod()); - - /** @var Request $thirdRequest */ - $thirdRequest = $container[1]['request']; - $this->assertNotEmpty($thirdRequest->getHeader('Authorization')); - $this->assertEquals( - ['Basic xyz'], - $thirdRequest->getHeader('Authorization') - ); - - /** @var Request $secondRequest */ - $secondRequest = $container[2]['request']; - $this->assertEquals('{"query":"query_string","variables":{"name":"val"}}', (string) $secondRequest->getBody()); - - /** @var Request $fourthRequest */ - $fourthRequest = $container[3]['request']; - $this->assertNotEmpty($fourthRequest->getHeader('Authorization')); - $this->assertNotEmpty($fourthRequest->getHeader('User-Agent')); - $this->assertEquals(['Basic zyx'], $fourthRequest->getHeader('Authorization')); - $this->assertEquals(['test'], $fourthRequest->getHeader('User-Agent')); - } - - /** - * @covers \GraphQL\Client::__construct - * @covers \GraphQL\Exception\MethodNotSupportedException - */ - public function testConstructClientWithGetRequestMethod() - { - $this->expectException(MethodNotSupportedException::class); - $client = new Client('', [], [], null, 'GET'); - } - - /** - * @covers \GraphQL\Client::runQuery - */ - public function testRunQueryBuilder() - { - $this->mockHandler->append(new Response(200, [], json_encode([ - 'data' => [ - 'someData' - ] - ]))); - - $response = $this->client->runQuery((new QueryBuilder('obj'))->selectField('field')); - $this->assertNotNull($response->getData()); - } - - /** - * @covers \GraphQL\Client::runQuery - */ - public function testRunInvalidQueryClass() - { - $this->expectException(TypeError::class); - $this->client->runQuery(new RawObject('obj')); - } - - /** - * @covers \GraphQL\Client::runRawQuery - */ - public function testValidQueryResponse() - { - $this->mockHandler->append(new Response(200, [], json_encode([ - 'data' => [ - 'someField' => [ - [ - 'data' => 'value', - ], [ - 'data' => 'value', - ] - ] - ] - ]))); - - $objectResults = $this->client->runRawQuery(''); - $this->assertIsObject($objectResults->getResults()); - } - - /** - * @covers \GraphQL\Client::runRawQuery - */ - public function testValidQueryResponseToArray() - { - $this->mockHandler->append(new Response(200, [], json_encode([ - 'data' => [ - 'someField' => [ - [ - 'data' => 'value', - ], [ - 'data' => 'value', - ] - ] - ] - ]))); - - $arrayResults = $this->client->runRawQuery('', true); - $this->assertIsArray($arrayResults->getResults()); - } - - /** - * @covers \GraphQL\Client::runRawQuery - */ - public function testInvalidQueryResponseWith200() - { - $this->mockHandler->append(new Response(200, [], json_encode([ - 'errors' => [ - [ - 'message' => 'some syntax error', - 'location' => [ - [ - 'line' => 1, - 'column' => 3, - ] - ], - ] - ] - ]))); - - $this->expectException(QueryError::class); - $this->client->runRawQuery(''); - } - - /** - * @covers \GraphQL\Client::runRawQuery - */ - public function testInvalidQueryResponseWith400() - { - $this->mockHandler->append(new ClientException('', new Request('post', ''), - new Response(400, [], json_encode([ - 'errors' => [ - [ - 'message' => 'some syntax error', - 'location' => [ - [ - 'line' => 1, - 'column' => 3, - ] - ], - ] - ] - ])))); - - $this->expectException(QueryError::class); - $this->client->runRawQuery(''); - } - - /** - * @covers \GraphQL\Client::runRawQuery - */ - public function testUnauthorizedResponse() - { - $this->mockHandler->append(new ClientException('', new Request('post', ''), - new Response(401, [], json_encode('Unauthorized')) - )); - - $this->expectException(ClientException::class); - $this->client->runRawQuery(''); - } - - /** - * @covers \GraphQL\Client::runRawQuery - */ - public function testNotFoundResponse() - { - $this->mockHandler->append(new ClientException('', new Request('post', ''), new Response(404, []))); - - $this->expectException(ClientException::class); - $this->client->runRawQuery(''); - } - - /** - * @covers \GraphQL\Client::runRawQuery - */ - public function testInternalServerErrorResponse() - { - $this->mockHandler->append(new ServerException('', new Request('post', ''), new Response(500, []))); - - $this->expectException(ServerException::class); - $this->client->runRawQuery(''); - } - - /** - * @covers \GraphQL\Client::runRawQuery - */ - public function testConnectTimeoutResponse() - { - $this->mockHandler->append(new ConnectException('Time Out', new Request('post', ''))); - $this->expectException(ConnectException::class); - $this->client->runRawQuery(''); - } -} diff --git a/tests/InlineFragmentTest.php b/tests/InlineFragmentTest.php deleted file mode 100644 index 43a67a6..0000000 --- a/tests/InlineFragmentTest.php +++ /dev/null @@ -1,113 +0,0 @@ -setSelectionSet( - [ - 'field1', - 'field2', - ] - ); - - $this->assertEquals( - '... on Test { -field1 -field2 -}', - (string) $fragment - ); - } - - /** - * @covers \GraphQL\InlineFragment::__construct - * @covers \GraphQL\InlineFragment::setSelectionSet - * @covers \GraphQL\InlineFragment::constructSelectionSet - * @covers \GraphQL\InlineFragment::__toString - */ - public function testConvertNestedFragmentToString() - { - $fragment = new InlineFragment('Test'); - $fragment->setSelectionSet( - [ - 'field1', - 'field2', - (new Query('sub_field')) - ->setArguments( - [ - 'first' => 5 - ] - ) - ->setSelectionSet( - [ - 'sub_field3', - (new InlineFragment('Nested')) - ->setSelectionSet( - [ - 'another_field' - ] - ), - ] - ) - ] - ); - - $this->assertEquals( - '... on Test { -field1 -field2 -sub_field(first: 5) { -sub_field3 -... on Nested { -another_field -} -} -}', - (string) $fragment - ); - } - - /** - * @covers \GraphQL\InlineFragment::__construct - * @covers \GraphQL\InlineFragment::setSelectionSet - * @covers \GraphQL\InlineFragment::getSelectionSet - * @covers \GraphQL\InlineFragment::constructSelectionSet - * @covers \GraphQL\InlineFragment::__toString - */ - public function testConvertQueryBuilderToString() - { - $queryBuilder = new QueryBuilder(); - - $fragment = new InlineFragment('Test', $queryBuilder); - $queryBuilder->selectField('field1'); - $queryBuilder->selectField('field2'); - - $this->assertEquals( - '... on Test { -field1 -field2 -}', - (string) $fragment - ); - } -} diff --git a/tests/MutationBuilderTest.php b/tests/MutationBuilderTest.php deleted file mode 100644 index d98e1fc..0000000 --- a/tests/MutationBuilderTest.php +++ /dev/null @@ -1,44 +0,0 @@ -mutationBuilder = new MutationBuilder('createObject'); - } - - /** - * @covers \GraphQL\QueryBuilder\MutationBuilder::__construct - * @covers \GraphQL\QueryBuilder\MutationBuilder::getQuery - * @covers \GraphQL\QueryBuilder\MutationBuilder::getMutation - */ - public function testConstruct() - { - $builder = new MutationBuilder('createObject'); - $builder->selectField('field_one'); - $this->assertInstanceOf(Mutation::class, $builder->getQuery()); - $this->assertInstanceOf(Mutation::class, $builder->getMutation()); - - $expectedString = 'mutation { -createObject { -field_one -} -}'; - $this->assertEquals($expectedString, (string) $builder->getQuery()); - $this->assertEquals($expectedString, (string) $builder->getMutation()); - } -} \ No newline at end of file diff --git a/tests/MutationTest.php b/tests/MutationTest.php deleted file mode 100644 index fb58ff1..0000000 --- a/tests/MutationTest.php +++ /dev/null @@ -1,113 +0,0 @@ -assertEquals( - 'mutation { -createObject -}', - (string) $mutation - ); - } - - /** - * - */ - public function testMutationWithOperationType() - { - $mutation = new Mutation(); - $mutation - ->setSelectionSet( - [ - (new Mutation('createObject')) - ->setArguments(['name' => 'TestObject']) - ] - ); - - $this->assertEquals( - 'mutation { -createObject(name: "TestObject") -}', - (string) $mutation - ); - } - - /** - * - */ - public function testMutationWithoutSelectedFields() - { - $mutation = (new Mutation('createObject')) - ->setArguments(['name' => 'TestObject', 'type' => 'TestType']); - $this->assertEquals( - 'mutation { -createObject(name: "TestObject" type: "TestType") -}', - (string) $mutation); - } - - /** - * - */ - public function testMutationWithFields() - { - $mutation = (new Mutation('createObject')) - ->setSelectionSet( - [ - 'fieldOne', - 'fieldTwo', - ] - ); - - $this->assertEquals( - 'mutation { -createObject { -fieldOne -fieldTwo -} -}', - (string) $mutation - ); - } - - /** - * - */ - public function testMutationWithArgumentsAndFields() - { - $mutation = (new Mutation('createObject')) - ->setSelectionSet( - [ - 'fieldOne', - 'fieldTwo', - ] - )->setArguments( - [ - 'argOne' => 1, - 'argTwo' => 'val' - ] - ); - - $this->assertEquals( - 'mutation { -createObject(argOne: 1 argTwo: "val") { -fieldOne -fieldTwo -} -}', - (string) $mutation - ); - } -} \ No newline at end of file diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php deleted file mode 100644 index 828c004..0000000 --- a/tests/QueryBuilderTest.php +++ /dev/null @@ -1,338 +0,0 @@ -queryBuilder = new QueryBuilder('Object'); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::__construct - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::__construct - */ - public function testConstruct() - { - $builder = new QueryBuilder('Object'); - $builder->selectField('field_one'); - $this->assertEquals( - 'query { -Object { -field_one -} -}', - (string) $builder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::__construct - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::__construct - */ - public function testConstructWithAlias() - { - $builder = new QueryBuilder('Object', 'ObjectAlias'); - $builder->selectField('field_one'); - $this->assertEquals( - 'query { -ObjectAlias: Object { -field_one -} -}', - (string) $builder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::__construct - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::__construct - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::setAlias - */ - public function testSetAlias() - { - $builder = (new QueryBuilder('Object')) - ->setAlias('ObjectAlias');; - $builder->selectField('field_one'); - $this->assertEquals( - 'query { -ObjectAlias: Object { -field_one -} -}', - (string) $builder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::setVariable - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::setVariable - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::getQuery - */ - public function testAddVariables() - { - $this->queryBuilder - ->setVariable('var', 'String') - ->setVariable('intVar', 'Int', false, 4) - ->selectField('fieldOne'); - $this->assertEquals( - 'query($var: String $intVar: Int=4) { -Object { -fieldOne -} -}', - (string) $this->queryBuilder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::getQuery - */ - public function testAddVariablesToSecondLevelQueryDoesNothing() - { - $this->queryBuilder - ->setVariable('var', 'String') - ->selectField('fieldOne') - ->selectField( - (new QueryBuilder('Nested')) - ->setVariable('var', 'String') - ->selectField('fieldTwo') - ); - $this->assertEquals( - 'query($var: String) { -Object { -fieldOne -Nested { -fieldTwo -} -} -}', - (string) $this->queryBuilder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery - * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::selectField - */ - public function testSelectScalarFields() - { - $this->queryBuilder->selectField('field_one'); - $this->queryBuilder->selectField('field_two'); - $this->assertEquals( - 'query { -Object { -field_one -field_two -} -}', - (string) $this->queryBuilder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery - * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField - */ - public function testSelectNestedQuery() - { - $this->queryBuilder->selectField( - (new Query('Nested')) - ->setSelectionSet(['some_field']) - ); - $this->assertEquals( - 'query { -Object { -Nested { -some_field -} -} -}', - (string) $this->queryBuilder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery - * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField - */ - public function testSelectNestedQueryBuilder() - { - $this->queryBuilder->selectField( - (new QueryBuilder('Nested')) - ->selectField('some_field') - ); - $this->assertEquals( - 'query { -Object { -Nested { -some_field -} -} -}', - (string) $this->queryBuilder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::__construct - * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery - * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField - */ - public function testQueryBuilderWithoutFieldName() - { - $builder = (new QueryBuilder()) - ->selectField( - (new QueryBuilder('Object')) - ->selectField('one') - ) - ->selectField( - (new QueryBuilder('Another')) - ->selectField('two') - ); - - $this->assertEquals('query { -Object { -one -} -Another { -two -} -}', - (string) $builder->getQuery()); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery - * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::selectField - */ - public function testSelectInlineFragment() - { - $this->queryBuilder->selectField( - (new InlineFragment('Type')) - ->setSelectionSet(['field']) - ); - $this->assertEquals( - 'query { -Object { -... on Type { -field -} -} -}', - (string) $this->queryBuilder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery - * @covers \GraphQL\QueryBuilder\QueryBuilder::setArgument - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::setArgument - */ - public function testSelectArguments() - { - $this->queryBuilder->selectField('field'); - $this->queryBuilder->setArgument('str_arg', 'value'); - $this->assertEquals( - 'query { -Object(str_arg: "value") { -field -} -}', - (string) $this->queryBuilder->getQuery() - ); - - $this->queryBuilder->setArgument('bool_arg', true); - $this->assertEquals( - 'query { -Object(str_arg: "value" bool_arg: true) { -field -} -}', - (string) $this->queryBuilder->getQuery() - ); - - $this->queryBuilder->setArgument('int_arg', 10); - $this->assertEquals( - 'query { -Object(str_arg: "value" bool_arg: true int_arg: 10) { -field -} -}', - (string) $this->queryBuilder->getQuery() - ); - - $this->queryBuilder->setArgument('array_arg', ['one', 'two', 'three']); - $this->assertEquals( - 'query { -Object(str_arg: "value" bool_arg: true int_arg: 10 array_arg: ["one", "two", "three"]) { -field -} -}', - (string) $this->queryBuilder->getQuery() - ); - - $this->queryBuilder->setArgument('input_object_arg', new RawObject('{field_not: "x"}')); - $this->assertEquals( - 'query { -Object(str_arg: "value" bool_arg: true int_arg: 10 array_arg: ["one", "two", "three"] input_object_arg: {field_not: "x"}) { -field -} -}', - (string) $this->queryBuilder->getQuery() - ); - } - - /** - * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery - * @covers \GraphQL\QueryBuilder\QueryBuilder::setArgument - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::setArgument - * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField - * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::selectField - */ - public function testSetTwoLevelArguments() - { - $this->queryBuilder->selectField( - (new QueryBuilder('Nested')) - ->selectField('some_field') - ->selectField('another_field') - ->setArgument('nested_arg', [1, 2, 3]) - ) - ->setArgument('outer_arg', 'outer val'); - $this->assertEquals( - 'query { -Object(outer_arg: "outer val") { -Nested(nested_arg: [1, 2, 3]) { -some_field -another_field -} -} -}', - (string) $this->queryBuilder->getQuery() - ); - } -} \ No newline at end of file diff --git a/tests/QueryErrorTest.php b/tests/QueryErrorTest.php deleted file mode 100644 index 414e58b..0000000 --- a/tests/QueryErrorTest.php +++ /dev/null @@ -1,145 +0,0 @@ - [ - [ - 'message' => $exceptionMessage, - 'location' => [ - [ - 'line' => 1, - 'column' => 3, - ] - ], - ] - ] - ]; - - $queryError = new QueryError($errorData); - $this->assertEquals($exceptionMessage, $queryError->getMessage()); - $this->assertEquals( - [ - 'message' => 'some syntax error', - 'location' => [ - [ - 'line' => 1, - 'column' => 3, - ] - ] - ], - $queryError->getErrorDetails() - ); - $this->assertEquals([], $queryError->getData()); - } - - /** - * @covers \GraphQL\Exception\QueryError::__construct - * @covers \GraphQL\Exception\QueryError::getErrorDetails - */ - public function testConstructQueryErrorWhenResponseHasData() - { - $errorData = [ - 'errors' => [ - [ - 'message' => 'first error message', - 'location' => [ - [ - 'line' => 1, - 'column' => 3, - ] - ], - ], - [ - 'message' => 'second error message', - 'location' => [ - [ - 'line' => 2, - 'column' => 4, - ] - ], - ], - ], - 'data' => [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ] - ]; - - $queryError = new QueryError($errorData); - $this->assertEquals('first error message', $queryError->getMessage()); - $this->assertEquals( - [ - 'message' => 'first error message', - 'location' => [ - [ - 'line' => 1, - 'column' => 3, - ] - ] - ], - $queryError->getErrorDetails() - ); - - $this->assertEquals( - [ - [ - 'message' => 'first error message', - 'location' => [ - [ - 'line' => 1, - 'column' => 3, - ] - ] - ], - [ - 'message' => 'second error message', - 'location' => [ - [ - 'line' => 2, - 'column' => 4, - ] - ] - ] - ], - $queryError->getErrors() - ); - - $this->assertEquals( - [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ], - $queryError->getData() - ); - } -} diff --git a/tests/QueryTest.php b/tests/QueryTest.php deleted file mode 100644 index e3bdb87..0000000 --- a/tests/QueryTest.php +++ /dev/null @@ -1,825 +0,0 @@ -assertIsString((string) $query, 'Failed to convert query to string'); - - return $query; - } - - /** - * @depends testConvertsToString - * - * @covers \GraphQL\Query::constructArguments - * - * @param Query $query - * - * @return Query - */ - public function testEmptyArguments(Query $query) - { - $this->assertStringNotContainsString("()", (string) $query, 'Query has empty arguments list'); - - return $query; - } - - /** - * @covers \GraphQL\Query::__toString - * @covers FieldTrait::constructSelectionSet - */ - public function testQueryWithoutFieldName() - { - $query = new Query(); - - $this->assertEquals( - "query", - (string) $query - ); - - $query->setSelectionSet( - [ - (new Query('Object')) - ->setSelectionSet(['one']), - (new Query('Another')) - ->setSelectionSet(['two']) - ] - ); - - $this->assertEquals( - "query { -Object { -one -} -Another { -two -} -}", - (string) $query - ); - } - - /** - * @depends testConvertsToString - * - * @covers \GraphQL\Query::generateSignature - * @covers \GraphQL\Query::__toString - */ - public function testQueryWithAlias() - { - $query = (new Query('Object', 'ObjectAlias')) - ->setSelectionSet([ - 'one' - ]); - - $this->assertEquals( - "query { -ObjectAlias: Object { -one -} -}", - (string) $query - ); - } - - /** - * @depends testConvertsToString - * - * @covers \GraphQL\Query::setAlias - * @covers \GraphQL\Query::generateSignature - * @covers \GraphQL\Query::__toString - */ - public function testQueryWithSetAlias() - { - $query = (new Query('Object')) - ->setAlias('ObjectAlias') - ->setSelectionSet([ - 'one' - ]); - - $this->assertEquals( - "query { -ObjectAlias: Object { -one -} -}", - (string) $query - ); - } - - /** - * @depends testConvertsToString - * - * @covers \GraphQL\Query::generateSignature - * @covers \GraphQL\Query::setOperationName - * @covers \GraphQL\Query::__toString - */ - public function testQueryWithOperationName() - { - $query = (new Query('Object')) - ->setOperationName('retrieveObject'); - $this->assertEquals( -'query retrieveObject { -Object -}', - (string) $query - ); - } - - /** - * @depends testQueryWithoutFieldName - * @depends testQueryWithOperationName - * - * @covers \GraphQL\Query::generateSignature - * @covers \GraphQL\Query::setOperationName - * @covers \GraphQL\Query::__toString - */ - public function testQueryWithOperationNameAndOperationType() - { - $query = (new Query()) - ->setOperationName('retrieveObject') - ->setSelectionSet([new Query('Object')]); - $this->assertEquals( - 'query retrieveObject { -Object -}', - (string) $query - ); - } - - /** - * @depends testQueryWithOperationName - * - * @covers \GraphQL\Query::generateSignature - * @covers \GraphQL\Query::setOperationName - * @covers \GraphQL\Query::__toString - */ - public function testQueryWithOperationNameInSecondLevelDoesNothing() - { - $query = (new Query('Object')) - ->setOperationName('retrieveObject') - ->setSelectionSet([(new Query('Nested'))->setOperationName('opName')]); - $this->assertEquals( - 'query retrieveObject { -Object { -Nested -} -}', - (string) $query - ); - } - - /** - * @covers \GraphQL\Query::setVariables - * @covers \GraphQL\Exception\InvalidVariableException - */ - public function testSetVariablesWithoutVariableObjects() - { - $this->expectException(InvalidVariableException::class); - (new Query('Object'))->setVariables(['one', 'two']); - } - - /** - * @depends testConvertsToString - * - * @covers \GraphQL\Query::setVariables - * @covers \GraphQL\Query::generateSignature - * @covers \GraphQL\Query::constructVariables - * @covers \GraphQL\Query::__toString - */ - public function testQueryWithOneVariable() - { - $query = (new Query('Object')) - ->setVariables([new Variable('var', 'String')]); - $this->assertEquals( - 'query($var: String) { -Object -}', - (string) $query - ); - } - - /** - * @depends testQueryWithOneVariable - * - * @covers \GraphQL\Query::setVariables - * @covers \GraphQL\Query::generateSignature - * @covers \GraphQL\Query::constructVariables - * @covers \GraphQL\Query::__toString - */ - public function testQueryWithMultipleVariables() - { - $query = (new Query('Object')) - ->setVariables([new Variable('var', 'String'), new Variable('intVar', 'Int', false, 4)]); - $this->assertEquals( - 'query($var: String $intVar: Int=4) { -Object -}', - (string) $query - ); - } - - /** - * @depends testConvertsToString - * - * @covers \GraphQL\Query::__toString - */ - public function testQueryWithVariablesInSecondLevelDoesNothing() - { - $query = (new Query('Object')) - ->setVariables([new Variable('var', 'String'), new Variable('intVar', 'Int', false, 4)]) - ->setSelectionSet([(new Query('Nested'))]) - ->setVariables([new Variable('var', 'String'), new Variable('intVar', 'Int', false, 4)]); - $this->assertEquals( - 'query($var: String $intVar: Int=4) { -Object { -Nested -} -}', - (string) $query - ); - } - - /** - * @depends testQueryWithMultipleVariables - * @depends testQueryWithOperationName - * - * @covers \GraphQL\Query::generateSignature - * @covers \GraphQL\Query::__toString - */ - public function testQueryWithOperationNameAndVariables() - { - $query = (new Query('Object')) - ->setOperationName('retrieveObject') - ->setVariables([new Variable('var', 'String')]); - $this->assertEquals( - 'query retrieveObject($var: String) { -Object -}', - (string) $query - ); - } - - /** - * @depends clone testEmptyArguments - * - * @covers \GraphQL\Query::__toString - * - * @param Query $query - * - * @return Query - */ - public function testEmptyQuery(Query $query) - { - $this->assertEquals( - "query { -Object -}", - (string) $query, - 'Incorrect empty query string' - ); - - return $query; - } - - /** - * @depends clone testEmptyArguments - * - * @covers \GraphQL\Exception\ArgumentException - * @covers \GraphQL\Query::setArguments - * - * @param Query $query - * - * @return Query - */ - public function testArgumentWithoutName(Query $query) - { - $this->expectException(ArgumentException::class); - $query->setArguments(['val']); - - return $query; - } - - /** - * @depends clone testEmptyArguments - * - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - * - * @param Query $query - * - * @return Query - */ - public function testStringArgumentValue(Query $query) - { - $query->setArguments(['arg1' => 'value']); - $this->assertEquals( - "query { -Object(arg1: \"value\") -}", - (string) $query, - 'Query has improperly formatted parameter list' - ); - - return $query; - } - - /** - * @depends clone testEmptyArguments - * - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - * - * @param Query $query - * - * @return Query - */ - public function testIntegerArgumentValue(Query $query) - { - $query->setArguments(['arg1' => 23]); - $this->assertEquals( - "query { -Object(arg1: 23) -}", - (string) $query - ); - - return $query; - } - - /** - * @depends clone testEmptyArguments - - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - * - * @param Query $query - * - * @return Query - */ - public function testBooleanArgumentValue(Query $query) - { - $query->setArguments(['arg1' => true]); - $this->assertEquals( - "query { -Object(arg1: true) -}", - (string) $query - ); - - return $query; - } - - /** - * @depends clone testEmptyArguments - * - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - * - * @param Query $query - * - * @return Query - */ - public function testNullArgumentValue(Query $query) - { - $query->setArguments(['arg1' => null]); - $this->assertEquals( - "query { -Object(arg1: null) -}" - , (string) $query - ); - - return $query; - } - - /** - * @depends clone testEmptyArguments - * - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - * - * @param Query $query - * - * @return Query - */ - public function testArrayIntegerArgumentValue(Query $query) - { - $query->setArguments(['arg1' => [1, 2, 3]]); - $this->assertEquals( - "query { -Object(arg1: [1, 2, 3]) -}", - (string) $query - ); - - return $query; - } - - /** - * @depends clone testEmptyArguments - * - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - * @covers \GraphQL\RawObject::__toString - * - * @param Query $query - * - * @return Query - */ - public function testJsonObjectArgumentValue(Query $query) - { - $query->setArguments(['obj' => new RawObject('{json_string_array: ["json value"]}')]); - $this->assertEquals( - "query { -Object(obj: {json_string_array: [\"json value\"]}) -}" - , (string) $query - ); - - return $query; - } - - /** - * @depends clone testEmptyArguments - * - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - * - * @param Query $query - * - * @return Query - */ - public function testArrayStringArgumentValue(Query $query) - { - $query->setArguments(['arg1' => ['one', 'two', 'three']]); - $this->assertEquals( - "query { -Object(arg1: [\"one\", \"two\", \"three\"]) -}", - (string) $query - ); - - return $query; - } - - /** - * @depends clone testStringArgumentValue - * @depends testIntegerArgumentValue - * @depends testBooleanArgumentValue - * - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - * - * @param Query $query - * - * @return Query - */ - public function testTwoOrMoreArguments(Query $query) - { - $query->setArguments(['arg1' => 'val1', 'arg2' => 2, 'arg3' => true]); - $this->assertEquals( - "query { -Object(arg1: \"val1\" arg2: 2 arg3: true) -}", - (string) $query, - 'Query has improperly formatted parameter list' - ); - - return $query; - } - - /** - * @depends testStringArgumentValue - * - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - * @covers \GraphQL\Query::setArguments - * @covers \GraphQL\Query::constructArguments - */ - public function testStringWrappingWorks() - { - // TODO: Remove this in v1.0 release - $queryWrapped = new Query('Object'); - $queryWrapped->setArguments(['arg1' => '"val"']); - - $queryNotWrapped = new Query('Object'); - $queryNotWrapped->setArguments(['arg1' => 'val']); - - $this->assertEquals((string) $queryWrapped, (string) $queryWrapped); - } - - /** - * @depends clone testEmptyQuery - * - * @covers \GraphQL\Query::setSelectionSet - * @covers \GraphQL\FieldTrait::constructSelectionSet - * - * @param Query $query - * - * @return Query - */ - public function testSingleSelectionField(Query $query) - { - $query->setSelectionSet(['field1']); - $this->assertEquals( - "query { -Object { -field1 -} -}", - (string) $query, - 'Query has improperly formatted selection set' - ); - - return $query; - } - - /** - * @depends clone testEmptyQuery - * - * @covers \GraphQL\Query::setSelectionSet - * @covers \GraphQL\FieldTrait::constructSelectionSet - * - * @param Query $query - * - * @return Query - */ - public function testTwoOrMoreSelectionFields(Query $query) - { - $query->setSelectionSet(['field1', 'field2']); - $this->assertEquals( - "query { -Object { -field1 -field2 -} -}", - (string) $query, - 'Query has improperly formatted selection set' - ); - - return $query; - } - - /** - * @depends clone testEmptyQuery - * - * @covers \GraphQL\Exception\InvalidSelectionException - * @covers \GraphQL\Query::setSelectionSet - * - * @param Query $query - * - * @return Query - */ - public function testSelectNonStringValues(Query $query) - { - $this->expectException(InvalidSelectionException::class); - $query->setSelectionSet([true, 1.5]); - - return $query; - } - - /** - * @depends clone testEmptyQuery - * - * @coversNothing - * - * @param Query $query - * - * @return Query - */ - public function testOneLevelQuery(Query $query) - { - $query->setSelectionSet(['field1', 'field2']); - $query->setArguments(['arg1' => 'val1', 'arg2' => 'val2']); - $this->assertEquals( - "query { -Object(arg1: \"val1\" arg2: \"val2\") { -field1 -field2 -} -}", - (string) $query, - 'One level query not formatted correctly' - ); - - return $query; - } - - /** - * @depends clone testOneLevelQuery - * - * @covers \GraphQL\FieldTrait::constructSelectionSet - * @covers \GraphQL\Query::setAsNested - * - * @param Query $query - * - * @return Query - */ - public function testTwoLevelQueryDoesNotContainWordQuery(Query $query) - { - $query->setSelectionSet( - [ - 'field1', - 'field2', - (new Query('Object2')) - ->setSelectionSet(['field3']) - ] - ); - $this->assertStringNotContainsString( - "\nquery {", - (string) $query, - 'Nested query contains "query" word' - ); - - return $query; - } - - /** - * @depends clone testTwoLevelQueryDoesNotContainWordQuery - * - * @covers \GraphQL\Query::setAsNested - * - * @param Query $query - * - * @return Query - */ - public function testTwoLevelQuery(Query $query) - { - $query->setSelectionSet( - [ - 'field1', - 'field2', - (new Query('Object2')) - ->setSelectionSet(['field3']) - ] - ); - $this->assertEquals( - "query { -Object(arg1: \"val1\" arg2: \"val2\") { -field1 -field2 -Object2 { -field3 -} -} -}", - (string) $query, - 'Two level query not formatted correctly' - ); - - return $query; - } - - /** - * @depends clone testTwoLevelQueryDoesNotContainWordQuery - * - * @param Query $query - * - * @return Query - */ - public function testTwoLevelQueryWithInlineFragment(Query $query) - { - $query->setSelectionSet( - [ - 'field1', - (new InlineFragment('Object')) - ->setSelectionSet( - [ - 'fragment_field1', - 'fragment_field2', - ] - ), - ] - ); - $this->assertEquals( - 'query { -Object(arg1: "val1" arg2: "val2") { -field1 -... on Object { -fragment_field1 -fragment_field2 -} -} -}', - (string) $query - ); - - return $query; - } - - /** - * @covers \GraphQL\Query::getArguments - */ - public function testGettingArguments() - { - $gql = (new Query('things')) - ->setArguments( - [ - 'someClientId' => 'someValueBasedOnCodebase' - ] - ); - $cursor_id = 'someCursor'; - $new_args = $gql->getArguments(); - $gql->setArguments( - array_merge( - $new_args, - [ - 'after' => $cursor_id - ] - ) - ); - self::assertEquals( - 'query { -things(someClientId: "someValueBasedOnCodebase" after: "someCursor") -}', - (string) $gql - ); - } - - /** - * @covers \GraphQL\Query::getFieldName - */ - public function testGettingNameAndAltering() - { - $gql = (new Query('things')) - ->setSelectionSet( - [ - 'id', - 'name', - (new Query('subThings')) - ->setArguments( - [ - 'filter' => 'providerId123', - ] - ) - ->setSelectionSet( - [ - 'id', - 'name' - ] - ) - ]); - $sets = $gql->getSelectionSet(); - foreach ($sets as $set) { - if (($set instanceof Query) === false) { - continue; - } - $name = $set->getFieldName(); - if ($name !== 'subThings') { - continue; - } - $set->setArguments( - [ - 'filter' => 'providerId456' - ] - ); - $set->setSelectionSet( - array_merge( - $set->getSelectionSet(), - [ - 'someField', - 'someOtherField' - ] - ) - ); - } - self::assertEquals( - 'query { -things { -id -name -subThings(filter: "providerId456") { -id -name -someField -someOtherField -} -} -}', - (string) $gql); - } -} diff --git a/tests/RawObjectTest.php b/tests/RawObjectTest.php deleted file mode 100644 index 40c75cf..0000000 --- a/tests/RawObjectTest.php +++ /dev/null @@ -1,24 +0,0 @@ -assertEquals('[1, 4, "y", 6.7]', (string) $json); - - // Test convert graphql object - $json = new RawObject('{arr: [1, "z"], str: "val", int: 1, obj: {x: "y"}}'); - $this->assertEquals('{arr: [1, "z"], str: "val", int: 1, obj: {x: "y"}}', (string) $json); - } -} \ No newline at end of file diff --git a/tests/ResultsTest.php b/tests/ResultsTest.php deleted file mode 100644 index 65a48cb..0000000 --- a/tests/ResultsTest.php +++ /dev/null @@ -1,275 +0,0 @@ -mockHandler = new MockHandler(); - $this->client = new Client(['handler' => $this->mockHandler]); - } - - /** - * @covers \GraphQL\Results::__construct - * @covers \GraphQL\Results::getResponseObject - * @covers \GraphQL\Results::getResponseBody - * @covers \GraphQL\Results::getResults - * @covers \GraphQL\Results::getData - */ - public function testGetSuccessResponseAsObject() - { - $body = json_encode([ - 'data' => [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ] - ]); - $response = new Response(200, [], $body); - $this->mockHandler->append($response); - - $response = $this->client->post('', []); - $results = new Results($response); - - $this->assertEquals($response, $results->getResponseObject()); - $this->assertEquals($body, $results->getResponseBody()); - - $object = new stdClass(); - $object->data = new stdClass(); - $object->data->someField = []; - $object->data->someField[] = new stdClass(); - $object->data->someField[] = new stdClass(); - $object->data->someField[0]->data = 'value'; - $object->data->someField[1]->data = 'value'; - $this->assertEquals( - $object, - $results->getResults() - ); - $this->assertEquals( - $object->data, - $results->getData() - ); - } - - /** - * @covers \GraphQL\Results::__construct - * @covers \GraphQL\Results::getResponseObject - * @covers \GraphQL\Results::getResponseBody - * @covers \GraphQL\Results::getResults - * @covers \GraphQL\Results::getData - */ - public function testGetSuccessResponseAsArray() - { - $body = json_encode([ - 'data' => [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ] - ]); - $originalResponse = new Response(200, [], $body); - $this->mockHandler->append($originalResponse); - - $response = $this->client->post('', []); - $results = new Results($response, true); - - $this->assertEquals($originalResponse, $results->getResponseObject()); - $this->assertEquals($body, $results->getResponseBody()); - $this->assertEquals( - [ - 'data' => [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ] - ], - $results->getResults() - ); - $this->assertEquals( - [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ], - $results->getData() - ); - } - - /** - * @covers \GraphQL\Results::__construct - */ - public function testGetQueryInvalidSyntaxError() - { - $body = json_encode([ - 'errors' => [ - [ - 'message' => 'some syntax error', - 'location' => [ - [ - 'line' => 1, - 'column' => 3, - ] - ], - ] - ] - ]); - $originalResponse = new Response(200, [], $body); - $this->mockHandler->append($originalResponse); - - $response = $this->client->post('', []); - $this->expectException(QueryError::class); - new Results($response); - } - - /** - * @covers \GraphQL\Results::__construct - * @covers \GraphQL\Results::reformatResults - * @covers \GraphQL\Results::getResponseObject - * @covers \GraphQL\Results::getResponseBody - * @covers \GraphQL\Results::getResults - * @covers \GraphQL\Results::getData - */ - public function testReformatResultsFromObjectToArray() - { - $body = json_encode([ - 'data' => [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ] - ]); - $originalResponse = new Response(200, [], $body); - $this->mockHandler->append($originalResponse); - - $response = $this->client->post('', []); - $results = new Results($response); - $results->reformatResults(true); - - $this->assertEquals( - [ - 'data' => [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ] - ], - $results->getResults() - ); - $this->assertEquals( - [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ], - $results->getData() - ); - } - - /** - * @covers \GraphQL\Results::__construct - * @covers \GraphQL\Results::reformatResults - * @covers \GraphQL\Results::getResponseObject - * @covers \GraphQL\Results::getResponseBody - * @covers \GraphQL\Results::getResults - * @covers \GraphQL\Results::getData - */ - public function testReformatResultsFromArrayToObject() - { - $body = json_encode([ - 'data' => [ - 'someField' => [ - [ - 'data' => 'value', - ], - [ - 'data' => 'value', - ] - ] - ] - ]); - $originalResponse = new Response(200, [], $body); - $this->mockHandler->append($originalResponse); - - $response = $this->client->post('', []); - $results = new Results($response, true); - $results->reformatResults(false); - - $object = new stdClass(); - $object->data = new stdClass(); - $object->data->someField = []; - $object->data->someField[] = new stdClass(); - $object->data->someField[] = new stdClass(); - $object->data->someField[0]->data = 'value'; - $object->data->someField[1]->data = 'value'; - $this->assertEquals( - $object, - $results->getResults() - ); - $this->assertEquals( - $object->data, - $results->getData() - ); - } -} \ No newline at end of file diff --git a/tests/StringLiteralFormatterTest.php b/tests/StringLiteralFormatterTest.php deleted file mode 100644 index 1888ec6..0000000 --- a/tests/StringLiteralFormatterTest.php +++ /dev/null @@ -1,127 +0,0 @@ -assertEquals('null', $nullString); - - // String tests - $emptyString = StringLiteralFormatter::formatValueForRHS(''); - $this->assertEquals('""', $emptyString); - - $formattedString = StringLiteralFormatter::formatValueForRHS('someString'); - $this->assertEquals('"someString"', $formattedString); - - $formattedString = StringLiteralFormatter::formatValueForRHS('"quotedString"'); - $this->assertEquals('"\"quotedString\""', $formattedString); - - $formattedString = StringLiteralFormatter::formatValueForRHS("\"quotedString\""); - $this->assertEquals('"\"quotedString\""', $formattedString); - - $formattedString = StringLiteralFormatter::formatValueForRHS(''); - $this->assertEquals('""', $formattedString); - - $formattedString = StringLiteralFormatter::formatValueForRHS('\'singleQuotes\''); - $this->assertEquals('"\'singleQuotes\'"', $formattedString); - - $formattedString = StringLiteralFormatter::formatValueForRHS("with \n newlines"); - $this->assertEquals("\"\"\"with \n newlines\"\"\"", $formattedString); - - $formattedString = StringLiteralFormatter::formatValueForRHS('$var'); - $this->assertEquals('$var', $formattedString); - - $formattedString = StringLiteralFormatter::formatValueForRHS('$400'); - $this->assertEquals('"$400"', $formattedString); - - // Integer tests - $integerString = StringLiteralFormatter::formatValueForRHS(25); - $this->assertEquals('25', $integerString); - - // Float tests - $floatString = StringLiteralFormatter::formatValueForRHS(123.123); - $this->assertEquals('123.123', $floatString); - - // Bool tests - $stringTrue = StringLiteralFormatter::formatValueForRHS(true); - $this->assertEquals('true', $stringTrue); - - $stringFalse = StringLiteralFormatter::formatValueForRHS(false); - $this->assertEquals('false', $stringFalse); - } - - /** - * @covers \GraphQL\Util\StringLiteralFormatter::formatArrayForGQLQuery - */ - public function testFormatArrayForGQLQuery() - { - $emptyArray = []; - $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($emptyArray); - $this->assertEquals('[]', $stringArray); - - $oneValueArray = [1]; - $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($oneValueArray); - $this->assertEquals('[1]', $stringArray); - - $twoValueArray = [1, 2]; - $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($twoValueArray); - $this->assertEquals('[1, 2]', $stringArray); - - $stringArray = ['one', 'two']; - $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($stringArray); - $this->assertEquals('["one", "two"]', $stringArray); - - $booleanArray = [true, false]; - $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($booleanArray); - $this->assertEquals('[true, false]', $stringArray); - - $floatArray = [1.1, 2.2]; - $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($floatArray); - $this->assertEquals('[1.1, 2.2]', $stringArray); - } - - /** - * @covers \GraphQL\Util\StringLiteralFormatter::formatUpperCamelCase - */ - public function testFormatUpperCamelCase() - { - $snakeCase = 'some_snake_case'; - $camelCase = StringLiteralFormatter::formatUpperCamelCase($snakeCase); - $this->assertEquals('SomeSnakeCase', $camelCase); - - $nonSnakeCase = 'somenonSnakeCase'; - $camelCase = StringLiteralFormatter::formatUpperCamelCase($nonSnakeCase); - $this->assertEquals('SomenonSnakeCase', $camelCase); - } - - /** - * @covers \GraphQL\Util\StringLiteralFormatter::formatLowerCamelCase - */ - public function testFormatLowerCamelCase() - { - $snakeCase = 'some_snake_case'; - $camelCase = StringLiteralFormatter::formatLowerCamelCase($snakeCase); - $this->assertEquals('someSnakeCase', $camelCase); - - $nonSnakeCase = 'somenonSnakeCase'; - $camelCase = StringLiteralFormatter::formatLowerCamelCase($nonSnakeCase); - $this->assertEquals('somenonSnakeCase', $camelCase); - } - - -} diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php new file mode 100644 index 0000000..4f21370 --- /dev/null +++ b/tests/Unit/ClientTest.php @@ -0,0 +1,344 @@ +append($response); + $handlerStack = HandlerStack::create($handler); + $client = new Client('', [], ['handler' => $handlerStack]); + + $this->expectException($expectedExceptionClass); + + $client->runRawQuery(''); + } + + public static function provideUnsuccessfulResponses(): \Generator + { + yield '200 with syntax error' => [ + QueryError::class, + new Response(200, [], json_encode([ + 'errors' => [ + [ + 'message' => 'some syntax error', + 'location' => [ + [ + 'line' => 1, + 'column' => 3, + ], + ], + ], + ], + ])), + ]; + + yield 'ClientException: 400 with syntax error' => [ + QueryError::class, + new ClientException( + '', + new Request('post', ''), + new Response(400, [], json_encode([ + 'errors' => [ + [ + 'message' => 'some syntax error', + 'location' => [ + [ + 'line' => 1, + 'column' => 3, + ], + ], + ], + ], + ])), + ), + ]; + + yield 'ClientException: 401 Unauthorized' => [ + ClientException::class, + new ClientException( + '', + new Request('post', ''), + new Response(401, [], '"Unauthorized"') + ), + ]; + + yield 'ClientException: 404 Not Found' => [ + ClientException::class, + new ClientException( + '', + new Request('post', ''), + new Response(404, [], '"Not Found"') + ), + ]; + + yield 'ServerException: 500 Server Error' => [ + ServerException::class, + new ServerException( + '', + new Request('post', ''), + new Response(500, [], '"Server Error"') + ), + ]; + + yield 'ConnectException: Time Out' => [ + ConnectException::class, + new ConnectException('Time Out', new Request('post', '')), + ]; + } + + + #[Test] + public function itOnlySupportsPostRequests(): void + { + self::expectException(MethodNotSupportedException::class); + + new Client('', [], [], null, 'GET'); + } + + #[Test] + public function itReturnsResultsContainingResponse(): void + { + $expected = new Response(200, [], json_encode([ + 'data' => ['firstField' => [['data' => 'value']]], + ])); + + $handler = new MockHandler(); + $handler->append($expected); + $handlerStack = HandlerStack::create($handler); + + $sut = new Client('', [], ['handler' => $handlerStack]); + + $actual = $sut->runRawQuery('')->getResponseObject(); + + self::assertEquals($expected, $actual); + } + + /** + * @param array $variables + * @param array> $headers + */ + #[Test] + #[DataProvider('provideQueries')] + public function itSendsQueries( + string $expectedQueryString, + Query|QueryBuilderInterface $query, + string $method, + array $variables, + array $headers, + ): void { + $handler = new MockHandler(); + $handler->append(new Response(200)); + $handlerStack = HandlerStack::create($handler); + + $sut = new Client( + '', + $headers, + ['handler' => $handlerStack], + null, + $method, + ); + + $sut->runQuery($query, false, $variables); + + self::assertSame( + $expectedQueryString, + (string) $handler->getLastRequest()->getBody(), + ); + + self::assertSame( + $method, + $handler->getLastRequest()->getMethod(), + ); + + foreach ($headers as $header => $value) { + self::assertSame( + is_string($value) ? [$value] : $value, + $handler->getLastRequest()->getHeader($header), + ); + } + } + + /** + * @param array $variables + * @param array> $headers + */ + #[Test] + #[DataProvider('provideRawQueries')] + public function itSendsRawQueries( + string $expectedQueryString, + string $rawQueryString, + string $method, + array $variables, + array $headers, + ): void { + $handler = new MockHandler(); + $handler->append(new Response(200)); + $handlerStack = HandlerStack::create($handler); + + $sut = new Client( + '', + $headers, + ['handler' => $handlerStack], + null, + $method, + ); + + $sut->runRawQuery($rawQueryString, false, $variables); + + self::assertSame( + $expectedQueryString, + (string) $handler->getLastRequest()->getBody(), + ); + + self::assertSame( + $method, + $handler->getLastRequest()->getMethod(), + ); + + foreach ($headers as $header => $value) { + self::assertSame( + is_string($value) ? [$value] : $value, + $handler->getLastRequest()->getHeader($header), + ); + } + } + + + /** @return \Generator, + * 4: array>, + * }> + */ + public static function provideQueries(): \Generator + { + yield 'minimal query' => [ + '{"query":"query","variables":{}}', + new Query(), + 'POST', + [], + [], + ]; + + yield 'minimal query builder' => [ + '{"query":"query","variables":{}}', + new QueryBuilder(), + 'POST', + [], + [], + ]; + + yield 'one variable' => [ + '{"query":"query","variables":{"name":"value"}}', + new Query(), + 'POST', + ['name' => 'value'], + [], + ]; + + yield 'one variable in query' => [ + '{"query":"query( $name: string )","variables":{}}', + (new Query())->setVariables([new Variable('name', 'string')]), + 'POST', + [], + [], + ]; + + yield 'authorization header' => [ + '{"query":"query","variables":{}}', + new Query(), + 'POST', + [], + ['Authorization' => 'Basic xyz'], + ]; + } + + /** @return \Generator, + * 4: array>, + * }> + */ + public static function provideRawQueries(): \Generator + { + yield 'minimal raw query' => [ + '{"query":"query_string","variables":{}}', + 'query_string', + 'POST', + [], + [], + ]; + + yield 'one variable' => [ + '{"query":"query_string","variables":{"name":"value"}}', + 'query_string', + 'POST', + ['name' => 'value'], + [], + ]; + + yield 'authorization header' => [ + '{"query":"query_string","variables":{}}', + 'query_string', + 'POST', + [], + ['Authorization' => 'Basic xyz'], + ]; + } + + #[Test] + public function testValidQueryResponseToArray(): void + { + $handler = new MockHandler(); + $handler->append(new Response(200, [], json_encode([ + 'data' => [ + 'someField' => [ + [ + 'data' => 'value', + ], [ + 'data' => 'value', + ], + ], + ], + ]))); + + $handlerStack = HandlerStack::create($handler); + $sut = new Client('', [], ['handler' => $handlerStack]); + + $arrayResults = $sut->runRawQuery('', true); + $this->assertIsArray($arrayResults->getResults()); + } +} diff --git a/tests/Unit/Exception/QueryErrorTest.php b/tests/Unit/Exception/QueryErrorTest.php new file mode 100644 index 0000000..0291ca3 --- /dev/null +++ b/tests/Unit/Exception/QueryErrorTest.php @@ -0,0 +1,61 @@ + [['message' => $expected]]]; + + $sut = new QueryError($errorDetails); + + self::assertEquals($expected, $sut->getMessage()); + } + + #[Test] + public function itGetsErrorDetails(): void + { + + $expected = [ + 'message' => 'some syntax error', + 'location' => [['line' => 1, 'column' => 3]], + ]; + + $errorDetails = ['errors' => [$expected]]; + + $sut = new QueryError($errorDetails); + + self::assertEquals($expected, $sut->getErrorDetails()); + } + + #[Test] + public function itGetsErrorData(): void + { + $expected = [ + 'someField' => [['data' => 'firstValue'], ['data' => 'secondValue']] + ]; + + $errorDetails = [ + 'errors' => [[ + 'message' => 'some syntax error', + 'location' => [['line' => 1, 'column' => 3]], + ]], + 'data' => $expected, + ]; + + $sut = new QueryError($errorDetails); + + self::assertEquals($expected, $sut->getData()); + } +} diff --git a/tests/Unit/InlineFragmentTest.php b/tests/Unit/InlineFragmentTest.php new file mode 100644 index 0000000..a037ed7 --- /dev/null +++ b/tests/Unit/InlineFragmentTest.php @@ -0,0 +1,112 @@ +*/ + public static function provideInlineFragmentsCastToString(): \Generator + { + yield 'minimal example' => [ + '... on minimal', + new InlineFragment('minimal'), + ]; + + yield 'one selection' => [ + '... on OneSelection { field1 }', + (new InlineFragment('OneSelection'))->setSelectionSet(['field1']), + ]; + + yield 'three selections' => [ + '... on ThreeSelections { field1 field2 field3 }', + (new InlineFragment('ThreeSelections')) + ->setSelectionSet(['field1', 'field2', 'field3']), + ]; + + yield 'minimal subquery selection' => [ + '... on query_selection { sub_query }', + (new InlineFragment('query_selection'))->setSelectionSet([ + (new Query('sub_query')) + ]), + ]; + + yield 'subquery (with args) selection ' => [ + '... on query_selection { sub_query(firstArg: 5) }', + (new InlineFragment('query_selection'))->setSelectionSet([ + (new Query('sub_query'))->setArguments(['firstArg' => 5]), + ]), + ]; + + + yield 'subquery (with subselection) selection ' => [ + '... on query_selection { sub_query { sub_selection } }', + (new InlineFragment('query_selection'))->setSelectionSet([ + (new Query('sub_query'))->setSelectionSet(['sub_selection']), + ]), + ]; + + yield 'subquery (with sub-fragment selection) selection ' => [ + '... on query_selection { sub_query { ... on Sub_Sub_Fragment { sub_sub_sub_field } } }', + (new InlineFragment('query_selection'))->setSelectionSet([ + (new Query('sub_query'))->setSelectionSet([ + (new InlineFragment('Sub_Sub_Fragment')) + ->setSelectionSet(['sub_sub_sub_field']), + ]), + ]), + ]; + + yield 'minimal querybuilder selection' => [ + '... on QueryBuilder_Selection { }', + (new InlineFragment('QueryBuilder_Selection')) + ->setSelectionSet([new QueryBuilder()]), + ]; + + yield 'querybuilder (with alias) selection' => [ + '... on QueryBuilder_Selection { query_alias: { sub_field } }', + (new InlineFragment('QueryBuilder_Selection'))->setSelectionSet([ + (new QueryBuilder()) + ->setAlias('query_alias') + ->selectField('sub_field') + ]), + ]; + } + + /** + * @covers \GraphQL\InlineFragment::__construct + * @covers \GraphQL\InlineFragment::setSelectionSet + * @covers \GraphQL\InlineFragment::getSelectionSet + * @covers \GraphQL\InlineFragment::constructSelectionSet + * @covers \GraphQL\InlineFragment::__toString + */ + public function testConvertQueryBuilderToString() + { + $queryBuilder = new QueryBuilder(); + + $fragment = new InlineFragment('Test', $queryBuilder); + $queryBuilder->selectField('field1'); + $queryBuilder->selectField('field2'); + + $this->assertEquals( + '... on Test { field1 field2 }', + (string) $fragment + ); + } +} diff --git a/tests/Unit/MutationTest.php b/tests/Unit/MutationTest.php new file mode 100644 index 0000000..d94607e --- /dev/null +++ b/tests/Unit/MutationTest.php @@ -0,0 +1,325 @@ +setVariables(['one', 'two']); + } + + #[Test] + #[TestDox('setArguments() MUST receive an array with string keys')] + public function itCannotSetArgumentsFromList(): void + { + $sut = new Mutation('Object'); + + self::expectException(ArgumentException::class); + + $sut->setArguments(['val']); + } + + #[Test] + public function itGetsArguments(): void + { + $arguments = ['someField' => 'someValue']; + $sut = (new Mutation('things'))->setArguments($arguments); + + self::assertSame($arguments, $sut->getArguments()); + } + + #[Test] + #[DataProvider('provideMutationsToCastToString')] + public function itIsStringable(string $expected, Mutation $sut): void + { + self::assertSame($expected, $sut->__toString()); + } + + /** @return Generator */ + public static function provideMutationsToCastToString(): Generator + { + yield 'empty mutation' => ['mutation', new Mutation()]; + + yield 'without fieldName' => (function () { + $mutation = new Mutation(); + $mutation->setSelectionSet([ + (new Mutation('First'))->setSelectionSet(['one']), + (new Mutation('Second'))->setSelectionSet(['two']) + ]); + return [ + 'mutation { First { one } Second { two } }', + $mutation, + ]; + })(); + + yield 'alias set in constructor' => (function () { + $mutation = new Mutation('Object', 'ObjectAlias'); + $mutation->setSelectionSet(['one']); + return [ + 'mutation { ObjectAlias: Object { one } }', + $mutation, + ]; + })(); + + yield 'alias set after construction' => (function () { + $mutation = (new Mutation('Object')) + ->setAlias('ObjectAlias') + ->setSelectionSet([ + 'one' + ]); + return [ + 'mutation { ObjectAlias: Object { one } }', + $mutation, + ]; + })(); + + yield 'operation name' => (function () { + $mutation = (new Mutation('Object')) + ->setOperationName('retrieveObject'); + + return [ + 'mutation retrieveObject { Object }', + $mutation + ]; + })(); + + yield 'operation name and selection set' => (function () { + $mutation = (new Mutation()) + ->setOperationName('retrieveObject') + ->setSelectionSet([new Mutation('Object')]); + + return [ + 'mutation retrieveObject { Object }', + $mutation + ]; + })(); + + yield 'nested operation name has no effect' => [ + 'mutation retrieveObject { Object { Nested } }', + (new Mutation('Object')) + ->setOperationName('retrieveObject') + ->setSelectionSet([ + (new Mutation('Nested')) + ->setOperationName('opName') + ]) + ]; + + yield 'mutation with one variable' => [ + 'mutation( $var: String ) { Object }', + (new Mutation('Object')) + ->setVariables([new Variable('var', 'String')]) + ]; + + yield 'mutation with two variables' => [ + 'mutation( $var: String $intVar: Int=4 ) { Object }', + (new Mutation('Object')) + ->setVariables([ + new Variable('var', 'String'), + new Variable('intVar', 'Int', false, 4) + ]) + ]; + + yield 'setting variables a second time overwrites the first set' => [ + 'mutation( $secondString: String $secondInt: Int=4 ) { Object }', + (new Mutation('Object')) + ->setVariables([ + new Variable('firstString', 'String'), + new Variable('firstInt', 'Int', false, 4) + ]) + ->setVariables([ + new Variable('secondString', 'String'), + new Variable('secondInt', 'Int', false, 4) + ]) + ]; + + yield 'operation name and variables' => [ + 'mutation retrieveObject( $var: String ) { Object }', + (new Mutation('Object')) + ->setOperationName('retrieveObject') + ->setVariables([new Variable('var', 'String')]), + ]; + + yield 'bool argument' => [ + 'mutation { Object(boolArg: true) }', + (new Mutation('Object'))->setArguments(['boolArg' => true]), + ]; + + yield 'float argument' => [ + 'mutation { Object(floatArg: 3.14) }', + (new Mutation('Object'))->setArguments(['floatArg' => 3.14]), + ]; + + yield 'int argument' => [ + 'mutation { Object(intArg: 34) }', + (new Mutation('Object'))->setArguments(['intArg' => 34]), + ]; + + yield 'string argument' => [ + 'mutation { Object(stringArg: "hello world") }', + (new Mutation('Object'))->setArguments(['stringArg' => 'hello world']), + ]; + + yield 'null argument' => [ + 'mutation { Object(nullArg: null) }', + (new Mutation('Object'))->setArguments(['nullArg' => null]), + ]; + + yield 'int list argument' => [ + 'mutation { Object(intListArg: [1, 2, 3]) }', + (new Mutation('Object'))->setArguments(['intListArg' => [1, 2, 3]]), + ]; + + yield 'string list argument' => [ + 'mutation { Object(stringListArg: ["hello", "world"]) }', + (new Mutation('Object')) + ->setArguments(['stringListArg' => ['hello', 'world']]), + ]; + + yield 'json object argument' => [ + 'mutation { Object(obj: {json_string_array: ["json value"]}) }', + (new Mutation('Object')) + ->setArguments(['obj' => new RawObject('{json_string_array: ["json value"]}')]), + ]; + + yield 'multiple arguments' => [ + "mutation { Object(arg1: \"val1\" arg2: 2 arg3: true) }", + (new Mutation('Object')) + ->setArguments(['arg1' => 'val1', 'arg2' => 2, 'arg3' => true]), + ]; + + yield 'it overwrites previous set selection set' => [ + 'mutation { Object { field2 field3 } }', + (new Mutation('Object')) + ->setSelectionSet(['field1']) + ->setSelectionSet(['field2', 'field3']), + ]; + + yield 'nested mutation' => [ + "mutation { Object { field1 field2 Object2 { field3 } } }", + (new Mutation('Object')) + ->setSelectionSet([ + 'field1', + 'field2', + (new Mutation('Object2')) + ->setSelectionSet(['field3']) + ]) + ]; + + yield 'nested inline fragment' => [ + 'mutation { Object { field1 ... on Object { fragment_field1 fragment_field2 } } }', + (new Mutation('Object')) + ->setSelectionSet([ + 'field1', + (new InlineFragment('Object')) + ->setSelectionSet( + [ + 'fragment_field1', + 'fragment_field2', + ] + ), + ]), + ]; + } + + #[Test] + public function testMutationWithoutOperationType(): void + { + $mutation = new Mutation('createObject'); + + $this->assertEquals( + 'mutation { createObject }', + (string) $mutation + ); + } + + #[Test] + public function testMutationWithOperationType(): void + { + $mutation = new Mutation(); + $mutation + ->setSelectionSet( + [ + (new Mutation('createObject')) + ->setArguments(['name' => 'TestObject']) + ] + ); + + $this->assertEquals( + 'mutation { createObject(name: "TestObject") }', + (string) $mutation + ); + } + + #[Test] + public function testMutationWithoutSelectedFields(): void + { + $mutation = (new Mutation('createObject')) + ->setArguments(['name' => 'TestObject', 'type' => 'TestType']); + $this->assertEquals( + 'mutation { createObject(name: "TestObject" type: "TestType") }', + (string) $mutation + ); + } + + #[Test] + public function testMutationWithFields(): void + { + $mutation = (new Mutation('createObject')) + ->setSelectionSet( + [ + 'fieldOne', + 'fieldTwo', + ] + ); + + $this->assertEquals( + 'mutation { createObject { fieldOne fieldTwo } }', + (string) $mutation + ); + } + + #[Test] + public function testMutationWithArgumentsAndFields(): void + { + $mutation = (new Mutation('createObject')) + ->setSelectionSet( + [ + 'fieldOne', + 'fieldTwo', + ] + )->setArguments( + [ + 'argOne' => 1, + 'argTwo' => 'val' + ] + ); + + $this->assertEquals( + 'mutation { createObject(argOne: 1 argTwo: "val") { fieldOne fieldTwo } }', + (string) $mutation + ); + } +} diff --git a/tests/Unit/QueryBuilder/MutationBuilderTest.php b/tests/Unit/QueryBuilder/MutationBuilderTest.php new file mode 100644 index 0000000..25f19bc --- /dev/null +++ b/tests/Unit/QueryBuilder/MutationBuilderTest.php @@ -0,0 +1,162 @@ +selectField( + (new MutationBuilder('Object')) + ->selectField('one') + ) + ->selectField( + (new MutationBuilder('Another')) + ->selectField('two') + ); + + $this->assertEquals( + 'mutation { Object { one } Another { two } }', + (string) $builder->getMutation() + ); + } + + + /** + * @param array $selectionSet + * @param Variable[] $variables + * @param array $arguments + */ + #[Test] + #[DataProvider('provideDataToBuildMutation')] + public function itBuildsQueries( + string $name, + string $alias = '', + array $selectionSet = [], + array $variables = [], + array $arguments = [], + ): void { + $expected = (new Mutation($name, $alias)) + ->setSelectionSet($selectionSet) + ->setVariables($variables) + ->setArguments($arguments); + + $sut = new MutationBuilder($name, $alias); + + foreach ($selectionSet as $selection) { + $sut->selectField($selection); + } + + foreach ($variables as $variable) { + $sut->setVariable( + $variable->name, + $variable->type, + $variable->nonNullable, + $variable->defaultValue, + ); + } + + foreach ($arguments as $argumentName => $argumentValue) { + $sut->setArgument($argumentName, $argumentValue); + } + + self::assertEquals($expected, $sut->getMutation()); + } + + /** @return \Generator, + * 3?: Variable[], + * 4?: array, + * }> + */ + public static function provideDataToBuildMutation(): \Generator + { + yield 'minimal mutation' => ['Test']; + + yield 'alias' => ['Test', 'Test_Alias']; + + yield 'one selection' => ['One_Selection', '', ['first']]; + + yield 'three selections' => [ + 'three_selections', + '', + ['first', 'second', 'third'], + ]; + + yield sprintf('%s selection', InlineFragment::class) => [ + 'WithInlineFragmentSelection', + '', + [(new InlineFragment('Nested'))->setSelectionSet(['field'])], + ]; + + yield sprintf('%s selection', Mutation::class) => [ + 'WithMutationSelection', + '', + [(new Mutation('Nested'))->setSelectionSet(['some_field'])], + ]; + + yield sprintf('%s selection', MutationBuilder::class) => [ + 'WithMutationBuilderSelection', + '', + [(new MutationBuilder('Nested'))->selectField('fieldTwo')], + ]; + + yield 'one variable' => [ + 'one_variable', + '', + [], + [new Variable('first_var', 'String', true, 'default string')], + ]; + + yield 'three variables' => [ + 'ThreeVariables', + '', + [], + [ + new Variable('first_var', 'String', true, 'default string'), + new Variable('second_var', 'Int', true, 5), + new Variable('third_var', 'Array', true, [1, 2, 4]), + ], + ]; + + yield 'one argument' => [ + 'one_argument', + '', + [], + [], + ['string_argument' => 'value'] + ]; + + yield 'three arguments' => [ + 'Three_Arguments', + '', + [], + [], + [ + 'string_argument' => 'value', + 'int_argument' => 1, + 'object_argument' => new RawObject('{field_not: "x"}'), + ] + ]; + } +} diff --git a/tests/Unit/QueryBuilder/QueryBuilderTest.php b/tests/Unit/QueryBuilder/QueryBuilderTest.php new file mode 100644 index 0000000..f879be0 --- /dev/null +++ b/tests/Unit/QueryBuilder/QueryBuilderTest.php @@ -0,0 +1,206 @@ +setArgument('name', 'test') + ->setArgument('description', 'pipeline description') + ->setArgument('labels', [new RawObject('{distribution: alpine}')]); + $queryStack[] = $pipeline; + + $container = (new QueryBuilder('container')); + $queryStack[] = $container; + + + $from = (new QueryBuilder('from')) + ->setArgument('address', 'alpine:3.16.2'); + $queryStack[] = $from; + + $withExec = (new QueryBuilder('withExec')) + ->setArgument('args', ['cat', '/etc/alpine-release']); + $queryStack[] = $withExec; + + $queryStack[] = new QueryBuilder('stdout'); + + foreach ($queryStack as $queryBuilder) { + $rootQb = $rootQb->selectField($queryBuilder); + } + + self::assertSame( + 'query { pipeline' . + '(' . + 'name: "test" ' . + 'description: "pipeline description" ' . + 'labels: [{distribution: alpine}]' . + ') ' . + 'container ' . + 'from(address: "alpine:3.16.2") ' . + 'withExec(args: ["cat", "/etc/alpine-release"]) ' . + 'stdout }', + (string) $rootQb->getQuery() + ); + } + + #[Test] + public function itCanBuildQueryWithoutName(): void + { + $builder = (new QueryBuilder()) + ->selectField( + (new QueryBuilder('Object')) + ->selectField('one') + ) + ->selectField( + (new QueryBuilder('Another')) + ->selectField('two') + ); + + $this->assertEquals( + 'query { Object { one } Another { two } }', + (string) $builder->getQuery() + ); + } + + + /** + * @param array $selectionSet + * @param Variable[] $variables + * @param array $arguments + */ + #[Test] + #[DataProvider('provideDataToBuildQuery')] + public function itBuildsQueries( + string $name, + string $alias = '', + array $selectionSet = [], + array $variables = [], + array $arguments = [], + ): void { + $expected = (new Query($name, $alias)) + ->setSelectionSet($selectionSet) + ->setVariables($variables) + ->setArguments($arguments); + + $sut = new QueryBuilder($name, $alias); + + foreach ($selectionSet as $selection) { + $sut->selectField($selection); + } + + foreach ($variables as $variable) { + $sut->setVariable( + $variable->name, + $variable->type, + $variable->nonNullable, + $variable->defaultValue, + ); + } + + foreach ($arguments as $argumentName => $argumentValue) { + $sut->setArgument($argumentName, $argumentValue); + } + + self::assertEquals($expected, $sut->getQuery()); + } + + /** @return \Generator, + * 3?: Variable[], + * 4?: array, + * }> + */ + public static function provideDataToBuildQuery(): \Generator + { + yield 'minimal query' => ['Test']; + + yield 'alias' => ['Test', 'Test_Alias']; + + yield 'one selection' => ['One_Selection', '', ['first']]; + + yield 'three selections' => [ + 'three_selections', + '', + ['first', 'second', 'third'], + ]; + + yield sprintf('%s selection', InlineFragment::class) => [ + 'WithInlineFragmentSelection', + '', + [(new InlineFragment('Nested'))->setSelectionSet(['field'])], + ]; + + yield sprintf('%s selection', Query::class) => [ + 'WithQuerySelection', + '', + [(new Query('Nested'))->setSelectionSet(['some_field'])], + ]; + + yield sprintf('%s selection', QueryBuilder::class) => [ + 'WithQueryBuilderSelection', + '', + [(new QueryBuilder('Nested'))->selectField('fieldTwo')], + ]; + + yield 'one variable' => [ + 'one_variable', + '', + [], + [new Variable('first_var', 'String', true, 'default string')], + ]; + + yield 'three variables' => [ + 'ThreeVariables', + '', + [], + [ + new Variable('first_var', 'String', true, 'default string'), + new Variable('second_var', 'Int', true, 5), + new Variable('third_var', 'Array', true, [1, 2, 4]), + ], + ]; + + yield 'one argument' => [ + 'one_argument', + '', + [], + [], + ['string_argument' => 'value'] + ]; + + yield 'three arguments' => [ + 'Three_Arguments', + '', + [], + [], + [ + 'string_argument' => 'value', + 'int_argument' => 1, + 'object_argument' => new RawObject('{field_not: "x"}'), + ] + ]; + } +} diff --git a/tests/Unit/QueryTest.php b/tests/Unit/QueryTest.php new file mode 100644 index 0000000..9c85d04 --- /dev/null +++ b/tests/Unit/QueryTest.php @@ -0,0 +1,383 @@ +setVariables(['one', 'two']); + } + + #[Test] + #[TestDox('setArguments() MUST receive an array with string keys')] + public function itCannotSetArgumentsFromList(): void + { + $sut = new Query('Object'); + + self::expectException(ArgumentException::class); + + $sut->setArguments(['val']); + } + + #[Test] + public function itGetsArguments(): void + { + $arguments = ['someField' => 'someValue']; + $sut = (new Query('things'))->setArguments($arguments); + + self::assertSame($arguments, $sut->getArguments()); + } + + + /** @param array $arguments */ + #[Test, DataProvider('provideInvalidArguments')] + public function itInvalidatesUnsupportedArguments(array $arguments): void + { + $sut = new Query(); + + self::expectException(ArgumentException::class); + + $sut->setArguments($arguments); + } + + #[Test] + #[DataProvider('provideQueriesToCastToString')] + public function itIsStringable(string $expected, Query $sut): void + { + self::assertSame($expected, $sut->__toString()); + } + + /** @return Generator }> */ + public static function provideInvalidArguments(): Generator + { + yield \DateTime::class => [[new \DateTime()]]; + + yield sprintf('nested array of %s', \DateTime::class) => [ + [[[[new \DateTime()]]]], + ]; + } + + /** @return Generator */ + public static function provideQueriesToCastToString(): Generator + { + yield 'empty query' => ['query', new Query()]; + + yield 'without fieldName' => (function () { + $query = new Query(); + $query->setSelectionSet([ + (new Query('First'))->setSelectionSet(['one']), + (new Query('Second'))->setSelectionSet(['two']) + ]); + return [ + 'query { First { one } Second { two } }', + $query, + ]; + })(); + + yield 'alias set in constructor' => (function () { + $query = new Query('Object', 'ObjectAlias'); + $query->setSelectionSet(['one']); + return [ + 'query { ObjectAlias: Object { one } }', + $query, + ]; + })(); + + yield 'alias set after construction' => (function () { + $query = (new Query('Object')) + ->setAlias('ObjectAlias') + ->setSelectionSet([ + 'one' + ]); + return [ + 'query { ObjectAlias: Object { one } }', + $query, + ]; + })(); + + yield 'operation name' => (function () { + $query = (new Query('Object')) + ->setOperationName('retrieveObject'); + + return [ + 'query retrieveObject { Object }', + $query + ]; + })(); + + yield 'operation name and selection set' => (function () { + $query = (new Query()) + ->setOperationName('retrieveObject') + ->setSelectionSet([new Query('Object')]); + + return [ + 'query retrieveObject { Object }', + $query + ]; + })(); + + yield 'nested operation name has no effect' => [ + 'query retrieveObject { Object { Nested } }', + (new Query('Object')) + ->setOperationName('retrieveObject') + ->setSelectionSet([ + (new Query('Nested')) + ->setOperationName('opName') + ]) + ]; + + yield 'query with one variable' => [ + 'query( $var: String ) { Object }', + (new Query('Object')) + ->setVariables([new Variable('var', 'String')]) + ]; + + yield 'query with two variables' => [ + 'query( $var: String $intVar: Int=4 ) { Object }', + (new Query('Object')) + ->setVariables([ + new Variable('var', 'String'), + new Variable('intVar', 'Int', false, 4) + ]) + ]; + + yield 'setting variables a second time overwrites the first set' => [ + 'query( $secondString: String $secondInt: Int=4 ) { Object }', + (new Query('Object')) + ->setVariables([ + new Variable('firstString', 'String'), + new Variable('firstInt', 'Int', false, 4) + ]) + ->setVariables([ + new Variable('secondString', 'String'), + new Variable('secondInt', 'Int', false, 4) + ]) + ]; + + yield 'operation name and variables' => [ + 'query retrieveObject( $var: String ) { Object }', + (new Query('Object')) + ->setOperationName('retrieveObject') + ->setVariables([new Variable('var', 'String')]), + ]; + + yield 'bool argument' => [ + 'query { Object(boolArg: true) }', + (new Query('Object'))->setArguments(['boolArg' => true]), + ]; + + yield 'float argument' => [ + 'query { Object(floatArg: 3.14) }', + (new Query('Object'))->setArguments(['floatArg' => 3.14]), + ]; + + yield 'int argument' => [ + 'query { Object(intArg: 34) }', + (new Query('Object'))->setArguments(['intArg' => 34]), + ]; + + yield 'string argument' => [ + 'query { Object(stringArg: "hello world") }', + (new Query('Object'))->setArguments(['stringArg' => 'hello world']), + ]; + + yield 'null argument' => [ + 'query { Object(nullArg: null) }', + (new Query('Object'))->setArguments(['nullArg' => null]), + ]; + + yield 'int list argument' => [ + 'query { Object(intListArg: [1, 2, 3]) }', + (new Query('Object'))->setArguments(['intListArg' => [1, 2, 3]]), + ]; + + yield 'string list argument' => [ + 'query { Object(stringListArg: ["hello", "world"]) }', + (new Query('Object')) + ->setArguments(['stringListArg' => ['hello', 'world']]), + ]; + + yield 'nested list argument' => [ + 'query { Object(nestedListArg: [[["hello", "world"]]]) }', + (new Query('Object')) + ->setArguments(['nestedListArg' => [[['hello', 'world']]]]), + ]; + + yield 'nested list of BackedEnums argument' => [ + 'query { Object(nestedEnumArg: [[[query, mutation, subscription]]]) }', + (new Query('Object')) + ->setArguments(['nestedEnumArg' => [[[ + OperationType::Query, + OperationType::Mutation, + OperationType::Subscription, + ]]]]), + ]; + + yield 'json object argument' => [ + 'query { Object(obj: {json_string_array: ["json value"]}) }', + (new Query('Object')) + ->setArguments(['obj' => new RawObject('{json_string_array: ["json value"]}')]), + ]; + + yield 'multiple arguments' => [ + "query { Object(arg1: \"val1\" arg2: 2 arg3: true) }", + (new Query('Object')) + ->setArguments(['arg1' => 'val1', 'arg2' => 2, 'arg3' => true]), + ]; + } + + #[Test] + public function itOverwritesPreviousSelectionSets() + { + $query = (new Query('Object')) + ->setSelectionSet(['field1']) + ->setSelectionSet(['field2', 'field3']); + $this->assertEquals( + 'query { Object { field2 field3 } }', + (string) $query, + 'Query has improperly formatted selection set' + ); + + return $query; + } + + public function testTwoLevelQuery() + { + $query = (new Query('Object')) + ->setSelectionSet([ + 'field1', + 'field2', + (new Query('Object2')) + ->setSelectionSet(['field3']) + ]); + $this->assertEquals( + "query { Object { field1 field2 Object2 { field3 } } }", + (string) $query, + 'Two level query not formatted correctly' + ); + + return $query; + } + + public function testTwoLevelQueryWithInlineFragment() + { + $query = (new Query('Object')) + ->setSelectionSet([ + 'field1', + (new InlineFragment('Object')) + ->setSelectionSet( + [ + 'fragment_field1', + 'fragment_field2', + ] + ), + ]); + $this->assertEquals( + 'query { Object { field1 ... on Object { fragment_field1 fragment_field2 } } }', + (string) $query + ); + + return $query; + } + + public function testGettingArguments() + { + $gql = (new Query('things')) + ->setArguments( + [ + 'someClientId' => 'someValueBasedOnCodebase' + ] + ); + $cursor_id = 'someCursor'; + $new_args = $gql->getArguments(); + $gql->setArguments( + array_merge( + $new_args, + [ + 'after' => $cursor_id + ] + ) + ); + self::assertEquals( + 'query { things(someClientId: "someValueBasedOnCodebase" after: "someCursor") }', + (string) $gql + ); + } + + public function testGettingNameAndAltering() + { + $gql = (new Query('things')) + ->setSelectionSet( + [ + 'id', + 'name', + (new Query('subThings')) + ->setArguments( + [ + 'filter' => 'providerId123', + ] + ) + ->setSelectionSet( + [ + 'id', + 'name' + ] + ) + ] + ); + $sets = $gql->getSelectionSet(); + foreach ($sets as $set) { + if (($set instanceof Query) === false) { + continue; + } + $name = $set->getFieldName(); + if ($name !== 'subThings') { + continue; + } + $set->setArguments( + [ + 'filter' => 'providerId456' + ] + ); + $set->setSelectionSet( + array_merge( + $set->getSelectionSet(), + [ + 'someField', + 'someOtherField' + ] + ) + ); + } + self::assertEquals( + 'query { things { id name subThings(filter: "providerId456") { id name someField someOtherField } } }', + (string) $gql + ); + } +} diff --git a/tests/Unit/RawObjectTest.php b/tests/Unit/RawObjectTest.php new file mode 100644 index 0000000..5e0db71 --- /dev/null +++ b/tests/Unit/RawObjectTest.php @@ -0,0 +1,36 @@ + */ + public static function provideJson(): \Generator + { + yield 'array' => [ + '[1, 4, "y", 6.7]', + ]; + + yield 'object' => [ + '{arr: [1, "z"], str: "val", int: 1, obj: {x: "y"}}', + ]; + } +} diff --git a/tests/Unit/ResultsTest.php b/tests/Unit/ResultsTest.php new file mode 100644 index 0000000..d3969c7 --- /dev/null +++ b/tests/Unit/ResultsTest.php @@ -0,0 +1,206 @@ + [ + [ + 'message' => 'some syntax error', + 'location' => [ + [ + 'line' => 1, + 'column' => 3, + ] + ], + ] + ] + ])); + + $this->expectException(QueryError::class); + + new Results($response); + } + + /** + * @param array{data:array>} $body + */ + #[Test] + #[DataProvider('provideResponses')] + public function itGetsResponses(array $body): void + { + $response = new Response(200, [], json_encode($body)); + $results = new Results($response); + + self::assertEquals($response, $results->getResponseObject()); + } + + /** + * @param array{data:array>} $body + */ + #[Test] + #[DataProvider('provideResponses')] + public function itGetsResponseBodies(array $body): void + { + $response = new Response(200, [], json_encode($body)); + $results = new Results($response); + + self::assertEquals($response->getBody(), $results->getResponseBody()); + } + + /** + * @param array{data:array>} $body + */ + #[Test] + #[DataProvider('provideResponses')] + public function itGetsResults(array $body): void + { + $response = new Response(200, [], json_encode($body)); + $results = new Results($response); + + self::assertEquals(json_decode(json_encode($body)), $results->getResults()); + } + + /** + * @param array{data:array>} $body + */ + #[Test] + #[DataProvider('provideResponses')] + public function itGetsResponseData(array $body): void + { + $response = new Response(200, [], json_encode($body)); + $results = new Results($response); + + self::assertEquals(json_decode(json_encode($body['data']), false), $results->getData()); + } + + /** + * @return \Generator> + * }}> + */ + public static function provideResponses(): \Generator + { + yield 'one field' => [['data' => [ + 'firstField' => [['data' => 'firstValue']] + ]]]; + + yield 'two fields' => [['data' => [ + 'firstField' => [['data' => 'firstValue']], + 'secondField' => [['data' => 'secondValue']], + ]]]; + + yield 'one field, two values' => [['data' => [ + 'firstField' => [ + ['data' => 'firstValue'], + ['data' => 'secondValue'] + ], + ]]]; + } + + // #[Test] + // public function testReformatResultsFromObjectToArray() + // { + // $body = json_encode([ + // 'data' => [ + // 'someField' => [ + // [ + // 'data' => 'value', + // ], + // [ + // 'data' => 'value', + // ] + // ] + // ] + // ]); + // $originalResponse = new Response(200, [], $body); + // $this->mockHandler->append($originalResponse); + + // $response = $this->client->post('', []); + // $results = new Results($response); + // $results->reformatResults(true); + + // $this->assertEquals( + // [ + // 'data' => [ + // 'someField' => [ + // [ + // 'data' => 'value', + // ], + // [ + // 'data' => 'value', + // ] + // ] + // ] + // ], + // $results->getResults() + // ); + // $this->assertEquals( + // [ + // 'someField' => [ + // [ + // 'data' => 'value', + // ], + // [ + // 'data' => 'value', + // ] + // ] + // ], + // $results->getData() + // ); + // } + + // #[Test] + // public function testReformatResultsFromArrayToObject(): void + // { +// $body = json_encode([ +// 'data' => [ +// 'someField' => [ +// [ +// 'data' => 'value', +// ], +// [ +// 'data' => 'value', +// ] +// ] +// ] +// ]); +// $originalResponse = new Response(200, [], $body); +// $this->mockHandler->append($originalResponse); +// +// $response = $this->client->post('', []); +// $results = new Results($response, true); +// $results->reformatResults(false); +// +// $object = new stdClass(); +// $object->data = new stdClass(); +// $object->data->someField = []; +// $object->data->someField[] = new stdClass(); +// $object->data->someField[] = new stdClass(); +// $object->data->someField[0]->data = 'value'; +// $object->data->someField[1]->data = 'value'; + // self::assertEquals( + // $object, + // $results->getResults() + // ); + // self::assertEquals( + // $object->data, + // $results->getData() + // ); + // } +} diff --git a/tests/Unit/Util/StringLiteralFormatterTest.php b/tests/Unit/Util/StringLiteralFormatterTest.php new file mode 100644 index 0000000..8de49fe --- /dev/null +++ b/tests/Unit/Util/StringLiteralFormatterTest.php @@ -0,0 +1,217 @@ + $array + */ + #[Test] + #[DataProvider('provideArraysToFormatForGQLQueries')] + public function itFormatsArraysForGQLQueries( + string $expected, + array $array, + ): void { + $actual = StringLiteralFormatter::formatArrayForGQLQuery($array); + + self::assertSame($expected, $actual); + } + + #[Test] + #[DataProvider('provideStringsToFormatToUpperCamelCase')] + public function itFormatsSnakeCaseToUpperCamelCase( + string $expected, + string $stringToFormat, + ): void { + $actual = StringLiteralFormatter::formatUpperCamelCase($stringToFormat); + + self::assertSame($expected, $actual); + } + + #[Test] + #[DataProvider('provideStringsToFormatToLowerCamelCase')] + public function itFormatsStringsToLowerCamelCase( + string $expected, + string $stringToFormat, + ): void { + $actual = StringLiteralFormatter::formatLowerCamelCase($stringToFormat); + + self::assertSame($expected, $actual); + } + + /** @return Generator */ + public static function provideValuesToFormatForRHS(): Generator + { + yield 'null' => ['null', null]; + + yield 'empty string' => ['""', '']; + yield 'non-empty string' => ['"someString"', 'someString']; + yield 'unescaped double quotes in string' => [ + '"\"quotedString\""', + '"quotedString"', + ]; + yield 'escaped double quotes in string' => [ + '"\\\\\"quotedString\\\\\""', + '\\"quotedString\\"', + ]; + + yield 'escaped double quoted string in html' => [ + '""', + '', + + ]; + + yield 'unescaped single quotes in string' => [ + '"\'singleQuotes\'"', + "'singleQuotes'", + ]; + + yield 'escaped single quotes in string' => [ + '"\\\\\'singleQuotes\\\\\'"', + "\\'singleQuotes\\'", + ]; + + yield 'string with newlines' => [ + "\"\"\"with \n newlines\"\"\"", + "with \n newlines", + ]; + + yield 'string variable name' => ['$var', '$var']; + + yield 'string starts with $ but not a variable' => ['"$400"', '$400']; + + yield 'integer 0' => ['0', 0]; + + yield 'integer 25' => ['25', 25]; + + yield 'float' => ['3.14', 3.14]; + + yield 'bool false' => ['false', false]; + + yield 'bool true' => ['true', true]; + + yield RawObject::class => [ + '["one", "two", "three"]', + new RawObject('["one", "two", "three"]'), + ]; + + yield Stringable::class => [ + 'Hello World', + new class () implements Stringable { + public function __toString(): string + { + return 'Hello World'; + } + } + ]; + + yield BackedEnum::class => ['query', OperationType::Query]; + } + + /** @return Generator }> */ + public static function provideArraysToFormatForGQLQueries(): Generator + { + yield 'empty' => ['[]', []]; + + yield 'one float' => ['[3.14]', [3.14]]; + yield 'three floats' => ['[3.14, 9.81, 1.67]', [3.14, 9.81, 1.67]]; + + yield 'one integer' => ['[1]', [1]]; + yield 'three integers' => ['[1, 2, 3]', [1, 2, 3]]; + + yield 'one string' => ['["one"]', ['one']]; + yield 'three strings' => [ + '["one", "two", "three"]', + ['one', 'two', 'three'], + ]; + + yield 'one bool' => ['[true]', [true]]; + yield 'three bools' => ['[true, false, true]', [true, false, true]]; + + yield 'nested string array' => ['[["one"]]', [['one']]]; + yield 'nested string arrays' => [ + '[["one"], ["two"], ["three"]]', + [['one'], ['two'], ['three']], + ]; + yield 'nested strings array' => [ + '[["one", "two", "three"]]', + [['one', 'two', 'three']], + ]; + yield 'nested strings arrays' => [ + '[["one", "two", "three"], ["four", "five", "six"], ["seven", "eight", "nine"]]', + [['one', 'two', 'three'], ['four', 'five', 'six'], ['seven', 'eight', 'nine']], + ]; + + yield sprintf('nested %s array', BackedEnum::class) => [ + '[[query], [mutation, subscription]]', + [ + [OperationType::Query], + [OperationType::Mutation, OperationType::Subscription], + ] + ]; + } + + /** @return Generator */ + public static function provideStringsToFormatToUpperCamelCase(): Generator + { + yield 'some_snake_case' => [ + 'SomeSnakeCase', + 'some_snake_case', + ]; + + yield 'lowerCamelCase' => [ + 'LowerCamelCase', + 'lowerCamelCase', + ]; + + yield 'UpperCamelCase' => [ + 'UpperCamelCase', + 'UpperCamelCase', + ]; + } + + /** @return Generator */ + public static function provideStringsToFormatToLowerCamelCase(): Generator + { + yield 'some_snake_case' => [ + 'someSnakeCase', + 'some_snake_case', + ]; + + yield 'lowerCamelCase' => [ + 'lowerCamelCase', + 'lowerCamelCase', + ]; + + yield 'UpperCamelCase' => [ + 'upperCamelCase', + 'UpperCamelCase', + ]; + } +} diff --git a/tests/Unit/VariableTest.php b/tests/Unit/VariableTest.php new file mode 100644 index 0000000..881ab76 --- /dev/null +++ b/tests/Unit/VariableTest.php @@ -0,0 +1,87 @@ + */ + public static function provideVariables(): Generator + { + yield 'nullable string' => [ + '$nullableString: String', + new Variable('nullableString', 'String'), + ]; + + yield 'nullable string with default' => [ + '$nullableString: String="default"', + new Variable('nullableString', 'String', false, 'default'), + ]; + + yield 'non-nullable string' => [ + '$nonNullableString: String!', + new Variable('nonNullableString', 'String', true), + ]; + + yield 'non-nullable string with default' => [ + '$nonNullableString: String!="default"', + new Variable('nonNullableString', 'String', true, 'default'), + ]; + + yield 'nullable int' => [ + '$nullableInt: Int', + new Variable('nullableInt', 'Int', false), + ]; + + yield 'nullable int with default' => [ + '$nullableInt: Int=4', + new Variable('nullableInt', 'Int', false, 4), + ]; + + yield 'nullable string with a numeric-string default' => [ + '$nullableString: String="4"', + new Variable('nullableString', 'String', false, '4'), + ]; + + yield 'nullable bool with default true' => [ + '$nullableBool: Boolean=true', + new Variable('nullableBool', 'Boolean', false, true), + ]; + + yield 'nullable bool with default false' => [ + '$nullableBool: Boolean=false', + new Variable('nullableBool', 'Boolean', false, false), + ]; + + yield 'nullable string with a bool-string default' => [ + '$nullableString: String="true"', + new Variable('nullableString', 'String', false, 'true'), + ]; + + yield 'nullable int list' => [ + '$nullableIntList: [Int]', + new Variable('nullableIntList', '[Int]', false, null) + ]; + + yield 'non-nullable int list, default empty array' => [ + '$nonNullableIntList: [Int]!=[]', + new Variable('nonNullableIntList', '[Int]', true, []) + ]; + } +} diff --git a/tests/VariableTest.php b/tests/VariableTest.php deleted file mode 100644 index 6100e30..0000000 --- a/tests/VariableTest.php +++ /dev/null @@ -1,69 +0,0 @@ -assertEquals('$var: String', (string) $variable); - } - - /** - * @depends testCreateVariable - * - * @covers \GraphQL\Variable::__construct - * @covers \GraphQL\Variable::__toString - */ - public function testCreateRequiredVariable() - { - $variable = new Variable('var', 'String', true); - $this->assertEquals('$var: String!', (string) $variable); - } - - /** - * @depends testCreateRequiredVariable - * - * @covers \GraphQL\Variable::__construct - * @covers \GraphQL\Variable::__toString - */ - public function testRequiredVariableWithDefaultValueDoesNothing() - { - $variable = new Variable('var', 'String', true, 'def'); - $this->assertEquals('$var: String!', (string) $variable); - } - - /** - * @depends testCreateVariable - * - * @covers \GraphQL\Variable::__construct - * @covers \GraphQL\Variable::__toString - */ - public function testOptionalVariableWithDefaultValue() - { - $variable = new Variable('var', 'String', false, 'def'); - $this->assertEquals('$var: String="def"', (string) $variable); - - $variable = new Variable('var', 'String', false, '4'); - $this->assertEquals('$var: String="4"', (string) $variable); - - $variable = new Variable('var', 'Int', false, 4); - $this->assertEquals('$var: Int=4', (string) $variable); - - $variable = new Variable('var', 'Boolean', false, true); - $this->assertEquals('$var: Boolean=true', (string) $variable); - } -} \ No newline at end of file