From 50ced223c6ab9ebc850c30505bacef2b2d28c1e4 Mon Sep 17 00:00:00 2001 From: Teisha McRae Date: Tue, 6 Oct 2026 11:48:06 -0400 Subject: [PATCH] Handle rollback service tasks without engine Persisted tokens created during rollback may not have a live engine attached. This change prevents inline service-task execution when no engine is available and instead queues the job. It also adds a regression test covering service tasks without an engine and fixes a couple of assertion comparisons in the rollback tests. --- .../Nayra/Managers/WorkflowManagerDefault.php | 8 ++++-- .../RollbackProcessRequestTest.php | 25 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php index 126abeafa7..89f5462cb8 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php @@ -202,8 +202,10 @@ private function runInlineTask(Token $token, $jobClass) private function canRunInlineTask(Token $token, EntityInterface $element): bool { $instance = $token->getInstance(); - $engine = $instance->getEngine(); - if (!$engine->isInlineTaskExecutionEnabled()) { + // A persisted token, such as one copied during rollback, is not attached + // to a running engine and must be queued instead of executed inline. + $engine = $instance?->getEngine(); + if ($engine === null || !$engine->isInlineTaskExecutionEnabled()) { return false; } @@ -265,6 +267,7 @@ public function runScripTask(ScriptTaskInterface $scriptTask, Token $token) if ($this->canRunInlineTask($token, $scriptTask)) { $this->runInlineTask($token, RunScriptTask::class); + return; } @@ -285,6 +288,7 @@ public function runServiceTask(ServiceTaskInterface $serviceTask, Token $token) if ($this->canRunInlineTask($token, $serviceTask)) { $this->runInlineTask($token, RunServiceTask::class); + return; } diff --git a/tests/unit/ProcessMaker/RollbackProcessRequestTest.php b/tests/unit/ProcessMaker/RollbackProcessRequestTest.php index 754ee5307a..779c872484 100644 --- a/tests/unit/ProcessMaker/RollbackProcessRequestTest.php +++ b/tests/unit/ProcessMaker/RollbackProcessRequestTest.php @@ -4,8 +4,10 @@ use Facades\ProcessMaker\RollbackProcessRequest; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Bus; use Mockery; use ProcessMaker\Facades\WorkflowManager; +use ProcessMaker\Jobs\RunServiceTask; use ProcessMaker\Models\Comment; use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessRequest; @@ -82,6 +84,25 @@ public function testRollbackToServiceTask() $this->assertEquals('ACTIVE', $this->processRequest->refresh()->status); } + public function testServiceTaskWithoutEngineIsQueued(): void + { + Bus::fake(); + + $token = ProcessRequestToken::factory()->create([ + 'status' => 'ACTIVE', + 'element_type' => 'serviceTask', + 'element_id' => 'node_2', + ]); + $serviceTask = Mockery::mock(ServiceTaskInterface::class); + $serviceTask->shouldReceive('getId')->andReturn('node_2'); + + WorkflowManager::runServiceTask($serviceTask, $token); + + Bus::assertDispatched(RunServiceTask::class, function (RunServiceTask $job) use ($token) { + return $job->tokenId === $token->id; + }); + } + private function mockRunScriptTask() { $mocksScriptTask = Mockery::mock(ScriptTaskInterface::class); @@ -91,7 +112,7 @@ private function mockRunScriptTask() ->andReturn($mocksScriptTask); WorkflowManager::shouldReceive('runScripTask') ->withArgs(function ($scriptTask, $task) use ($mocksScriptTask) { - return $scriptTask === $mocksScriptTask && $task->element_id = 'node_5'; + return $scriptTask === $mocksScriptTask && $task->element_id === 'node_5'; }); return $mockProcessDefinitions; @@ -106,7 +127,7 @@ private function mockRunServiceTask() ->andReturn($mockServiceTask); WorkflowManager::shouldReceive('runServiceTask') ->withArgs(function ($serviceTask, $task) use ($mockServiceTask) { - return $serviceTask === $mockServiceTask && $task->element_id = 'node_5'; + return $serviceTask === $mockServiceTask && $task->element_id === 'node_5'; }); return $mockProcessDefinitions;