From ad2c534af072897a03e99a4f2e46e351c8cbd2c1 Mon Sep 17 00:00:00 2001 From: Rafikooo Date: Thu, 2 Mar 2023 13:55:05 +0100 Subject: [PATCH] [ProductVariant][API] Add product validation --- .../HasAllPricesDefinedValidator.php | 12 +- .../HasAllPricesDefinedValidatorSpec.php | 160 ++++++++++++++++++ .../config/validation/ProductVariant.xml | 11 +- .../ProductVariantCombinationValidator.php | 3 +- ...ProductVariantCombinationValidatorSpec.php | 27 ++- 5 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 src/Sylius/Bundle/CoreBundle/spec/Validator/Constraints/HasAllPricesDefinedValidatorSpec.php diff --git a/src/Sylius/Bundle/CoreBundle/Validator/Constraints/HasAllPricesDefinedValidator.php b/src/Sylius/Bundle/CoreBundle/Validator/Constraints/HasAllPricesDefinedValidator.php index f1b577eb41..c00e20d458 100644 --- a/src/Sylius/Bundle/CoreBundle/Validator/Constraints/HasAllPricesDefinedValidator.php +++ b/src/Sylius/Bundle/CoreBundle/Validator/Constraints/HasAllPricesDefinedValidator.php @@ -14,6 +14,8 @@ declare(strict_types=1); namespace Sylius\Bundle\CoreBundle\Validator\Constraints; use Sylius\Component\Core\Model\ChannelPricingInterface; +use Sylius\Component\Core\Model\ProductInterface; +use Sylius\Component\Core\Model\ProductVariantInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Webmozart\Assert\Assert; @@ -22,9 +24,17 @@ final class HasAllPricesDefinedValidator extends ConstraintValidator { public function validate($value, Constraint $constraint): void { + Assert::isInstanceOf($value, ProductVariantInterface::class); Assert::isInstanceOf($constraint, HasAllPricesDefined::class); - $channels = $value->getProduct()->getChannels(); + /** @var ProductInterface|null $product */ + $product = $value->getProduct(); + + if ($product === null) { + return; + } + + $channels = $product->getChannels(); foreach ($channels as $channel) { /** @var ChannelPricingInterface|null $channelPricing */ diff --git a/src/Sylius/Bundle/CoreBundle/spec/Validator/Constraints/HasAllPricesDefinedValidatorSpec.php b/src/Sylius/Bundle/CoreBundle/spec/Validator/Constraints/HasAllPricesDefinedValidatorSpec.php new file mode 100644 index 0000000000..2efa235f48 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/spec/Validator/Constraints/HasAllPricesDefinedValidatorSpec.php @@ -0,0 +1,160 @@ +initialize($context); + } + + function it_is_a_constraint_validator(): void + { + $this->shouldImplement(ConstraintValidatorInterface::class); + } + + function it_throws_an_exception_if_value_is_not_a_product_variant( + ExecutionContextInterface $context, + ): void { + $context->buildViolation(Argument::any())->shouldNotBeCalled(); + + $this + ->shouldThrow(\InvalidArgumentException::class) + ->during('validate', [new \stdClass(), new HasAllPricesDefined()]) + ; + } + + function it_throws_an_exception_if_constraint_is_not_a_has_all_prices_defined_constraint( + ExecutionContextInterface $context, + Constraint $constraint, + ): void { + $context->buildViolation(Argument::any())->shouldNotBeCalled(); + + $this + ->shouldThrow(\InvalidArgumentException::class) + ->during('validate', [new \stdClass(), $constraint]) + ; + } + + function it_does_nothing_if_product_variant_has_no_product( + ExecutionContextInterface $context, + ProductVariantInterface $productVariant, + ): void { + $context->buildViolation(Argument::any())->shouldNotBeCalled(); + + $productVariant->getProduct()->willReturn(null); + + $this->validate($productVariant, new HasAllPricesDefined()); + } + + function it_adds_a_violation_if_product_variant_does_not_have_any_channel_pricing( + ExecutionContextInterface $context, + ChannelInterface $firstChannel, + ChannelInterface $secondChannel, + ChannelPricingInterface $channelPricing, + ConstraintViolationBuilderInterface $constraintViolationBuilder, + ProductInterface $product, + ProductVariantInterface $productVariant, + ): void { + $productVariant->getProduct()->willReturn($product); + $productVariant->getChannelPricingForChannel($firstChannel)->willReturn($channelPricing); + $productVariant->getChannelPricingForChannel($secondChannel)->willReturn(null); + + $channelPricing->getPrice()->willReturn(1000); + + $product + ->getChannels() + ->willReturn(new ArrayCollection([$firstChannel->getWrappedObject(), $secondChannel->getWrappedObject()])) + ; + + $constraintViolationBuilder->addViolation()->shouldBeCalled(); + $constraintViolationBuilder->atPath(Argument::any())->willReturn($constraintViolationBuilder); + $constraintViolationBuilder->setParameter(Argument::cetera())->willReturn($constraintViolationBuilder); + + $context->buildViolation((new HasAllPricesDefined())->message)->willReturn($constraintViolationBuilder); + + $this->validate($productVariant, new HasAllPricesDefined()); + } + + function it_adds_a_violation_if_product_variant_does_not_have_any_channel_pricing_price_defined( + ExecutionContextInterface $context, + ChannelInterface $firstChannel, + ChannelInterface $secondChannel, + ChannelPricingInterface $firstChannelPricing, + ChannelPricingInterface $secondChannelPricing, + ConstraintViolationBuilderInterface $constraintViolationBuilder, + ProductInterface $product, + ProductVariantInterface $productVariant, + ): void { + $productVariant->getProduct()->willReturn($product); + $productVariant->getChannelPricingForChannel($firstChannel)->willReturn($firstChannelPricing); + $productVariant->getChannelPricingForChannel($secondChannel)->willReturn($secondChannelPricing); + + $firstChannelPricing->getPrice()->willReturn(1000); + $secondChannelPricing->getPrice()->willReturn(null); + + $product + ->getChannels() + ->willReturn(new ArrayCollection([$firstChannel->getWrappedObject(), $secondChannel->getWrappedObject()])) + ; + + $constraintViolationBuilder->addViolation()->shouldBeCalled(); + $constraintViolationBuilder->atPath(Argument::any())->willReturn($constraintViolationBuilder); + $constraintViolationBuilder->setParameter(Argument::cetera())->willReturn($constraintViolationBuilder); + + $context->buildViolation((new HasAllPricesDefined())->message)->willReturn($constraintViolationBuilder); + + $this->validate($productVariant, new HasAllPricesDefined()); + } + + function it_does_not_add_a_violation_if_product_variant_has_all_channel_pricing_prices_defined( + ExecutionContextInterface $context, + ChannelInterface $firstChannel, + ChannelInterface $secondChannel, + ChannelPricingInterface $firstChannelPricing, + ChannelPricingInterface $secondChannelPricing, + ProductInterface $product, + ProductVariantInterface $productVariant, + ): void { + $productVariant->getProduct()->willReturn($product); + $productVariant->getChannelPricingForChannel($firstChannel)->willReturn($firstChannelPricing); + $productVariant->getChannelPricingForChannel($secondChannel)->willReturn($secondChannelPricing); + + $firstChannelPricing->getPrice()->willReturn(1000); + $secondChannelPricing->getPrice()->willReturn(2000); + + $product + ->getChannels() + ->willReturn(new ArrayCollection([$firstChannel->getWrappedObject(), $secondChannel->getWrappedObject()])) + ; + + $context->buildViolation(Argument::any())->shouldNotBeCalled(); + + $this->validate($productVariant, new HasAllPricesDefined()); + } +} diff --git a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariant.xml b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariant.xml index 2b53aa4309..ab787e146c 100644 --- a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariant.xml +++ b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariant.xml @@ -18,6 +18,9 @@ + + + @@ -29,8 +32,10 @@ - - - + + + + + diff --git a/src/Sylius/Bundle/ProductBundle/Validator/ProductVariantCombinationValidator.php b/src/Sylius/Bundle/ProductBundle/Validator/ProductVariantCombinationValidator.php index 52da56c13c..337243355d 100644 --- a/src/Sylius/Bundle/ProductBundle/Validator/ProductVariantCombinationValidator.php +++ b/src/Sylius/Bundle/ProductBundle/Validator/ProductVariantCombinationValidator.php @@ -37,7 +37,8 @@ final class ProductVariantCombinationValidator extends ConstraintValidator } $product = $value->getProduct(); - if (!$product->hasVariants() || !$product->hasOptions()) { + + if ($product === null || !$product->hasVariants() || !$product->hasOptions()) { return; } diff --git a/src/Sylius/Bundle/ProductBundle/spec/Validator/ProductVariantCombinationValidatorSpec.php b/src/Sylius/Bundle/ProductBundle/spec/Validator/ProductVariantCombinationValidatorSpec.php index 607fc8d090..f585155271 100644 --- a/src/Sylius/Bundle/ProductBundle/spec/Validator/ProductVariantCombinationValidatorSpec.php +++ b/src/Sylius/Bundle/ProductBundle/spec/Validator/ProductVariantCombinationValidatorSpec.php @@ -27,6 +27,7 @@ final class ProductVariantCombinationValidatorSpec extends ObjectBehavior function let(ExecutionContextInterface $context, ProductVariantsParityCheckerInterface $variantsParityChecker): void { $this->beConstructedWith($variantsParityChecker); + $this->initialize($context); } @@ -35,6 +36,28 @@ final class ProductVariantCombinationValidatorSpec extends ObjectBehavior $this->shouldImplement(ConstraintValidator::class); } + function it_does_not_add_violation_if_product_is_null( + ExecutionContextInterface $context, + ProductInterface $product, + ProductVariantInterface $variant, + ProductVariantsParityCheckerInterface $variantsParityChecker, + ): void { + $constraint = new ProductVariantCombination([ + 'message' => 'Variant with given options already exists', + ]); + + $variant->getProduct()->willReturn(null); + + $product->hasVariants()->shouldNotBeCalled(); + $product->hasOptions()->shouldNotBeCalled(); + + $variantsParityChecker->checkParity($variant, $product)->shouldNotBeCalled(); + + $context->addViolation(Argument::any())->shouldNotBeCalled(); + + $this->validate($variant, $constraint); + } + function it_does_not_add_violation_if_product_does_not_have_options( ExecutionContextInterface $context, ProductInterface $product, @@ -50,7 +73,7 @@ final class ProductVariantCombinationValidatorSpec extends ObjectBehavior $product->hasVariants()->willReturn(true); $product->hasOptions()->willReturn(false); - $variantsParityChecker->checkParity($variant, $product)->willReturn(false); + $variantsParityChecker->checkParity($variant, $product)->shouldNotBeCalled(); $context->addViolation(Argument::any())->shouldNotBeCalled(); @@ -74,7 +97,7 @@ final class ProductVariantCombinationValidatorSpec extends ObjectBehavior $context->addViolation(Argument::any())->shouldNotBeCalled(); - $variantsParityChecker->checkParity($variant, $product)->willReturn(false); + $variantsParityChecker->checkParity($variant, $product)->shouldNotBeCalled(); $this->validate($variant, $constraint); }