[API][Admin] Deleting coupons

This commit is contained in:
Jan Goralski 2023-11-16 15:37:31 +01:00 committed by Wojdylak
parent 41efb5f437
commit cb3770b564
12 changed files with 265 additions and 5 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,5 +1,14 @@
<?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;
@ -9,6 +18,7 @@ use Sylius\Behat\Client\ApiClientInterface;
use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources;
use Sylius\Behat\Service\Converter\SectionAwareIriConverterInterface;
use Sylius\Component\Core\Model\PromotionCouponInterface;
use Sylius\Component\Core\Model\PromotionInterface;
use Webmozart\Assert\Assert;
@ -35,6 +45,15 @@ final class ManagingPromotionCouponsContext implements Context
$this->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']);

View file

@ -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

View file

@ -28,6 +28,5 @@ default:
- sylius.behat.context.domain.notification
- sylius.behat.context.domain.security
filters:
tags: "@managing_promotion_coupons&&@domain"

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\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Sylius\Bundle\ApiBundle\Exception\PromotionCouponCannotBeRemoved;
use Sylius\Component\Core\Model\PromotionCouponInterface;
/** @experimental */
final class PromotionCouponDataPersister implements ContextAwareDataPersisterInterface
{
public function __construct(
private ContextAwareDataPersisterInterface $decoratedDataPersister,
) {
}
public function supports($data, array $context = []): bool
{
return $data instanceof PromotionCouponInterface;
}
public function persist($data, array $context = [])
{
return $this->decoratedDataPersister->persist($data, $context);
}
public function remove($data, array $context = [])
{
try {
return $this->decoratedDataPersister->remove($data, $context);
} catch (ForeignKeyConstraintViolationException) {
throw new PromotionCouponCannotBeRemoved();
}
}
}

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 PromotionCouponCannotBeRemoved extends \RuntimeException
{
public function __construct(
string $message = 'Cannot delete, the promotion coupon is in use.',
int $code = 0,
\Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
}

View file

@ -42,6 +42,10 @@
<attribute name="groups">admin:promotion_coupon:read</attribute>
</attribute>
</itemOperation>
<itemOperation name="admin_delete">
<attribute name="method">DELETE</attribute>
</itemOperation>
</itemOperations>
<property name="id" identifier="false" writable="false" />

View file

@ -85,6 +85,11 @@
<tag name="api_platform.data_persister" />
</service>
<service id="Sylius\Bundle\ApiBundle\DataPersister\PromotionCouponDataPersister">
<argument type="service" id="api_platform.doctrine.orm.data_persister" />
<tag name="api_platform.data_persister" />
</service>
<service id="Sylius\Bundle\ApiBundle\DataPersister\TranslatableDataPersister">
<argument type="service" id="sylius.translation_locale_provider" />
<tag name="api_platform.data_persister" priority="128" />

View file

@ -0,0 +1,66 @@
<?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\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Exception\PromotionCouponCannotBeRemoved;
use Sylius\Component\Core\Model\PromotionCouponInterface;
final class PromotionCouponDataPersisterSpec extends ObjectBehavior
{
function let(ContextAwareDataPersisterInterface $dataPersister): void
{
$this->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);
}
}

View file

@ -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);
}
}