chill-bundles/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowGuardTransitionTest.php
Julien Fastré 4d8de46ac9
Apply the voter to allow all transition on EntityWorkflowGuardTransition
This allow to effectively check that a user is allowed to apply all transitions on a workflow and, if yes, enable the given transition.
2024-09-16 14:47:00 +02:00

179 lines
8.1 KiB
PHP

<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Workflow\EventSubscriber;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Security\Authorization\EntityWorkflowTransitionVoter;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
use Chill\MainBundle\Workflow\EventSubscriber\EntityWorkflowGuardTransition;
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Workflow\DefinitionBuilder;
use Symfony\Component\Workflow\Exception\NotEnabledTransitionException;
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Workflow\WorkflowInterface;
/**
* @internal
*
* @coversNothing
*/
class EntityWorkflowGuardTransitionTest extends TestCase
{
use ProphecyTrait;
public static function buildRegistry(?EventSubscriberInterface $eventSubscriber): Registry
{
$builder = new DefinitionBuilder();
$builder
->setInitialPlaces(['initial'])
->addPlaces(['initial', 'intermediate', 'step1', 'step2', 'step3'])
->addTransition(new Transition('intermediate', 'initial', 'intermediate'))
->addTransition($transition1 = new Transition('transition1', 'intermediate', 'step1'))
->addTransition($transition2 = new Transition('transition2', 'intermediate', 'step2'))
->addTransition($transition3 = new Transition('transition3', 'intermediate', 'step3'))
;
$transitionMetadata = new \SplObjectStorage();
$transitionMetadata->attach($transition1, ['transitionGuard' => 'only-dest']);
$transitionMetadata->attach($transition2, ['transitionGuard' => 'only-dest+system']);
$transitionMetadata->attach($transition3, ['transitionGuard' => 'system']);
$builder->setMetadataStore(new InMemoryMetadataStore(transitionsMetadata: $transitionMetadata));
if (null !== $eventSubscriber) {
$eventDispatcher = new EventDispatcher();
$eventDispatcher->addSubscriber($eventSubscriber);
}
$workflow = new Workflow($builder->build(), new EntityWorkflowMarkingStore(), $eventDispatcher ?? null, 'dummy');
$registry = new Registry();
$registry->addWorkflow(
$workflow,
new class () implements WorkflowSupportStrategyInterface {
public function supports(WorkflowInterface $workflow, object $subject): bool
{
return true;
}
}
);
return $registry;
}
/**
* @dataProvider provideBlockingTransition
*/
public function testTransitionGuardBlocked(EntityWorkflow $entityWorkflow, string $transition, ?User $user, bool $isGrantedAllTransition, string $uuid): void
{
$userRender = $this->prophesize(UserRender::class);
$userRender->renderString(Argument::type(User::class), [])->willReturn('user-as-string');
$security = $this->prophesize(Security::class);
$security->getUser()->willReturn($user);
$security->isGranted(EntityWorkflowTransitionVoter::APPLY_ALL_TRANSITIONS, $entityWorkflow->getCurrentStep())
->willReturn($isGrantedAllTransition);
$transitionGuard = new EntityWorkflowGuardTransition($userRender->reveal(), $security->reveal());
$registry = self::buildRegistry($transitionGuard);
$workflow = $registry->get($entityWorkflow, 'dummy');
$context = new WorkflowTransitionContextDTO($entityWorkflow);
self::expectException(NotEnabledTransitionException::class);
try {
$workflow->apply($entityWorkflow, $transition, ['context' => $context, 'byUser' => $user, 'transition' => $transition, 'transitionAt' => new \DateTimeImmutable('now')]);
} catch (NotEnabledTransitionException $e) {
$list = $e->getTransitionBlockerList();
self::assertEquals(1, $list->count());
$list = iterator_to_array($list->getIterator());
self::assertEquals($uuid, $list[0]->getCode());
throw $e;
}
}
/**
* @dataProvider provideValidTransition
*/
public function testTransitionGuardValid(EntityWorkflow $entityWorkflow, string $transition, ?User $user, bool $isGrantedAllTransition, string $newStep): void
{
$userRender = $this->prophesize(UserRender::class);
$userRender->renderString(Argument::type(User::class), [])->willReturn('user-as-string');
$security = $this->prophesize(Security::class);
$security->getUser()->willReturn($user);
$security->isGranted(EntityWorkflowTransitionVoter::APPLY_ALL_TRANSITIONS, $entityWorkflow->getCurrentStep())
->willReturn($isGrantedAllTransition);
$transitionGuard = new EntityWorkflowGuardTransition($userRender->reveal(), $security->reveal());
$registry = self::buildRegistry($transitionGuard);
$workflow = $registry->get($entityWorkflow, 'dummy');
$context = new WorkflowTransitionContextDTO($entityWorkflow);
$workflow->apply($entityWorkflow, $transition, ['context' => $context, 'byUser' => $user, 'transition' => $transition, 'transitionAt' => new \DateTimeImmutable('now')]);
self::assertEquals($newStep, $entityWorkflow->getStep());
}
public static function provideBlockingTransition(): iterable
{
yield [self::buildEntityWorkflow([new User()]), 'transition1', new User(), false, 'f3eeb57c-7532-11ec-9495-e7942a2ac7bc'];
yield [self::buildEntityWorkflow([]), 'transition1', null, false, 'd9e39a18-704c-11ef-b235-8fe0619caee7'];
yield [self::buildEntityWorkflow([new User()]), 'transition1', null, false, 'd9e39a18-704c-11ef-b235-8fe0619caee7'];
yield [self::buildEntityWorkflow([$user = new User()]), 'transition3', $user, false, '5b6b95e0-704d-11ef-a5a9-4b6fc11a8eeb'];
yield [self::buildEntityWorkflow([$user = new User()]), 'transition3', $user, true, '5b6b95e0-704d-11ef-a5a9-4b6fc11a8eeb'];
}
public static function provideValidTransition(): iterable
{
yield [self::buildEntityWorkflow([$u = new User()]), 'transition1', $u, false, 'step1'];
yield [self::buildEntityWorkflow([$u = new User()]), 'transition2', $u, false, 'step2'];
yield [self::buildEntityWorkflow([new User()]), 'transition2', null, false, 'step2'];
yield [self::buildEntityWorkflow([]), 'transition2', null, false, 'step2'];
yield [self::buildEntityWorkflow([new User()]), 'transition3', null, false, 'step3'];
yield [self::buildEntityWorkflow([]), 'transition3', null, false, 'step3'];
// transition allowed thanks to permission "apply all transitions"
yield [self::buildEntityWorkflow([new User()]), 'transition1', new User(), true, 'step1'];
yield [self::buildEntityWorkflow([new User()]), 'transition2', new User(), true, 'step2'];
}
public static function buildEntityWorkflow(array $futureDestUsers): EntityWorkflow
{
$registry = self::buildRegistry(null);
$baseContext = ['transition' => 'intermediate', 'transitionAt' => new \DateTimeImmutable()];
// test a user not is destination is blocked
$entityWorkflow = new EntityWorkflow();
$workflow = $registry->get($entityWorkflow, 'dummy');
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers = $futureDestUsers;
$workflow->apply($entityWorkflow, 'intermediate', ['context' => $dto, ...$baseContext]);
return $entityWorkflow;
}
}