Add tests for channel-based promotion eligibility and application

This commit is contained in:
Tomasz Kaliński 2026-06-16 10:09:25 +02:00
parent d32696a6da
commit 16ac32798b
4 changed files with 900 additions and 0 deletions

View file

@ -0,0 +1,69 @@
<?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 Tests\Sylius\Bundle\CoreBundle\DependencyInjection\Compiler;
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Sylius\Bundle\CoreBundle\DependencyInjection\Compiler\OverridePromotionServicesPass;
use Sylius\Component\Core\Promotion\Action\ChannelAwarePromotionApplicator;
use Sylius\Component\Core\Promotion\Checker\Eligibility\ChannelAwarePromotionRulesEligibilityChecker;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
#[CoversClass(OverridePromotionServicesPass::class)]
final class OverridePromotionServicesPassTest extends AbstractCompilerPassTestCase
{
protected function registerCompilerPass(ContainerBuilder $container): void
{
$container->addCompilerPass(new OverridePromotionServicesPass());
}
public function testItReplacesClassOfRulesEligibilityChecker(): void
{
$this->setDefinition('sylius.checker.promotion.rules_eligibility', new Definition(\stdClass::class));
$this->compile();
$this->assertContainerBuilderHasService(
'sylius.checker.promotion.rules_eligibility',
ChannelAwarePromotionRulesEligibilityChecker::class,
);
}
public function testItReplacesClassOfPromotionApplicator(): void
{
$this->setDefinition('sylius.action.applicator.promotion', new Definition(\stdClass::class));
$this->compile();
$this->assertContainerBuilderHasService(
'sylius.action.applicator.promotion',
ChannelAwarePromotionApplicator::class,
);
}
public function testItDoesNothingIfRulesEligibilityCheckerServiceDoesNotExist(): void
{
$this->compile();
$this->assertContainerBuilderNotHasService('sylius.checker.promotion.rules_eligibility');
}
public function testItDoesNothingIfPromotionApplicatorServiceDoesNotExist(): void
{
$this->compile();
$this->assertContainerBuilderNotHasService('sylius.action.applicator.promotion');
}
}

View file

@ -0,0 +1,185 @@
<?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 Tests\Sylius\Bundle\CoreBundle\Validator\Constraints;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Sylius\Bundle\CoreBundle\Validator\Constraints\PromotionConfigurationChannelCodes;
use Sylius\Bundle\CoreBundle\Validator\Constraints\PromotionConfigurationChannelCodesValidator;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Promotion\ChannelAwareConfigurationInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
#[CoversClass(PromotionConfigurationChannelCodesValidator::class)]
final class PromotionConfigurationChannelCodesValidatorTest extends TestCase
{
private ChannelRepositoryInterface&MockObject $channelRepository;
private ExecutionContextInterface&MockObject $executionContext;
private PromotionConfigurationChannelCodesValidator $validator;
protected function setUp(): void
{
$this->channelRepository = $this->createMock(ChannelRepositoryInterface::class);
$this->executionContext = $this->createMock(ExecutionContextInterface::class);
$this->validator = new PromotionConfigurationChannelCodesValidator($this->channelRepository);
$this->validator->initialize($this->executionContext);
}
public function testItIsAConstraintValidator(): void
{
$this->assertInstanceOf(PromotionConfigurationChannelCodesValidator::class, $this->validator);
}
public function testItThrowsAnExceptionIfConstraintIsWrongType(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->channelRepository->expects($this->never())->method('findOneByCode');
$this->executionContext->expects($this->never())->method('buildViolation');
$this->validator->validate([], $this->createMock(Constraint::class));
}
public function testItDoesNothingIfValueIsNotAnArray(): void
{
$this->channelRepository->expects($this->never())->method('findOneByCode');
$this->executionContext->expects($this->never())->method('buildViolation');
$this->validator->validate('not_an_array', new PromotionConfigurationChannelCodes());
}
public function testItDoesNothingIfChannelsKeyIsMissing(): void
{
$this->channelRepository->expects($this->never())->method('findOneByCode');
$this->executionContext->expects($this->never())->method('buildViolation');
$this->validator->validate(['count' => 5], new PromotionConfigurationChannelCodes());
}
public function testItDoesNothingIfChannelsListIsEmpty(): void
{
$this->channelRepository->expects($this->never())->method('findOneByCode');
$this->executionContext->expects($this->never())->method('buildViolation');
$this->validator->validate(
[ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => []],
new PromotionConfigurationChannelCodes(),
);
}
public function testItDoesNotAddViolationIfAllChannelCodesExist(): void
{
$channel = $this->createMock(ChannelInterface::class);
$this->channelRepository
->expects($this->exactly(2))
->method('findOneByCode')
->willReturn($channel);
$this->executionContext->expects($this->never())->method('buildViolation');
$this->validator->validate(
[ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB', 'MOBILE']],
new PromotionConfigurationChannelCodes(),
);
}
public function testItAddsViolationForInvalidChannelCode(): void
{
$this->channelRepository
->expects($this->once())
->method('findOneByCode')
->with('INVALID_CODE')
->willReturn(null);
$violationBuilder = $this->createMock(ConstraintViolationBuilderInterface::class);
$violationBuilder
->expects($this->once())
->method('setParameter')
->with('{{ channelCode }}', 'INVALID_CODE')
->willReturn($violationBuilder);
$violationBuilder
->expects($this->once())
->method('atPath')
->with('[' . ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY . ']')
->willReturn($violationBuilder);
$violationBuilder->expects($this->once())->method('addViolation');
$this->executionContext
->expects($this->once())
->method('buildViolation')
->with((new PromotionConfigurationChannelCodes())->invalidChannelCodeMessage)
->willReturn($violationBuilder);
$this->validator->validate(
[ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['INVALID_CODE']],
new PromotionConfigurationChannelCodes(),
);
}
public function testItAddsOneViolationPerInvalidChannelCode(): void
{
$channel = $this->createMock(ChannelInterface::class);
$this->channelRepository
->expects($this->exactly(3))
->method('findOneByCode')
->willReturnMap([
['WEB', $channel],
['INVALID_ONE', null],
['INVALID_TWO', null],
]);
$violationBuilder = $this->createMock(ConstraintViolationBuilderInterface::class);
$violationBuilder->method('setParameter')->willReturn($violationBuilder);
$violationBuilder->method('atPath')->willReturn($violationBuilder);
$violationBuilder->expects($this->exactly(2))->method('addViolation');
$this->executionContext
->expects($this->exactly(2))
->method('buildViolation')
->willReturn($violationBuilder);
$this->validator->validate(
[ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB', 'INVALID_ONE', 'INVALID_TWO']],
new PromotionConfigurationChannelCodes(),
);
}
public function testItAddsViolationForNonStringChannelCode(): void
{
$this->channelRepository->expects($this->never())->method('findOneByCode');
$violationBuilder = $this->createMock(ConstraintViolationBuilderInterface::class);
$violationBuilder->method('setParameter')->willReturn($violationBuilder);
$violationBuilder->method('atPath')->willReturn($violationBuilder);
$violationBuilder->expects($this->once())->method('addViolation');
$this->executionContext
->expects($this->once())
->method('buildViolation')
->willReturn($violationBuilder);
$this->validator->validate(
[ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => [42]],
new PromotionConfigurationChannelCodes(),
);
}
}

View file

@ -0,0 +1,368 @@
<?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 Tests\Sylius\Component\Core\Promotion\Action;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Sylius\Component\Channel\Model\ChannelInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Promotion\Action\ChannelAwarePromotionApplicator;
use Sylius\Component\Core\Promotion\ChannelAwareConfigurationInterface;
use Sylius\Component\Promotion\Action\PromotionActionCommandInterface;
use Sylius\Component\Promotion\Action\PromotionApplicatorInterface;
use Sylius\Component\Promotion\Model\PromotionActionInterface;
use Sylius\Component\Promotion\Model\PromotionInterface;
use Sylius\Component\Promotion\Model\PromotionSubjectInterface;
use Sylius\Component\Registry\ServiceRegistryInterface;
#[CoversClass(ChannelAwarePromotionApplicator::class)]
final class ChannelAwarePromotionApplicatorTest extends TestCase
{
private MockObject&PromotionActionCommandInterface $actionCommand;
private MockObject&PromotionActionInterface $action;
private MockObject&PromotionInterface $promotion;
private MockObject&OrderInterface $order;
private MockObject&PromotionSubjectInterface $subject;
private ChannelInterface&MockObject $channel;
private MockObject&ServiceRegistryInterface $serviceRegistry;
private ChannelAwarePromotionApplicator $applicator;
protected function setUp(): void
{
$this->actionCommand = $this->createMock(PromotionActionCommandInterface::class);
$this->action = $this->createMock(PromotionActionInterface::class);
$this->promotion = $this->createMock(PromotionInterface::class);
$this->order = $this->createMock(OrderInterface::class);
$this->subject = $this->createMock(PromotionSubjectInterface::class);
$this->channel = $this->createMock(ChannelInterface::class);
$this->serviceRegistry = $this->createMock(ServiceRegistryInterface::class);
$this->applicator = new ChannelAwarePromotionApplicator($this->serviceRegistry);
}
public function testShouldImplementPromotionApplicatorInterfaceAndChannelAwareConfigurationInterface(): void
{
$this->assertInstanceOf(PromotionApplicatorInterface::class, $this->applicator);
$this->assertInstanceOf(ChannelAwareConfigurationInterface::class, $this->applicator);
}
public function testShouldExecuteActionAndAddPromotionIfActionReturnsTrue(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action->expects($this->exactly(2))->method('getConfiguration')->willReturn([]);
$this->action->expects($this->once())->method('getType')->willReturn('order_percentage_discount');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->actionCommand);
$this->actionCommand
->expects($this->once())
->method('execute')
->with($this->order, [], $this->promotion)
->willReturn(true);
$this->order->expects($this->once())->method('addPromotion')->with($this->promotion);
$this->applicator->apply($this->order, $this->promotion);
}
public function testShouldNotAddPromotionIfActionReturnsFalse(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action->expects($this->exactly(2))->method('getConfiguration')->willReturn([]);
$this->action->expects($this->once())->method('getType')->willReturn('order_percentage_discount');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->actionCommand);
$this->actionCommand
->expects($this->once())
->method('execute')
->with($this->order, [], $this->promotion)
->willReturn(false);
$this->order->expects($this->never())->method('addPromotion');
$this->applicator->apply($this->order, $this->promotion);
}
public function testShouldSkipActionDuringApplyIfChannelNotMatching(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action
->expects($this->once())
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('MOBILE');
$this->serviceRegistry->expects($this->never())->method('get');
$this->actionCommand->expects($this->never())->method('execute');
$this->order->expects($this->never())->method('addPromotion');
$this->applicator->apply($this->order, $this->promotion);
}
public function testShouldExecuteActionDuringApplyIfChannelMatches(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->action->expects($this->once())->method('getType')->willReturn('order_percentage_discount');
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('WEB');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->actionCommand);
$this->actionCommand
->expects($this->once())
->method('execute')
->with($this->order, [], $this->promotion)
->willReturn(true);
$this->order->expects($this->once())->method('addPromotion')->with($this->promotion);
$this->applicator->apply($this->order, $this->promotion);
}
public function testShouldExecuteActionIfChannelsListIsEmpty(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => []]);
$this->action->expects($this->once())->method('getType')->willReturn('order_percentage_discount');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->actionCommand);
$this->actionCommand
->expects($this->once())
->method('execute')
->with($this->order, [], $this->promotion)
->willReturn(true);
$this->order->expects($this->once())->method('addPromotion')->with($this->promotion);
$this->applicator->apply($this->order, $this->promotion);
}
public function testShouldNotSkipActionIfSubjectIsNotOrderInterface(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->action->expects($this->once())->method('getType')->willReturn('order_percentage_discount');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->actionCommand);
$this->actionCommand
->expects($this->once())
->method('execute')
->with($this->subject, [], $this->promotion)
->willReturn(true);
$this->subject->expects($this->once())->method('addPromotion')->with($this->promotion);
$this->applicator->apply($this->subject, $this->promotion);
}
public function testShouldStripChannelsKeyFromConfigurationDuringApply(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([
'amount' => 100,
ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB'],
]);
$this->action->expects($this->once())->method('getType')->willReturn('order_fixed_discount');
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('WEB');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->actionCommand);
$this->actionCommand
->expects($this->once())
->method('execute')
->with($this->order, ['amount' => 100], $this->promotion)
->willReturn(true);
$this->order->expects($this->once())->method('addPromotion')->with($this->promotion);
$this->applicator->apply($this->order, $this->promotion);
}
public function testShouldAddPromotionIfAtLeastOneActionAppliedDespiteOtherBeingSkipped(): void
{
$secondAction = $this->createMock(PromotionActionInterface::class);
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action, $secondAction]),
);
$this->action
->expects($this->once())
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$secondAction->expects($this->exactly(2))->method('getConfiguration')->willReturn([]);
$secondAction->expects($this->once())->method('getType')->willReturn('order_percentage_discount');
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('MOBILE');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->actionCommand);
$this->actionCommand
->expects($this->once())
->method('execute')
->with($this->order, [], $this->promotion)
->willReturn(true);
$this->order->expects($this->once())->method('addPromotion')->with($this->promotion);
$this->applicator->apply($this->order, $this->promotion);
}
public function testShouldNotAddPromotionIfAllActionsSkippedDueToChannel(): void
{
$secondAction = $this->createMock(PromotionActionInterface::class);
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action, $secondAction]),
);
$this->action
->expects($this->once())
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$secondAction
->expects($this->once())
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('MOBILE');
$this->serviceRegistry->expects($this->never())->method('get');
$this->order->expects($this->never())->method('addPromotion');
$this->applicator->apply($this->order, $this->promotion);
}
public function testShouldSkipActionDuringRevertIfChannelNotMatching(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action
->expects($this->once())
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('MOBILE');
$this->serviceRegistry->expects($this->never())->method('get');
$this->actionCommand->expects($this->never())->method('revert');
$this->order->expects($this->once())->method('removePromotion')->with($this->promotion);
$this->applicator->revert($this->order, $this->promotion);
}
public function testShouldRevertActionIfChannelMatches(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->action->expects($this->once())->method('getType')->willReturn('order_percentage_discount');
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('WEB');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->actionCommand);
$this->actionCommand
->expects($this->once())
->method('revert')
->with($this->order, [], $this->promotion);
$this->order->expects($this->once())->method('removePromotion')->with($this->promotion);
$this->applicator->revert($this->order, $this->promotion);
}
public function testShouldStripChannelsKeyFromConfigurationDuringRevert(): void
{
$this->promotion->expects($this->once())->method('getActions')->willReturn(
new ArrayCollection([$this->action]),
);
$this->action
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([
'amount' => 100,
ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB'],
]);
$this->action->expects($this->once())->method('getType')->willReturn('order_fixed_discount');
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('WEB');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->actionCommand);
$this->actionCommand
->expects($this->once())
->method('revert')
->with($this->order, ['amount' => 100], $this->promotion);
$this->order->expects($this->once())->method('removePromotion')->with($this->promotion);
$this->applicator->revert($this->order, $this->promotion);
}
}

View file

@ -0,0 +1,278 @@
<?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 Tests\Sylius\Component\Core\Promotion\Checker\Eligibility;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Sylius\Component\Channel\Model\ChannelInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Promotion\ChannelAwareConfigurationInterface;
use Sylius\Component\Core\Promotion\Checker\Eligibility\ChannelAwarePromotionRulesEligibilityChecker;
use Sylius\Component\Promotion\Checker\Eligibility\PromotionEligibilityCheckerInterface;
use Sylius\Component\Promotion\Checker\Rule\RuleCheckerInterface;
use Sylius\Component\Promotion\Model\PromotionInterface;
use Sylius\Component\Promotion\Model\PromotionRuleInterface;
use Sylius\Component\Promotion\Model\PromotionSubjectInterface;
use Sylius\Component\Registry\ServiceRegistryInterface;
#[CoversClass(ChannelAwarePromotionRulesEligibilityChecker::class)]
final class ChannelAwarePromotionRulesEligibilityCheckerTest extends TestCase
{
private MockObject&RuleCheckerInterface $ruleChecker;
private MockObject&PromotionRuleInterface $rule;
private MockObject&PromotionInterface $promotion;
private MockObject&OrderInterface $order;
private MockObject&PromotionSubjectInterface $subject;
private ChannelInterface&MockObject $channel;
private MockObject&ServiceRegistryInterface $serviceRegistry;
private ChannelAwarePromotionRulesEligibilityChecker $checker;
protected function setUp(): void
{
$this->ruleChecker = $this->createMock(RuleCheckerInterface::class);
$this->rule = $this->createMock(PromotionRuleInterface::class);
$this->promotion = $this->createMock(PromotionInterface::class);
$this->order = $this->createMock(OrderInterface::class);
$this->subject = $this->createMock(PromotionSubjectInterface::class);
$this->channel = $this->createMock(ChannelInterface::class);
$this->serviceRegistry = $this->createMock(ServiceRegistryInterface::class);
$this->checker = new ChannelAwarePromotionRulesEligibilityChecker($this->serviceRegistry);
}
public function testShouldImplementPromotionEligibilityCheckerInterfaceAndChannelAwareConfigurationInterface(): void
{
$this->assertInstanceOf(PromotionEligibilityCheckerInterface::class, $this->checker);
$this->assertInstanceOf(ChannelAwareConfigurationInterface::class, $this->checker);
}
public function testShouldReturnEligibleIfPromotionHasNoRules(): void
{
$this->promotion->expects($this->once())->method('hasRules')->willReturn(false);
$this->promotion->expects($this->never())->method('getRules');
$this->assertTrue($this->checker->isEligible($this->order, $this->promotion));
}
public function testShouldReturnEligibleIfAllRulesPassWithNoChannelRestriction(): void
{
$secondRule = $this->createMock(PromotionRuleInterface::class);
$this->promotion->expects($this->once())->method('hasRules')->willReturn(true);
$this->promotion->expects($this->once())->method('getRules')->willReturn(
new ArrayCollection([$this->rule, $secondRule]),
);
$this->rule->expects($this->exactly(2))->method('getConfiguration')->willReturn([]);
$this->rule->expects($this->once())->method('getType')->willReturn('item_total');
$secondRule->expects($this->exactly(2))->method('getConfiguration')->willReturn([]);
$secondRule->expects($this->once())->method('getType')->willReturn('cart_quantity');
$this->serviceRegistry->expects($this->exactly(2))->method('get')->willReturn($this->ruleChecker);
$this->ruleChecker->expects($this->exactly(2))->method('isEligible')->willReturn(true);
$this->assertTrue($this->checker->isEligible($this->order, $this->promotion));
}
public function testShouldReturnNotEligibleIfAnyRuleFails(): void
{
$secondRule = $this->createMock(PromotionRuleInterface::class);
$this->promotion->expects($this->once())->method('hasRules')->willReturn(true);
$this->promotion->expects($this->once())->method('getRules')->willReturn(
new ArrayCollection([$this->rule, $secondRule]),
);
$this->rule->expects($this->exactly(2))->method('getConfiguration')->willReturn([]);
$this->rule->expects($this->once())->method('getType')->willReturn('item_total');
$secondRule->expects($this->exactly(2))->method('getConfiguration')->willReturn([]);
$secondRule->expects($this->once())->method('getType')->willReturn('cart_quantity');
$firstRuleChecker = $this->createMock(RuleCheckerInterface::class);
$secondRuleChecker = $this->createMock(RuleCheckerInterface::class);
$this->serviceRegistry
->expects($this->exactly(2))
->method('get')
->willReturnOnConsecutiveCalls($firstRuleChecker, $secondRuleChecker);
$firstRuleChecker->expects($this->once())->method('isEligible')->willReturn(true);
$secondRuleChecker->expects($this->once())->method('isEligible')->willReturn(false);
$this->assertFalse($this->checker->isEligible($this->order, $this->promotion));
}
public function testShouldStopCheckingRulesAfterFirstFailure(): void
{
$secondRule = $this->createMock(PromotionRuleInterface::class);
$this->promotion->expects($this->once())->method('hasRules')->willReturn(true);
$this->promotion->expects($this->once())->method('getRules')->willReturn(
new ArrayCollection([$this->rule, $secondRule]),
);
$this->rule->expects($this->exactly(2))->method('getConfiguration')->willReturn([]);
$this->rule->expects($this->once())->method('getType')->willReturn('item_total');
$secondRule->expects($this->never())->method('getType');
$secondRule->expects($this->never())->method('getConfiguration');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->ruleChecker);
$this->ruleChecker->expects($this->once())->method('isEligible')->willReturn(false);
$this->assertFalse($this->checker->isEligible($this->order, $this->promotion));
}
public function testShouldSkipRuleIfCurrentChannelNotInChannelsList(): void
{
$this->promotion->expects($this->once())->method('hasRules')->willReturn(true);
$this->promotion->expects($this->once())->method('getRules')->willReturn(
new ArrayCollection([$this->rule]),
);
$this->rule
->expects($this->once())
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('MOBILE');
$this->serviceRegistry->expects($this->never())->method('get');
$this->assertTrue($this->checker->isEligible($this->order, $this->promotion));
}
public function testShouldNotSkipRuleIfCurrentChannelIsInChannelsList(): void
{
$this->promotion->expects($this->once())->method('hasRules')->willReturn(true);
$this->promotion->expects($this->once())->method('getRules')->willReturn(
new ArrayCollection([$this->rule]),
);
$this->rule
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->rule->expects($this->once())->method('getType')->willReturn('item_total');
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('WEB');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->ruleChecker);
$this->ruleChecker->expects($this->once())->method('isEligible')->willReturn(true);
$this->assertTrue($this->checker->isEligible($this->order, $this->promotion));
}
public function testShouldNotSkipRuleIfChannelsListIsEmpty(): void
{
$this->promotion->expects($this->once())->method('hasRules')->willReturn(true);
$this->promotion->expects($this->once())->method('getRules')->willReturn(
new ArrayCollection([$this->rule]),
);
$this->rule
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => []]);
$this->rule->expects($this->once())->method('getType')->willReturn('item_total');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->ruleChecker);
$this->ruleChecker->expects($this->once())->method('isEligible')->willReturn(true);
$this->assertTrue($this->checker->isEligible($this->order, $this->promotion));
}
public function testShouldNotSkipRuleIfSubjectIsNotAnOrderInterface(): void
{
$this->promotion->expects($this->once())->method('hasRules')->willReturn(true);
$this->promotion->expects($this->once())->method('getRules')->willReturn(
new ArrayCollection([$this->rule]),
);
$this->rule
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->rule->expects($this->once())->method('getType')->willReturn('item_total');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->ruleChecker);
$this->ruleChecker->expects($this->once())->method('isEligible')->willReturn(true);
$this->assertTrue($this->checker->isEligible($this->subject, $this->promotion));
}
public function testShouldStripChannelsKeyFromConfigurationBeforePassingToRuleChecker(): void
{
$this->promotion->expects($this->once())->method('hasRules')->willReturn(true);
$this->promotion->expects($this->once())->method('getRules')->willReturn(
new ArrayCollection([$this->rule]),
);
$this->rule
->expects($this->exactly(2))
->method('getConfiguration')
->willReturn([
'count' => 3,
ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB'],
]);
$this->rule->expects($this->once())->method('getType')->willReturn('cart_quantity');
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('WEB');
$this->serviceRegistry->expects($this->once())->method('get')->willReturn($this->ruleChecker);
$this->ruleChecker
->expects($this->once())
->method('isEligible')
->with($this->order, ['count' => 3])
->willReturn(true);
$this->assertTrue($this->checker->isEligible($this->order, $this->promotion));
}
public function testShouldReturnEligibleIfAllRulesForCurrentChannelAreSkipped(): void
{
$secondRule = $this->createMock(PromotionRuleInterface::class);
$this->promotion->expects($this->once())->method('hasRules')->willReturn(true);
$this->promotion->expects($this->once())->method('getRules')->willReturn(
new ArrayCollection([$this->rule, $secondRule]),
);
$this->rule
->expects($this->once())
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$secondRule
->expects($this->once())
->method('getConfiguration')
->willReturn([ChannelAwareConfigurationInterface::CHANNELS_CONFIGURATION_KEY => ['WEB']]);
$this->order->method('getChannel')->willReturn($this->channel);
$this->channel->method('getCode')->willReturn('MOBILE');
$this->serviceRegistry->expects($this->never())->method('get');
$this->assertTrue($this->checker->isEligible($this->order, $this->promotion));
}
}