mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-23 16:13:50 +00:00
Add test for detecting stale workflows and enhance handler
Added a new test to check if workflows are stale in EntityWorkflowTest. Enhanced CancelStaleWorkflowHandler to handle stale workflows more accurately, including checking if workflows have transitioned recently. Updated EntityWorkflow entity to cascade remove workflow steps. Refactor tests for handler, to avoid using $kernel during tests
This commit is contained in:
@@ -2,109 +2,160 @@
|
||||
|
||||
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 Services\Workflow;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
|
||||
use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowHandler;
|
||||
use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
|
||||
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||
use Symfony\Component\Workflow\DefinitionBuilder;
|
||||
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;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use DateTimeImmutable;
|
||||
|
||||
class CancelStaleWorkflowHandlerTest extends KernelTestCase
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class CancelStaleWorkflowHandlerTest extends TestCase
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
private Registry $registry;
|
||||
private LoggerInterface $logger;
|
||||
private EntityWorkflowRepository $workflowRepository;
|
||||
private ClockInterface $clock;
|
||||
use ProphecyTrait;
|
||||
|
||||
protected function setUp(): void
|
||||
public function testWorkflowWithOneStepOlderThan90DaysIsCanceled(): void
|
||||
{
|
||||
// Boot the Symfony kernel
|
||||
self::bootKernel();
|
||||
$clock = new MockClock('2024-01-01');
|
||||
$daysAgos = new \DateTimeImmutable('2023-09-01');
|
||||
|
||||
// Get the actual services from the container
|
||||
$this->em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
$this->registry = self::getContainer()->get(Registry::class);
|
||||
$this->logger = self::getContainer()->get(LoggerInterface::class);
|
||||
$this->clock = self::getContainer()->get(ClockInterface::class);
|
||||
$this->workflowRepository = $this->createMock(EntityWorkflowRepository::class);
|
||||
}
|
||||
|
||||
public function testWorkflowWithOneStepOlderThan90DaysIsDeleted(): void
|
||||
{
|
||||
$workflow = new EntityWorkflow();
|
||||
$initialStep = new EntityWorkflowStep();
|
||||
$initialStep->setTransitionAt(new DateTimeImmutable('-93 days'));
|
||||
$workflow->addStep($initialStep);
|
||||
$workflow->setWorkflowName('dummy_workflow');
|
||||
$workflow->setCreatedAt(new \DateTimeImmutable('2023-09-01'));
|
||||
$workflow->setStep('step1', new WorkflowTransitionContextDTO($workflow), 'to_step1', $daysAgos, new User());
|
||||
|
||||
$this->em->persist($workflow);
|
||||
$this->em->flush();
|
||||
$em = $this->prophesize(EntityManagerInterface::class);
|
||||
$em->flush()->shouldBeCalled();
|
||||
$em->remove($workflow)->shouldNotBeCalled();
|
||||
|
||||
$this->handleStaleWorkflow($workflow);
|
||||
$handler = $this->buildHandler($workflow, $em->reveal(), $clock);
|
||||
|
||||
$deletedWorkflow = $this->workflowRepository->find($workflow->getId());
|
||||
$this->assertNull($deletedWorkflow, 'The workflow should be deleted.');
|
||||
$handler(new CancelStaleWorkflowMessage(1));
|
||||
|
||||
$this->assertNull($this->em->getRepository(EntityWorkflowStep::class)->find($initialStep->getId()), 'The workflow step should be deleted.');
|
||||
self::assertEquals('canceled', $workflow->getStep());
|
||||
self::assertCount(3, $workflow->getSteps());
|
||||
}
|
||||
|
||||
public function testWorkflowWithMultipleStepsAndNoRecentTransitionsIsCanceled(): void
|
||||
public function testWorkflowNotInStaledHandlerIsUnrecoverable(): void
|
||||
{
|
||||
$this->expectException(UnrecoverableMessageHandlingException::class);
|
||||
|
||||
$clock = new MockClock('2024-01-01');
|
||||
$daysAgos = new \DateTimeImmutable('2023-12-31');
|
||||
|
||||
$workflow = new EntityWorkflow();
|
||||
$step1 = new EntityWorkflowStep();
|
||||
$step2 = new EntityWorkflowStep();
|
||||
$workflow->setWorkflowName('dummy_workflow');
|
||||
$workflow->setCreatedAt(new \DateTimeImmutable('2023-12-31'));
|
||||
$workflow->setStep('step1', new WorkflowTransitionContextDTO($workflow), 'to_step1', $daysAgos, new User());
|
||||
|
||||
$step1->setTransitionAt(new DateTimeImmutable('-92 days'));
|
||||
$step2->setTransitionAt(new DateTimeImmutable('-91 days'));
|
||||
$em = $this->prophesize(EntityManagerInterface::class);
|
||||
$em->flush()->shouldNotBeCalled();
|
||||
$em->remove($workflow)->shouldNotBeCalled();
|
||||
|
||||
$workflow->addStep($step1);
|
||||
$workflow->addStep($step2);
|
||||
$handler = $this->buildHandler($workflow, $em->reveal(), $clock);
|
||||
|
||||
$this->em->persist($workflow);
|
||||
$this->em->flush();
|
||||
|
||||
/** @var WorkflowInterface $workflowComponent */
|
||||
$workflowComponent = $this->registry->get($workflowComponent);
|
||||
|
||||
$transitions = $workflowComponent->getEnabledTransitions($workflow);
|
||||
$metadataStore = $workflowComponent->getMetadataStore();
|
||||
|
||||
$expectedTransition = null;
|
||||
|
||||
// Find the transition that was expected to be applied by the handler
|
||||
foreach ($transitions as $transition) {
|
||||
$isFinal = $metadataStore->getMetadata('isFinal', $transition);
|
||||
$isFinalPositive = $metadataStore->getMetadata('isFinalPositive', $transition);
|
||||
|
||||
if ($isFinal === true && $isFinalPositive === false) {
|
||||
$expectedTransition = $transition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertNotNull($expectedTransition, 'Expected to find a valid transition with isFinal = true and isFinalPositive = false.');
|
||||
|
||||
$this->handleStaleWorkflow($workflow);
|
||||
$updatedWorkflow = $this->workflowRepository->find($workflow->getId());
|
||||
|
||||
$this->assertEquals($expectedTransition->getName(), $updatedWorkflow->getCurrentStep());
|
||||
$handler(new CancelStaleWorkflowMessage(1));
|
||||
|
||||
self::assertEquals('canceled', $workflow->getStep());
|
||||
self::assertCount(3, $workflow->getSteps());
|
||||
}
|
||||
|
||||
public function handleStaleWorkflow($workflow): void
|
||||
public function testWorkflowStaledInInitialStateIsCompletelyRemoved(): void
|
||||
{
|
||||
$handler = new CancelStaleWorkflowHandler($this->workflowRepository, $this->registry, $this->em, $this->logger, $this->clock);
|
||||
$handler(new CancelStaleWorkflowMessage($workflow->getId()));
|
||||
$clock = new MockClock('2024-01-01');
|
||||
|
||||
$workflow = new EntityWorkflow();
|
||||
$workflow->setWorkflowName('dummy_workflow');
|
||||
$workflow->setCreatedAt(new \DateTimeImmutable('2023-09-01'));
|
||||
|
||||
$em = $this->prophesize(EntityManagerInterface::class);
|
||||
$em->flush()->shouldBeCalled();
|
||||
$em->remove($workflow)->shouldBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($workflow, $em->reveal(), $clock);
|
||||
|
||||
$handler(new CancelStaleWorkflowMessage(1));
|
||||
|
||||
self::assertEquals('canceled', $workflow->getStep());
|
||||
self::assertCount(2, $workflow->getSteps());
|
||||
}
|
||||
|
||||
private function buildHandler(
|
||||
EntityWorkflow $entityWorkflow,
|
||||
EntityManagerInterface $entityManager,
|
||||
ClockInterface $clock,
|
||||
): CancelStaleWorkflowHandler {
|
||||
// set an id for the workflow
|
||||
$reflection = new \ReflectionClass($entityWorkflow);
|
||||
$reflection->getProperty('id')->setValue($entityWorkflow, 1);
|
||||
|
||||
$repository = $this->prophesize(EntityWorkflowRepository::class);
|
||||
$repository->find(1)->willReturn($entityWorkflow);
|
||||
|
||||
return new CancelStaleWorkflowHandler($repository->reveal(), $this->buildRegistry(), $entityManager, new NullLogger(), $clock);
|
||||
}
|
||||
|
||||
private function buildRegistry(): Registry
|
||||
{
|
||||
$definitionBuilder = new DefinitionBuilder();
|
||||
|
||||
$definitionBuilder
|
||||
->setInitialPlaces('initial')
|
||||
->addPlaces(['initial', 'step1', 'canceled', 'final'])
|
||||
->addTransition(new Transition('to_step1', 'initial', 'step1'))
|
||||
->addTransition($cancelInit = new Transition('cancel', 'initial', 'canceled'))
|
||||
->addTransition($finalizeInit = new Transition('finalize', 'initial', 'final'))
|
||||
->addTransition($cancelStep1 = new Transition('cancel', 'step1', 'canceled'))
|
||||
->addTransition($finalizeStep1 = new Transition('finalize', 'step1', 'final'));
|
||||
|
||||
$transitionStorage = new \SplObjectStorage();
|
||||
$transitionStorage->attach($finalizeInit, ['isFinal' => true, 'isFinalPositive' => true]);
|
||||
$transitionStorage->attach($cancelInit, ['isFinal' => true, 'isFinalPositive' => false]);
|
||||
$transitionStorage->attach($finalizeStep1, ['isFinal' => true, 'isFinalPositive' => true]);
|
||||
$transitionStorage->attach($cancelStep1, ['isFinal' => true, 'isFinalPositive' => false]);
|
||||
|
||||
$definitionBuilder->setMetadataStore(new InMemoryMetadataStore([], [], $transitionStorage));
|
||||
$workflow = new Workflow($definitionBuilder->build(), new EntityWorkflowMarkingStore(), null, 'dummy_workflow');
|
||||
$supports =
|
||||
new class () implements WorkflowSupportStrategyInterface {
|
||||
public function supports(WorkflowInterface $workflow, object $subject): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$registry = new Registry();
|
||||
$registry->addWorkflow($workflow, $supports);
|
||||
|
||||
return $registry;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user