From cb3770b564c407ae059d67ea891602d35dd47237 Mon Sep 17 00:00:00 2001 From: Jan Goralski Date: Thu, 16 Nov 2023 15:37:31 +0100 Subject: [PATCH] [API][Admin] Deleting coupons --- .../managing_coupons/deleting_coupon.feature | 2 +- ...venting_deletion_of_coupons_in_use.feature | 2 +- phpstan-baseline.neon | 20 ++++++ .../Admin/ManagingPromotionCouponsContext.php | 65 ++++++++++++++++++ .../promotion/managing_promotion_coupons.yml | 10 +++ .../promotion/managing_promotion_coupons.yml | 1 - .../PromotionCouponDataPersister.php | 47 +++++++++++++ .../PromotionCouponCannotBeRemoved.php | 26 ++++++++ .../config/api_resources/PromotionCoupon.xml | 4 ++ .../config/services/data_persisters.xml | 5 ++ .../PromotionCouponDataPersisterSpec.php | 66 +++++++++++++++++++ tests/Api/Admin/PromotionCouponsTest.php | 22 ++++++- 12 files changed, 265 insertions(+), 5 deletions(-) create mode 100644 src/Sylius/Bundle/ApiBundle/DataPersister/PromotionCouponDataPersister.php create mode 100644 src/Sylius/Bundle/ApiBundle/Exception/PromotionCouponCannotBeRemoved.php create mode 100644 src/Sylius/Bundle/ApiBundle/spec/DataPersister/PromotionCouponDataPersisterSpec.php diff --git a/features/promotion/managing_coupons/deleting_coupon.feature b/features/promotion/managing_coupons/deleting_coupon.feature index efe196ac6f..08a737d410 100644 --- a/features/promotion/managing_coupons/deleting_coupon.feature +++ b/features/promotion/managing_coupons/deleting_coupon.feature @@ -9,7 +9,7 @@ Feature: Deleting a coupon And the store has promotion "Christmas sale" with coupon "SANTA2016" And I am logged in as an administrator - @domain @ui + @domain @ui @api Scenario: Deleted coupon should disappear from the registry When I delete "SANTA2016" coupon related to this promotion Then I should be notified that it has been successfully deleted diff --git a/features/promotion/managing_coupons/preventing_deletion_of_coupons_in_use.feature b/features/promotion/managing_coupons/preventing_deletion_of_coupons_in_use.feature index 527ffe64b5..77d37799c1 100644 --- a/features/promotion/managing_coupons/preventing_deletion_of_coupons_in_use.feature +++ b/features/promotion/managing_coupons/preventing_deletion_of_coupons_in_use.feature @@ -15,7 +15,7 @@ Feature: Not being able to delete a coupon which is in use And the customer chose "Free" shipping method to "United States" with "Cash on Delivery" payment And I am logged in as an administrator - @domain @ui + @domain @ui @api Scenario: Being unable to delete a used coupon When I try to delete "SANTA2016" coupon related to this promotion Then I should be notified that it is in use and cannot be deleted diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 783d53f57d..f34e5cc385 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -805,6 +805,26 @@ parameters: count: 1 path: src/Sylius/Bundle/ApiBundle/DataPersister/ProductTaxonDataPersister.php + - + message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\PromotionCouponDataPersister\\:\\:persist\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Sylius/Bundle/ApiBundle/DataPersister/PromotionCouponDataPersister.php + + - + message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\PromotionCouponDataPersister\\:\\:remove\\(\\) has no return type specified\\.$#" + count: 1 + path: src/Sylius/Bundle/ApiBundle/DataPersister/PromotionCouponDataPersister.php + + - + message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\PromotionCouponDataPersister\\:\\:remove\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Sylius/Bundle/ApiBundle/DataPersister/PromotionCouponDataPersister.php + + - + message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\PromotionCouponDataPersister\\:\\:supports\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Sylius/Bundle/ApiBundle/DataPersister/PromotionCouponDataPersister.php + - message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductTaxonDataPersister\\:\\:remove\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingPromotionCouponsContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingPromotionCouponsContext.php index 1a37fbf643..1e74f82b34 100644 --- a/src/Sylius/Behat/Context/Api/Admin/ManagingPromotionCouponsContext.php +++ b/src/Sylius/Behat/Context/Api/Admin/ManagingPromotionCouponsContext.php @@ -1,5 +1,14 @@ client->filter(); } + /** + * @When /^I delete ("[^"]+" coupon) related to this promotion$/ + * @When /^I try to delete ("[^"]+" coupon) related to this promotion$/ + */ + public function iDeleteCouponRelatedToThisPromotion(PromotionCouponInterface $coupon) + { + $this->client->delete(Resources::PROMOTION_COUPONS, $coupon->getCode()); + } + /** * @When /^I sort coupons by (ascending|descending) number of uses$/ */ @@ -118,6 +137,52 @@ final class ManagingPromotionCouponsContext implements Context )); } + /** + * @Then I should be notified that it has been successfully deleted + */ + public function iShouldBeNotifiedThatItHasBeenSuccessfullyDeleted(): void + { + Assert::true( + $this->responseChecker->isDeletionSuccessful($this->client->getLastResponse()), + 'Promotion coupon could not be deleted', + ); + } + + /** + * @Then /^(this coupon) should no longer exist in the coupon registry$/ + */ + public function couponShouldNotExistInTheRegistry(PromotionCouponInterface $coupon) + { + Assert::false($this->responseChecker->hasItemWithValue( + $this->client->index(Resources::PROMOTION_COUPONS), + 'code', + $coupon->getCode(), + )); + } + + /** + * @Then /^(this coupon) should still exist in the registry$/ + */ + public function couponShouldStillExistInTheRegistry(PromotionCouponInterface $coupon) + { + Assert::true($this->responseChecker->hasItemWithValue( + $this->client->index(Resources::PROMOTION_COUPONS), + 'code', + $coupon->getCode(), + )); + } + + /** + * @Then I should be notified that it is in use and cannot be deleted + */ + public function iShouldBeNotifiedThatItIsInUseAndCannotBeDeleted(): void + { + Assert::contains( + $this->responseChecker->getError($this->client->getLastResponse()), + 'Cannot delete, the promotion coupon is in use.', + ); + } + private function sortBy(string $order, string $field): void { $this->client->sort([$field => str_starts_with($order, 'de') ? 'desc' : 'asc']); diff --git a/src/Sylius/Behat/Resources/config/suites/api/promotion/managing_promotion_coupons.yml b/src/Sylius/Behat/Resources/config/suites/api/promotion/managing_promotion_coupons.yml index b39ec4817f..aee3762ad9 100644 --- a/src/Sylius/Behat/Resources/config/suites/api/promotion/managing_promotion_coupons.yml +++ b/src/Sylius/Behat/Resources/config/suites/api/promotion/managing_promotion_coupons.yml @@ -8,12 +8,22 @@ default: - sylius.behat.context.hook.doctrine_orm - sylius.behat.context.transform.coupon + - sylius.behat.context.transform.address + - sylius.behat.context.transform.customer - sylius.behat.context.transform.date_time + - sylius.behat.context.transform.lexical + - sylius.behat.context.transform.payment + - sylius.behat.context.transform.product - sylius.behat.context.transform.promotion + - sylius.behat.context.transform.shipping_method - sylius.behat.context.transform.shared_storage - sylius.behat.context.setup.channel + - sylius.behat.context.setup.payment + - sylius.behat.context.setup.product - sylius.behat.context.setup.promotion + - sylius.behat.context.setup.shipping + - sylius.behat.context.setup.order - sylius.behat.context.setup.admin_api_security - sylius.behat.context.api.admin.managing_promotion_coupons diff --git a/src/Sylius/Behat/Resources/config/suites/domain/promotion/managing_promotion_coupons.yml b/src/Sylius/Behat/Resources/config/suites/domain/promotion/managing_promotion_coupons.yml index d377ae9989..460c94b06b 100644 --- a/src/Sylius/Behat/Resources/config/suites/domain/promotion/managing_promotion_coupons.yml +++ b/src/Sylius/Behat/Resources/config/suites/domain/promotion/managing_promotion_coupons.yml @@ -28,6 +28,5 @@ default: - sylius.behat.context.domain.notification - sylius.behat.context.domain.security - filters: tags: "@managing_promotion_coupons&&@domain" diff --git a/src/Sylius/Bundle/ApiBundle/DataPersister/PromotionCouponDataPersister.php b/src/Sylius/Bundle/ApiBundle/DataPersister/PromotionCouponDataPersister.php new file mode 100644 index 0000000000..a2e8bb1d03 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/DataPersister/PromotionCouponDataPersister.php @@ -0,0 +1,47 @@ +decoratedDataPersister->persist($data, $context); + } + + public function remove($data, array $context = []) + { + try { + return $this->decoratedDataPersister->remove($data, $context); + } catch (ForeignKeyConstraintViolationException) { + throw new PromotionCouponCannotBeRemoved(); + } + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Exception/PromotionCouponCannotBeRemoved.php b/src/Sylius/Bundle/ApiBundle/Exception/PromotionCouponCannotBeRemoved.php new file mode 100644 index 0000000000..21d84fe82b --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Exception/PromotionCouponCannotBeRemoved.php @@ -0,0 +1,26 @@ +admin:promotion_coupon:read + + + DELETE + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml index 6a54784b8c..bc5248e118 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml @@ -85,6 +85,11 @@ + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/spec/DataPersister/PromotionCouponDataPersisterSpec.php b/src/Sylius/Bundle/ApiBundle/spec/DataPersister/PromotionCouponDataPersisterSpec.php new file mode 100644 index 0000000000..d1e7742ad0 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/spec/DataPersister/PromotionCouponDataPersisterSpec.php @@ -0,0 +1,66 @@ +beConstructedWith($dataPersister); + } + + function it_is_a_context_aware_persister(): void + { + $this->shouldImplement(ContextAwareDataPersisterInterface::class); + } + + function it_supports_only_promotion_coupon(PromotionCouponInterface $coupon): void + { + $this->supports(new \stdClass())->shouldReturn(false); + $this->supports($coupon)->shouldReturn(true); + } + + function it_uses_inner_persister_to_persist_promotion_coupon( + ContextAwareDataPersisterInterface $dataPersister, + PromotionCouponInterface $coupon, + ): void { + $dataPersister->persist($coupon, [])->shouldBeCalled(); + + $this->persist($coupon); + } + + function it_throws_cannot_be_removed_exception_if_constraint_fails_on_removal( + ContextAwareDataPersisterInterface $dataPersister, + PromotionCouponInterface $coupon, + ): void { + $dataPersister->remove($coupon, [])->willThrow(ForeignKeyConstraintViolationException::class); + + $this->shouldThrow(PromotionCouponCannotBeRemoved::class)->during('remove', [$coupon]); + } + + function it_uses_inner_persister_to_remove_promotion_coupon( + ContextAwareDataPersisterInterface $dataPersister, + PromotionCouponInterface $coupon, + ): void { + $dataPersister->remove($coupon, [])->shouldBeCalled(); + + $this->remove($coupon); + } +} diff --git a/tests/Api/Admin/PromotionCouponsTest.php b/tests/Api/Admin/PromotionCouponsTest.php index 6e57eade4c..c57fca2a6e 100644 --- a/tests/Api/Admin/PromotionCouponsTest.php +++ b/tests/Api/Admin/PromotionCouponsTest.php @@ -13,7 +13,7 @@ declare(strict_types=1); namespace Sylius\Tests\Api\Admin; -use Sylius\Component\Core\Model\PromotionInterface; +use Sylius\Component\Core\Model\PromotionCouponInterface; use Sylius\Tests\Api\JsonApiTestCase; use Sylius\Tests\Api\Utils\AdminUserLoginTrait; use Symfony\Component\HttpFoundation\Response; @@ -28,7 +28,7 @@ final class PromotionCouponsTest extends JsonApiTestCase $fixtures = $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'channel.yaml', 'promotion.yaml']); $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER); - /** @var PromotionInterface $promotion */ + /** @var PromotionCouponInterface $coupon */ $coupon = $fixtures['promotion_1_off_coupon_1']; $this->client->request( @@ -58,4 +58,22 @@ final class PromotionCouponsTest extends JsonApiTestCase Response::HTTP_OK, ); } + + /** @test */ + public function it_removes_a_promotion_coupon(): void + { + $fixtures = $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'channel.yaml', 'promotion.yaml']); + $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER); + + /** @var PromotionCouponInterface $coupon */ + $coupon = $fixtures['promotion_1_off_coupon_1']; + + $this->client->request( + method: 'DELETE', + uri: '/api/v2/admin/promotion-coupons/' . $coupon->getCode(), + server: $header, + ); + + $this->assertResponseCode($this->client->getResponse(), Response::HTTP_NO_CONTENT); + } }