Skip to content

Commit

Permalink
minor Sylius#15324 Prevent from removing taxon that is in use by a pr…
Browse files Browse the repository at this point in the history
…omotion rule (TheMilek)

This PR was merged into the 1.13 branch.

Discussion
----------

| Q               | A                                                            |
|-----------------|--------------------------------------------------------------|
| Branch?         | 1.13 <!-- see the comment below -->                  |
| Bug fix?        | yes                                                       |
| New feature?    | no                                                      |
| BC breaks?      | no                                                      |
| Deprecations?   | no <!-- don't forget to update the UPGRADE-*.md file --> |
| License         | MIT                                                          |

<!--
 - Bug fixes must be submitted against the 1.12 branch
 - Features and deprecations must be submitted against the 1.13 branch
 - Make sure that the correct base branch is set

 To be sure you are not breaking any Backward Compatibilities, check the documentation:
 https://docs.sylius.com/en/latest/book/organization/backward-compatibility-promise.html
-->
related to Sylius#15277

Commits
-------
  Prevent from removing taxon that is in use by a promotion rule
  [Upgrade] Update upgrade file
  Minor improvements
  • Loading branch information
jakubtobiasz authored Sep 20, 2023
2 parents f32c1fc + da9a512 commit b3fb1cc
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 6 deletions.
10 changes: 10 additions & 0 deletions UPGRADE-API-1.13.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# UPGRADE FROM `v1.12.x` TO `v1.13.0`

1. The constructor of 'Sylius\Bundle\ApiBundle\EventSubscriber\TaxonDeletionEventSubscriber' has changed:

````diff
public function __construct(
private ChannelRepositoryInterface $channelRepository,
+ private TaxonInPromotionRuleCheckerInterface $taxonInPromotionRuleChecker,
) {
}
````

1. The signature of constructor of 'Sylius\Bundle\ApiBundle\Command\Cart\ChangeItemQuantityInCart' command changed:

````diff
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Feature: Preventing from removing taxons that are used in promotion rules
And the store has "Mugs" taxonomy
And I am logged in as an administrator

@ui @javascript
@ui @javascript @api
Scenario: Being prevented from removing taxon that is in use by a promotion rule
Given there is a promotion "Christmas sale" with "Total price of items from taxon" rule configured with "Mugs" taxon and $100 amount for "United States" channel
When I try to delete taxon named "Mugs"
Expand Down
62 changes: 62 additions & 0 deletions src/Sylius/Behat/Context/Api/Admin/RemovingTaxonContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?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\Behat\Context\Api\Admin;

use Behat\Behat\Context\Context;
use Sylius\Behat\Client\ApiClientInterface;
use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources;
use Sylius\Behat\Service\SharedStorageInterface;
use Sylius\Component\Core\Model\TaxonInterface;
use Webmozart\Assert\Assert;

final class RemovingTaxonContext implements Context
{
public function __construct(
private ApiClientInterface $client,
private ResponseCheckerInterface $responseChecker,
private SharedStorageInterface $sharedStorage,
) {
}

/**
* @When I (try to) delete taxon named :taxon
*/
public function iDeleteTaxon(TaxonInterface $taxon): void
{
$this->client->delete(Resources::TAXONS, $taxon->getCode());
$this->sharedStorage->set('taxon', $taxon);
}

/**
* @Then /^(this taxon) should still exist$/
*/
public function theTaxonShouldStillExist(TaxonInterface $taxon): void
{
$this->client->show(Resources::TAXONS, $taxon->getCode());

Assert::true($this->responseChecker->isShowSuccessful($this->client->getLastResponse()));
}

/**
* @Then I should be notified that this taxon could not be deleted as it is in use by a promotion rule
*/
public function iShouldBeNotifiedThatThisTaxonCouldNotBeDeleted(): void
{
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
'Cannot delete a taxon that is in use by a promotion rule.',
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
</service>

<service id="Sylius\Behat\Context\Api\Admin\RemovingTaxonContext">
<argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
<argument type="service" id="sylius.behat.shared_storage" />
</service>

<service id="sylius.behat.context.api.admin.managing_promotions" class="Sylius\Behat\Context\Api\Admin\ManagingPromotionsContext">
<argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ default:
- sylius.behat.context.transform.lexical
- sylius.behat.context.transform.product
- sylius.behat.context.transform.promotion
- sylius.behat.context.transform.taxon
- sylius.behat.context.transform.shared_storage

- sylius.behat.context.setup.channel
- sylius.behat.context.setup.product
- sylius.behat.context.setup.promotion
- sylius.behat.context.setup.taxonomy
- sylius.behat.context.setup.admin_api_security

- sylius.behat.context.api.admin.managing_promotions
- Sylius\Behat\Context\Api\Admin\RemovingProductContext
- Sylius\Behat\Context\Api\Admin\RemovingTaxonContext

filters:
tags: "@managing_promotions&&@api"
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@

use ApiPlatform\Core\EventListener\EventPriorities;
use Sylius\Bundle\ApiBundle\Exception\CannotRemoveMenuTaxonException;
use Sylius\Bundle\ApiBundle\Exception\TaxonCannotBeRemoved;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Core\Promotion\Checker\TaxonInPromotionRuleCheckerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
Expand All @@ -27,13 +29,17 @@ final class TaxonDeletionEventSubscriber implements EventSubscriberInterface
{
public function __construct(
private ChannelRepositoryInterface $channelRepository,
private TaxonInPromotionRuleCheckerInterface $taxonInPromotionRuleChecker,
) {
}

public static function getSubscribedEvents(): array
{
return [
KernelEvents::VIEW => ['protectFromRemovingMenuTaxon', EventPriorities::PRE_WRITE],
KernelEvents::VIEW => [
['protectFromRemovingMenuTaxon', EventPriorities::PRE_WRITE],
['protectFromRemovingTaxonInUseByPromotionRule', EventPriorities::PRE_WRITE],
],
];
}

Expand All @@ -52,4 +58,18 @@ public function protectFromRemovingMenuTaxon(ViewEvent $event): void
throw new CannotRemoveMenuTaxonException($taxon->getCode());
}
}

public function protectFromRemovingTaxonInUseByPromotionRule(ViewEvent $event): void
{
$taxon = $event->getControllerResult();
$method = $event->getRequest()->getMethod();

if (!$taxon instanceof TaxonInterface || $method !== Request::METHOD_DELETE) {
return;
}

if ($this->taxonInPromotionRuleChecker->isInUse($taxon)) {
throw new TaxonCannotBeRemoved('Cannot delete a taxon that is in use by a promotion rule.');
}
}
}
26 changes: 26 additions & 0 deletions src/Sylius/Bundle/ApiBundle/Exception/TaxonCannotBeRemoved.php
Original file line number Diff line number Diff line change
@@ -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 TaxonCannotBeRemoved extends \RuntimeException
{
public function __construct(
string $message = 'Cannot delete, the taxon is in use.',
int $code = 0,
\Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ api_platform:
Sylius\Bundle\ApiBundle\Exception\ProductCannotBeRemoved: 422
Sylius\Bundle\ApiBundle\Exception\ProvinceCannotBeRemoved: 422
Sylius\Bundle\ApiBundle\Exception\ShippingMethodCannotBeRemoved: 422
Sylius\Bundle\ApiBundle\Exception\TaxonCannotBeRemoved: 422
Sylius\Bundle\ApiBundle\Exception\ZoneCannotBeRemoved: 422
Sylius\Bundle\ApiBundle\Exception\CannotRemoveMenuTaxonException: 409
Sylius\Bundle\LocaleBundle\Checker\Exception\LocaleIsUsedException: 422
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

<service id="Sylius\Bundle\ApiBundle\EventSubscriber\TaxonDeletionEventSubscriber">
<argument type="service" id="sylius.repository.channel" />
<argument type="service" id="Sylius\Component\Core\Promotion\Checker\TaxonInPromotionRuleCheckerInterface" />
<tag name="kernel.event_subscriber" />
</service>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,32 @@ function it_does_not_throw_exception_when_product_is_not_being_deleted(
$product->getWrappedObject(),
);

$productInPromotionRuleChecker->isInUse($product)->shouldNotBeCalled();

$this->shouldNotThrow()->during('protectFromRemovingProductInUseByPromotionRule', [$event]);
}

function it_does_not_throw_exception_when_product_is_not_in_use_by_a_promotion_rule(
ProductInPromotionRuleCheckerInterface $productInPromotionRuleChecker,
ProductInterface $product,
Request $request,
HttpKernelInterface $kernel,
): void {
$request->getMethod()->willReturn(Request::METHOD_POST);

$event = new ViewEvent(
$kernel->getWrappedObject(),
$request->getWrappedObject(),
HttpKernelInterface::MAIN_REQUEST,
$product->getWrappedObject(),
);

$productInPromotionRuleChecker->isInUse($product)->willReturn(false);

$this->shouldNotThrow()->during('protectFromRemovingProductInUseByPromotionRule', [$event]);
}

function it_throws_an_exception_when_trying_to_delete_product_assigned_to_promotion_rule(
function it_throws_an_exception_when_trying_to_delete_product_that_is_in_use_by_a_promotion_rule(
ProductInPromotionRuleCheckerInterface $productInPromotionRuleChecker,
ProductInterface $product,
Request $request,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,22 @@
namespace spec\Sylius\Bundle\ApiBundle\EventSubscriber;

use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Exception\TaxonCannotBeRemoved;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Core\Promotion\Checker\TaxonInPromotionRuleCheckerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

final class TaxonDeletionEventSubscriberSpec extends ObjectBehavior
{
function let(ChannelRepositoryInterface $channelRepository): void
{
$this->beConstructedWith($channelRepository);
function let(
ChannelRepositoryInterface $channelRepository,
TaxonInPromotionRuleCheckerInterface $taxonInPromotionRuleChecker,
): void {
$this->beConstructedWith($channelRepository, $taxonInPromotionRuleChecker);
}

function it_allows_to_remove_taxon_if_any_channel_has_not_it_as_a_menu_taxon(
Expand Down Expand Up @@ -81,4 +85,67 @@ function it_throws_an_exception_if_a_subject_is_menu_taxon(
)])
;
}

function it_does_not_throw_exception_when_taxon_is_not_being_deleted(
TaxonInPromotionRuleCheckerInterface $taxonInPromotionRuleChecker,
TaxonInterface $taxon,
Request $request,
HttpKernelInterface $kernel,
): void {
$request->getMethod()->willReturn(Request::METHOD_POST);

$event = new ViewEvent(
$kernel->getWrappedObject(),
$request->getWrappedObject(),
HttpKernelInterface::MAIN_REQUEST,
$taxon->getWrappedObject(),
);

$taxonInPromotionRuleChecker->isInUse($taxon)->shouldNotBeCalled();

$this->shouldNotThrow()->during('protectFromRemovingTaxonInUseByPromotionRule', [$event]);
}

function it_does_not_throw_exception_when_taxon_is_not_in_use_by_a_promotion_rule(
TaxonInPromotionRuleCheckerInterface $taxonInPromotionRuleChecker,
TaxonInterface $taxon,
Request $request,
HttpKernelInterface $kernel,
): void {
$request->getMethod()->willReturn(Request::METHOD_POST);

$event = new ViewEvent(
$kernel->getWrappedObject(),
$request->getWrappedObject(),
HttpKernelInterface::MAIN_REQUEST,
$taxon->getWrappedObject(),
);

$taxonInPromotionRuleChecker->isInUse($taxon)->willReturn(false);

$this->shouldNotThrow()->during('protectFromRemovingTaxonInUseByPromotionRule', [$event]);
}

function it_throws_an_exception_when_trying_to_delete_taxon_that_is_in_use_by_a_promotion_rule(
TaxonInPromotionRuleCheckerInterface $taxonInPromotionRuleChecker,
TaxonInterface $taxon,
Request $request,
HttpKernelInterface $kernel,
): void {
$request->getMethod()->willReturn(Request::METHOD_DELETE);

$event = new ViewEvent(
$kernel->getWrappedObject(),
$request->getWrappedObject(),
HttpKernelInterface::MAIN_REQUEST,
$taxon->getWrappedObject(),
);

$taxonInPromotionRuleChecker->isInUse($taxon)->willReturn(true);

$this
->shouldThrow(TaxonCannotBeRemoved::class)
->during('protectFromRemovingTaxonInUseByPromotionRule', [$event])
;
}
}

0 comments on commit b3fb1cc

Please sign in to comment.