Prevent from deleting last available channel

This commit is contained in:
Kamil Grygierzec 2023-10-19 09:33:46 +02:00
parent 67761b9626
commit 6e0edf1db3
No known key found for this signature in database
GPG key ID: 7F54EE42DAD4B9E9
5 changed files with 191 additions and 0 deletions

View file

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\ApiBundle\EventListener;
use Sylius\Bundle\ApiBundle\Exception\ChannelCannotBeRemoved;
use Sylius\Component\Channel\Model\ChannelInterface;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
/** @experimental */
final class ChannelDeletionEventListener
{
/**
* @param ChannelRepositoryInterface<ChannelInterface> $channelRepository
*/
public function __construct(private ChannelRepositoryInterface $channelRepository)
{
}
public function protectFromRemovalTheOnlyChannelInStore(ViewEvent $event): void
{
$channel = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$channel instanceof ChannelInterface || $method !== Request::METHOD_DELETE) {
return;
}
$results = $this->channelRepository->findBy(['enabled' => true]);
if (!$results || (count($results) === 1 && current($results) === $channel)) {
throw new ChannelCannotBeRemoved('The channel cannot be deleted. At least one enabled channel is required.');
}
}
}

View file

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\ApiBundle\Exception;
/** @experimental */
final class ChannelCannotBeRemoved extends \RuntimeException
{
public function __construct(
string $message = 'Cannot remove, the channel is in use.',
int $code = 0,
\Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
}

View file

@ -50,6 +50,7 @@ api_platform:
# Sylius exception to status code mapping
SM\SMException: 422
Sylius\Bundle\ApiBundle\Exception\CannotRemoveCurrentlyLoggedInUser: 422
Sylius\Bundle\ApiBundle\Exception\ChannelCannotBeRemoved: 422
Sylius\Bundle\ApiBundle\Exception\InvalidProductAttributeValueTypeException: 422
Sylius\Bundle\ApiBundle\Exception\OrderItemNotFoundException: 422
Sylius\Bundle\ApiBundle\Exception\OrderNoLongerEligibleForPromotion: 422

View file

@ -177,6 +177,11 @@
<tag name="kernel.event_listener" event="lexik_jwt_authentication.on_authentication_success" method="onAuthenticationSuccessResponse" />
</service>
<service id="Sylius\Bundle\ApiBundle\EventListener\ChannelDeletionEventListener">
<argument type="service" id="sylius.repository.channel"/>
<tag name="kernel.event_listener" event="kernel.view" method="protectFromRemovalTheOnlyChannelInStore" priority="33"/>
</service>
<service id="Sylius\Bundle\ApiBundle\Validator\ResourceInputDataPropertiesValidatorInterface" class="Sylius\Bundle\ApiBundle\Validator\ResourceApiInputDataPropertiesValidator">
<argument type="service" id="validator" />
</service>

View file

@ -0,0 +1,112 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace spec\Sylius\Bundle\ApiBundle\EventListener;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Exception\ChannelCannotBeRemoved;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
final class ChannelDeletionEventListenerSpec extends ObjectBehavior
{
function let(ChannelRepositoryInterface $channelRepository)
{
$this->beConstructedWith($channelRepository);
}
function it_throws_exception_when_trying_to_delete_the_only_enabled_channel(
Request $request,
ChannelInterface $channel,
ChannelRepositoryInterface $channelRepository,
HttpKernelInterface $kernel,
): void {
$request->getMethod()->willReturn(Request::METHOD_DELETE);
$event = new ViewEvent(
$kernel->getWrappedObject(),
$request->getWrappedObject(),
HttpKernelInterface::MAIN_REQUEST,
$channel->getWrappedObject(),
);
$channelRepository->findBy(['enabled' => true])->willReturn([$channel]);
$this->shouldThrow(ChannelCannotBeRemoved::class)->during('protectFromRemovalTheOnlyChannelInStore', [$event]);
}
function it_does_nothing_when_there_are_more_than_one_enabled_channels(
Request $request,
ChannelInterface $channel,
ChannelInterface $channel2,
ChannelRepositoryInterface $channelRepository,
HttpKernelInterface $kernel,
): void {
$request->getMethod()->willReturn(Request::METHOD_DELETE);
$event = new ViewEvent(
$kernel->getWrappedObject(),
$request->getWrappedObject(),
HttpKernelInterface::MAIN_REQUEST,
$channel->getWrappedObject(),
);
$channelRepository->findBy(['enabled' => true])->willReturn([$channel, $channel2]);
$this->shouldNotThrow(ChannelCannotBeRemoved::class)->during('protectFromRemovalTheOnlyChannelInStore', [$event]);
}
function it_does_nothing_when_request_method_is_not_delete(
Request $request,
ChannelInterface $channel,
ChannelRepositoryInterface $channelRepository,
HttpKernelInterface $kernel,
): void {
$request->getMethod()->willReturn(Request::METHOD_GET);
$event = new ViewEvent(
$kernel->getWrappedObject(),
$request->getWrappedObject(),
HttpKernelInterface::MAIN_REQUEST,
$channel->getWrappedObject(),
);
$channelRepository->findBy(['enabled' => true])->willReturn([$channel]);
$this->shouldNotThrow(ChannelCannotBeRemoved::class)->during('protectFromRemovalTheOnlyChannelInStore', [$event]);
}
function it_does_nothing_when_event_controller_result_is_not_channel(
Request $request,
ChannelRepositoryInterface $channelRepository,
HttpKernelInterface $kernel,
OrderInterface $order,
): void {
$request->getMethod()->willReturn(Request::METHOD_DELETE);
$event = new ViewEvent(
$kernel->getWrappedObject(),
$request->getWrappedObject(),
HttpKernelInterface::MAIN_REQUEST,
$order->getWrappedObject(),
);
$channelRepository->findBy(['enabled' => true])->willReturn([]);
$this->shouldNotThrow(ChannelCannotBeRemoved::class)->during('protectFromRemovalTheOnlyChannelInStore', [$event]);
}
}