[ApiBundle] Add generate promotion coupons action

This commit is contained in:
Wojdylak 2023-11-24 14:55:27 +01:00
parent 09e576fbba
commit 8e8d5a69e6
No known key found for this signature in database
GPG key ID: 7509E560A6821ABE
21 changed files with 755 additions and 13 deletions

View file

@ -14,7 +14,7 @@ Feature: Being unable to generate too many coupons with prefix and suffix
And it is coupon based promotion
And I am logged in as an administrator
@ui
@api @ui
Scenario: Being unable to generate too many coupons with prefix
Given I have generated 8 coupons for this promotion with code length 1 and prefix "CHRISTMAS_"
When I want to generate new coupons for this promotion
@ -25,7 +25,7 @@ Feature: Being unable to generate too many coupons with prefix and suffix
Then I should be notified that generating 2 coupons with code length equal to 1 is not possible
And there should still be 8 coupons related to this promotion
@ui
@api @ui
Scenario: Being unable to generate too many coupons with suffix
Given I have generated 8 coupons for this promotion with code length 1 and suffix "_CHRISTMAS"
When I want to generate new coupons for this promotion
@ -36,7 +36,7 @@ Feature: Being unable to generate too many coupons with prefix and suffix
Then I should be notified that generating 2 coupons with code length equal to 1 is not possible
And there should still be 8 coupons related to this promotion
@ui
@api @ui
Scenario: Being unable to generate too many coupons with prefix and suffix
Given I have generated 8 coupons for this promotion with code length 1, prefix "CHRISTMAS_" and suffix "_SALE"
When I want to generate new coupons for this promotion

View file

@ -10,7 +10,7 @@ Feature: Coupon generate instruction validation
And it is coupon based promotion
And I am logged in as an administrator
@ui
@api @ui
Scenario: Trying to generate new coupons without specifying their amount
When I want to generate new coupons for this promotion
And I do not specify its amount
@ -21,7 +21,7 @@ Feature: Coupon generate instruction validation
Then I should be notified that generate amount is required
And there should be 0 coupon related to this promotion
@ui
@api @ui
Scenario: Trying to generate new coupons without specifying their code length
When I want to generate new coupons for this promotion
And I do not specify their code length
@ -32,7 +32,7 @@ Feature: Coupon generate instruction validation
Then I should be notified that generate code length is required
And there should be 0 coupon related to this promotion
@ui
@api @ui
Scenario: Trying to generate new coupons with code length impossible to generate
When I want to generate new coupons for this promotion
And I specify their code length as 50
@ -43,7 +43,7 @@ Feature: Coupon generate instruction validation
Then I should be notified that generate code length is out of range
And there should be 0 coupon related to this promotion
@ui
@api @ui
Scenario: Trying to generate new coupons with amount and code length impossible to generate
When I want to generate new coupons for this promotion
And I specify their code length as 1

View file

@ -10,7 +10,7 @@ Feature: Generating new coupons
And it is coupon based promotion
And I am logged in as an administrator
@ui
@api @ui
Scenario: Generating a new coupons
When I want to generate new coupons for this promotion
And I choose the amount of 5 coupons to be generated
@ -21,7 +21,7 @@ Feature: Generating new coupons
Then I should be notified that they have been successfully generated
And there should be 5 coupons related to this promotion
@ui
@api @ui
Scenario: Generating new coupons without expiration date
When I want to generate new coupons for this promotion
And I choose the amount of 5 coupons to be generated
@ -31,7 +31,7 @@ Feature: Generating new coupons
Then I should be notified that they have been successfully generated
And there should be 5 coupons related to this promotion
@ui
@api @ui
Scenario: Generating new coupons with a large long code length value
When I want to generate new coupons for this promotion
And I choose the amount of 10 coupons to be generated

View file

@ -10,7 +10,7 @@ Feature: Generating new coupons with prefix and suffix
And it is coupon based promotion
And I am logged in as an administrator
@ui
@api @ui
Scenario: Generating new coupons with prefix
When I want to generate new coupons for this promotion
And I choose the amount of 5 coupons to be generated
@ -21,7 +21,7 @@ Feature: Generating new coupons with prefix and suffix
And there should be 5 coupons related to this promotion
And all of the coupon codes should be prefixed with "CHRISTMAS_"
@ui
@api @ui
Scenario: Generating new coupons with suffix
When I want to generate new coupons for this promotion
And I choose the amount of 5 coupons to be generated
@ -32,7 +32,7 @@ Feature: Generating new coupons with prefix and suffix
And there should be 5 coupons related to this promotion
And all of the coupon codes should be suffixed with "_CHRISTMAS"
@ui
@api @ui
Scenario: Generating new coupons with prefix and suffix
When I want to generate new coupons for this promotion
And I choose the amount of 5 coupons to be generated

View file

@ -15,6 +15,8 @@ namespace Sylius\Behat\Context\Api\Admin;
use Behat\Behat\Context\Context;
use Sylius\Behat\Client\ApiClientInterface;
use Sylius\Behat\Client\RequestFactoryInterface;
use Sylius\Behat\Client\RequestInterface;
use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources;
use Sylius\Behat\Service\Converter\SectionAwareIriConverterInterface;
@ -24,8 +26,11 @@ use Webmozart\Assert\Assert;
final class ManagingPromotionCouponsContext implements Context
{
private ?RequestInterface $request = null;
public function __construct(
private ApiClientInterface $client,
private RequestFactoryInterface $requestFactory,
private ResponseCheckerInterface $responseChecker,
private SectionAwareIriConverterInterface $sectionAwareIriConverter,
) {
@ -73,6 +78,15 @@ final class ManagingPromotionCouponsContext implements Context
$this->client->buildUpdateRequest(Resources::PROMOTION_COUPONS, $coupon->getCode());
}
/**
* @When /^I want to generate new coupons for (this promotion)$/
*/
public function iWantToGenerateNewCouponsForThisPromotion(PromotionInterface $promotion): void
{
$this->request = $this->requestFactory->create('admin', 'promotion-coupons/generate', 'Bearer');
$this->request->updateContent(['promotionCode' => $promotion->getCode()]);
}
/**
* @When I (try to) delete :coupon coupon related to this promotion
*/
@ -124,6 +138,55 @@ final class ManagingPromotionCouponsContext implements Context
$this->client->addRequestData('reusableFromCancelledOrders', false);
}
/**
* @When I choose the amount of :amount coupons to be generated
*/
public function iSpecifyItsAmountAs(int $amount): void
{
$this->request->updateContent(['instruction' => ['amount' => $amount]]);
}
/**
* @When I specify their prefix as :prefix
*/
public function specifyPrefixAs(string $prefix): void
{
$this->request->updateContent(['instruction' => ['prefix' => $prefix]]);
}
/**
* @When I specify their suffix as :suffix
*/
public function specifySuffixAs(string $suffix): void
{
$this->request->updateContent(['instruction' => ['suffix' => $suffix]]);
}
/**
* @When /^I specify their code length as (\d+)$/
* @When I do not specify their code length
*/
public function iSpecifyTheirCodeLengthAs(?int $codeLength = null): void
{
$this->request->updateContent(['instruction' => ['codeLength' => $codeLength]]);
}
/**
* @When /^I limit generated coupons usage to (\d+) times?$/
*/
public function iSetGeneratedCouponsUsageLimitTo(int $limit):void
{
$this->request->updateContent(['instruction' => ['usageLimit' => $limit]]);
}
/**
* @When I make generated coupons valid until :date
*/
public function iMakeGeneratedCouponsValidUntil(\DateTimeInterface $date): void
{
$this->request->updateContent(['instruction' => ['expiresAt' => $date->format('Y-m-d')]]);
}
/**
* @When I do not specify its :field
*/
@ -140,6 +203,15 @@ final class ManagingPromotionCouponsContext implements Context
$this->client->create();
}
/**
* @When I (try to) generate it
* @When I (try to) generate these coupons
*/
public function iResetIt(): void
{
$this->client->executeCustomRequest($this->request);
}
/**
* @When /^I sort coupons by (ascending|descending) number of uses$/
*/
@ -331,6 +403,26 @@ final class ManagingPromotionCouponsContext implements Context
));
}
/**
* @Then all of the coupon codes should be prefixed with :prefix
*/
public function allOfTheCouponCodesShouldBePrefixedWith(string $prefix): void
{
foreach ($this->responseChecker->getCollection($this->client->getLastResponse()) as $promotionCoupon) {
Assert::startsWith($promotionCoupon['code'], $prefix);
}
}
/**
* @Then all of the coupon codes should be suffixed with :suffix
*/
public function allOfTheCouponCodesShouldBeSuffixedWith(string $suffix): void
{
foreach ($this->responseChecker->getCollection($this->client->getLastResponse()) as $promotionCoupon) {
Assert::endsWith($promotionCoupon['code'], $suffix);
}
}
/**
* @Then /^there should still be only one coupon with code "([^"]+)" related to (this promotion)$/
*/
@ -433,6 +525,65 @@ final class ManagingPromotionCouponsContext implements Context
);
}
/**
* @Then I should be notified that generating :amount coupons with code length equal to :codeLength is not possible
*/
public function iShouldBeNotifiedThatGeneratingCouponsWithCodeLengthIsNotPossible(int $amount, int $codeLength): void
{
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
sprintf(
'Invalid coupon code length or coupons amount. It is not possible to generate %d unique coupons with %d code length',
$amount,
$codeLength,
),
);
}
/**
* @Then I should be notified that generate amount is required
*/
public function iShouldBeNotifiedThatGenerateAmountIsRequired(): void
{
Assert::same(
$this->responseChecker->getError($this->client->getLastResponse()),
'instruction.amount: Please enter amount of coupons to generate.',
);
}
/**
* @Then I should be notified that generate code length is required
*/
public function iShouldBeNotifiedThatCodeLengthIsRequired(): void
{
Assert::same(
$this->responseChecker->getError($this->client->getLastResponse()),
'instruction.codeLength: Please enter coupon code length.',
);
}
/**
* @Then I should be notified that generate code length is out of range
*/
public function iShouldBeNotifiedThatCodeLengthIsOutOfRange(): void
{
Assert::same(
$this->responseChecker->getError($this->client->getLastResponse()),
'instruction.codeLength: Coupon code length must be between 1 and 40.',
);
}
/**
* @Then I should be notified that they have been successfully generated
*/
public function iShouldBeNotifiedThatTheyHaveBeenSuccessfullyGenerated(): void
{
Assert::true(
$this->responseChecker->isCreationSuccessful($this->client->getLastResponse()),
'Promotion coupon could not be generated',
);
}
/**
* @Then I should be notified that coupon with this code already exists
*/

View file

@ -293,6 +293,7 @@
<service id="sylius.behat.context.api.admin.managing_promotion_coupons" class="Sylius\Behat\Context\Api\Admin\ManagingPromotionCouponsContext">
<argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="sylius.behat.request_factory" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
<argument type="service" id="sylius.behat.section_iri_converter" />
</service>

View file

@ -0,0 +1,36 @@
<?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\Command\Promotion;
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInstructionInterface;
/** @experimental */
class GeneratePromotionCoupon
{
public function __construct(
private string $promotionCode,
private PromotionCouponGeneratorInstructionInterface $instruction,
) {
}
public function getPromotionCode(): string
{
return $this->promotionCode;
}
public function getPromotionCouponGeneratorInstruction(): PromotionCouponGeneratorInstructionInterface
{
return $this->instruction;
}
}

View file

@ -0,0 +1,90 @@
<?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\Command\Promotion;
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInstructionInterface;
/** @experimental */
class PromotionCouponGeneratorInstruction implements PromotionCouponGeneratorInstructionInterface
{
public function __construct(
private ?string $prefix = null,
private ?int $codeLength = null,
private ?string $suffix = null,
private ?int $amount = null,
private ?\DateTimeInterface $expiresAt = null,
private ?int $usageLimit = null,
) {
}
public function getAmount(): int
{
return $this->amount;
}
public function setAmount(?int $amount): void
{
$this->amount = $amount;
}
public function getCodeLength(): int
{
return $this->codeLength;
}
public function setCodeLength(?int $codeLength): void
{
$this->codeLength = $codeLength;
}
public function getPrefix(): ?string
{
return $this->prefix;
}
public function setPrefix(?string $prefix): void
{
$this->prefix = $prefix;
}
public function getSuffix(): ?string
{
return $this->suffix;
}
public function setSuffix(?string $suffix): void
{
$this->suffix = $suffix;
}
public function getExpiresAt(): ?\DateTimeInterface
{
return $this->expiresAt;
}
public function setExpiresAt(?\DateTimeInterface $expiresAt): void
{
$this->expiresAt = $expiresAt;
}
public function getUsageLimit(): ?int
{
return $this->usageLimit;
}
public function setUsageLimit(?int $usageLimit): void
{
$this->usageLimit = $usageLimit;
}
}

View file

@ -0,0 +1,52 @@
<?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\CommandHandler\Promotion;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon;
use Sylius\Bundle\ApiBundle\Exception\PromotionNotFoundException;
use Sylius\Component\Core\Model\PromotionInterface;
use Sylius\Component\Core\Repository\PromotionRepositoryInterface;
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/** @experimental */
final class GeneratePromotionCouponHandler implements MessageHandlerInterface
{
/**
* @param PromotionRepositoryInterface<PromotionInterface> $promotionRepository
*/
public function __construct(
private PromotionRepositoryInterface $promotionRepository,
private PromotionCouponGeneratorInterface $promotionCouponGenerator,
) {
}
public function __invoke(GeneratePromotionCoupon $generatePromotionCoupon): Collection
{
/** @var PromotionInterface|null $promotion */
$promotion = $this->promotionRepository->findOneBy(['code' => $generatePromotionCoupon->getPromotionCode()]);
if ($promotion === null) {
throw new PromotionNotFoundException();
}
$promotionCoupons = $this->promotionCouponGenerator->generate(
$promotion,
$generatePromotionCoupon->getPromotionCouponGeneratorInstruction()
);
return new ArrayCollection($promotionCoupons);
}
}

View file

@ -0,0 +1,30 @@
<?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;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** @experimental */
final class PromotionNotFoundException extends NotFoundHttpException
{
/** @param array<array-key, mixed> $headers */
public function __construct(
string $message = 'Promotion not found.',
\Throwable $previous = null,
int $code = 0,
array $headers = [],
) {
parent::__construct($message, $previous, $code, $headers);
}
}

View file

@ -44,6 +44,22 @@
<attribute name="groups">admin:promotion_coupon:create</attribute>
</attribute>
</collectionOperation>
<collectionOperation name="admin_generate">
<attribute name="method">POST</attribute>
<attribute name="messenger">input</attribute>
<attribute name="path">/promotion-coupons/generate</attribute>
<attribute name="input">Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon</attribute>
<attribute name="denormalization_context">
<attribute name="groups">admin:promotion_coupon:update</attribute>
</attribute>
<attribute name="normalization_context">
<attribute name="groups">admin:promotion_coupon:read</attribute>
</attribute>
<attribute name="openapi_context">
<attribute name="summary">Generate promotion coupons</attribute>
</attribute>
</collectionOperation>
</collectionOperations>
<itemOperations>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" ?>
<!--
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.
-->
<serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
>
<class name="Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon">
<attribute name="promotionCode">
<group>admin:promotion_coupon:update</group>
</attribute>
<attribute name="instruction">
<group>admin:promotion_coupon:update</group>
</attribute>
</class>
</serializer>

View file

@ -0,0 +1,38 @@
<?xml version="1.0" ?>
<!--
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.
-->
<serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
>
<class name="Sylius\Bundle\ApiBundle\Command\Promotion\PromotionCouponGeneratorInstruction">
<attribute name="prefix">
<group>admin:promotion_coupon:update</group>
</attribute>
<attribute name="codeLength">
<group>admin:promotion_coupon:update</group>
</attribute>
<attribute name="suffix">
<group>admin:promotion_coupon:update</group>
</attribute>
<attribute name="amount">
<group>admin:promotion_coupon:update</group>
</attribute>
<attribute name="expiresAt">
<group>admin:promotion_coupon:update</group>
</attribute>
<attribute name="usageLimit">
<group>admin:promotion_coupon:update</group>
</attribute>
</class>
</serializer>

View file

@ -224,5 +224,12 @@
<tag name="messenger.message_handler" bus="sylius.command_bus" />
<tag name="messenger.message_handler" bus="sylius_default.bus" />
</service>
<service id="Sylius\Bundle\ApiBundle\CommandHandler\Promotion\GeneratePromotionCouponHandler">
<argument type="service" id="sylius.repository.promotion" />
<argument type="service" id="sylius.promotion_coupon_generator" />
<tag name="messenger.message_handler" bus="sylius.command_bus" />
<tag name="messenger.message_handler" bus="sylius_default.bus" />
</service>
</services>
</container>

View file

@ -138,5 +138,15 @@
<service id="Sylius\Bundle\ApiBundle\Serializer\TranslatableLocaleKeyDenormalizer">
<tag name="serializer.normalizer" priority="96" />
</service>
<service
id="sylius.api.serializer.denormalizer.promotion_coupon_generator_instruction"
class="Sylius\Bundle\ApiBundle\Serializer\ObjectDenormalizer"
>
<argument type="service" id="serializer.normalizer.object" />
<argument type="string">Sylius\Bundle\ApiBundle\Command\Promotion\PromotionCouponGeneratorInstruction</argument>
<argument type="string">Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInstructionInterface</argument>
<tag name="serializer.normalizer" priority="64" />
</service>
</services>
</container>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon">
<property name="promotionCode">
<constraint name="NotBlank">
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</property>
<property name="instruction">
<constraint name="Valid" />
</property>
</class>
</constraint-mapping>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" ?>
<!--
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.
-->
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\Bundle\ApiBundle\Command\Promotion\PromotionCouponGeneratorInstruction">
<property name="amount">
<constraint name="NotBlank">
<option name="message">sylius.promotion_coupon_generator_instruction.amount.not_blank</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
<constraint name="Range">
<option name="min">1</option>
<option name="minMessage">sylius.promotion_coupon_generator_instruction.amount.min</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</property>
<property name="codeLength">
<constraint name="NotBlank">
<option name="message">sylius.promotion_coupon_generator_instruction.code_length.not_blank</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
<constraint name="Range">
<option name="min">1</option>
<option name="max">40</option>
<option name="notInRangeMessage">sylius.promotion_coupon_generator_instruction.code_length.not_in_range</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</property>
<property name="usageLimit">
<constraint name="Range">
<option name="min">1</option>
<option name="minMessage">sylius.promotion_coupon_generator_instruction.usage_limit.min</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</property>
</class>
</constraint-mapping>

View file

@ -0,0 +1,43 @@
<?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\Serializer;
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
/** @experimental */
final class ObjectDenormalizer implements ContextAwareDenormalizerInterface
{
public function __construct(
private DenormalizerInterface $objectNormalizer,
private string $classType,
private string $interfaceType,
) {
}
public function denormalize($data, $type, $format = null, array $context = [])
{
return $this->objectNormalizer->denormalize(
$data,
$this->classType,
$format,
$context,
);
}
public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
{
return $type === $this->interfaceType;
}
}

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\CommandHandler\Promotion;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon;
use Sylius\Bundle\ApiBundle\Exception\PromotionNotFoundException;
use Sylius\Component\Core\Model\PromotionCouponInterface;
use Sylius\Component\Core\Model\PromotionInterface;
use Sylius\Component\Core\Repository\PromotionRepositoryInterface;
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInstructionInterface;
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInterface;
final class GeneratePromotionCouponHandlerSpec extends ObjectBehavior
{
function let(
PromotionRepositoryInterface $promotionRepository,
PromotionCouponGeneratorInterface $promotionCouponGenerator,
) {
$this->beConstructedWith(
$promotionRepository,
$promotionCouponGenerator,
);
}
function it_throws_exception_if_promotion_is_not_found(
PromotionRepositoryInterface $promotionRepository,
PromotionCouponGeneratorInstructionInterface $promotionCouponGeneratorInstruction
): void {
$promotionRepository->findOneBy(['code' => 'promotion_code'])->willReturn(null);
$generatePromotionCoupon = new GeneratePromotionCoupon('promotion_code', $promotionCouponGeneratorInstruction->getWrappedObject());
$this->shouldThrow(PromotionNotFoundException::class)
->during('__invoke', [$generatePromotionCoupon])
;
}
function it_generates_promotion_coupons(
PromotionRepositoryInterface $promotionRepository,
PromotionCouponGeneratorInterface $promotionCouponGenerator,
PromotionInterface $promotion,
PromotionCouponGeneratorInstructionInterface $promotionCouponGeneratorInstruction,
PromotionCouponInterface $promotionCouponOne,
PromotionCouponInterface $promotionCouponTwo
): void {
$promotionRepository->findOneBy(['code' => 'promotion_code'])->willReturn($promotion);
$generatePromotionCoupon = new GeneratePromotionCoupon('promotion_code', $promotionCouponGeneratorInstruction->getWrappedObject());
$promotionCouponGenerator->generate($promotion, $promotionCouponGeneratorInstruction)->willReturn([$promotionCouponOne, $promotionCouponTwo]);
$this($generatePromotionCoupon)->shouldIterateAs([$promotionCouponOne, $promotionCouponTwo]);
}
}

View file

@ -117,6 +117,38 @@ final class PromotionCouponsTest extends JsonApiTestCase
Response::HTTP_OK,
);
}
/** @test */
public function it_generates_a_promotion_coupons(): void
{
$fixtures = $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'channel.yaml', 'promotion/promotion.yaml']);
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
/** @var PromotionInterface $promotion */
$promotion = $fixtures['promotion_50_off'];
$this->client->request(
method: 'POST',
uri: '/api/v2/admin/promotion-coupons/generate' ,
server: $header,
content: json_encode([
'promotionCode' => $promotion->getCode(),
'instruction' => [
'amount' => 4,
'prefix' => 'ABC',
'codeLength' => 6,
'suffix' => 'XYZ',
'usageLimit' => 10,
'expiresAt' => '2020-01-01 12:00:00',
],
], JSON_THROW_ON_ERROR)
);
$this->assertResponse(
$this->client->getResponse(),
'admin/promotion_coupon/generate_promotion_coupons_response',
Response::HTTP_CREATED,
);
}
/** @test */
public function it_removes_a_promotion_coupon(): void

View file

@ -0,0 +1,60 @@
{
"@context": "\/api\/v2\/contexts\/PromotionCoupon",
"@id": "\/api\/v2\/admin\/promotion-coupons",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "@string@.startsWith('\/api\/v2\/admin\/promotion-coupons\/ABC').endsWith('XYZ')",
"@type": "PromotionCoupon",
"perCustomerUsageLimit": null,
"reusableFromCancelledOrders": true,
"code": "@string@.startsWith('ABC').endsWith('XYZ')",
"usageLimit": 10,
"used": 0,
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
"expiresAt": "2020-01-01 12:00:00",
"createdAt": @date@,
"updatedAt": @date@
},
{
"@id": "@string@.startsWith('\/api\/v2\/admin\/promotion-coupons\/ABC').endsWith('XYZ')",
"@type": "PromotionCoupon",
"perCustomerUsageLimit": null,
"reusableFromCancelledOrders": true,
"code": "@string@.startsWith('ABC').endsWith('XYZ')",
"usageLimit": 10,
"used": 0,
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
"expiresAt": "2020-01-01 12:00:00",
"createdAt": @date@,
"updatedAt": @date@
},
{
"@id": "@string@.startsWith('\/api\/v2\/admin\/promotion-coupons\/ABC').endsWith('XYZ')",
"@type": "PromotionCoupon",
"perCustomerUsageLimit": null,
"reusableFromCancelledOrders": true,
"code": "@string@.startsWith('ABC').endsWith('XYZ')",
"usageLimit": 10,
"used": 0,
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
"expiresAt": "2020-01-01 12:00:00",
"createdAt": @date@,
"updatedAt": @date@
},
{
"@id": "@string@.startsWith('\/api\/v2\/admin\/promotion-coupons\/ABC').endsWith('XYZ')",
"@type": "PromotionCoupon",
"perCustomerUsageLimit": null,
"reusableFromCancelledOrders": true,
"code": "@string@.startsWith('ABC').endsWith('XYZ')",
"usageLimit": 10,
"used": 0,
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
"expiresAt": "2020-01-01 12:00:00",
"createdAt": @date@,
"updatedAt": @date@
}
],
"hydra:totalItems": 4
}