Skip to content

Commit

Permalink
[ApiBundle] Add promotion coupon endpoint as a subresource
Browse files Browse the repository at this point in the history
  • Loading branch information
Wojdylak committed Nov 24, 2023
1 parent c1b1a27 commit ba59fa7
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Feature: Coupon validation

@api @no-ui
Scenario: Trying to add a new coupon with no promotion
Given I want to create a new coupon
When I want to create a new coupon
And I specify its code as "RANDOM"
And I limit its usage to 30 times
And I limit its per customer usage to 3 times
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,11 @@ public function __construct(
*/
public function iWantToViewAllCouponsOfThisPromotion(PromotionInterface $promotion): void
{
$this->client->index(Resources::PROMOTION_COUPONS);
$this->client->addFilter(
'promotion',
$this->sectionAwareIriConverter->getIriFromResourceInSection($promotion, 'admin'),
$this->client->subResourceIndex(
Resources::PROMOTIONS,
Resources::PROMOTION_COUPONS,
$promotion->getCode()
);
$this->client->filter();
}

/**
Expand All @@ -66,16 +65,15 @@ public function iWantToCreateANewCouponForPromotion(PromotionInterface $promotio
}

/**
* @When /^I want to modify the ("[^"]+" coupon) for this promotion$/
* @When I want to modify the :coupon coupon for this promotion
*/
public function iWantToModifyTheCouponOfThisPromotion(PromotionCouponInterface $coupon): void
{
$this->client->buildUpdateRequest(Resources::PROMOTION_COUPONS, $coupon->getCode());
}

/**
* @When /^I delete ("[^"]+" coupon) related to this promotion$/
* @When /^I try to delete ("[^"]+" coupon) related to this promotion$/
* @When I (try to) delete :coupon coupon related to this promotion
*/
public function iDeleteCouponRelatedToThisPromotion(PromotionCouponInterface $coupon): void
{
Expand Down Expand Up @@ -186,13 +184,7 @@ public function iSortCouponsByExpirationDate(string $order): void
*/
public function thereShouldBeCountCouponsRelatedToThisPromotion(int $count, PromotionInterface $promotion): void
{
$coupons = $this->responseChecker->getCollectionItemsWithValue(
$this->client->index(Resources::PROMOTION_COUPONS),
'promotion',
$this->sectionAwareIriConverter->getIriFromResourceInSection($promotion, 'admin'),
);

Assert::same(count($coupons), $count);
Assert::same(count($this->responseChecker->getCollection($this->client->getLastResponse())), $count);
}

/**
Expand All @@ -201,7 +193,7 @@ public function thereShouldBeCountCouponsRelatedToThisPromotion(int $count, Prom
public function thereShouldBeACouponWithCode(string $code): void
{
Assert::true($this->responseChecker->hasItemWithValue(
$this->client->index(Resources::PROMOTION_COUPONS),
$this->client->getLastResponse(),
'code',
$code,
));
Expand Down Expand Up @@ -340,10 +332,13 @@ public function couponShouldStillExistInTheRegistry(PromotionCouponInterface $co
*/
public function thereShouldStillBeOnlyOneCouponWithCodeRelatedTo(string $code, PromotionInterface $promotion): void
{
Assert::count(
$this->responseChecker->getCollectionItemsWithValue($this->client->index(Resources::PROMOTION_COUPONS), 'code', $code),
1
$coupons = $this->responseChecker->getCollectionItemsWithValue(
$this->client->subResourceIndex(Resources::PROMOTIONS, Resources::PROMOTION_COUPONS, $promotion->getCode()),
'code',
$code
);

Assert::count($coupons, 1);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ Example configuration for `order_fixed_discount` action type:
</itemOperation>
</itemOperations>

<subresourceOperations>
<subresourceOperation name="coupons_get_subresource">
<attribute name="method">GET</attribute>
<attribute name="path">/admin/promotions/{code}/promotion-coupons</attribute>
</subresourceOperation>
</subresourceOperations>

<property name="id" identifier="false" writable="false" />
<property name="code" identifier="true" readable="true" />
<property name="name" readable="true" />
Expand All @@ -141,7 +148,9 @@ Example configuration for `order_fixed_discount` action type:
<property name="startsAt" readable="true" />
<property name="endsAt" readable="true" />
<property name="couponBased" readable="true" />
<property name="coupons" readable="true" />
<property name="coupons" readable="true" writable="true">
<subresource resourceClass="%sylius.model.promotion_coupon.class%" collection="true" />
</property>
<property name="rules" readable="true" />
<property name="actions" readable="true" />
<property name="channels" readable="true" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@
</itemOperation>
</itemOperations>

<subresourceOperations>
<subresourceOperation name="api_promotions_coupons_get_subresource">
<attribute name="normalization_context">
<attribute name="groups">admin:promotion_coupon:read</attribute>
</attribute>
</subresourceOperation>
</subresourceOperations>

<property name="id" identifier="false" writable="false" />
<property name="code" identifier="true" readable="true" />
<property name="usageLimit" readable="true" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
use Sylius\Component\Promotion\Model\PromotionCouponInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Webmozart\Assert\Assert;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;

final class PromotionNotCouponBasedValidator extends ConstraintValidator
{
Expand All @@ -27,11 +28,13 @@ public function validate(mixed $value, Constraint $constraint): void
return;
}

/** @var PromotionNotCouponBased $constraint */
Assert::isInstanceOf($constraint, PromotionNotCouponBased::class);
if (!$constraint instanceof PromotionNotCouponBased) {
throw new UnexpectedTypeException($constraint, PromotionNotCouponBased::class);
}

/** @var PromotionCouponInterface $value */
Assert::isInstanceOf($value, PromotionCouponInterface::class);
if (!$value instanceof PromotionCouponInterface) {
throw new UnexpectedValueException($value, PromotionCouponInterface::class);
}

$promotion = $value->getPromotion();
if (null === $promotion) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;

final class PromotionNotCouponBasedValidatorSpec extends ObjectBehavior
Expand Down Expand Up @@ -50,7 +52,7 @@ function it_throws_an_exception_when_constraint_is_not_promotion_not_coupon_base
$context->buildViolation(Argument::any())->shouldNotBeCalled();

$this
->shouldThrow(\InvalidArgumentException::class)
->shouldThrow(UnexpectedTypeException::class)
->during('validate', [$coupon, $constraint])
;
}
Expand All @@ -60,7 +62,7 @@ function it_throws_an_exception_when_value_is_not_a_coupon(ExecutionContextInter
$context->buildViolation(Argument::any())->shouldNotBeCalled();

$this
->shouldThrow(\InvalidArgumentException::class)
->shouldThrow(UnexpectedValueException::class)
->during('validate', [new \stdClass(), new PromotionNotCouponBased()])
;
}
Expand Down
22 changes: 22 additions & 0 deletions tests/Api/Admin/PromotionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ public function it_gets_promotions(): void
);
}

/** @test */
public function it_gets_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_1_off'];

$this->client->request(
method: 'GET',
uri: sprintf('/api/v2/admin/promotions/%s/promotion-coupons', $promotion->getCode()),
server: $header
);

$this->assertResponse(
$this->client->getResponse(),
'admin/promotion/get_promotion_coupons_response',
Response::HTTP_OK,
);
}

/** @test */
public function it_creates_promotion(): void
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"@context": "\/api\/v2\/contexts\/PromotionCoupon",
"@id": "\/api\/v2\/admin\/promotions/dollar_off/promotion-coupons",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "\/api\/v2\/admin\/promotion-coupons\/XYZ1",
"@type": "PromotionCoupon",
"perCustomerUsageLimit": 1,
"reusableFromCancelledOrders": true,
"code": "XYZ1",
"usageLimit": 2,
"used": 1,
"promotion": "\/api\/v2\/admin\/promotions\/dollar_off",
"expiresAt": null,
"createdAt": @date@,
"updatedAt": @date@
},
{
"@id": "\/api\/v2\/admin\/promotion-coupons\/XYZ2",
"@type": "PromotionCoupon",
"perCustomerUsageLimit": null,
"reusableFromCancelledOrders": false,
"code": "XYZ2",
"usageLimit": null,
"used": 0,
"promotion": "\/api\/v2\/admin\/promotions\/dollar_off",
"expiresAt": @date@,
"createdAt": @date@,
"updatedAt": @date@
}
],
"hydra:totalItems": 2
}

0 comments on commit ba59fa7

Please sign in to comment.