From ac5fec58f2f44a86d40ba41e40e62017495c1360 Mon Sep 17 00:00:00 2001 From: meyerbaptiste Date: Tue, 20 Aug 2019 09:58:05 +0200 Subject: [PATCH] Add assertions for testing response against JSON Schema from API resource Co-authored-by: Teoh Han Hui --- composer.json | 2 +- phpstan.neon.dist | 4 +- .../Bundle/Test/ApiTestAssertionsTrait.php | 38 ++++++++++++++++++- .../Test/Constraint/MatchesJsonSchema.php | 13 ++++--- src/Hydra/JsonSchema/SchemaFactory.php | 30 +++++++++------ .../Command/JsonSchemaGenerateCommand.php | 15 ++++---- src/JsonSchema/Schema.php | 6 ++- src/JsonSchema/SchemaFactory.php | 22 +++++------ src/JsonSchema/SchemaFactoryInterface.php | 2 +- src/JsonSchema/TypeFactory.php | 5 +-- .../Serializer/DocumentationNormalizer.php | 14 +++---- .../Symfony/Bundle/Test/ApiTestCaseTest.php | 13 +++++++ .../SerializableItemDataProvider.php | 16 +++++--- tests/Hydra/JsonSchema/SchemaFactoryTest.php | 5 ++- tests/JsonSchema/TypeFactoryTest.php | 2 +- 15 files changed, 127 insertions(+), 60 deletions(-) diff --git a/composer.json b/composer.json index d6d2cf0a30b..32c7dcf9e24 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "friendsofsymfony/user-bundle": "^2.2@dev", "guzzlehttp/guzzle": "^6.0", "jangregor/phpstan-prophecy": "^0.3", - "justinrainbow/json-schema": "^5.0", + "justinrainbow/json-schema": "^5.2", "nelmio/api-doc-bundle": "^2.13.4", "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", "phpdocumentor/type-resolver": "^0.3 || ^0.4", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 1f5f787dbdd..4f6219cc550 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -57,6 +57,9 @@ parameters: - message: '#Parameter \#1 \$docblock of method phpDocumentor\\Reflection\\DocBlockFactoryInterface::create\(\) expects string, ReflectionClass given\.#' path: %currentWorkingDirectory%/src/Metadata/Resource/Factory/PhpDocResourceMetadataFactory.php + - + message: '#Parameter \#1 \$objectValue of method GraphQL\\Type\\Definition\\InterfaceType::resolveType\(\) expects object, array()? given.#' + path: %currentWorkingDirectory%/tests/GraphQl/Type/TypeBuilderTest.php - message: '#Property ApiPlatform\\Core\\Test\\DoctrineMongoDbOdmFilterTestCase::\$repository \(Doctrine\\ODM\\MongoDB\\Repository\\DocumentRepository\) does not accept Doctrine\\ORM\\EntityRepository\.#' path: %currentWorkingDirectory%/src/Test/DoctrineMongoDbOdmFilterTestCase.php @@ -81,7 +84,6 @@ parameters: - message: '#Binary operation "\+" between (float\|int\|)?string and 0 results in an error\.#' path: %currentWorkingDirectory%/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php - - '#Parameter \#1 \$objectValue of method GraphQL\\Type\\Definition\\InterfaceType::resolveType\(\) expects object, array()? given.#' # Expected, due to optional interfaces - '#Method ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\QueryCollectionExtensionInterface::applyToCollection\(\) invoked with 5 parameters, 3-4 required\.#' diff --git a/src/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php b/src/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php index 97304347755..b2c1405c851 100644 --- a/src/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php +++ b/src/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php @@ -13,9 +13,13 @@ namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Test; +use ApiPlatform\Core\Api\OperationType; use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint\ArraySubset; use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint\MatchesJsonSchema; +use ApiPlatform\Core\JsonSchema\Schema; +use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface; use PHPUnit\Framework\ExpectationFailedException; +use Psr\Container\ContainerInterface; use Symfony\Contracts\HttpClient\ResponseInterface; /** @@ -28,9 +32,9 @@ trait ApiTestAssertionsTrait use BrowserKitAssertionsTrait; /** - * Asserts that the retrieved JSON contains has the specified subset. + * Asserts that the retrieved JSON contains the specified subset. * - * This method delegates to self::assertArraySubset(). + * This method delegates to static::assertArraySubset(). * * @param array|string $subset * @@ -91,6 +95,7 @@ public static function assertJsonEquals($json, string $message = ''): void public static function assertArraySubset($subset, $array, bool $checkForObjectIdentity = false, string $message = ''): void { $constraint = new ArraySubset($subset, $checkForObjectIdentity); + static::assertThat($array, $constraint, $message); } @@ -100,9 +105,24 @@ public static function assertArraySubset($subset, $array, bool $checkForObjectId public static function assertMatchesJsonSchema($jsonSchema, ?int $checkMode = null, string $message = ''): void { $constraint = new MatchesJsonSchema($jsonSchema, $checkMode); + static::assertThat(self::getHttpResponse()->toArray(false), $constraint, $message); } + public static function assertMatchesResourceCollectionJsonSchema(string $resourceClass, ?string $operationName = null, string $format = 'jsonld'): void + { + $schema = self::getSchemaFactory()->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, OperationType::COLLECTION, $operationName); + + static::assertMatchesJsonSchema((string) json_encode($schema)); + } + + public static function assertMatchesResourceItemJsonSchema(string $resourceClass, ?string $operationName = null, string $format = 'jsonld'): void + { + $schema = self::getSchemaFactory()->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, OperationType::ITEM, $operationName); + + static::assertMatchesJsonSchema((string) json_encode($schema)); + } + private static function getHttpClient(Client $newClient = null): ?Client { static $client; @@ -126,4 +146,18 @@ private static function getHttpResponse(): ResponseInterface return $response; } + + private static function getSchemaFactory(): SchemaFactoryInterface + { + /** @var ContainerInterface $container */ + $container = static::$container ?? static::$kernel->getContainer(); + + $schemaFactory = $container->has('api_platform.json_schema.schema_factory') ? $container->get('api_platform.json_schema.schema_factory') : null; + + if (!$schemaFactory instanceof SchemaFactoryInterface) { + throw new \LogicException(sprintf('You cannot use the resource JSON Schema assertions because the "%s" service cannot be fetched from the container.', 'api_platform.json_schema.schema_factory')); + } + + return $schemaFactory; + } } diff --git a/src/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php b/src/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php index eee5837667c..67977344c76 100644 --- a/src/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php +++ b/src/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint; +use ApiPlatform\Core\JsonSchema\Schema; use JsonSchema\Validator; use PHPUnit\Framework\Constraint\Constraint; @@ -33,8 +34,8 @@ final class MatchesJsonSchema extends Constraint */ public function __construct($schema, ?int $checkMode = null) { - $this->checkMode = $checkMode; $this->schema = \is_array($schema) ? (object) $schema : json_decode($schema); + $this->checkMode = $checkMode; } /** @@ -46,7 +47,7 @@ public function toString(): string } /** - * @param array $other + * @param array|object $other */ protected function matches($other): bool { @@ -54,7 +55,8 @@ protected function matches($other): bool throw new \RuntimeException('The "justinrainbow/json-schema" library must be installed to use "assertMatchesJsonSchema()". Try running "composer require --dev justinrainbow/json-schema".'); } - $other = (object) $other; + $other = (string) json_encode($other); + $other = json_decode($other); $validator = new Validator(); $validator->validate($other, $this->schema, $this->checkMode); @@ -63,11 +65,12 @@ protected function matches($other): bool } /** - * @param object $other + * @param array|object $other */ protected function additionalFailureDescription($other): string { - $other = (object) $other; + $other = (string) json_encode($other); + $other = json_decode($other); $validator = new Validator(); $validator->check($other, $this->schema); diff --git a/src/Hydra/JsonSchema/SchemaFactory.php b/src/Hydra/JsonSchema/SchemaFactory.php index 2400daf3364..adfcad521f2 100644 --- a/src/Hydra/JsonSchema/SchemaFactory.php +++ b/src/Hydra/JsonSchema/SchemaFactory.php @@ -47,9 +47,9 @@ public function __construct(BaseSchemaFactory $schemaFactory) /** * {@inheritdoc} */ - public function buildSchema(string $resourceClass, string $format = 'jsonld', bool $output = true, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema + public function buildSchema(string $resourceClass, string $format = 'jsonld', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema { - $schema = $this->schemaFactory->buildSchema($resourceClass, $format, $output, $operationType, $operationName, $schema, $serializerContext, $forceCollection); + $schema = $this->schemaFactory->buildSchema($resourceClass, $format, $type, $operationType, $operationName, $schema, $serializerContext, $forceCollection); if ('jsonld' !== $format) { return $schema; } @@ -74,24 +74,29 @@ public function buildSchema(string $resourceClass, string $format = 'jsonld', bo ], 'hydra:totalItems' => [ 'type' => 'integer', - 'minimum' => 1, + 'minimum' => 0, ], 'hydra:view' => [ 'type' => 'object', 'properties' => [ - '@id' => ['type' => 'string'], - '@type' => ['type' => 'string'], + '@id' => [ + 'type' => 'string', + 'format' => 'iri-reference', + ], + '@type' => [ + 'type' => 'string', + ], 'hydra:first' => [ - 'type' => 'integer', - 'minimum' => 1, + 'type' => 'string', + 'format' => 'iri-reference', ], 'hydra:last' => [ - 'type' => 'integer', - 'minimum' => 1, + 'type' => 'string', + 'format' => 'iri-reference', ], 'hydra:next' => [ - 'type' => 'integer', - 'minimum' => 1, + 'type' => 'string', + 'format' => 'iri-reference', ], ], ], @@ -116,6 +121,9 @@ public function buildSchema(string $resourceClass, string $format = 'jsonld', bo ], ], ]; + $schema['required'] = [ + 'hydra:member', + ]; return $schema; } diff --git a/src/JsonSchema/Command/JsonSchemaGenerateCommand.php b/src/JsonSchema/Command/JsonSchemaGenerateCommand.php index e930854d186..c322da6ac9b 100644 --- a/src/JsonSchema/Command/JsonSchemaGenerateCommand.php +++ b/src/JsonSchema/Command/JsonSchemaGenerateCommand.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Core\JsonSchema\Command; use ApiPlatform\Core\Api\OperationType; +use ApiPlatform\Core\JsonSchema\Schema; use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidOptionException; @@ -53,7 +54,7 @@ protected function configure() ->addOption('itemOperation', null, InputOption::VALUE_REQUIRED, 'The item operation') ->addOption('collectionOperation', null, InputOption::VALUE_REQUIRED, 'The collection operation') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The response format', (string) $this->formats[0]) - ->addOption('type', null, InputOption::VALUE_REQUIRED, 'The type of schema to generate (input or output)', 'input'); + ->addOption('type', null, InputOption::VALUE_REQUIRED, sprintf('The type of schema to generate (%s or %s)', Schema::TYPE_INPUT, Schema::TYPE_OUTPUT), Schema::TYPE_INPUT); } /** @@ -71,11 +72,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $collectionOperation = $input->getOption('collectionOperation'); /** @var string $format */ $format = $input->getOption('format'); - /** @var string $outputType */ - $outputType = $input->getOption('type'); + /** @var string $type */ + $type = $input->getOption('type'); - if (!\in_array($outputType, ['input', 'output'], true)) { - $io->error('You can only use "input" or "output" for the "type" option'); + if (!\in_array($type, [Schema::TYPE_INPUT, Schema::TYPE_OUTPUT], true)) { + $io->error(sprintf('You can only use "%s" or "%s" for the "type" option', Schema::TYPE_INPUT, Schema::TYPE_OUTPUT)); return 1; } @@ -100,10 +101,10 @@ protected function execute(InputInterface $input, OutputInterface $output) $operationName = $itemOperation ?? $collectionOperation; } - $schema = $this->schemaFactory->buildSchema($resource, $format, 'output' === $outputType, $operationType, $operationName); + $schema = $this->schemaFactory->buildSchema($resource, $format, $type, $operationType, $operationName); if (null !== $operationType && null !== $operationName && !$schema->isDefined()) { - $io->error(sprintf('There is no %ss defined for the operation "%s" of the resource "%s".', $outputType, $operationName, $resource)); + $io->error(sprintf('There is no %s defined for the operation "%s" of the resource "%s".', $type, $operationName, $resource)); return 1; } diff --git a/src/JsonSchema/Schema.php b/src/JsonSchema/Schema.php index 0bc333094e6..29a9add5ba8 100644 --- a/src/JsonSchema/Schema.php +++ b/src/JsonSchema/Schema.php @@ -27,9 +27,11 @@ */ final class Schema extends \ArrayObject { + public const TYPE_INPUT = 'input'; + public const TYPE_OUTPUT = 'output'; public const VERSION_JSON_SCHEMA = 'json-schema'; - public const VERSION_SWAGGER = 'swagger'; public const VERSION_OPENAPI = 'openapi'; + public const VERSION_SWAGGER = 'swagger'; private $version; @@ -49,6 +51,8 @@ public function getVersion(): string } /** + * {@inheritdoc} + * * @param bool $includeDefinitions if set to false, definitions will not be included in the resulting array */ public function getArrayCopy(bool $includeDefinitions = true): array diff --git a/src/JsonSchema/SchemaFactory.php b/src/JsonSchema/SchemaFactory.php index 923f0bb145d..addfe1e5d87 100644 --- a/src/JsonSchema/SchemaFactory.php +++ b/src/JsonSchema/SchemaFactory.php @@ -62,20 +62,20 @@ public function addDistinctFormat(string $format): void /** * {@inheritdoc} */ - public function buildSchema(string $resourceClass, string $format = 'json', bool $output = true, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema + public function buildSchema(string $resourceClass, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema { $schema = $schema ?? new Schema(); - if (null === $metadata = $this->getMetadata($resourceClass, $output, $operationType, $operationName, $serializerContext)) { + if (null === $metadata = $this->getMetadata($resourceClass, $type, $operationType, $operationName, $serializerContext)) { return $schema; } [$resourceMetadata, $serializerContext, $inputOrOutputClass] = $metadata; $version = $schema->getVersion(); - $definitionName = $this->buildDefinitionName($resourceClass, $format, $output, $operationType, $operationName, $serializerContext); + $definitionName = $this->buildDefinitionName($resourceClass, $format, $type, $operationType, $operationName, $serializerContext); $method = (null !== $operationType && null !== $operationName) ? $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method') : 'GET'; - if (!$output && !\in_array($method, ['POST', 'PATCH', 'PUT'], true)) { + if (Schema::TYPE_OUTPUT !== $type && !\in_array($method, ['POST', 'PATCH', 'PUT'], true)) { return $schema; } @@ -196,9 +196,9 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str $schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = $propertySchema; } - private function buildDefinitionName(string $resourceClass, string $format = 'json', bool $output = true, ?string $operationType = null, ?string $operationName = null, ?array $serializerContext = null): string + private function buildDefinitionName(string $resourceClass, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?array $serializerContext = null): string { - [$resourceMetadata, $serializerContext, $inputOrOutputClass] = $this->getMetadata($resourceClass, $output, $operationType, $operationName, $serializerContext); + [$resourceMetadata, $serializerContext, $inputOrOutputClass] = $this->getMetadata($resourceClass, $type, $operationType, $operationName, $serializerContext); $prefix = $resourceMetadata->getShortName(); if (null !== $inputOrOutputClass && $resourceClass !== $inputOrOutputClass) { @@ -220,10 +220,10 @@ private function buildDefinitionName(string $resourceClass, string $format = 'js return $name; } - private function getMetadata(string $resourceClass, bool $output, ?string $operationType, ?string $operationName, ?array $serializerContext): ?array + private function getMetadata(string $resourceClass, string $type = Schema::TYPE_OUTPUT, ?string $operationType, ?string $operationName, ?array $serializerContext): ?array { $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $output ? 'output' : 'input'; + $attribute = Schema::TYPE_OUTPUT === $type ? 'output' : 'input'; if (null === $operationType || null === $operationName) { $inputOrOutput = $resourceMetadata->getAttribute($attribute, ['class' => $resourceClass]); } else { @@ -237,14 +237,14 @@ private function getMetadata(string $resourceClass, bool $output, ?string $opera return [ $resourceMetadata, - $serializerContext ?? $this->getSerializerContext($resourceMetadata, $output, $operationType, $operationName), + $serializerContext ?? $this->getSerializerContext($resourceMetadata, $type, $operationType, $operationName), $inputOrOutput['class'], ]; } - private function getSerializerContext(ResourceMetadata $resourceMetadata, bool $output, ?string $operationType, ?string $operationName): array + private function getSerializerContext(ResourceMetadata $resourceMetadata, string $type = Schema::TYPE_OUTPUT, ?string $operationType, ?string $operationName): array { - $attribute = $output ? 'normalization_context' : 'denormalization_context'; + $attribute = Schema::TYPE_OUTPUT === $type ? 'normalization_context' : 'denormalization_context'; if (null === $operationType || null === $operationName) { return $resourceMetadata->getAttribute($attribute, []); diff --git a/src/JsonSchema/SchemaFactoryInterface.php b/src/JsonSchema/SchemaFactoryInterface.php index fcae80c4a7c..3f43c58ac86 100644 --- a/src/JsonSchema/SchemaFactoryInterface.php +++ b/src/JsonSchema/SchemaFactoryInterface.php @@ -27,5 +27,5 @@ interface SchemaFactoryInterface /** * @throws ResourceClassNotFoundException */ - public function buildSchema(string $resourceClass, string $format = 'json', bool $output = true, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema; + public function buildSchema(string $resourceClass, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema; } diff --git a/src/JsonSchema/TypeFactory.php b/src/JsonSchema/TypeFactory.php index dbbba31efd5..9b1eae4b582 100644 --- a/src/JsonSchema/TypeFactory.php +++ b/src/JsonSchema/TypeFactory.php @@ -82,12 +82,9 @@ private function getClassType(?string $className, string $format = 'json', ?bool $version = $schema->getVersion(); $subSchema = new Schema($version); - /* - * @var Schema $schema Prevents a false positive in PHPStan - */ $subSchema->setDefinitions($schema->getDefinitions()); // Populate definitions of the main schema - $this->schemaFactory->buildSchema($className, $format, true, null, null, $subSchema, $serializerContext); + $this->schemaFactory->buildSchema($className, $format, Schema::TYPE_OUTPUT, null, null, $subSchema, $serializerContext); return ['$ref' => $subSchema['$ref']]; } diff --git a/src/Swagger/Serializer/DocumentationNormalizer.php b/src/Swagger/Serializer/DocumentationNormalizer.php index 3cb9f5b199b..d975cec1818 100644 --- a/src/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Swagger/Serializer/DocumentationNormalizer.php @@ -288,10 +288,10 @@ private function getPathOperation(bool $v3, string $operationName, array $operat /** * @return array the update message as first value, and if the schema is defined as second */ - private function addSchemas(bool $v3, array $message, \ArrayObject $definitions, string $resourceClass, string $operationType, string $operationName, array $mimeTypes, bool $output = true, bool $forceCollection = false) + private function addSchemas(bool $v3, array $message, \ArrayObject $definitions, string $resourceClass, string $operationType, string $operationName, array $mimeTypes, string $type = Schema::TYPE_OUTPUT, bool $forceCollection = false) { if (!$v3) { - $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $output, $operationType, $operationName, 'json', null, $forceCollection); + $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $type, $operationType, $operationName, 'json', null, $forceCollection); if (!$jsonSchema->isDefined()) { return [$message, false]; } @@ -302,7 +302,7 @@ private function addSchemas(bool $v3, array $message, \ArrayObject $definitions, } foreach ($mimeTypes as $mimeType => $format) { - $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $output, $operationType, $operationName, $format, null, $forceCollection); + $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $type, $operationType, $operationName, $format, null, $forceCollection); if (!$jsonSchema->isDefined()) { return [$message, false]; } @@ -437,7 +437,7 @@ private function addSubresourceOperation(bool $v3, array $subresourceOperation, $successResponse = [ 'description' => sprintf('%s %s response', $subresourceOperation['shortNames'][0], $collection ? 'collection' : 'resource'), ]; - [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $subresourceOperation['resource_class'], OperationType::SUBRESOURCE, $operationName, $mimeTypes, true, $collection); + [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $subresourceOperation['resource_class'], OperationType::SUBRESOURCE, $operationName, $mimeTypes, Schema::TYPE_OUTPUT, $collection); $pathOperation['responses'] = ['200' => $successResponse, '404' => ['description' => 'Resource not found']]; @@ -525,7 +525,7 @@ private function addRequestBody(bool $v3, \ArrayObject $pathOperation, \ArrayObj return $pathOperation; } - [$message, $defined] = $this->addSchemas($v3, [], $definitions, $resourceClass, $operationType, $operationName, $requestMimeTypes, false); + [$message, $defined] = $this->addSchemas($v3, [], $definitions, $resourceClass, $operationType, $operationName, $requestMimeTypes, Schema::TYPE_INPUT); if (!$defined) { return $pathOperation; } @@ -570,12 +570,12 @@ private function addItemOperationParameters(bool $v3, \ArrayObject $pathOperatio return $pathOperation; } - private function getJsonSchema(bool $v3, \ArrayObject $definitions, string $resourceClass, bool $output, ?string $operationType, ?string $operationName, string $format = 'json', ?array $serializerContext = null, bool $forceCollection = false): Schema + private function getJsonSchema(bool $v3, \ArrayObject $definitions, string $resourceClass, string $type, ?string $operationType, ?string $operationName, string $format = 'json', ?array $serializerContext = null, bool $forceCollection = false): Schema { $schema = new Schema($v3 ? Schema::VERSION_OPENAPI : Schema::VERSION_SWAGGER); $schema->setDefinitions($definitions); - $this->jsonSchemaFactory->buildSchema($resourceClass, $format, $output, $operationType, $operationName, $schema, $serializerContext, $forceCollection); + $this->jsonSchemaFactory->buildSchema($resourceClass, $format, $type, $operationType, $operationName, $schema, $serializerContext, $forceCollection); return $schema; } diff --git a/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php b/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php index 8fe4b94f0c6..30c565f1f05 100644 --- a/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php +++ b/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Core\Tests\Bridge\Symfony\Bundle\Test; use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Model\ResourceInterface; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Runner\Version; @@ -120,6 +121,18 @@ public function testAssertMatchesJsonSchema(): void $this->assertMatchesJsonSchema(json_decode($jsonSchema, true)); } + public function testAssertMatchesResourceCollectionJsonSchema(): void + { + self::createClient()->request('GET', '/resource_interfaces'); + $this->assertMatchesResourceCollectionJsonSchema(ResourceInterface::class); + } + + public function testAssertMatchesResourceItemJsonSchema(): void + { + self::createClient()->request('GET', '/resource_interfaces/some-id'); + $this->assertMatchesResourceItemJsonSchema(ResourceInterface::class); + } + // Next tests have been imported from dms/phpunit-arraysubset-asserts, because the original constraint has been deprecated. public function testAssertArraySubsetPassesStrictConfig(): void diff --git a/tests/Fixtures/TestBundle/DataProvider/SerializableItemDataProvider.php b/tests/Fixtures/TestBundle/DataProvider/SerializableItemDataProvider.php index c9e9522d31e..8e677953500 100644 --- a/tests/Fixtures/TestBundle/DataProvider/SerializableItemDataProvider.php +++ b/tests/Fixtures/TestBundle/DataProvider/SerializableItemDataProvider.php @@ -14,16 +14,16 @@ namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\DataProvider; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; +use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use ApiPlatform\Core\DataProvider\SerializerAwareDataProviderInterface; use ApiPlatform\Core\DataProvider\SerializerAwareDataProviderTrait; -use ApiPlatform\Core\Exception\ResourceClassNotSupportedException; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\SerializableResource as SerializableResourceDocument; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\SerializableResource; /** * @author Vincent Chalamon */ -class SerializableItemDataProvider implements ItemDataProviderInterface, SerializerAwareDataProviderInterface +class SerializableItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface, SerializerAwareDataProviderInterface { use SerializerAwareDataProviderTrait; @@ -32,10 +32,6 @@ class SerializableItemDataProvider implements ItemDataProviderInterface, Seriali */ public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) { - if (!\in_array($resourceClass, [SerializableResource::class, SerializableResourceDocument::class], true)) { - throw new ResourceClassNotSupportedException(); - } - return $this->getSerializer()->deserialize(<<<'JSON' { "id": 1, @@ -45,4 +41,12 @@ public function getItem(string $resourceClass, $id, string $operationName = null JSON , $resourceClass, 'json'); } + + /** + * {@inheritdoc} + */ + public function supports(string $resourceClass, string $operationName = null, array $context = []): bool + { + return \in_array($resourceClass, [SerializableResource::class, SerializableResourceDocument::class], true); + } } diff --git a/tests/Hydra/JsonSchema/SchemaFactoryTest.php b/tests/Hydra/JsonSchema/SchemaFactoryTest.php index 545901f2076..e3ecfbbfd28 100644 --- a/tests/Hydra/JsonSchema/SchemaFactoryTest.php +++ b/tests/Hydra/JsonSchema/SchemaFactoryTest.php @@ -15,6 +15,7 @@ use ApiPlatform\Core\Api\OperationType; use ApiPlatform\Core\Hydra\JsonSchema\SchemaFactory; +use ApiPlatform\Core\JsonSchema\Schema; use ApiPlatform\Core\JsonSchema\SchemaFactory as BaseSchemaFactory; use ApiPlatform\Core\JsonSchema\TypeFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; @@ -77,7 +78,7 @@ public function testHasRootDefinitionKeyBuildSchema(): void public function testSchemaTypeBuildSchema(): void { - $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', true, OperationType::COLLECTION); + $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, OperationType::COLLECTION); $this->assertNull($resultSchema->getRootDefinitionKey()); $this->assertArrayHasKey('properties', $resultSchema); @@ -86,7 +87,7 @@ public function testSchemaTypeBuildSchema(): void $this->assertArrayHasKey('hydra:view', $resultSchema['properties']); $this->assertArrayHasKey('hydra:search', $resultSchema['properties']); - $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', true, null, null, null, null, true); + $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, null, null, null, null, true); $this->assertNull($resultSchema->getRootDefinitionKey()); $this->assertArrayHasKey('properties', $resultSchema); diff --git a/tests/JsonSchema/TypeFactoryTest.php b/tests/JsonSchema/TypeFactoryTest.php index 653bc56c226..3afb48a0419 100644 --- a/tests/JsonSchema/TypeFactoryTest.php +++ b/tests/JsonSchema/TypeFactoryTest.php @@ -47,7 +47,7 @@ public function testGetClassType(): void { $schemaFactoryProphecy = $this->prophesize(SchemaFactoryInterface::class); - $schemaFactoryProphecy->buildSchema(Dummy::class, 'jsonld', true, null, null, Argument::type(Schema::class), ['foo' => 'bar'])->will(function (array $args) { + $schemaFactoryProphecy->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, null, null, Argument::type(Schema::class), ['foo' => 'bar'])->will(function (array $args) { $args[5]['$ref'] = 'ref'; return $args[5];