diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php index 1f32a1985..6e6cee077 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php @@ -38,7 +38,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface /** * @var Collection */ - #[ORM\OneToMany(targetEntity: EntityWorkflowComment::class, mappedBy: 'entityWorkflow', orphanRemoval: true)] + #[ORM\OneToMany(mappedBy: 'entityWorkflow', targetEntity: EntityWorkflowComment::class, orphanRemoval: true)] private Collection $comments; #[ORM\Id] diff --git a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php index a3eefb193..2aae89f3c 100644 --- a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php +++ b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php @@ -35,7 +35,7 @@ class CancelStaleWorkflowHandler $workflow = $this->workflowRepository->find($workflowId); - if (in_array($workflow, $staleWorkflows, true)) { + if (!in_array($workflow, $staleWorkflows, true)) { $this->logger->alert('Workflow has transitioned in the meantime.', [$workflowId]); return; } @@ -69,7 +69,7 @@ class CancelStaleWorkflowHandler } if (!$transitionApplied) { - $this->logger->error('No valid transition found for EntityWorkflow %d.', [$workflowId]); + $this->logger->error('No valid transition found for EntityWorkflow.', [$workflowId]); throw new UnrecoverableMessageHandlingException(sprintf('No valid transition found for EntityWorkflow %d.', $workflowId)); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php index dbf88098f..23c99d16a 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php @@ -2,139 +2,117 @@ 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\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; -use Chill\MainBundle\Service\Workflow\CancelStaleWorkflow; use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowHandler; use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; -use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; -use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Workflow\Registry; -use Symfony\Component\Workflow\Transition; use Symfony\Component\Workflow\WorkflowInterface; +use Symfony\Component\Yaml\Yaml; +use DateTimeImmutable; -/** - * @internal - * - * @coversNothing - */ -class CancelStaleWorkflowHandlerTest extends TestCase +class CancelStaleWorkflowHandlerTest extends KernelTestCase { - public function testInvokeWorkflowWithOneStep(): void + private EntityManagerInterface $em; + private Registry $registry; + private LoggerInterface $logger; + private EntityWorkflowRepository $workflowRepository; + private ClockInterface $clock; + private string $workflowName; + + protected function setUp(): void { - $workflow = $this->createMock(EntityWorkflow::class); - $workflow->method('getSteps')->willReturn(new ArrayCollection([$this->createMock(EntityWorkflowStep::class)])); - $workflow->expects($this->once())->method('getCurrentStep')->willReturn(new EntityWorkflowStep()); + // Boot the Symfony kernel + self::bootKernel(); - $workflowRepository = $this->createMock(EntityWorkflowRepository::class); - $workflowRepository->expects($this->once())->method('find')->with($this->identicalTo(1))->willReturn($workflow); + // 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); - $em = $this->createMock(EntityManagerInterface::class); - $em->expects($this->exactly(2))->method('remove')->with($this->isInstanceOf(EntityWorkflowStep::class)); - $em->expects($this->once())->method('flush'); + // Retrieve the workflow configuration dynamically + $configPath = self::$kernel->getProjectDir() . '/config/packages/workflow.yaml'; // Adjust the path if needed + $config = Yaml::parseFile($configPath); - $registry = $this->createMock(Registry::class); - $logger = $this->createMock(LoggerInterface::class); - - $handler = new CancelStaleWorkflowHandler($workflowRepository, $registry, $em, $logger); - - $handler(new CancelStaleWorkflowMessage(1)); + // Extract the workflow name from the configuration + $this->workflowName = array_key_first($config['framework']['workflows']); } - public function testInvokeWorkflowWithMultipleStepsAndValidTransition(): void + public function testWorkflowWithOneStepOlderThan90DaysIsDeleted(): void { - $workflow = $this->createMock(EntityWorkflow::class); - $workflow->method('getSteps')->willReturn(new ArrayCollection([$this->createMock(EntityWorkflowStep::class), $this->createMock(EntityWorkflowStep::class)])); + $workflow = new EntityWorkflow(); + $initialStep = new EntityWorkflowStep(); + $initialStep->setTransitionAt(new DateTimeImmutable('-93 days')); + $workflow->addStep($initialStep); - $transition = $this->createMock(Transition::class); + $this->em->persist($workflow); + $this->em->flush(); - $workflowComponent = $this->createMock(WorkflowInterface::class); - $registryMock = $this->createMock(Registry::class); - $registryMock->method('get') - ->willReturn($workflowComponent); + $this->handleStaleWorkflow($workflow); - $workflowComponent->method('getEnabledTransitions')->willReturn([$transition]); - $workflowComponent->expects($this->once())->method('apply')->with($workflow, 'annule'); + $deletedWorkflow = $this->workflowRepository->find($workflow->getId()); + $this->assertNull($deletedWorkflow, 'The workflow should be deleted.'); - $metadataStore = $this->createMock(MetadataStore::class); - $metadataStore->method('getMetadata')->willReturnMap([ - ['isFinal', $transition, true], - ['isFinalPositive', $transition, false], - ]); - - $workflowComponent->method('getMetadataStore')->willReturn($metadataStore); - - $workflowRepository = $this->createMock(EntityWorkflowRepository::class); - $workflowRepository->expects($this->once())->method('find')->with($this->identicalTo(1))->willReturn($workflow); - - $em = $this->createMock(EntityManagerInterface::class); - $em->expects($this->never())->method('remove'); - $em->expects($this->once())->method('flush'); - - $registry = $this->createMock(Registry::class); - $registry->method('get')->willReturn($workflowComponent); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once())->method('info')->with('EntityWorkflow 1 has been transitioned.'); - - $handler = new CancelStaleWorkflowHandler($workflowRepository, $registry, $em, $logger); - - $handler(new CancelStaleWorkflowMessage(1)); + $this->assertNull($this->em->getRepository(EntityWorkflowStep::class)->find($initialStep->getId()), 'The workflow step should be deleted.'); } - public function testInvokeWorkflowWithMultipleStepsAndNoValidTransition(): void + public function testWorkflowWithMultipleStepsAndNoRecentTransitionsIsCanceled(): void { - $this->expectException(UnrecoverableMessageHandlingException::class); - $this->expectExceptionMessage('No valid transition found for EntityWorkflow 1.'); + $workflow = new EntityWorkflow(); + $step1 = new EntityWorkflowStep(); + $step2 = new EntityWorkflowStep(); - $workflow = $this->createMock(EntityWorkflow::class); - $workflow->method('getSteps')->willReturn(new ArrayCollection([$this->createMock(EntityWorkflowStep::class), $this->createMock(EntityWorkflowStep::class)])); + $step1->setTransitionAt(new DateTimeImmutable('-92 days')); + $step2->setTransitionAt(new DateTimeImmutable('-91 days')); - $transition = $this->createMock(Transition::class); + $workflow->addStep($step1); + $workflow->addStep($step2); - $workflowComponent = $this->createMock(WorkflowInterface::class); - $registryMock = $this->createMock(Registry::class); - $registryMock->method('get') - ->willReturn($workflowComponent); - $workflowComponent->method('getEnabledTransitions')->willReturn([$transition]); + $this->em->persist($workflow); + $this->em->flush(); - $metadataStore = $this->createMock(MetadataStoreInterface::class); - $metadataStore->method('getMetadata')->willReturnMap([ - ['isFinal', $transition, false], - ['isFinalPositive', $transition, true], - ]); + /** @var WorkflowInterface $workflowComponent */ + $workflowComponent = $this->registry->get($workflow, $this->workflowName); - $workflowComponent->method('getMetadataStore')->willReturn($metadataStore); + $transitions = $workflowComponent->getEnabledTransitions($workflow); + $metadataStore = $workflowComponent->getMetadataStore(); - $workflowRepository = $this->createMock(EntityWorkflowRepository::class); - $workflowRepository->expects($this->once())->method('find')->with($this->identicalTo(1))->willReturn($workflow); + $expectedTransition = null; - $em = $this->createMock(EntityManagerInterface::class); - $em->expects($this->never())->method('remove'); - $em->expects($this->never())->method('flush'); + // 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); - $registry = $this->createMock(Registry::class); - $registry->method('get')->willReturn($workflowComponent); + if ($isFinal === true && $isFinalPositive === false) { + $expectedTransition = $transition; + break; + } + } - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once())->method('error')->with('No valid transition found for EntityWorkflow 1.'); + $this->assertNotNull($expectedTransition, 'Expected to find a valid transition with isFinal = true and isFinalPositive = false.'); - $handler = new CancelStaleWorkflowHandler($workflowRepository, $registry, $em, $logger); + $this->handleStaleWorkflow($workflow); + $updatedWorkflow = $this->workflowRepository->find($workflow->getId()); + + $this->assertEquals($expectedTransition->getName(), $updatedWorkflow->getCurrentStep()); - $handler(new CancelStaleWorkflowMessage(1)); } + + public function handleStaleWorkflow($workflow): void + { + $handler = new CancelStaleWorkflowHandler($this->workflowRepository, $this->registry, $this->em, $this->logger, $this->clock); + $handler(new CancelStaleWorkflowMessage($workflow->getId())); + } + }