[API] Adding inexistent product variant

This commit is contained in:
arti0090 2021-05-07 13:46:38 +02:00
parent 3b9cbb5ee8
commit f5f16454f7
8 changed files with 261 additions and 9 deletions

View file

@ -0,0 +1,18 @@
@checkout
Feature: Preventing adding to cart inexistent product variant
In order to have correct products in cart when adding them
As a Customer
I want to have the added product variants validated
Background:
Given the store operates on a single channel in "United States"
And the store has a "Super Cool T-Shirt" configurable product
And this product has "Small", "Medium" and "Large" variants
And I am a logged in customer
@api
Scenario: Preventing customer from adding inexistent variant
Given the "Large" product variant is disabled
When I pick up my cart
And I try to add product "Super Cool T-Shirt" with variant code "Magic"
Then I should be informed that product variant with code "Magic" does not exist

View file

@ -804,6 +804,17 @@ final class CheckoutContext implements Context
));
}
/**
* @Then I should be informed that product variant with code :code does not exist
*/
public function iShouldBeInformedThatProductVariantWithCodeDoesNotExist(string $code): void
{
Assert::true($this->isViolationWithMessageInResponse(
$this->ordersClient->getLastResponse(),
sprintf('The product variant with %s does not exist.', $code)
));
}
/**
* @Then I should not see the thank you page
*/
@ -867,6 +878,15 @@ final class CheckoutContext implements Context
$this->putVariantToCart($productVariant, $tokenValue);
}
/**
* @When /^I try to add (product "[^"]+") with variant code "([^"]+)"$/
*/
public function iTryToAddProductVariantWithCode(ProductInterface $product, string $code): void
{
$tokenValue = $this->getCartTokenValue();
$this->putProductWithVariantCode($product, $tokenValue, $code);
}
/**
* @When /^I try to remove (product "[^"]+") from the (cart)$/
*/
@ -1150,6 +1170,30 @@ final class CheckoutContext implements Context
$this->sharedStorage->set('response', $this->ordersClient->executeCustomRequest($request));
}
private function putProductWithVariantCode(ProductInterface $product, string $tokenValue, string $code): void
{
$request = $this->preparePutProductRequest($tokenValue);
$request->setContent([
'productCode' => $product->getCode(),
'productVariantCode' => $code,
'quantity' => 1,
]);
$this->sharedStorage->set('response', $this->ordersClient->executeCustomRequest($request));
}
private function preparePutProductRequest(string $tokenValue): Request
{
return Request::customItemAction(
'shop',
'orders',
$tokenValue,
HTTPRequest::METHOD_PATCH,
'items'
);
}
private function removeOrderItemFromCart(int $orderItemId, string $tokenValue): void
{
$request = Request::customItemAction(

View file

@ -49,6 +49,11 @@
<tag name="validator.constraint_validator" alias="sylius_api_validator_product_or_variant_enabled" />
</service>
<service id="sylius.api.validator.product_variant_exist" class="Sylius\Bundle\ApiBundle\Validator\Constraints\ProductVariantExistValidator">
<argument type="service" id="sylius.repository.product_variant" />
<tag name="validator.constraint_validator" alias="sylius_api_validator_product_variant_exist" />
</service>
<service id="sylius.api.validator.payment_method_eligibility" class="Sylius\Bundle\ApiBundle\Validator\Constraints\OrderPaymentMethodEligibilityValidator">
<argument type="service" id="sylius.repository.order" />
<tag name="validator.constraint_validator" alias="sylius_api_order_payment_method_eligibility" />

View file

@ -13,15 +13,22 @@
<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\Cart\AddItemToCart">
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\ProductOrVariantEnabled">
<option name="groups">
<value>sylius</value>
</option>
</constraint>
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\ProductAvailableInChannel">
<option name="groups">
<value>sylius</value>
</option>
<constraint name="Sequentially">
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\ProductVariantExist">
<option name="groups">
<value>sylius</value>
</option>
</constraint>
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\ProductOrVariantEnabled">
<option name="groups">
<value>sylius</value>
</option>
</constraint>
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\ProductAvailableInChannel">
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</constraint>
<property name="quantity">
<constraint name="Range">

View file

@ -4,5 +4,7 @@ sylius:
product_does_not_exist: 'The product %productName% does not exist.'
product:
not_exist: 'The product %productName% does not exist.'
product_variant:
not_exist: 'The product variant with %productVariantCode% does not exist.'
shipment:
shipped: 'You cannot ship a shipment that was shipped before.'

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/** @experimental */
final class ProductVariantExist extends Constraint
{
/** @var string */
public $message = 'sylius.product_variant.not_exist';
public function validatedBy(): string
{
return 'sylius_api_validator_product_variant_exist';
}
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Validator\Constraints;
use Sylius\Bundle\ApiBundle\Command\Cart\AddItemToCart;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Product\Repository\ProductVariantRepositoryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Webmozart\Assert\Assert;
/** @experimental */
final class ProductVariantExistValidator extends ConstraintValidator
{
/** @var ProductVariantRepositoryInterface */
private $productVariantRepository;
public function __construct(ProductVariantRepositoryInterface $productVariantRepository)
{
$this->productVariantRepository = $productVariantRepository;
}
public function validate($value, Constraint $constraint): void
{
Assert::isInstanceOf($value, AddItemToCart::class);
/** @var ProductVariantExist $constraint */
Assert::isInstanceOf($constraint, ProductVariantExist::class);
/** @var ProductVariantInterface|null $productVariant */
if ($this->productVariantRepository->findOneBy(['code' =>$value->productVariantCode]) === null) {
$this->context->addViolation(
$constraint->message,
['%productVariantCode%' => $value->productVariantCode]
);
}
}
}

View file

@ -0,0 +1,94 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Validator\Constraints;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Command\Cart\AddItemToCart;
use Sylius\Bundle\ApiBundle\Command\Checkout\CompleteOrder;
use Sylius\Bundle\ApiBundle\Validator\Constraints\ProductVariantExist;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
final class ProductVariantExistValidatorSpec extends ObjectBehavior
{
function let(ProductVariantRepositoryInterface $productVariantRepository): void
{
$this->beConstructedWith($productVariantRepository);
}
function it_is_a_constraint_validator(): void
{
$this->shouldImplement(ConstraintValidatorInterface::class);
}
function it_throws_an_exception_if_value_is_not_an_instance_of_add_item_to_cart_command(): void
{
$this
->shouldThrow(\InvalidArgumentException::class)
->during('validate', [new CompleteOrder(), new ProductVariantExist()]);
}
function it_throws_an_exception_if_constraint_is_not_an_instance_of_product_variant_exist(): void
{
$this
->shouldThrow(\InvalidArgumentException::class)
->during('validate', [new AddItemToCart('productCode', 'productVariantCode', 1), new class() extends Constraint {
}])
;
}
function it_adds_violation_if_product_variant_does_not_exist(
ProductVariantRepositoryInterface $productVariantRepository,
ExecutionContextInterface $executionContext
): void {
$this->initialize($executionContext);
$value = new AddItemToCart('productCode', 'productVariantCode', 1);
$constraint = new ProductVariantExist();
$constraint->message = 'message';
$productVariantRepository->findOneBy(['code' => 'productVariantCode'])->willReturn(null);
$executionContext
->addViolation('message', ['%productVariantCode%' => 'productVariantCode'])
->shouldBeCalled()
;
$this->validate($value, $constraint);
}
function it_does_nothing_if_product_variant_does_exist(
ProductVariantRepositoryInterface $productVariantRepository,
ProductVariantInterface $productVariant,
ExecutionContextInterface $executionContext
): void {
$this->initialize($executionContext);
$value = new AddItemToCart('productCode', 'productVariantCode', 1);
$constraint = new ProductVariantExist();
$constraint->message = 'message';
$productVariantRepository->findOneBy(['code' => 'productVariantCode'])->willReturn($productVariant);
$executionContext
->addViolation('message', ['%productVariantCode%' => 'productVariantCode'])
->shouldNotBeCalled()
;
$this->validate($value, $constraint);
}
}