[ProductVariant][API] Add product validation

This commit is contained in:
Rafikooo 2023-03-02 13:55:05 +01:00
parent 4875256995
commit ad2c534af0
No known key found for this signature in database
GPG key ID: 4A26D8327BC2442B
5 changed files with 206 additions and 7 deletions

View file

@ -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 */

View file

@ -0,0 +1,160 @@
<?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\CoreBundle\Validator\Constraints;
use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\CoreBundle\Validator\Constraints\HasAllPricesDefined;
use Sylius\Component\Core\Model\ChannelInterface;
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\ConstraintValidatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
final class HasAllPricesDefinedValidatorSpec extends ObjectBehavior
{
function let(ExecutionContextInterface $context): void
{
$this->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());
}
}

View file

@ -18,6 +18,9 @@
<option name="message">sylius.product_variant.code.unique</option>
<option name="groups">sylius</option>
</constraint>
<constraint name="Sylius\Bundle\ProductBundle\Validator\Constraint\ProductVariantCombination">
<option name="groups">sylius</option>
</constraint>
<property name="code">
<constraint name="NotBlank">
<option name="message">sylius.product_variant.code.not_blank</option>
@ -29,8 +32,10 @@
<option name="groups">sylius</option>
</constraint>
</property>
<constraint name="Sylius\Bundle\ProductBundle\Validator\Constraint\ProductVariantCombination">
<option name="groups">sylius</option>
</constraint>
<property name="product">
<constraint name="NotBlank">
<option name="groups">sylius</option>
</constraint>
</property>
</class>
</constraint-mapping>

View file

@ -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;
}

View file

@ -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);
}