diff --git a/features/main/crud.feature b/features/main/crud.feature index 61b6a78a1b7..3cd5760ff4d 100644 --- a/features/main/crud.feature +++ b/features/main/crud.feature @@ -91,6 +91,12 @@ Feature: Create-Retrieve-Update-Delete } """ + Scenario: Create a resource with empty body + When I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/dummies" + Then the response status code should be 400 + And the JSON node "hydra:description" should be equal to "Syntax error" + Scenario: Get a not found exception When I send a "GET" request to "/dummies/42" Then the response status code should be 404 @@ -526,42 +532,8 @@ Feature: Create-Retrieve-Update-Delete Scenario: Update a resource with empty body When I add "Content-Type" header equal to "application/ld+json" And I send a "PUT" request to "/dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/dummies/1" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies/1", - "@type": "Dummy", - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": "2018-12-01T13:12:00+00:00", - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": null, - "relatedDummies": [], - "jsonData": [ - { - "key": "value1" - }, - { - "key": "value2" - } - ], - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "A nice dummy", - "alias": null, - "foo": null - } - """ + Then the response status code should be 400 + And the JSON node "hydra:description" should be equal to "Syntax error" Scenario: Delete a resource When I send a "DELETE" request to "/dummies/1" diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index 2aebccb946b..e1fecd99901 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -32,7 +32,7 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Messenger\MessageBusInterface; -use Symfony\Component\Serializer\Exception\ExceptionInterface; +use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerExceptionInterface; /** * The configuration of the bundle. @@ -369,7 +369,7 @@ private function addExceptionToStatusSection(ArrayNodeDefinition $rootNode): voi ->children() ->arrayNode('exception_to_status') ->defaultValue([ - ExceptionInterface::class => Response::HTTP_BAD_REQUEST, + SerializerExceptionInterface::class => Response::HTTP_BAD_REQUEST, InvalidArgumentException::class => Response::HTTP_BAD_REQUEST, FilterValidationException::class => Response::HTTP_BAD_REQUEST, OptimisticLockException::class => Response::HTTP_CONFLICT, diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 458a990747c..47e1aa9f81d 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -154,6 +154,7 @@ + @@ -161,7 +162,7 @@ - + @@ -170,6 +171,7 @@ + @@ -177,6 +179,7 @@ + diff --git a/src/EventListener/DeserializeListener.php b/src/EventListener/DeserializeListener.php index 8bb02323655..2eecf7682db 100644 --- a/src/EventListener/DeserializeListener.php +++ b/src/EventListener/DeserializeListener.php @@ -16,6 +16,8 @@ use ApiPlatform\Core\Api\FormatMatcher; use ApiPlatform\Core\Api\FormatsProviderInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use ApiPlatform\Core\Util\RequestAttributesExtractor; use Symfony\Component\HttpFoundation\Request; @@ -31,6 +33,10 @@ */ final class DeserializeListener { + use ToggleableOperationAttributeTrait; + + public const OPERATION_ATTRIBUTE_KEY = 'deserialize'; + private $serializer; private $serializerContextBuilder; private $formats = []; @@ -40,7 +46,7 @@ final class DeserializeListener /** * @throws InvalidArgumentException */ - public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, /* FormatsProviderInterface */$formatsProvider) + public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, /* FormatsProviderInterface */$formatsProvider, ResourceMetadataFactoryInterface $resourceMetadataFactory = null) { $this->serializer = $serializer; $this->serializerContextBuilder = $serializerContextBuilder; @@ -54,6 +60,7 @@ public function __construct(SerializerInterface $serializer, SerializerContextBu $this->formatsProvider = $formatsProvider; } + $this->resourceMetadataFactory = $resourceMetadataFactory; } /** @@ -69,18 +76,12 @@ public function onKernelRequest(GetResponseEvent $event): void || $request->isMethodSafe(false) || !($attributes = RequestAttributesExtractor::extractAttributes($request)) || !$attributes['receive'] - || ( - '' === ($requestContent = $request->getContent()) - && ('POST' === $method || 'PUT' === $method) - ) + || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) ) { return; } $context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes); - if (isset($context['input']) && \array_key_exists('class', $context['input']) && null === $context['input']['class']) { - return; - } // BC check to be removed in 3.0 if (null !== $this->formatsProvider) { @@ -96,9 +97,7 @@ public function onKernelRequest(GetResponseEvent $event): void $request->attributes->set( 'data', - $this->serializer->deserialize( - $requestContent, $context['resource_class'], $format, $context - ) + $this->serializer->deserialize($request->getContent(), $context['resource_class'], $format, $context) ); } diff --git a/src/EventListener/ReadListener.php b/src/EventListener/ReadListener.php index 0116f6e0322..d4e9215dcf2 100644 --- a/src/EventListener/ReadListener.php +++ b/src/EventListener/ReadListener.php @@ -20,6 +20,8 @@ use ApiPlatform\Core\Exception\InvalidIdentifierException; use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Identifier\IdentifierConverterInterface; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use ApiPlatform\Core\Util\RequestAttributesExtractor; use ApiPlatform\Core\Util\RequestParser; @@ -34,16 +36,20 @@ final class ReadListener { use OperationDataProviderTrait; + use ToggleableOperationAttributeTrait; + + public const OPERATION_ATTRIBUTE_KEY = 'read'; private $serializerContextBuilder; - public function __construct(CollectionDataProviderInterface $collectionDataProvider, ItemDataProviderInterface $itemDataProvider, SubresourceDataProviderInterface $subresourceDataProvider = null, SerializerContextBuilderInterface $serializerContextBuilder = null, IdentifierConverterInterface $identifierConverter = null) + public function __construct(CollectionDataProviderInterface $collectionDataProvider, ItemDataProviderInterface $itemDataProvider, SubresourceDataProviderInterface $subresourceDataProvider = null, SerializerContextBuilderInterface $serializerContextBuilder = null, IdentifierConverterInterface $identifierConverter = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null) { $this->collectionDataProvider = $collectionDataProvider; $this->itemDataProvider = $itemDataProvider; $this->subresourceDataProvider = $subresourceDataProvider; $this->serializerContextBuilder = $serializerContextBuilder; $this->identifierConverter = $identifierConverter; + $this->resourceMetadataFactory = $resourceMetadataFactory; } /** @@ -57,6 +63,8 @@ public function onKernelRequest(GetResponseEvent $event): void if ( !($attributes = RequestAttributesExtractor::extractAttributes($request)) || !$attributes['receive'] + || $request->isMethod('POST') && isset($attributes['collection_operation_name']) + || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) ) { return; } @@ -74,7 +82,7 @@ public function onKernelRequest(GetResponseEvent $event): void } if (isset($attributes['collection_operation_name'])) { - $request->attributes->set('data', $request->isMethod('POST') ? null : $this->getCollectionData($attributes, $context)); + $request->attributes->set('data', $this->getCollectionData($attributes, $context)); return; } diff --git a/src/EventListener/SerializeListener.php b/src/EventListener/SerializeListener.php index 7598bcbf534..bfb9abd318b 100644 --- a/src/EventListener/SerializeListener.php +++ b/src/EventListener/SerializeListener.php @@ -14,6 +14,8 @@ namespace ApiPlatform\Core\EventListener; use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Core\Serializer\ResourceList; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use ApiPlatform\Core\Util\RequestAttributesExtractor; @@ -32,13 +34,18 @@ */ final class SerializeListener { + use ToggleableOperationAttributeTrait; + + public const OPERATION_ATTRIBUTE_KEY = 'serialize'; + private $serializer; private $serializerContextBuilder; - public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder) + public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, ResourceMetadataFactoryInterface $resourceMetadataFactory = null) { $this->serializer = $serializer; $this->serializerContextBuilder = $serializerContextBuilder; + $this->resourceMetadataFactory = $resourceMetadataFactory; } /** @@ -49,7 +56,11 @@ public function onKernelView(GetResponseForControllerResultEvent $event): void $controllerResult = $event->getControllerResult(); $request = $event->getRequest(); - if ($controllerResult instanceof Response || !(($attributes = RequestAttributesExtractor::extractAttributes($request))['respond'] ?? $request->attributes->getBoolean('_api_respond', false))) { + if ( + $controllerResult instanceof Response + || !(($attributes = RequestAttributesExtractor::extractAttributes($request))['respond'] ?? $request->attributes->getBoolean('_api_respond', false)) + || $attributes && $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) + ) { return; } diff --git a/src/EventListener/WriteListener.php b/src/EventListener/WriteListener.php index 844205860a9..26ae4829baa 100644 --- a/src/EventListener/WriteListener.php +++ b/src/EventListener/WriteListener.php @@ -16,7 +16,9 @@ use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\DataPersister\DataPersisterInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Core\Util\RequestAttributesExtractor; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; /** @@ -27,6 +29,10 @@ */ final class WriteListener { + use ToggleableOperationAttributeTrait; + + public const OPERATION_ATTRIBUTE_KEY = 'write'; + private $dataPersister; private $iriConverter; private $resourceMetadataFactory; @@ -43,12 +49,19 @@ public function __construct(DataPersisterInterface $dataPersister, IriConverterI */ public function onKernelView(GetResponseForControllerResultEvent $event): void { + $controllerResult = $event->getControllerResult(); $request = $event->getRequest(); - if ($request->isMethodSafe(false) || !($attributes = RequestAttributesExtractor::extractAttributes($request)) || !$attributes['persist']) { + + if ( + $controllerResult instanceof Response + || $request->isMethodSafe(false) + || !($attributes = RequestAttributesExtractor::extractAttributes($request)) + || !$attributes['persist'] + || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) + ) { return; } - $controllerResult = $event->getControllerResult(); if (!$this->dataPersister->supports($controllerResult, $attributes)) { return; } diff --git a/src/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php b/src/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php index 7702f558617..c5809ffb0c6 100644 --- a/src/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php +++ b/src/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php @@ -66,12 +66,20 @@ private function getTransformedOperations(array $operations, array $resourceAttr $operation['output'] = isset($operation['output']) ? $this->transformInputOutput($operation['output']) : $resourceAttributes['output']; if ( - !isset($operation['status']) - && isset($operation['output']) + isset($operation['input']) + && \array_key_exists('class', $operation['input']) + && null === $operation['input']['class'] + ) { + $operation['deserialize'] ?? $operation['deserialize'] = false; + $operation['validate'] ?? $operation['validate'] = false; + } + + if ( + isset($operation['output']) && \array_key_exists('class', $operation['output']) && null === $operation['output']['class'] ) { - $operation['status'] = 204; + $operation['status'] ?? $operation['status'] = 204; } } diff --git a/src/Metadata/Resource/ToggleableOperationAttributeTrait.php b/src/Metadata/Resource/ToggleableOperationAttributeTrait.php new file mode 100644 index 00000000000..3551faf6f24 --- /dev/null +++ b/src/Metadata/Resource/ToggleableOperationAttributeTrait.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Metadata\Resource; + +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; + +/** + * @internal + */ +trait ToggleableOperationAttributeTrait +{ + /** + * @var ResourceMetadataFactoryInterface|null + */ + private $resourceMetadataFactory; + + private function isOperationAttributeDisabled(array $attributes, string $attribute, bool $default = false, bool $resourceFallback = true): bool + { + if (null === $this->resourceMetadataFactory) { + return $default; + } + + $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); + + return !((bool) $resourceMetadata->getOperationAttribute($attributes, $attribute, !$default, $resourceFallback)); + } +} diff --git a/src/Validator/EventListener/ValidateListener.php b/src/Validator/EventListener/ValidateListener.php index 9ab4503676d..ff1f00fd3eb 100644 --- a/src/Validator/EventListener/ValidateListener.php +++ b/src/Validator/EventListener/ValidateListener.php @@ -15,8 +15,10 @@ use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Core\Util\RequestAttributesExtractor; use ApiPlatform\Core\Validator\ValidatorInterface; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; /** @@ -26,6 +28,10 @@ */ final class ValidateListener { + use ToggleableOperationAttributeTrait; + + public const OPERATION_ATTRIBUTE_KEY = 'validate'; + private $validator; private $resourceMetadataFactory; @@ -42,23 +48,23 @@ public function __construct(ValidatorInterface $validator, ResourceMetadataFacto */ public function onKernelView(GetResponseForControllerResultEvent $event): void { + $controllerResult = $event->getControllerResult(); $request = $event->getRequest(); + if ( - $request->isMethodSafe(false) + $controllerResult instanceof Response + || $request->isMethodSafe(false) || $request->isMethod('DELETE') || !($attributes = RequestAttributesExtractor::extractAttributes($request)) || !$attributes['receive'] + || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) ) { return; } $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - $inputMetadata = $resourceMetadata->getOperationAttribute($attributes, 'input', [], true); - if (\array_key_exists('class', $inputMetadata) && null === $inputMetadata['class']) { - return; - } $validationGroups = $resourceMetadata->getOperationAttribute($attributes, 'validation_groups', null, true); - $this->validator->validate($event->getControllerResult(), ['groups' => $validationGroups]); + $this->validator->validate($controllerResult, ['groups' => $validationGroups]); } } diff --git a/tests/Bridge/Symfony/Validator/EventListener/ValidateListenerTest.php b/tests/Bridge/Symfony/Validator/EventListener/ValidateListenerTest.php index 24acf3b971e..781116e84f7 100644 --- a/tests/Bridge/Symfony/Validator/EventListener/ValidateListenerTest.php +++ b/tests/Bridge/Symfony/Validator/EventListener/ValidateListenerTest.php @@ -37,18 +37,17 @@ class ValidateListenerTest extends TestCase public function testNotAnApiPlatformRequest() { $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate()->shouldNotBeCalled(); + $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); $validator = $validatorProphecy->reveal(); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create()->shouldNotBeCalled(); $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); $request = new Request(); $request->setMethod('POST'); $event = $this->prophesize(GetResponseForControllerResultEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); + $event->getRequest()->willReturn($request); $listener = new ValidateListener($validator, $resourceMetadataFactory); $listener->onKernelView($event->reveal()); diff --git a/tests/EventListener/DeserializeListenerTest.php b/tests/EventListener/DeserializeListenerTest.php index a28d72527f2..16426e65798 100644 --- a/tests/EventListener/DeserializeListenerTest.php +++ b/tests/EventListener/DeserializeListenerTest.php @@ -15,7 +15,10 @@ use ApiPlatform\Core\Api\FormatsProviderInterface; use ApiPlatform\Core\EventListener\DeserializeListener; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Symfony\Component\HttpFoundation\Request; @@ -40,7 +43,7 @@ public function testDoNotCallWhenRequestMethodIsSafe() $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize()->shouldNotBeCalled(); + $serializerProphecy->deserialize(Argument::cetera())->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); @@ -52,20 +55,16 @@ public function testDoNotCallWhenRequestMethodIsSafe() $listener->onKernelRequest($eventProphecy->reveal()); } - /** - * @dataProvider allowedEmptyRequestMethodsProvider - */ - public function testDoNotCallWhenSendingAndEmptyRequestContent($method) + public function testDoNotCallWhenRequestNotManaged() { $eventProphecy = $this->prophesize(GetResponseEvent::class); - $request = new Request([], [], ['data' => new \stdClass(), '_api_resource_class' => 'Foo', '_api_item_operation_name' => 'put'], [], [], [], ''); - $request->setMethod($method); - $request->headers->set('Content-Type', 'application/json'); + $request = new Request([], [], ['data' => new \stdClass()], [], [], [], '{}'); + $request->setMethod('POST'); $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize()->shouldNotBeCalled(); + $serializerProphecy->deserialize(Argument::cetera())->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); @@ -77,71 +76,51 @@ public function testDoNotCallWhenSendingAndEmptyRequestContent($method) $listener->onKernelRequest($eventProphecy->reveal()); } - public function allowedEmptyRequestMethodsProvider() - { - return [['PUT'], ['POST']]; - } - - public function testDoNotCallWhenRequestNotManaged() + public function testDoNotDeserializeWhenReceiveFlagIsFalse() { - $eventProphecy = $this->prophesize(GetResponseEvent::class); - - $request = new Request([], [], ['data' => new \stdClass()], [], [], [], '{}'); - $request->setMethod('POST'); - $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); - $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize()->shouldNotBeCalled(); + $serializerProphecy->deserialize(Argument::cetera())->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->shouldNotBeCalled(); + + $request = new Request([], [], ['data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post', '_api_receive' => false]); + $request->setMethod('POST'); + + $eventProphecy = $this->prophesize(GetResponseEvent::class); + $eventProphecy->getRequest()->willReturn($request); $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal()); $listener->onKernelRequest($eventProphecy->reveal()); } - public function testDoNotCallWhenReceiveFlagIsFalse() + public function testDoNotDeserializeWhenDisabledInOperationAttribute() { - $eventProphecy = $this->prophesize(GetResponseEvent::class); - - $request = new Request([], [], ['data' => new \stdClass(), '_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'post', '_api_receive' => false]); - $request->setMethod('POST'); - $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); - $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize()->shouldNotBeCalled(); + $serializerProphecy->deserialize(Argument::cetera())->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->shouldNotBeCalled(); + $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array')); - $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal()); - $listener->onKernelRequest($eventProphecy->reveal()); - } + $resourceMetadata = new ResourceMetadata('Dummy', null, null, [], [ + 'post' => [ + 'deserialize' => false, + ], + ]); - public function testDoNotCallWhenInputClassDisabled() - { - $eventProphecy = $this->prophesize(GetResponseEvent::class); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($resourceMetadata); - $request = new Request([], [], ['data' => new \stdClass(), '_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'post'], [], [], [], 'content'); + $request = new Request([], [], ['data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post']); $request->setMethod('POST'); - $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); - $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize()->shouldNotBeCalled(); - - $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->willReturn(['input' => ['class' => null], 'output' => ['class' => null]]); - - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->shouldNotBeCalled(); + $eventProphecy = $this->prophesize(GetResponseEvent::class); + $eventProphecy->getRequest()->willReturn($request); - $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal()); + $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($eventProphecy->reveal()); } @@ -260,7 +239,7 @@ public function testNotSupportedContentType() $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize()->shouldNotBeCalled(); + $serializerProphecy->deserialize(Argument::cetera())->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->willReturn(['input' => ['class' => 'Foo'], 'output' => ['class' => 'Foo']]); @@ -289,7 +268,7 @@ public function testNoContentType() $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize()->shouldNotBeCalled(); + $serializerProphecy->deserialize(Argument::cetera())->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->willReturn(['input' => ['class' => 'Foo'], 'output' => ['class' => 'Foo']]); @@ -311,10 +290,8 @@ public function testBadFormatsProviderParameterThrowsException() $this->expectExceptionMessage('The "$formatsProvider" argument is expected to be an implementation of the "ApiPlatform\\Core\\Api\\FormatsProviderInterface" interface.'); $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize()->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - $serializerContextBuilderProphecy->createFromRequest()->shouldNotBeCalled(); new DeserializeListener( $serializerProphecy->reveal(), @@ -330,10 +307,8 @@ public function testBadFormatsProviderParameterThrowsException() public function testLegacyFormatsParameter() { $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize()->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - $serializerContextBuilderProphecy->createFromRequest()->shouldNotBeCalled(); new DeserializeListener( $serializerProphecy->reveal(), diff --git a/tests/EventListener/ReadListenerTest.php b/tests/EventListener/ReadListenerTest.php index 2185e58e63c..f023cb440e3 100644 --- a/tests/EventListener/ReadListenerTest.php +++ b/tests/EventListener/ReadListenerTest.php @@ -20,6 +20,9 @@ use ApiPlatform\Core\Exception\InvalidIdentifierException; use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Identifier\IdentifierConverterInterface; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Symfony\Component\HttpFoundation\Request; @@ -72,26 +75,58 @@ public function testLegacyConstructor() $listener->onKernelRequest($event->reveal()); } - public function testDoNotCallWhenReceiveFlagIsFalse() + public function testDoNotReadWhenReceiveFlagIsFalse() { + $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); + $collectionDataProvider->getCollection(Argument::cetera())->shouldNotBeCalled(); + + $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); + $itemDataProvider->getItem(Argument::cetera())->shouldNotBeCalled(); + + $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); + $subresourceDataProvider->getSubresource(Argument::cetera())->shouldNotBeCalled(); + $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); + $request = new Request([], [], ['id' => 1, 'data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'put', '_api_receive' => false]); + $request->setMethod('PUT'); + + $event = $this->prophesize(GetResponseEvent::class); + $event->getRequest()->willReturn($request); + + $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); + $listener->onKernelRequest($event->reveal()); + } + + public function testDoNotReadWhenDisabledInOperationAttribute() + { $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); + $collectionDataProvider->getCollection(Argument::cetera())->shouldNotBeCalled(); $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); + $itemDataProvider->getItem(Argument::cetera())->shouldNotBeCalled(); $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource()->shouldNotBeCalled(); + $subresourceDataProvider->getSubresource(Argument::cetera())->shouldNotBeCalled(); + + $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); + + $resourceMetadata = new ResourceMetadata('Dummy', null, null, [ + 'put' => [ + 'read' => false, + ], + ]); + + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($resourceMetadata); - $request = new Request([], [], ['data' => new \stdClass(), '_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'post', '_api_receive' => false]); + $request = new Request([], [], ['id' => 1, 'data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'put']); $request->setMethod('PUT'); $event = $this->prophesize(GetResponseEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); + $event->getRequest()->willReturn($request); - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); + $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($event->reveal()); } @@ -112,13 +147,12 @@ public function testRetrieveCollectionPost() $request->setMethod('POST'); $event = $this->prophesize(GetResponseEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); + $event->getRequest()->willReturn($request); $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); $listener->onKernelRequest($event->reveal()); - $this->assertTrue($request->attributes->has('data')); - $this->assertNull($request->attributes->get('data')); + $this->assertFalse($request->attributes->has('data')); } public function testRetrieveCollectionGet() diff --git a/tests/EventListener/SerializeListenerTest.php b/tests/EventListener/SerializeListenerTest.php index 0507945d7a4..8c1c163f263 100644 --- a/tests/EventListener/SerializeListenerTest.php +++ b/tests/EventListener/SerializeListenerTest.php @@ -14,8 +14,11 @@ namespace ApiPlatform\Core\Tests\EventListener; use ApiPlatform\Core\EventListener\SerializeListener; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Core\Serializer\ResourceList; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Symfony\Component\HttpFoundation\Request; @@ -29,20 +32,19 @@ */ class SerializeListenerTest extends TestCase { - public function testDoNotSerializeResponse() + public function testDoNotSerializeWhenControllerResultIsResponse() { $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->serialize(Argument::cetera())->shouldNotBeCalled(); $request = new Request(); - $request->setRequestFormat('xml'); $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); - $eventProphecy->getControllerResult()->willReturn(new Response())->shouldBeCalled(); - $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); + $eventProphecy->getControllerResult()->willReturn(new Response()); + $eventProphecy->getRequest()->willReturn($request); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - $serializerContextBuilderProphecy->createFromRequest(Argument::cetera())->shouldNotBeCalled(); + $serializerContextBuilderProphecy->createFromRequest(Argument::cetera()); $listener = new SerializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); @@ -55,10 +57,13 @@ public function testDoNotSerializeWhenRespondFlagIsFalse() $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - $request = new Request([], [], ['_api_respond' => false]); + $dummy = new Dummy(); + + $request = new Request([], [], ['data' => $dummy, '_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post', '_api_respond' => false]); + $request->setMethod('POST'); $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); - $eventProphecy->getControllerResult()->willReturn(new \stdClass()); + $eventProphecy->getControllerResult()->willReturn($dummy); $eventProphecy->getRequest()->willReturn($request); $eventProphecy->setControllerResult(Argument::any())->shouldNotBeCalled(); @@ -66,6 +71,36 @@ public function testDoNotSerializeWhenRespondFlagIsFalse() $listener->onKernelView($eventProphecy->reveal()); } + public function testDoNotSerializeWhenDisabledInOperationAttribute() + { + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->serialize(Argument::cetera())->shouldNotBeCalled(); + + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + + $resourceMetadata = new ResourceMetadata('Dummy', null, null, [], [ + 'post' => [ + 'serialize' => false, + ], + ]); + + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($resourceMetadata); + + $dummy = new Dummy(); + + $request = new Request([], [], ['data' => $dummy, '_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post']); + $request->setMethod('POST'); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn($dummy); + $eventProphecy->getRequest()->willReturn($request); + $eventProphecy->setControllerResult(Argument::any())->shouldNotBeCalled(); + + $listener = new SerializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $listener->onKernelView($eventProphecy->reveal()); + } + public function testSerializeCollectionOperation() { $expectedContext = ['request_uri' => '', 'resource_class' => 'Foo', 'collection_operation_name' => 'get']; diff --git a/tests/EventListener/WriteListenerTest.php b/tests/EventListener/WriteListenerTest.php index 04b4834d778..d6b3412cda7 100644 --- a/tests/EventListener/WriteListenerTest.php +++ b/tests/EventListener/WriteListenerTest.php @@ -23,6 +23,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -215,21 +216,44 @@ public function testOnKernelViewWithSafeMethod() (new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event); } - public function testOnKernelViewWithPersistFlagOff() + public function testDoNotWriteWhenControllerResultIsResponse() + { + $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); + $dataPersisterProphecy->supports(Argument::cetera())->shouldNotBeCalled(); + $dataPersisterProphecy->persist(Argument::cetera())->shouldNotBeCalled(); + $dataPersisterProphecy->remove(Argument::cetera())->shouldNotBeCalled(); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + + $request = new Request(); + + $response = new Response(); + + $event = new GetResponseForControllerResultEvent( + $this->prophesize(HttpKernelInterface::class)->reveal(), + $request, + HttpKernelInterface::MASTER_REQUEST, + $response + ); + + $listener = new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()); + $listener->onKernelView($event); + } + + public function testDoNotWriteWhenPersistFlagIsFalse() { $dummy = new Dummy(); $dummy->setName('Dummyrino'); $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->shouldNotBeCalled(); - $dataPersisterProphecy->persist($dummy, Argument::type('array'))->shouldNotBeCalled(); - $dataPersisterProphecy->remove($dummy, Argument::type('array'))->shouldNotBeCalled(); + $dataPersisterProphecy->supports(Argument::cetera())->shouldNotBeCalled(); + $dataPersisterProphecy->persist(Argument::cetera())->shouldNotBeCalled(); + $dataPersisterProphecy->remove(Argument::cetera())->shouldNotBeCalled(); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled(); - $request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'head', '_api_persist' => false]); - $request->setMethod('HEAD'); + $request = new Request([], [], ['data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post', '_api_persist' => false]); + $request->setMethod('POST'); $event = new GetResponseForControllerResultEvent( $this->prophesize(HttpKernelInterface::class)->reveal(), @@ -238,7 +262,43 @@ public function testOnKernelViewWithPersistFlagOff() $dummy ); - (new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event); + $listener = new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()); + $listener->onKernelView($event); + } + + public function testDoNotWriteWhenDisabledInOperationAttribute() + { + $dummy = new Dummy(); + $dummy->setName('Dummyrino'); + + $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); + $dataPersisterProphecy->supports(Argument::cetera())->shouldNotBeCalled(); + $dataPersisterProphecy->persist(Argument::cetera())->shouldNotBeCalled(); + $dataPersisterProphecy->remove(Argument::cetera())->shouldNotBeCalled(); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + + $resourceMetadata = new ResourceMetadata('Dummy', null, null, [], [ + 'post' => [ + 'write' => false, + ], + ]); + + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($resourceMetadata); + + $request = new Request([], [], ['data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post']); + $request->setMethod('POST'); + + $event = new GetResponseForControllerResultEvent( + $this->prophesize(HttpKernelInterface::class)->reveal(), + $request, + HttpKernelInterface::MASTER_REQUEST, + $dummy + ); + + $listener = new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $listener->onKernelView($event); } public function testOnKernelViewWithNoResourceClass() diff --git a/tests/Validator/EventListener/ValidateListenerTest.php b/tests/Validator/EventListener/ValidateListenerTest.php index 892c5bd2b5e..56422cec36b 100644 --- a/tests/Validator/EventListener/ValidateListenerTest.php +++ b/tests/Validator/EventListener/ValidateListenerTest.php @@ -20,7 +20,9 @@ use ApiPlatform\Core\Validator\Exception\ValidationException; use ApiPlatform\Core\Validator\ValidatorInterface; use PHPUnit\Framework\TestCase; +use Prophecy\Argument; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -35,21 +37,19 @@ class ValidateListenerTest extends TestCase public function testNotAnApiPlatformRequest() { $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate()->shouldNotBeCalled(); - $validator = $validatorProphecy->reveal(); + $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create()->shouldNotBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); $request = new Request(); $request->setMethod('POST'); - $event = $this->prophesize(GetResponseForControllerResultEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn([]); + $eventProphecy->getRequest()->willReturn($request); - $listener = new ValidateListener($validator, $resourceMetadataFactory); - $listener->onKernelView($event->reveal()); + $listener = new ValidateListener($validatorProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $listener->onKernelView($eventProphecy->reveal()); } public function testValidatorIsCalled() @@ -67,19 +67,73 @@ public function testValidatorIsCalled() $validationViewListener->onKernelView($event); } - public function testDoNotCallWhenReceiveFlagIsFalse() + public function testDoNotValidateWhenControllerResultIsResponse() { - $data = new DummyEntity(); - $expectedValidationGroups = ['a', 'b', 'c']; + $validatorProphecy = $this->prophesize(ValidatorInterface::class); + $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); + + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + + $dummy = new DummyEntity(); + + $request = new Request([], [], ['data' => $dummy, '_api_resource_class' => DummyEntity::class, '_api_collection_operation_name' => 'post', '_api_receive' => false]); + $request->setMethod('POST'); + + $response = new Response(); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn($response); + $eventProphecy->getRequest()->willReturn($request); + + $validationViewListener = new ValidateListener($validatorProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $validationViewListener->onKernelView($eventProphecy->reveal()); + } + public function testDoNotValidateWhenReceiveFlagIsFalse() + { $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate($data, ['groups' => $expectedValidationGroups])->shouldNotBeCalled(); - $validator = $validatorProphecy->reveal(); + $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); - [$resourceMetadataFactory, $event] = $this->createEventObject($expectedValidationGroups, $data, false); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $validationViewListener = new ValidateListener($validator, $resourceMetadataFactory); - $validationViewListener->onKernelView($event); + $dummy = new DummyEntity(); + + $request = new Request([], [], ['data' => $dummy, '_api_resource_class' => DummyEntity::class, '_api_collection_operation_name' => 'post', '_api_receive' => false]); + $request->setMethod('POST'); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn($dummy); + $eventProphecy->getRequest()->willReturn($request); + + $validationViewListener = new ValidateListener($validatorProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $validationViewListener->onKernelView($eventProphecy->reveal()); + } + + public function testDoNotValidateWhenDisabledInOperationAttribute() + { + $validatorProphecy = $this->prophesize(ValidatorInterface::class); + $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); + + $resourceMetadata = new ResourceMetadata('DummyEntity', null, null, [], [ + 'post' => [ + 'validate' => false, + ], + ]); + + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(DummyEntity::class)->willReturn($resourceMetadata); + + $dummy = new DummyEntity(); + + $request = new Request([], [], ['data' => $dummy, '_api_resource_class' => DummyEntity::class, '_api_collection_operation_name' => 'post']); + $request->setMethod('POST'); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn($dummy); + $eventProphecy->getRequest()->willReturn($request); + + $validationViewListener = new ValidateListener($validatorProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $validationViewListener->onKernelView($eventProphecy->reveal()); } public function testThrowsValidationExceptionWithViolationsFound()