Skip to content

Commit

Permalink
Addding validation for product in current channel
Browse files Browse the repository at this point in the history
  • Loading branch information
mamazu committed Sep 5, 2019
1 parent fc25f36 commit 1365c5d
Show file tree
Hide file tree
Showing 18 changed files with 322 additions and 29 deletions.
2 changes: 2 additions & 0 deletions doc/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ paths:
description: "Invalid input, validation failed."
schema:
$ref: "#/definitions/GeneralError"
500:
description: "An error occurred - maybe the product is not in the channel of the cart"

/carts/{token}/multiple-items:
parameters:
Expand Down
49 changes: 49 additions & 0 deletions spec/Checker/ProductInCartChannelCheckerSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);

namespace spec\Sylius\ShopApiPlugin\Checker;

use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\ShopApiPlugin\Checker\ProductInCartChannelCheckerInterface;

final class ProductInCartChannelCheckerSpec extends ObjectBehavior
{
function it_implements_product_in_cart_channel_checker_interface(): void
{
$this->shouldImplement(ProductInCartChannelCheckerInterface::class);
}

function it_returns_true_if_the_channels_match(
ProductInterface $product,
OrderInterface $order,
ChannelInterface $channel
): void
{
$product->getChannels()->willReturn(new ArrayCollection([$channel->getWrappedObject()]));

$order->getChannel()->willReturn($channel);

$this->isProductInCartChannel($product, $order)->shouldReturn(true);
}

function it_returns_false_if_the_channels_do_not_match(
ProductInterface $product,
OrderInterface $order,
ChannelInterface $orderChannel,
ChannelInterface $productChannel1,
ChannelInterface $productChannel2
): void {
$product->getChannels()->willReturn(new ArrayCollection([
$productChannel1->getWrappedObject(),
$productChannel2->getWrappedObject()
]));

$order->getChannel()->willReturn($orderChannel);

$this->isProductInCartChannel($product, $order)->shouldReturn(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
use Sylius\Component\Product\Model\ProductOptionValueInterface;
use Sylius\ShopApiPlugin\Checker\ProductInCartChannelCheckerInterface;
use Sylius\ShopApiPlugin\Command\Cart\PutOptionBasedConfigurableItemToCart;
use Sylius\ShopApiPlugin\Modifier\OrderModifierInterface;

Expand All @@ -21,15 +22,17 @@ final class PutOptionBasedConfigurableItemToCartHandlerSpec extends ObjectBehavi
function let(
OrderRepositoryInterface $orderRepository,
ProductRepositoryInterface $productRepository,
OrderModifierInterface $orderModifier
OrderModifierInterface $orderModifier,
ProductInCartChannelCheckerInterface $channelChecker
): void {
$this->beConstructedWith($orderRepository, $productRepository, $orderModifier);
$this->beConstructedWith($orderRepository, $productRepository, $orderModifier, $channelChecker);
}

function it_handles_putting_new_item_to_cart(
OrderInterface $cart,
OrderRepositoryInterface $orderRepository,
OrderModifierInterface $orderModifier,
ProductInCartChannelCheckerInterface $channelChecker,
ProductInterface $tShirt,
ProductOptionValueInterface $blueOptionValue,
ProductOptionValueInterface $redOptionValue,
Expand All @@ -47,6 +50,7 @@ function it_handles_putting_new_item_to_cart(
$blueTShirt->getOptionValues()->willReturn(new ArrayCollection([$blueOptionValue->getWrappedObject()]));
$blueOptionValue->getCode()->willReturn('BLUE_OPTION_VALUE_CODE');
$blueOptionValue->getOptionCode()->willReturn('COLOR_OPTION_CODE');
$channelChecker->isProductInCartChannel($tShirt, $cart)->willReturn(true);

$redTShirt->getOptionValues()->willReturn(new ArrayCollection([$redOptionValue->getWrappedObject()]));
$redOptionValue->getCode()->willReturn('RED_OPTION_VALUE_CODE');
Expand Down Expand Up @@ -85,6 +89,7 @@ function it_throws_an_exception_if_product_variant_cannot_be_resolved(
OrderInterface $cart,
CartItemFactoryInterface $cartItemFactory,
OrderRepositoryInterface $orderRepository,
ProductInCartChannelCheckerInterface $channelChecker,
ProductInterface $tShirt,
ProductVariantInterface $blueTShirt,
ProductVariantInterface $redTShirt,
Expand All @@ -102,6 +107,7 @@ function it_throws_an_exception_if_product_variant_cannot_be_resolved(
$blueTShirt->getOptionValues()->willReturn(new ArrayCollection([$blueOptionValue->getWrappedObject()]));
$blueOptionValue->getCode()->willReturn('BLUE_OPTION_VALUE_CODE');
$blueOptionValue->getOptionCode()->willReturn('COLOR_OPTION_CODE');
$channelChecker->isProductInCartChannel($tShirt, $cart);

$redTShirt->getOptionValues()->willReturn(new ArrayCollection([$redOptionValue->getWrappedObject()]));
$redOptionValue->getCode()->willReturn('GREEN_OPTION_VALUE_CODE');
Expand All @@ -114,4 +120,22 @@ function it_throws_an_exception_if_product_variant_cannot_be_resolved(
new PutOptionBasedConfigurableItemToCart('ORDERTOKEN', 'T_SHIRT_CODE', ['COLOR_OPTION_CODE' => 'RED_OPTION_VALUE_CODE'], 5),
]);
}

function it_throws_an_exception_if_product_is_not_in_same_channel_as_cart(
OrderInterface $cart,
OrderRepositoryInterface $orderRepository,
ProductInCartChannelCheckerInterface $channelChecker,
ProductInterface $tShirt,
ProductRepositoryInterface $productRepository
): void {
$productRepository->findOneByCode('T_SHIRT_CODE')->willReturn($tShirt);

$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($cart);

$channelChecker->isProductInCartChannel($tShirt, $cart)->willReturn(false);

$this->shouldThrow(\InvalidArgumentException::class)->during('__invoke', [
new PutOptionBasedConfigurableItemToCart('ORDERTOKEN', 'T_SHIRT_CODE', ['COLOR_OPTION_CODE' => 'RED_OPTION_VALUE_CODE'], 5),
]);
}
}
37 changes: 34 additions & 3 deletions spec/Handler/Cart/PutSimpleItemToCartHandlerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
use Sylius\ShopApiPlugin\Checker\ProductInCartChannelCheckerInterface;
use Sylius\ShopApiPlugin\Command\Cart\PutSimpleItemToCart;
use Sylius\ShopApiPlugin\Modifier\OrderModifierInterface;

Expand All @@ -19,9 +20,10 @@ final class PutSimpleItemToCartHandlerSpec extends ObjectBehavior
function let(
OrderRepositoryInterface $cartRepository,
ProductRepositoryInterface $productRepository,
OrderModifierInterface $orderModifier
OrderModifierInterface $orderModifier,
ProductInCartChannelCheckerInterface $channelChecker
): void {
$this->beConstructedWith($cartRepository, $productRepository, $orderModifier);
$this->beConstructedWith($cartRepository, $productRepository, $orderModifier, $channelChecker);
}

function it_handles_putting_new_item_to_cart(
Expand All @@ -30,14 +32,17 @@ function it_handles_putting_new_item_to_cart(
ProductInterface $product,
ProductRepositoryInterface $productRepository,
ProductVariantInterface $productVariant,
OrderModifierInterface $orderModifier
OrderModifierInterface $orderModifier,
ProductInCartChannelCheckerInterface $channelChecker
): void {
$productRepository->findOneBy(['code' => 'T_SHIRT_CODE'])->willReturn($product);
$product->getVariants()->willReturn(new ArrayCollection([$productVariant->getWrappedObject()]));
$product->isSimple()->willReturn(true);

$cartRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($cart);

$channelChecker->isProductInCartChannel($product, $cart)->willReturn(true);

$orderModifier->modify($cart, $productVariant, 5)->shouldBeCalled();

$this(new PutSimpleItemToCart('ORDERTOKEN', 'T_SHIRT_CODE', 5));
Expand Down Expand Up @@ -68,17 +73,43 @@ function it_throws_an_exception_if_product_has_not_been_found(
function it_throws_an_exception_if_product_is_configurable(
OrderInterface $cart,
OrderRepositoryInterface $cartRepository,
ProductInCartChannelCheckerInterface $channelChecker,
ProductInterface $product,
ProductRepositoryInterface $productRepository,
ProductVariantInterface $productVariant
): void {
$cartRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($cart);
$productRepository->findOneBy(['code' => 'T_SHIRT_CODE'])->willReturn($product);
$product->getVariants()->willReturn(new ArrayCollection([$productVariant->getWrappedObject()]));

$channelChecker->isProductInCartChannel($product, $cart)->willReturn(true);

$product->isSimple()->willReturn(false);

$this->shouldThrow(\InvalidArgumentException::class)->during('__invoke', [
new PutSimpleItemToCart('ORDERTOKEN', 'T_SHIRT_CODE', 5),
]);
}

function it_throws_an_exception_if_product_is_not_in_same_channel_as_cart(
OrderInterface $cart,
OrderRepositoryInterface $cartRepository,
ProductInCartChannelCheckerInterface $channelChecker,
ProductInterface $product,
ProductRepositoryInterface $productRepository,
ProductVariantInterface $productVariant
): void {
$cartRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($cart);
$productRepository->findOneBy(['code' => 'T_SHIRT_CODE'])->willReturn($product);
$product->getVariants()->willReturn(new ArrayCollection([$productVariant->getWrappedObject()]));
$product->isSimple()->willReturn(false);

$channelChecker->isProductInCartChannel($product, $cart)->willReturn(false);

$this->shouldThrow(\InvalidArgumentException::class)->during(
'__invoke', [
new PutSimpleItemToCart('ORDERTOKEN', 'T_SHIRT_CODE', 5),
]
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

use PhpSpec\ObjectBehavior;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface;
use Sylius\ShopApiPlugin\Checker\ProductInCartChannelCheckerInterface;
use Sylius\ShopApiPlugin\Command\Cart\PutVariantBasedConfigurableItemToCart;
use Sylius\ShopApiPlugin\Modifier\OrderModifierInterface;

Expand All @@ -17,19 +19,25 @@ final class PutVariantBasedConfigurableItemToCartHandlerSpec extends ObjectBehav
function let(
OrderRepositoryInterface $cartRepository,
ProductVariantRepositoryInterface $productVariantRepository,
OrderModifierInterface $orderModifier
OrderModifierInterface $orderModifier,
ProductInCartChannelCheckerInterface $channelChecker
): void {
$this->beConstructedWith($cartRepository, $productVariantRepository, $orderModifier);
$this->beConstructedWith($cartRepository, $productVariantRepository, $orderModifier, $channelChecker);
}

function it_handles_putting_new_item_to_cart(
OrderInterface $cart,
OrderModifierInterface $orderModifier,
OrderRepositoryInterface $cartRepository,
ProductInCartChannelCheckerInterface $channelChecker,
ProductVariantInterface $productVariant,
ProductInterface $product,
ProductVariantRepositoryInterface $productVariantRepository
): void {
$productVariantRepository->findOneByCodeAndProductCode('RED_SMALL_T_SHIRT_CODE', 'T_SHIRT_CODE')->willReturn($productVariant);
$productVariant->getProduct()->willReturn($product);

$channelChecker->isProductInCartChannel($product, $cart)->willReturn(true);

$cartRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($cart);

Expand Down Expand Up @@ -59,4 +67,24 @@ function it_throws_an_exception_if_product_has_not_been_found(
new PutVariantBasedConfigurableItemToCart('ORDERTOKEN', 'T_SHIRT_CODE', 'RED_SMALL_T_SHIRT_CODE', 5),
]);
}

function it_throws_an_exception_if_product_has_different_channel_than_cart(
OrderInterface $cart,
OrderRepositoryInterface $cartRepository,
ProductInCartChannelCheckerInterface $channelChecker,
ProductVariantRepositoryInterface $productVariantRepository,
ProductVariantInterface $productVariant,
ProductInterface $product
): void {
$productVariantRepository->findOneByCodeAndProductCode('RED_SMALL_T_SHIRT_CODE', 'T_SHIRT_CODE')->willReturn($productVariant);
$productVariant->getProduct()->willReturn($product);

$cartRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($cart);

$channelChecker->isProductInCartChannel($product, $cart)->willReturn(false);

$this->shouldThrow(\InvalidArgumentException::class)->during('__invoke', [
new PutVariantBasedConfigurableItemToCart('ORDERTOKEN', 'T_SHIRT_CODE', 'RED_SMALL_T_SHIRT_CODE', 5),
]);
}
}
15 changes: 15 additions & 0 deletions src/Checker/ProductInCartChannelChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);

namespace Sylius\ShopApiPlugin\Checker;

use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\ProductInterface;

final class ProductInCartChannelChecker implements ProductInCartChannelCheckerInterface
{
public function isProductInCartChannel(ProductInterface $product, OrderInterface $cart): bool
{
return in_array($cart->getChannel(), $product->getChannels()->toArray(), true);
}
}
12 changes: 12 additions & 0 deletions src/Checker/ProductInCartChannelCheckerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);

namespace Sylius\ShopApiPlugin\Checker;

use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\ProductInterface;

interface ProductInCartChannelCheckerInterface
{
public function isProductInCartChannel(ProductInterface $product, OrderInterface $cart): bool;
}
12 changes: 9 additions & 3 deletions src/Handler/Cart/PutOptionBasedConfigurableItemToCartHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
use Sylius\Component\Product\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\ShopApiPlugin\Checker\ProductInCartChannelCheckerInterface;
use Sylius\ShopApiPlugin\Command\Cart\PutOptionBasedConfigurableItemToCart;
use Sylius\ShopApiPlugin\Modifier\OrderModifierInterface;
use Webmozart\Assert\Assert;
Expand All @@ -24,14 +25,19 @@ final class PutOptionBasedConfigurableItemToCartHandler
/** @var OrderModifierInterface */
private $orderModifier;

/** @var ProductInCartChannelCheckerInterface */
private $channelChecker;

public function __construct(
OrderRepositoryInterface $cartRepository,
ProductRepositoryInterface $productRepository,
OrderModifierInterface $orderModifier
OrderModifierInterface $orderModifier,
ProductInCartChannelCheckerInterface $channelChecker
) {
$this->cartRepository = $cartRepository;
$this->productRepository = $productRepository;
$this->orderModifier = $orderModifier;
$this->channelChecker = $channelChecker;
}

public function __invoke(PutOptionBasedConfigurableItemToCart $putConfigurableItemToCart): void
Expand All @@ -43,8 +49,8 @@ public function __invoke(PutOptionBasedConfigurableItemToCart $putConfigurableIt

/** @var ProductInterface $product */
$product = $this->productRepository->findOneByCode($putConfigurableItemToCart->product());

Assert::notNull($product, 'Product has not been found');
Assert::true($this->channelChecker->isProductInCartChannel($product, $cart), 'Product is not in same channel as cart');

$productVariant = $this->getVariant($putConfigurableItemToCart->options(), $product);

Expand Down
12 changes: 9 additions & 3 deletions src/Handler/Cart/PutSimpleItemToCartHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
use Sylius\ShopApiPlugin\Checker\ProductInCartChannelCheckerInterface;
use Sylius\ShopApiPlugin\Command\Cart\PutSimpleItemToCart;
use Sylius\ShopApiPlugin\Modifier\OrderModifierInterface;
use Webmozart\Assert\Assert;
Expand All @@ -23,27 +24,32 @@ final class PutSimpleItemToCartHandler
/** @var OrderModifierInterface */
private $orderModifier;

/** @var ProductInCartChannelCheckerInterface */
private $channelChecker;

public function __construct(
OrderRepositoryInterface $cartRepository,
ProductRepositoryInterface $productRepository,
OrderModifierInterface $orderModifier
OrderModifierInterface $orderModifier,
ProductInCartChannelCheckerInterface $channelChecker
) {
$this->cartRepository = $cartRepository;
$this->productRepository = $productRepository;
$this->orderModifier = $orderModifier;
$this->channelChecker = $channelChecker;
}

public function __invoke(PutSimpleItemToCart $putSimpleItemToCart): void
{
/** @var OrderInterface $cart */
$cart = $this->cartRepository->findOneBy(['tokenValue' => $putSimpleItemToCart->orderToken()]);

Assert::notNull($cart, 'Cart has not been found');

/** @var ProductInterface $product */
$product = $this->productRepository->findOneBy(['code' => $putSimpleItemToCart->product()]);

Assert::notNull($product, 'Product has not been found');

Assert::true($this->channelChecker->isProductInCartChannel($product, $cart), 'Product is not in same channel as cart');
Assert::true($product->isSimple(), 'Product has to be simple');

$productVariant = $product->getVariants()[0];
Expand Down
Loading

0 comments on commit 1365c5d

Please sign in to comment.