[Promotion] Switch catalog promotion action validation from custom solution to symfony

This commit is contained in:
Jan Goralski 2024-01-11 14:23:32 +01:00 committed by Kamil Grygierzec
parent 2c3f7ee0e8
commit d0fe806281
No known key found for this signature in database
GPG key ID: 7F54EE42DAD4B9E9
23 changed files with 701 additions and 154 deletions

View file

@ -4560,6 +4560,11 @@ parameters:
count: 1
path: src/Sylius/Bundle/PromotionBundle/Validator/CatalogPromotionActionValidator.php
-
message: "#^Property Sylius\\\\Bundle\\\\PromotionBundle\\\\Validator\\\\CatalogPromotionActionValidator\\:\\:\\$actionTypes is never read, only written.$#"
count: 1
path: src/Sylius/Bundle/PromotionBundle/Validator/CatalogPromotionActionValidator.php
-
message: "#^Method Sylius\\\\Bundle\\\\PromotionBundle\\\\Validator\\\\CatalogPromotionScope\\\\ScopeValidatorInterface\\:\\:validate\\(\\) has parameter \\$configuration with no value type specified in iterable type array\\.$#"
count: 1

View file

@ -21,6 +21,12 @@ sylius_promotion:
model: Sylius\Component\Core\Model\PromotionCoupon
repository: Sylius\Bundle\PromotionBundle\Doctrine\ORM\PromotionCouponRepository
catalog_promotion_action:
validation_groups:
fixed_discount:
- 'sylius'
- 'sylius_catalog_promotion_action_fixed_discount'
catalog_promotion_scope:
validation_groups:
for_products:

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of the Sylius package.
(c) Sylius Sp. z o.o.
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<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\Component\Promotion\Model\CatalogPromotionAction">
<property name="configuration">
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ChannelCodeCollection">
<option name="groups">sylius_catalog_promotion_action_fixed_discount</option>
<option name="constraints">
<constraint name="Collection">
<option name="groups">sylius_catalog_promotion_action_fixed_discount</option>
<option name="fields">
<value key="amount">
<constraint name="NotBlank">
<option name="groups">sylius_catalog_promotion_action_fixed_discount</option>
</constraint>
<constraint name="Type">
<option name="type">numeric</option>
<option name="groups">sylius_catalog_promotion_action_fixed_discount</option>
</constraint>
</value>
</option>
</constraint>
</option>
<option name="allowExtraFields">true</option>
</constraint>
</property>
</class>
</constraint-mapping>

View file

@ -76,6 +76,15 @@ final class Configuration implements ConfigurationInterface
->end()
->end()
->end()
->arrayNode('catalog_promotion_action')
->addDefaultsIfNotSet()
->children()
->arrayNode('validation_groups')
->useAttributeAsKey('name')
->variablePrototype()->end()
->end()
->end()
->end()
->arrayNode('catalog_promotion_scope')
->addDefaultsIfNotSet()
->children()

View file

@ -33,14 +33,20 @@ final class SyliusPromotionExtension extends AbstractResourceExtension
$loader->load('services.xml');
$loader->load(sprintf('services/integrations/%s.xml', $config['driver']));
$container->setParameter('sylius.promotion.promotion_action.validation_groups', $config['promotion_action']['validation_groups']);
$container->setParameter('sylius.promotion.promotion_rule.validation_groups', $config['promotion_rule']['validation_groups']);
$container->setParameter('sylius.promotion.catalog_promotion_scope.validation_groups', $config['catalog_promotion_scope']['validation_groups']);
$this->registerResources('sylius', $config['driver'], $config['resources'], $container);
$this->registerValidationParameters($container, $config);
$this->registerAutoconfiguration($container);
}
/** @param array<string, array<string, array<array-key, string,>>> $configuration */
private function registerValidationParameters(ContainerBuilder $container, array $configuration): void
{
$container->setParameter('sylius.promotion.promotion_action.validation_groups', $configuration['promotion_action']['validation_groups']);
$container->setParameter('sylius.promotion.promotion_rule.validation_groups', $configuration['promotion_rule']['validation_groups']);
$container->setParameter('sylius.promotion.catalog_promotion_action.validation_groups', $configuration['catalog_promotion_action']['validation_groups']);
$container->setParameter('sylius.promotion.catalog_promotion_scope.validation_groups', $configuration['catalog_promotion_scope']['validation_groups']);
}
private function registerAutoconfiguration(ContainerBuilder $container): void
{
$container->registerAttributeForAutoconfiguration(

View file

@ -17,7 +17,6 @@ use Sylius\Bundle\MoneyBundle\Form\Type\MoneyType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Type;
final class FixedDiscountActionConfigurationType extends AbstractType
{
@ -26,9 +25,6 @@ final class FixedDiscountActionConfigurationType extends AbstractType
$builder
->add('amount', MoneyType::class, [
'label' => 'sylius.ui.amount',
'constraints' => [
new Type(['type' => 'numeric', 'groups' => ['sylius']]),
],
'currency' => $options['currency'],
])
;

View file

@ -16,8 +16,6 @@ namespace Sylius\Bundle\PromotionBundle\Form\Type\CatalogPromotionAction;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PercentType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Range;
final class PercentageDiscountActionConfigurationType extends AbstractType
{
@ -28,18 +26,6 @@ final class PercentageDiscountActionConfigurationType extends AbstractType
'label' => 'sylius.ui.amount',
'html5' => true,
'scale' => 2,
'constraints' => [
new NotBlank([
'groups' => 'sylius',
'message' => 'sylius.catalog_promotion_action.percentage_discount.not_number_or_empty',
]),
new Range([
'min' => 0,
'max' => 1,
'notInRangeMessage' => 'sylius.catalog_promotion_action.percentage_discount.not_in_range',
'groups' => ['sylius'],
]),
],
])
;
}

View file

@ -26,6 +26,7 @@ sylius_promotion:
unit_percentage_discount:
- 'sylius'
- 'sylius_promotion_action_unit_percentage_discount'
promotion_rule:
validation_groups:
cart_quantity:
@ -52,3 +53,9 @@ sylius_promotion:
item_total:
- 'sylius'
- 'sylius_promotion_rule_item_total'
catalog_promotion_action:
validation_groups:
percentage_discount:
- 'sylius'
- 'sylius_catalog_promotion_action_percentage_discount'

View file

@ -35,6 +35,16 @@
<tag name="validator.constraint_validator" alias="sylius_catalog_promotion_action" />
</service>
<service id="Sylius\Bundle\PromotionBundle\Validator\CatalogPromotionActionGroupValidator">
<argument>%sylius.promotion.catalog_promotion_action.validation_groups%</argument>
<tag name="validator.constraint_validator" alias="sylius_catalog_promotion_action_group" />
</service>
<service id="Sylius\Bundle\PromotionBundle\Validator\CatalogPromotionActionTypeValidator">
<argument>%sylius.catalog_promotion.actions_types%</argument>
<tag name="validator.constraint_validator" alias="sylius_catalog_promotion_action_type_validator" />
</service>
<service id="Sylius\Bundle\PromotionBundle\Validator\CatalogPromotionScopeValidator">
<argument>%sylius.catalog_promotion.scopes_types%</argument>
<argument type="tagged_iterator" tag="sylius.catalog_promotion.scope_validator" index-by="key" />

View file

@ -13,8 +13,39 @@
<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\Component\Promotion\Model\CatalogPromotionAction">
<constraint name="Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionAction">
<constraint name="Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionActionType">
<option name="groups">sylius</option>
</constraint>
<constraint name="Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionActionGroup">
<option name="groups">sylius</option>
</constraint>
<property name="type">
<constraint name="NotBlank">
<option name="message">sylius.catalog_promotion_action.type.not_blank</option>
<option name="groups">sylius</option>
</constraint>
</property>
<property name="configuration">
<constraint name="Collection">
<option name="groups">sylius_catalog_promotion_action_percentage_discount</option>
<option name="fields">
<value key="amount">
<constraint name="NotBlank">
<option name="message">sylius.catalog_promotion_action.percentage_discount.not_number_or_empty</option>
<option name="groups">sylius_catalog_promotion_action_percentage_discount</option>
</constraint>
<constraint name="Range">
<option name="min">0</option>
<option name="max">1</option>
<option name="notInRangeMessage">sylius.catalog_promotion_action.percentage_discount.not_in_range</option>
<option name="groups">sylius_catalog_promotion_action_percentage_discount</option>
</constraint>
</value>
</option>
<option name="allowExtraFields">true</option>
</constraint>
</property>
</class>
</constraint-mapping>

View file

@ -16,6 +16,9 @@ sylius:
state:
processing: The catalog promotion cannot be edited as it is currently being processed.
catalog_promotion_action:
type:
not_blank: Please choose an action type.
invalid: Catalog promotion action type is invalid. Available types are {{ available_action_types }}.
invalid_type: Catalog promotion action type is invalid. Please choose a valid type.
percentage_discount:
not_in_range: The percentage discount amount must be between 0% and 100%.

View file

@ -175,6 +175,35 @@ final class SyliusPromotionExtensionTest extends AbstractExtensionTestCase
);
}
/** @test */
public function it_loads_catalog_promotion_action_validation_groups_parameter_value_properly(): void
{
$this->load([
'catalog_promotion_action' => [
'validation_groups' => [
'something' => ['sylius', 'something'],
'test' => ['sylius', 'test'],
],
],
]);
$this->assertContainerBuilderHasParameter(
'sylius.promotion.catalog_promotion_action.validation_groups',
['something' => ['sylius', 'something'], 'test' => ['sylius', 'test']],
);
}
/** @test */
public function it_loads_empty_catalog_promotion_action_validation_groups_parameter_value(): void
{
$this->load();
$this->assertContainerBuilderHasParameter(
'sylius.promotion.catalog_promotion_action.validation_groups',
[],
);
}
/** @test */
public function it_loads_catalog_promotion_scope_validation_groups_parameter_value_properly(): void
{

View file

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* 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\PromotionBundle\Validator;
use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionAction;
use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionActionGroup;
use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class CatalogPromotionActionGroupValidator extends ConstraintValidator
{
/** @param array<string, array<string, string>> $validationGroups */
public function __construct(private array $validationGroups)
{
}
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof CatalogPromotionActionGroup) {
throw new UnexpectedTypeException($constraint, CatalogPromotionActionGroup::class);
}
if (!$value instanceof CatalogPromotionActionInterface) {
throw new UnexpectedTypeException($value, CatalogPromotionActionInterface::class);
}
$type = $value->getType();
if (null === $type || '' === $type) {
return;
}
$validator = $this->context->getValidator()->inContext($this->context);
$groups = $this->validationGroups[$type] ?? null;
if (null !== $groups) {
$validator->validate(value: $value, groups: $groups);
if ($this->context->getViolations()->count() > 0) {
return;
}
}
$validator->validate(
$value,
new CatalogPromotionAction(groups: $constraint->groups),
$groups ?? $constraint->groups,
);
}
}

View file

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* 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\PromotionBundle\Validator;
use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionActionType;
use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionScopeType;
use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class CatalogPromotionActionTypeValidator extends ConstraintValidator
{
/** @param array<array-key, string> $actionTypes */
public function __construct(private array $actionTypes)
{
}
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof CatalogPromotionActionType) {
throw new UnexpectedTypeException($constraint, CatalogPromotionScopeType::class);
}
if (!$value instanceof CatalogPromotionActionInterface) {
throw new UnexpectedTypeException($value, CatalogPromotionActionInterface::class);
}
$type = $value->getType();
if (null === $type || '' === $type) {
return;
}
if (!in_array($type, $this->actionTypes, true)) {
$this->context->buildViolation($constraint->invalidType)
->setParameter('{{ available_action_types }}', implode(', ', $this->actionTypes))
->atPath('type')
->addViolation()
;
}
}
}

View file

@ -15,7 +15,6 @@ namespace Sylius\Bundle\PromotionBundle\Validator;
use Sylius\Bundle\PromotionBundle\Validator\CatalogPromotionAction\ActionValidatorInterface;
use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionAction;
use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Webmozart\Assert\Assert;
@ -34,13 +33,6 @@ final class CatalogPromotionActionValidator extends ConstraintValidator
/** @var CatalogPromotionAction $constraint */
Assert::isInstanceOf($constraint, CatalogPromotionAction::class);
/** @var CatalogPromotionActionInterface $value */
if (!in_array($value->getType(), $this->actionTypes, true)) {
$this->context->buildViolation($constraint->invalidType)->atPath('type')->addViolation();
return;
}
$type = $value->getType();
if (!array_key_exists($type, $this->actionValidators)) {
return;

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* 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\PromotionBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
final class CatalogPromotionActionGroup extends Constraint
{
public function validatedBy(): string
{
return 'sylius_catalog_promotion_action_group';
}
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* 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\PromotionBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
final class CatalogPromotionActionType extends Constraint
{
public string $invalidType = 'sylius.catalog_promotion_action.type.invalid';
public function validatedBy(): string
{
return 'sylius_catalog_promotion_action_type_validator';
}
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}

View file

@ -0,0 +1,179 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* 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\PromotionBundle\Validator;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionAction;
use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionActionGroup;
use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Validator\ContextualValidatorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
final class CatalogPromotionActionGroupValidatorSpec extends ObjectBehavior
{
private const VALIDATION_GROUPS = [
'test' => [
'test_group',
],
'another_test' => [
'another_test_group',
],
];
function let(ExecutionContextInterface $context): void
{
$this->beConstructedWith(self::VALIDATION_GROUPS);
$this->initialize($context);
}
function it_is_a_constraint_validator(): void
{
$this->shouldHaveType(ConstraintValidator::class);
}
function it_throws_exception_when_constraint_is_not_catalog_promotion_action_group(
CatalogPromotionActionInterface $action,
Constraint $constraint,
): void {
$this
->shouldThrow(UnexpectedTypeException::class)
->during('validate', [$action, $constraint])
;
}
function it_throws_exception_when_value_is_not_catalog_promotion_action(): void
{
$this
->shouldThrow(UnexpectedTypeException::class)
->during('validate', [new \stdClass(), new CatalogPromotionActionGroup()])
;
}
function it_does_nothing_when_type_is_null(
ExecutionContextInterface $context,
CatalogPromotionActionInterface $action,
): void {
$action->getType()->willReturn(null);
$context->getValidator()->shouldNotBeCalled();
$this->validate($action, new CatalogPromotionActionGroup());
}
function it_does_nothing_when_type_is_an_empty_string(
ExecutionContextInterface $context,
CatalogPromotionActionInterface $action,
): void {
$action->getType()->willReturn('');
$context->getValidator()->shouldNotBeCalled();
$this->validate($action, new CatalogPromotionActionGroup());
}
function it_passes_configured_validation_groups_for_further_validation(
ExecutionContextInterface $context,
ValidatorInterface $validator,
ContextualValidatorInterface $contextualValidator,
ConstraintViolationListInterface $violationList,
CatalogPromotionActionInterface $action,
): void {
$constraint = new CatalogPromotionActionGroup();
$action->getType()->willReturn('test');
$context->getValidator()->willReturn($validator);
$validator->inContext($context)->willReturn($contextualValidator);
$contextualValidator
->validate($action, null, ['test_group'])
->shouldBeCalled()
->willReturn($contextualValidator)
;
$context->getViolations()->willReturn($violationList);
$violationList->count()->willReturn(1);
$contextualValidator
->validate($action, new CatalogPromotionAction(null, $constraint->groups), ['test_group'])
->shouldNotBeCalled()
;
$this->validate($action, $constraint);
}
function it_falls_back_to_previous_abstraction_when_no_violation_has_been_added(
ExecutionContextInterface $context,
ValidatorInterface $validator,
ContextualValidatorInterface $contextualValidator,
ConstraintViolationListInterface $violationList,
CatalogPromotionActionInterface $action,
): void {
$constraint = new CatalogPromotionActionGroup();
$action->getType()->willReturn('test');
$context->getValidator()->willReturn($validator);
$validator->inContext($context)->willReturn($contextualValidator);
$contextualValidator
->validate($action, null, ['test_group'])
->willReturn($contextualValidator)
->shouldBeCalled()
;
$context->getViolations()->willReturn($violationList);
$violationList->count()->willReturn(0);
$contextualValidator
->validate($action, new CatalogPromotionAction(null, $constraint->groups), ['test_group'])
->shouldBeCalled()
->willReturn($contextualValidator)
;
$this->validate($action, $constraint);
}
function it_falls_back_to_previous_abstraction_when_no_validation_group_for_the_action_type_has_been_passed(
ExecutionContextInterface $context,
ValidatorInterface $validator,
ContextualValidatorInterface $contextualValidator,
CatalogPromotionActionInterface $action,
): void {
$constraint = new CatalogPromotionActionGroup();
$action->getType()->willReturn('not_existing_type');
$context->getValidator()->willReturn($validator);
$validator->inContext($context)->willReturn($contextualValidator);
$contextualValidator->validate($action, null, Argument::any())->shouldNotBeCalled();
$context->getViolations()->shouldNotBeCalled();
$contextualValidator
->validate($action, new CatalogPromotionAction(null, $constraint->groups), $constraint->groups)
->shouldBeCalled()
->willReturn($contextualValidator)
;
$this->validate($action, $constraint);
}
}

View file

@ -0,0 +1,113 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* 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\PromotionBundle\Validator;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionActionType;
use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface;
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\Violation\ConstraintViolationBuilderInterface;
final class CatalogPromotionActionTypeValidatorSpec extends ObjectBehavior
{
private const ACTION_TYPES = [
'test',
'another_test',
];
function let(ExecutionContextInterface $context): void
{
$this->beConstructedWith(self::ACTION_TYPES);
$this->initialize($context);
}
function it_is_a_constraint_validator(): void
{
$this->shouldHaveType(ConstraintValidator::class);
}
function it_throws_exception_when_constraint_is_not_catalog_promotion_action_type(
Constraint $constraint,
CatalogPromotionActionInterface $action,
): void {
$this
->shouldThrow(UnexpectedTypeException::class)
->during('validate', [$action, $constraint])
;
}
function it_throws_exception_when_value_is_not_catalog_promotion_action(): void
{
$this
->shouldThrow(UnexpectedTypeException::class)
->during('validate', [new \stdClass(), new CatalogPromotionActionType()])
;
}
function it_does_nothing_when_passed_action_has_null_as_type(
ExecutionContextInterface $context,
CatalogPromotionActionInterface $action,
): void {
$action->getType()->willReturn(null);
$context->buildViolation(Argument::any())->shouldNotBeCalled();
$this->validate($action, new CatalogPromotionActionType());
}
function it_does_nothing_when_passed_action_has_an_empty_string_as_type(
ExecutionContextInterface $context,
CatalogPromotionActionInterface $action,
): void {
$action->getType()->willReturn('');
$context->buildViolation(Argument::any())->shouldNotBeCalled();
$this->validate($action, new CatalogPromotionActionType());
}
function it_does_nothing_when_catalog_promotion_action_has_valid_type(
ExecutionContextInterface $context,
CatalogPromotionActionInterface $action,
): void {
$action->getType()->willReturn('test');
$context->buildViolation(Argument::any())->shouldNotBeCalled();
$this->validate($action, new CatalogPromotionActionType());
}
function it_adds_violation_when_type_is_unknown(
ExecutionContextInterface $context,
ConstraintViolationBuilderInterface $violationBuilder,
CatalogPromotionActionInterface $action,
): void {
$constraint = new CatalogPromotionActionType();
$action->getType()->willReturn('not_existing_type');
$context->buildViolation($constraint->invalidType)->willReturn($violationBuilder);
$violationBuilder
->setParameter('{{ available_action_types }}', implode(', ', self::ACTION_TYPES))
->willReturn($violationBuilder)
;
$violationBuilder->atPath('type')->willReturn($violationBuilder);
$violationBuilder->addViolation()->shouldBeCalled();
$this->validate($action, $constraint);
}
}

View file

@ -14,6 +14,7 @@ declare(strict_types=1);
namespace spec\Sylius\Bundle\PromotionBundle\Validator;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\PromotionBundle\Validator\CatalogPromotionAction\ActionValidatorInterface;
use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionAction;
use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface;
@ -44,16 +45,13 @@ final class CatalogPromotionActionValidatorSpec extends ObjectBehavior
$this->shouldHaveType(ConstraintValidator::class);
}
function it_adds_violation_if_catalog_promotion_action_has_invalid_type(
function it_does_nothing_when_passed_action_type_has_no_validators(
ExecutionContextInterface $executionContext,
ConstraintViolationBuilderInterface $constraintViolationBuilder,
CatalogPromotionActionInterface $action,
): void {
$action->getType()->willReturn('wrong_type');
$action->getType()->willReturn('custom');
$executionContext->buildViolation('sylius.catalog_promotion_action.invalid_type')->willReturn($constraintViolationBuilder);
$constraintViolationBuilder->atPath('type')->willReturn($constraintViolationBuilder);
$constraintViolationBuilder->addViolation()->shouldBeCalled();
$executionContext->buildViolation(Argument::any())->shouldNotBeCalled();
$this->validate($action, new CatalogPromotionAction());
}

View file

@ -403,6 +403,12 @@ final class CatalogPromotionsTest extends JsonApiTestCase
'/api/v2/admin/channels/WEB',
],
'actions' => [
[
'type' => '',
'configuration' => [
'amount' => 0.5,
],
],
[
'type' => 'invalid_type',
'configuration' => [
@ -413,6 +419,12 @@ final class CatalogPromotionsTest extends JsonApiTestCase
'type' => PercentageDiscountPriceCalculator::TYPE,
'configuration' => [],
],
[
'type' => PercentageDiscountPriceCalculator::TYPE,
'configuration' => [
'amount' => null,
],
],
[
'type' => PercentageDiscountPriceCalculator::TYPE,
'configuration' => [
@ -429,12 +441,6 @@ final class CatalogPromotionsTest extends JsonApiTestCase
'type' => FixedDiscountPriceCalculator::TYPE,
'configuration' => [],
],
[
'type' => FixedDiscountPriceCalculator::TYPE,
'configuration' => [
'WEB' => [],
],
],
[
'type' => FixedDiscountPriceCalculator::TYPE,
'configuration' => [
@ -446,6 +452,16 @@ final class CatalogPromotionsTest extends JsonApiTestCase
[
'type' => FixedDiscountPriceCalculator::TYPE,
'configuration' => [
'WEB' => [],
'MOBILE' => [],
],
],
[
'type' => FixedDiscountPriceCalculator::TYPE,
'configuration' => [
'MOBILE' => [
'amount' => null,
],
'WEB' => [
'amount' => 'text',
],
@ -471,11 +487,64 @@ final class CatalogPromotionsTest extends JsonApiTestCase
], \JSON_THROW_ON_ERROR),
);
$this->assertResponse(
$this->client->getResponse(),
'admin/catalog_promotion/post_catalog_promotion_with_invalid_actions_response',
Response::HTTP_UNPROCESSABLE_ENTITY,
);
$this->assertJsonResponseViolations($this->client->getResponse(), [
[
'propertyPath' => 'actions[0].type',
'message' => 'Please choose an action type.',
],
[
'propertyPath' => 'actions[1].type',
'message' => 'Catalog promotion action type is invalid. Available types are fixed_discount, percentage_discount.',
],
[
'propertyPath' => 'actions[2].configuration[amount]',
'message' => 'This field is missing.',
],
[
'propertyPath' => 'actions[3].configuration[amount]',
'message' => 'The percentage discount amount must be a number and can not be empty.',
],
[
'propertyPath' => 'actions[4].configuration[amount]',
'message' => 'The percentage discount amount must be between 0% and 100%.',
],
[
'propertyPath' => 'actions[5].configuration[amount]',
'message' => 'This value should be a valid number.',
],
[
'propertyPath' => 'actions[6].configuration[MOBILE]',
'message' => 'This field is missing.',
],
[
'propertyPath' => 'actions[6].configuration[WEB]',
'message' => 'This field is missing.',
],
[
'propertyPath' => 'actions[7].configuration[MOBILE]',
'message' => 'This field is missing.',
],
[
'propertyPath' => 'actions[7].configuration[WEB]',
'message' => 'This field is missing.',
],
[
'propertyPath' => 'actions[8].configuration[WEB][amount]',
'message' => 'This field is missing.',
],
[
'propertyPath' => 'actions[8].configuration[MOBILE][amount]',
'message' => 'This field is missing.',
],
[
'propertyPath' => 'actions[9].configuration[MOBILE][amount]',
'message' => 'This value should not be blank.',
],
[
'propertyPath' => 'actions[9].configuration[WEB][amount]',
'message' => 'This value should be of type numeric.',
],
]);
}
/** @test */

View file

@ -1,48 +0,0 @@
{
"@context": "/api/v2/contexts/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description": "actions[0].type: Catalog promotion action type is invalid. Please choose a valid type.\nactions[1].configuration.amount: The percentage discount amount must be a number and can not be empty.\nactions[2].configuration.amount: The percentage discount amount must be between 0% and 100%.\nactions[3].configuration.amount: The percentage discount amount must be a number and can not be empty.\nactions[4].configuration: Provided configuration contains errors. Please add the fixed discount amount that is a number greater than 0.\nactions[5].configuration: Provided configuration contains errors. Please add the fixed discount amount that is a number greater than 0.\nactions[6].configuration: Configuration for one of the required channels is not provided.\nactions[7].configuration: Provided configuration contains errors. Please add the fixed discount amount that is a number greater than 0.",
"violations": [
{
"propertyPath": "actions[0].type",
"message": "Catalog promotion action type is invalid. Please choose a valid type.",
"code": null
},
{
"propertyPath": "actions[1].configuration.amount",
"message": "The percentage discount amount must be a number and can not be empty.",
"code": null
},
{
"propertyPath": "actions[2].configuration.amount",
"message": "The percentage discount amount must be between 0% and 100%.",
"code": null
},
{
"propertyPath": "actions[3].configuration.amount",
"message": "The percentage discount amount must be a number and can not be empty.",
"code": null
},
{
"propertyPath": "actions[4].configuration",
"message": "Provided configuration contains errors. Please add the fixed discount amount that is a number greater than 0.",
"code": null
},
{
"propertyPath": "actions[5].configuration",
"message": "Provided configuration contains errors. Please add the fixed discount amount that is a number greater than 0.",
"code": null
},
{
"propertyPath": "actions[6].configuration",
"message": "Configuration for one of the required channels is not provided.",
"code": null
},
{
"propertyPath": "actions[7].configuration",
"message": "Provided configuration contains errors. Please add the fixed discount amount that is a number greater than 0.",
"code": null
}
]
}

View file

@ -1,58 +0,0 @@
{
"@context": "/api/v2/contexts/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description": "scopes[0].type: Catalog promotion scope type is invalid. Please choose a valid type.\nscopes[1].configuration.variants: Please add at least 1 variant.\nscopes[2].configuration.variants: Please add at least 1 variant.\nscopes[3].configuration.variants: Provided configuration contains errors. Please add only existing variants.\nscopes[4].configuration.products: Provided configuration contains errors. Please add at least 1 product.\nscopes[5].configuration.products: Provided configuration contains errors. Please add at least 1 product.\nscopes[6].configuration.products: Provided configuration contains errors. Please add only existing products.\nscopes[7].configuration.taxons: Provided configuration contains errors. Please add at least 1 taxon.\nscopes[8].configuration.taxons: Provided configuration contains errors. Please add at least 1 taxon.\nscopes[9].configuration.taxons: Provided configuration contains errors. Please add only existing taxons.",
"violations": [
{
"propertyPath": "scopes[0].type",
"message": "Catalog promotion scope type is invalid. Please choose a valid type.",
"code": null
},
{
"propertyPath": "scopes[1].configuration.variants",
"message": "Please add at least 1 variant.",
"code": null
},
{
"propertyPath": "scopes[2].configuration.variants",
"message": "Please add at least 1 variant.",
"code": null
},
{
"propertyPath": "scopes[3].configuration.variants",
"message": "Provided configuration contains errors. Please add only existing variants.",
"code": null
},
{
"propertyPath": "scopes[4].configuration.products",
"message": "Provided configuration contains errors. Please add at least 1 product.",
"code": null
},
{
"propertyPath": "scopes[5].configuration.products",
"message": "Provided configuration contains errors. Please add at least 1 product.",
"code": null
},
{
"propertyPath": "scopes[6].configuration.products",
"message": "Provided configuration contains errors. Please add only existing products.",
"code": null
},
{
"propertyPath": "scopes[7].configuration.taxons",
"message": "Provided configuration contains errors. Please add at least 1 taxon.",
"code": null
},
{
"propertyPath": "scopes[8].configuration.taxons",
"message": "Provided configuration contains errors. Please add at least 1 taxon.",
"code": null
},
{
"propertyPath": "scopes[9].configuration.taxons",
"message": "Provided configuration contains errors. Please add only existing taxons.",
"code": null
}
]
}