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:
2024-09-09 14:59:26 +02:00
parent d152efe084
commit f4356ac249
5 changed files with 226 additions and 107 deletions

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Service\Workflow;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Clock\ClockInterface;
@@ -20,58 +21,68 @@ use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Workflow\Registry;
#[AsMessageHandler]
class CancelStaleWorkflowHandler
final readonly class CancelStaleWorkflowHandler
{
public const string KEEP_INTERVAL = 'P90D';
public function __construct(private readonly EntityWorkflowRepository $workflowRepository, private readonly Registry $registry, private readonly EntityManagerInterface $em, private readonly LoggerInterface $logger, private readonly ClockInterface $clock) {}
public function __construct(
private EntityWorkflowRepository $workflowRepository,
private Registry $registry,
private EntityManagerInterface $em,
private LoggerInterface $logger,
private ClockInterface $clock,
) {}
public function __invoke(CancelStaleWorkflowMessage $message): void
{
$workflowId = $message->getWorkflowId();
$olderThanDate = $this->clock->now()->sub(new \DateInterval(CancelStaleWorkflowCronJob::KEEP_INTERVAL));
$olderThanDate = $this->clock->now()->sub(new \DateInterval(self::KEEP_INTERVAL));
$staleWorkflows = $this->workflowRepository->findWorkflowsWithoutFinalStepAndOlderThan($olderThanDate);
$workflow = $this->workflowRepository->find($message->getWorkflowId());
if (null === $workflow) {
$this->logger->alert('Workflow was not found!', [$workflowId]);
$workflow = $this->workflowRepository->find($workflowId);
if (!in_array($workflow, $staleWorkflows, true)) {
$this->logger->alert('Workflow has transitioned in the meantime.', [$workflowId]);
return;
}
if (null === $workflow) {
$this->logger->alert('Workflow was not found!', [$workflowId]);
return;
if (false === $workflow->isStaledAt($olderThanDate)) {
$this->logger->alert('Workflow has transitioned in the meantime.', [$workflowId]);
throw new UnrecoverableMessageHandlingException('the workflow is not staled any more');
}
$workflowComponent = $this->registry->get($workflow, $workflow->getWorkflowName());
$metadataStore = $workflowComponent->getMetadataStore();
$transitions = $workflowComponent->getEnabledTransitions($workflow);
$steps = $workflow->getSteps();
$transitionApplied = false;
$wasInInitialPosition = 'initial' === $workflow->getStep();
if (1 === count($steps)) {
$this->em->remove($workflow->getCurrentStep());
$this->em->remove($workflow);
} else {
foreach ($transitions as $transition) {
$isFinal = $metadataStore->getMetadata('isFinal', $transition);
$isFinalPositive = $metadataStore->getMetadata('isFinalPositive', $transition);
foreach ($transitions as $transition) {
$isFinal = $metadataStore->getMetadata('isFinal', $transition);
$isFinalPositive = $metadataStore->getMetadata('isFinalPositive', $transition);
if ($isFinal && !$isFinalPositive) {
$workflowComponent->apply($workflow, $transition->getName());
$this->logger->info('EntityWorkflow has been cancelled automatically.', [$workflowId]);
$transitionApplied = true;
break;
}
}
if (!$transitionApplied) {
$this->logger->error('No valid transition found for EntityWorkflow.', [$workflowId]);
throw new UnrecoverableMessageHandlingException(sprintf('No valid transition found for EntityWorkflow %d.', $workflowId));
if ($isFinal && !$isFinalPositive) {
$dto = new WorkflowTransitionContextDTO($workflow);
$workflowComponent->apply($workflow, $transition->getName(), [
'context' => $dto,
'byUser' => null,
'transitionAt' => $this->clock->now(),
'transition' => $transition->getName(),
]);
$this->logger->info('EntityWorkflow has been cancelled automatically.', [$workflowId]);
$transitionApplied = true;
break;
}
}
if (!$transitionApplied) {
$this->logger->error('No valid transition found for EntityWorkflow.', [$workflowId]);
throw new UnrecoverableMessageHandlingException(sprintf('No valid transition found for EntityWorkflow %d.', $workflowId));
}
if ($wasInInitialPosition) {
$this->em->remove($workflow);
}
$this->em->flush();
}
}