diff --git a/ProcessMaker/BpmnEngine.php b/ProcessMaker/BpmnEngine.php index 1a1d1133bc..fc921ab316 100644 --- a/ProcessMaker/BpmnEngine.php +++ b/ProcessMaker/BpmnEngine.php @@ -189,6 +189,11 @@ public function setInlineTaskExecutionEnabled(bool $enabled): void public function scheduleInlineJob(array $job): void { + // This is a single pending-job slot; replacing it would silently skip a task. + if ($this->nextInlineJob !== null) { + throw new \LogicException('An inline BPMN job is already scheduled.'); + } + $this->nextInlineJob = $job; } diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php index 11a8d3007f..126abeafa7 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php @@ -23,7 +23,9 @@ use ProcessMaker\Models\Process as Definitions; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestToken as Token; +use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface; use ProcessMaker\Nayra\Contracts\Bpmn\BoundaryEventInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\CallActivityInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EntityInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EventDefinitionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\ProcessInterface; @@ -194,7 +196,10 @@ private function runInlineTask(Token $token, $jobClass) ]); } - private function canRunInlineTask(Token $token): bool + /** + * Determine whether a task can safely reuse the current linear execution context. + */ + private function canRunInlineTask(Token $token, EntityInterface $element): bool { $instance = $token->getInstance(); $engine = $instance->getEngine(); @@ -202,6 +207,39 @@ private function canRunInlineTask(Token $token): bool return false; } + // These execution models require isolation across requests or task instances. + if ($instance->getRawOriginal('process_collaboration_id') + || $instance->getRawOriginal('parent_request_id') + || $token->isMultiInstance()) { + return false; + } + + // Boundary events depend on normal queued failure and event handling. + if ($element instanceof ActivityInterface && $element->getBoundaryEvents()->count() > 0) { + return false; + } + + $process = $element->getProcess(); + // BPMN participant metadata detects collaboration when request metadata is unavailable. + if ($process?->getProperty(ProcessInterface::BPMN_PROPERTY_PARTICIPANT)) { + return false; + } + + // Keep parent tasks asynchronous before and after child-process execution. + foreach ($process?->getActivities() ?? [] as $activity) { + if ($activity instanceof CallActivityInterface) { + return false; + } + } + + // Inline execution must not bypass an explicitly configured service queue. + if ($element instanceof ServiceTaskInterface) { + $configuration = json_decode($element->getProperty('config', '{}'), true) ?: []; + if (($configuration['queue'] ?? 'bpmn') !== 'bpmn') { + return false; + } + } + $activeTokens = collect($instance->getTokens()) ->filter(fn ($currentToken) => !in_array( $currentToken->getStatus(), @@ -210,6 +248,7 @@ private function canRunInlineTask(Token $token): bool )) ->values(); + // Reuse is safe only for the current token in a strictly linear state. return $activeTokens->count() === 1 && (string) $activeTokens->first()->getId() === (string) $token->getId(); } @@ -224,7 +263,7 @@ public function runScripTask(ScriptTaskInterface $scriptTask, Token $token) { Log::info('Dispatch a script task: ' . $scriptTask->getId() . ' #' . $token->getId()); - if ($this->canRunInlineTask($token)) { + if ($this->canRunInlineTask($token, $scriptTask)) { $this->runInlineTask($token, RunScriptTask::class); return; } @@ -244,7 +283,7 @@ public function runServiceTask(ServiceTaskInterface $serviceTask, Token $token) { Log::info('Dispatch a service task: ' . $serviceTask->getId()); - if ($this->canRunInlineTask($token)) { + if ($this->canRunInlineTask($token, $serviceTask)) { $this->runInlineTask($token, RunServiceTask::class); return; } diff --git a/tests/Feature/Api/InlineTaskExecutionTest.php b/tests/Feature/Api/InlineTaskExecutionTest.php new file mode 100644 index 0000000000..98a315f49b --- /dev/null +++ b/tests/Feature/Api/InlineTaskExecutionTest.php @@ -0,0 +1,492 @@ +setUpMockScriptRunners(); + InlineTaskTestService::reset(); + InlineRevisionTestService::$duringRun = null; + + $implementations = [ + 'inline-test-service-a' => InlineTaskTestService::class, + 'inline-test-service-b' => InlineTaskTestServiceB::class, + 'inline-test-service-c' => InlineTaskTestService::class, + 'inline-test-parallel-service' => InlineTaskTestService::class, + 'inline-test-collaboration-service' => InlineTaskTestService::class, + 'inline-test-call-child-service' => InlineTaskTestService::class, + 'inline-test-mi-service' => InlineTaskTestService::class, + 'inline-test-mi-setup-service' => InlineTaskTestService::class, + 'inline-test-custom-queue-service' => InlineTaskTestService::class, + 'inline-test-revision-service' => InlineRevisionTestService::class, + ]; + + foreach ($implementations as $implementation => $class) { + $this->assertTrue(WorkflowManager::registerServiceImplementation($implementation, $class)); + } + + } + + public function teardownInlineTaskExecution(): void + { + InlineRevisionTestService::$duringRun = null; + InlineTaskTestService::reset(); + } + + public function testSeq01RunsSequentialScriptAndServiceTasksInOneJob(): void + { + Bus::fake(); + $request = $this->startInlineProcess('InlineSequentialScriptServiceTasks.bpmn', 'start'); + + $this->completeUserTask($request, 'user_task'); + $firstJob = $this->onlyJob(RunScriptTask::class, 'script_a', $request->id); + $firstJob->handle(); + + $this->assertCount(1, $this->jobs(RunScriptTask::class, requestId: $request->id)); + $this->assertCount(0, $this->jobs(RunServiceTask::class, requestId: $request->id)); + $this->assertRequestData($request, [ + 'seq_script_a' => true, + 'seq_script_b' => true, + 'seq_service_c' => true, + ]); + $this->assertRequestCompleted($request); + $this->assertNoRequestLocks($request); + } + + public function testSeq02RunsDifferentServiceImplementationsInOneJob(): void + { + Bus::fake(); + $request = $this->startInlineProcess('InlineSequentialServiceTasks.bpmn', 'start'); + + $this->onlyJob(RunServiceTask::class, 'service_a', $request->id)->handle(); + + $this->assertCount(1, $this->jobs(RunServiceTask::class, requestId: $request->id)); + $this->assertSame( + ['service_a', 'service_b', 'service_c'], + InlineTaskTestService::elementRunsForRequest($request->id) + ); + $this->assertRequestData($request, [ + 'seq_service_a' => true, + 'seq_service_b' => true, + 'seq_service_c' => true, + ]); + $this->assertRequestCompleted($request); + $this->assertNoRequestLocks($request); + } + + public function testPar01KeepsParallelBranchTasksAsynchronous(): void + { + Bus::fake(); + $request = $this->startInlineProcess('InlineParallelScriptServiceTasks.bpmn', 'start'); + + $this->assertCount(2, $request->tokens()->where('status', 'ACTIVE')->get()); + $scriptJob = $this->onlyJob(RunScriptTask::class, 'branch_script', $request->id); + $serviceJob = $this->onlyJob(RunServiceTask::class, 'branch_service', $request->id); + + $scriptJob->handle(); + $this->assertSame('ACTIVE', $request->refresh()->status); + $serviceJob->handle(); + + $this->assertRequestData($request, [ + 'parallel_script' => true, + 'parallel_service' => true, + ]); + $this->assertRequestCompleted($request); + $this->assertNoRequestLocks($request); + } + + public function testExc01KeepsInterruptingAndNonInterruptingBoundaryTasksAsynchronous(): void + { + Bus::fake(); + $interrupting = $this->startInlineProcess( + 'InlineBoundaryEventScriptTask.bpmn', + 'start_interrupting' + ); + + $this->onlyJob(RunScriptTask::class, 'interrupting_setup', $interrupting->id)->handle(); + $this->onlyJob(RunScriptTask::class, 'script_interrupting', $interrupting->id)->handle(); + + $this->assertRequestData($interrupting, ['interrupting_setup' => true]); + $this->assertRequestCompleted($interrupting); + $this->assertNoRequestLocks($interrupting); + + Bus::fake(); + $nonInterrupting = $this->startInlineProcess( + 'InlineBoundaryEventScriptTask.bpmn', + 'start_non_interrupting' + ); + + $this->onlyJob(RunScriptTask::class, 'non_interrupting_setup', $nonInterrupting->id)->handle(); + $this->onlyJob(RunScriptTask::class, 'script_non_interrupting', $nonInterrupting->id)->handle(); + + $failingToken = $nonInterrupting->tokens() + ->where('element_id', 'script_non_interrupting') + ->where('status', 'FAILING') + ->first(); + $this->assertNotNull($failingToken); + $this->assertRequestData($nonInterrupting, ['non_interrupting_setup' => true]); + $this->assertNoRequestLocks($nonInterrupting); + } + + public function testExc02StopsInlineExecutionAtIntermediateCatchEvent(): void + { + Bus::fake(); + $request = $this->startInlineProcess('InlineIntermediateEventChain.bpmn', 'start'); + + $this->onlyJob(RunScriptTask::class, 'script_a', $request->id)->handle(); + $this->assertNotNull($this->activeToken($request, 'wait_for_signal')); + $this->assertCount(0, $this->jobs(RunScriptTask::class, 'script_b', $request->id)); + + (new CatchSignalEventInRequest($request->refresh(), [], 'INLINE_CONTINUE_SIGNAL'))->handle(); + $this->onlyJob(RunScriptTask::class, 'script_b', $request->id)->handle(); + + $this->assertRequestData($request, [ + 'intermediate_script_a' => true, + 'intermediate_script_b' => true, + ]); + $this->assertRequestCompleted($request); + $this->assertNoRequestLocks($request); + } + + public function testExc03KeepsCollaborationTasksAsynchronousAcrossParticipants(): void + { + Bus::fake(); + $participantA = $this->startInlineProcess('InlineCollaborationScriptTasks.bpmn', 'start_a'); + + $this->onlyJob(RunScriptTask::class, 'participant_a_script_a', $participantA->id)->handle(); + $this->onlyJob(RunScriptTask::class, 'participant_a_script_b', $participantA->id)->handle(); + + $collaborationId = $participantA->refresh()->process_collaboration_id; + $participantB = ProcessRequest::query() + ->where('process_collaboration_id', $collaborationId) + ->whereKeyNot($participantA->id) + ->firstOrFail(); + + $this->onlyJob(RunServiceTask::class, 'participant_b_service', $participantB->id)->handle(); + $this->onlyJob(RunScriptTask::class, 'participant_b_script', $participantB->id)->handle(); + + $this->assertRequestData($participantA, [ + 'participant_a_script_a' => true, + 'participant_a_script_b' => true, + ]); + $this->assertRequestData($participantB, [ + 'participant_b_service' => true, + 'participant_b_script' => true, + ]); + $this->assertRequestCompleted($participantA); + $this->assertRequestCompleted($participantB); + $this->assertNoRequestLocks($participantA, $participantB); + } + + public function testExc04KeepsCallActivityAndChildRequestAsynchronous(): void + { + Bus::fake(); + $child = $this->createInlineProcess('InlineCallActivityChild.bpmn'); + $parent = $this->createInlineProcess('InlineCallActivityChain.bpmn', [ + '[child_process_id]' => (string) $child->id, + ]); + $parentRequest = $this->startProcess($parent, 'start'); + + $this->onlyJob(RunScriptTask::class, 'script_a', $parentRequest->id)->handle(); + $childRequest = ProcessRequest::where('parent_request_id', $parentRequest->id)->firstOrFail(); + $this->assertCount(0, $this->jobs(RunScriptTask::class, 'script_b', $parentRequest->id)); + + $this->onlyJob(RunServiceTask::class, 'child_service', $childRequest->id)->handle(); + $this->onlyJob(RunScriptTask::class, 'script_b', $parentRequest->id)->handle(); + + $this->assertRequestData($childRequest, ['call_child_service' => true]); + $this->assertRequestData($parentRequest, [ + 'call_parent_script_a' => true, + 'call_child_service' => true, + 'call_parent_script_b' => true, + ]); + $this->assertRequestCompleted($childRequest); + $this->assertRequestCompleted($parentRequest); + $this->assertNoRequestLocks($parentRequest, $childRequest); + } + + public function testExc05KeepsSequentialAndParallelMultiInstanceTasksAsynchronous(): void + { + Bus::fake(); + $sequential = $this->startInlineProcess( + 'InlineMultiInstanceAutomatedTask.bpmn', + 'start_sequential', + ['sequential_items' => [['id' => 1], ['id' => 2], ['id' => 3]]] + ); + $this->onlyJob(RunScriptTask::class, 'sequential_setup', $sequential->id)->handle(); + + for ($index = 0; $index < 3; $index++) { + $jobs = $this->jobs(RunServiceTask::class, 'sequential_service', $sequential->id); + $this->assertCount($index + 1, $jobs); + $jobs->get($index)->handle(); + } + + $this->assertSame( + ['sequential_service', 'sequential_service', 'sequential_service'], + InlineTaskTestService::elementRunsForRequest($sequential->id) + ); + $this->assertRequestCompleted($sequential); + $this->assertNoRequestLocks($sequential); + + InlineTaskTestService::reset(); + Bus::fake(); + $parallel = $this->startInlineProcess( + 'InlineMultiInstanceAutomatedTask.bpmn', + 'start_parallel', + ['parallel_items' => [['id' => 1], ['id' => 2], ['id' => 3]]] + ); + $this->onlyJob(RunServiceTask::class, 'parallel_setup', $parallel->id)->handle(); + + $parallelJobs = $this->jobs(RunScriptTask::class, 'parallel_script', $parallel->id); + $this->assertCount(3, $parallelJobs); + $parallelJobs->each(fn (RunScriptTask $job) => $job->handle()); + + $this->assertCount( + 3, + $parallel->tokens()->where('element_id', 'parallel_script')->get() + ); + $this->assertRequestCompleted($parallel); + $this->assertNoRequestLocks($parallel); + } + + public function testErr01AttributesFailureAndDoesNotRunSubsequentTask(): void + { + Bus::fake(); + $request = $this->startInlineProcess('InlineFailingTaskChain.bpmn', 'start'); + + $this->onlyJob(RunScriptTask::class, 'successful_task', $request->id)->handle(); + + $failingToken = $request->tokens() + ->where('element_id', 'failing_task') + ->where('status', 'FAILING') + ->first(); + $this->assertNotNull($failingToken); + $this->assertSame('ERROR', $request->refresh()->status); + $this->assertTrue($request->data['successful_task_ran'] ?? false); + $this->assertArrayNotHasKey('must_not_run', $request->data); + $this->assertFalse($request->tokens()->where('element_id', 'must_not_run')->exists()); + $this->assertCount(0, $this->jobs(RunScriptTask::class, 'must_not_run', $request->id)); + $this->assertNoRequestLocks($request); + } + + public function testFbk01ReloadsContextAfterExecutionRevisionChanges(): void + { + Bus::fake(); + $request = $this->startInlineProcess('InlineRevisionMismatch.bpmn', 'start'); + $this->completeUserTask($request, 'user_task'); + $initialRevision = $request->refresh()->execution_revision; + + InlineRevisionTestService::$duringRun = static function (string $tokenId): void { + $token = ProcessRequestToken::findOrFail($tokenId); + $request = $token->processRequest; + $request->data = array_merge($request->data, ['external_revision_write' => true]); + $request->execution_revision = (int) $request->execution_revision + 1; + $request->saveQuietly(); + }; + + $this->onlyJob(RunServiceTask::class, 'service_a', $request->id)->handle(); + $scriptJob = $this->jobs(RunScriptTask::class, 'script_b', $request->id)->first(); + $scriptJob?->handle(); + + $this->assertRequestData($request, [ + 'external_revision_write' => true, + 'revision_service_a' => true, + 'revision_script_b' => true, + ]); + $this->assertGreaterThan($initialRevision, $request->refresh()->execution_revision); + $this->assertRequestCompleted($request); + $this->assertNoRequestLocks($request); + } + + public function testExc06DispatchesCustomQueueServiceInsteadOfRunningInline(): void + { + Bus::fake(); + $request = $this->startInlineProcess('InlineCustomQueueServiceTask.bpmn', 'start'); + + $this->onlyJob(RunScriptTask::class, 'script_a', $request->id)->handle(); + $serviceJob = $this->onlyJob(RunServiceTask::class, 'service_custom', $request->id); + + $this->assertSame('inline-custom-queue', $serviceJob->queue); + $this->assertSame([], InlineTaskTestService::elementRunsForRequest($request->id)); + $serviceJob->handle(); + + $this->assertRequestData($request, [ + 'custom_queue_script_a' => true, + 'custom_queue_service' => true, + ]); + $this->assertRequestCompleted($request); + $this->assertNoRequestLocks($request); + } + + private function startInlineProcess(string $fixture, string $startEvent, array $data = []): ProcessRequest + { + return $this->startProcess($this->createInlineProcess($fixture), $startEvent, $data); + } + + private function createInlineProcess(string $fixture, array $replacements = []): Process + { + $bpmn = file_get_contents(__DIR__ . self::FIXTURE_PATH . $fixture); + $this->assertNotFalse($bpmn); + + return $this->createProcess([ + 'bpmn' => strtr($bpmn, $replacements), + ]); + } + + private function activeToken(ProcessRequest $request, string $elementId): ProcessRequestToken + { + return $request->tokens() + ->where('element_id', $elementId) + ->where('status', 'ACTIVE') + ->firstOrFail(); + } + + private function completeUserTask(ProcessRequest $request, string $elementId): void + { + $this->completeTask($this->activeToken($request, $elementId)); + $jobs = Bus::dispatchedSync(CompleteActivity::class); + $this->assertCount(1, $jobs, 'Expected the user-task completion job to be dispatched synchronously.'); + $jobs->first()->handle(); + } + + private function jobs(string $class, ?string $elementId = null, ?int $requestId = null) + { + return Bus::dispatched($class, function ($job) use ($elementId, $requestId) { + $jobElementId = $job->elementId ?? null; + if ($elementId !== null && $jobElementId === null && isset($job->tokenId)) { + $jobElementId = ProcessRequestToken::find($job->tokenId)?->element_id; + } + + return ($elementId === null || $jobElementId === $elementId) + && ($requestId === null || (int) $job->instanceId === $requestId); + })->values(); + } + + private function onlyJob(string $class, string $elementId, int $requestId) + { + $jobs = $this->jobs($class, $elementId, $requestId); + $this->assertCount(1, $jobs, sprintf( + 'Expected one %s job for request %d and element %s.', + class_basename($class), + $requestId, + $elementId + )); + + return $jobs->first(); + } + + private function assertRequestData(ProcessRequest $request, array $expected): void + { + $request->refresh(); + foreach ($expected as $key => $value) { + $this->assertArrayHasKey($key, $request->data); + $this->assertSame($value, $request->data[$key]); + } + } + + private function assertRequestCompleted(ProcessRequest $request): void + { + $this->assertSame('COMPLETED', $request->refresh()->status); + $this->assertCount(0, $request->tokens()->where('status', 'ACTIVE')->get()); + } + + private function assertNoRequestLocks(ProcessRequest ...$requests): void + { + $requestIds = collect($requests)->pluck('id')->map(fn ($id) => (int) $id); + $locks = ProcessRequestLock::all()->filter(function (ProcessRequestLock $lock) use ($requestIds) { + $lockedIds = collect($lock->request_ids ?? [])->map(fn ($id) => (int) $id); + + return $requestIds->contains((int) $lock->request_id) + || $lockedIds->intersect($requestIds)->isNotEmpty(); + }); + + $this->assertCount(0, $locks, 'Expected BPMN request locks to be cleaned up.'); + } +} + +class InlineTaskTestService implements ServiceTaskImplementationInterface +{ + private static array $runs = []; + + public static function reset(): void + { + self::$runs = []; + } + + public static function elementRunsForRequest(int $requestId): array + { + return collect(self::$runs) + ->where('request_id', $requestId) + ->pluck('element_id') + ->values() + ->all(); + } + + public function run(array $data, array $config, $tokenId = '') + { + $token = ProcessRequestToken::findOrFail($tokenId); + self::$runs[] = [ + 'request_id' => (int) $token->process_request_id, + 'element_id' => $token->element_id, + ]; + + $output = match ($token->element_id) { + 'service_a' => 'seq_service_a', + 'service_b' => 'seq_service_b', + 'service_c' => 'seq_service_c', + 'branch_service' => 'parallel_service', + 'participant_b_service' => 'participant_b_service', + 'child_service' => 'call_child_service', + 'sequential_service' => 'sequential_instance_service', + 'parallel_setup' => 'parallel_setup_service', + 'service_custom' => 'custom_queue_service', + default => 'service_' . $token->element_id, + }; + + return [$output => true]; + } +} + +class InlineTaskTestServiceB extends InlineTaskTestService +{ +} + +class InlineRevisionTestService extends InlineTaskTestService +{ + public static $duringRun; + + public function run(array $data, array $config, $tokenId = '') + { + if (self::$duringRun) { + (self::$duringRun)((string) $tokenId); + } + + parent::run($data, $config, $tokenId); + + return ['revision_service_a' => true]; + } +} diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineBoundaryEventScriptTask.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineBoundaryEventScriptTask.bpmn new file mode 100644 index 0000000000..8419b354d8 --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineBoundaryEventScriptTask.bpmn @@ -0,0 +1,62 @@ + + + + + + flow_start_interrupting_setup + + + flow_start_interrupting_setup + flow_setup_interrupting + true];]]> + + + flow_setup_interrupting + flow_interrupting_success + + + + flow_interrupting_boundary + + + + flow_interrupting_success + + + flow_interrupting_boundary + + + + flow_start_non_interrupting_setup + + + flow_start_non_interrupting_setup + flow_setup_non_interrupting + true];]]> + + + flow_setup_non_interrupting + flow_non_interrupting_main + + + + flow_non_interrupting_boundary + + + + flow_non_interrupting_main + + + flow_non_interrupting_boundary + + + + + + + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChain.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChain.bpmn new file mode 100644 index 0000000000..1f2f627225 --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChain.bpmn @@ -0,0 +1,30 @@ + + + + + + flow_start_a + + + flow_start_a + flow_a_call + true];]]> + + + flow_a_call + flow_call_b + + + flow_call_b + flow_b_end + true];]]> + + + flow_b_end + + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChild.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChild.bpmn new file mode 100644 index 0000000000..6cad57bd9f --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChild.bpmn @@ -0,0 +1,18 @@ + + + + + + flow_child_start_service + + + flow_child_start_service + flow_child_service_end + + + flow_child_service_end + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineCollaborationScriptTasks.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineCollaborationScriptTasks.bpmn new file mode 100644 index 0000000000..e9e37fed0a --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineCollaborationScriptTasks.bpmn @@ -0,0 +1,60 @@ + + + + + + + + + + + + + flow_a_start_script_a + + + flow_a_start_script_a + flow_a_script_a_script_b + true];]]> + + + flow_a_script_a_script_b + flow_a_script_b_send + true];]]> + + + flow_a_script_b_send + flow_a_send_end + + + + flow_a_send_end + + + + + + + + + + flow_b_start_service + + + + flow_b_start_service + flow_b_service_script + + + flow_b_service_script + flow_b_script_end + true];]]> + + + flow_b_script_end + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineCustomQueueServiceTask.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineCustomQueueServiceTask.bpmn new file mode 100644 index 0000000000..bf14ee5a39 --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineCustomQueueServiceTask.bpmn @@ -0,0 +1,24 @@ + + + + + + flow_start_script + + + flow_start_script + flow_script_service + true];]]> + + + flow_script_service + flow_service_end + + + flow_service_end + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineFailingTaskChain.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineFailingTaskChain.bpmn new file mode 100644 index 0000000000..5111e2ae6d --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineFailingTaskChain.bpmn @@ -0,0 +1,31 @@ + + + + + + flow_start_success + + + flow_start_success + flow_success_failure + true];]]> + + + flow_success_failure + flow_failure_later + + + + flow_failure_later + flow_later_end + true];]]> + + + flow_later_end + + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineIntermediateEventChain.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineIntermediateEventChain.bpmn new file mode 100644 index 0000000000..cae877d0bb --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineIntermediateEventChain.bpmn @@ -0,0 +1,32 @@ + + + + + + + flow_start_a + + + flow_start_a + flow_a_catch + true];]]> + + + flow_a_catch + flow_catch_b + + + + flow_catch_b + flow_b_end + true];]]> + + + flow_b_end + + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineMultiInstanceAutomatedTask.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineMultiInstanceAutomatedTask.bpmn new file mode 100644 index 0000000000..3b038e14b1 --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineMultiInstanceAutomatedTask.bpmn @@ -0,0 +1,64 @@ + + + + + + flow_start_sequential_setup + + + flow_start_sequential_setup + flow_setup_sequential + true];]]> + + + flow_setup_sequential + flow_sequential_end + + + + sequential_items_input + + + + + sequential_items_input + + + + flow_sequential_end + + + + flow_start_parallel_setup + + + flow_start_parallel_setup + flow_setup_parallel + + + flow_setup_parallel + flow_parallel_end + + + + parallel_items_input + + + + + parallel_items_input + + true];]]> + + + flow_parallel_end + + + + + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineParallelScriptServiceTasks.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineParallelScriptServiceTasks.bpmn new file mode 100644 index 0000000000..95597d0ac8 --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineParallelScriptServiceTasks.bpmn @@ -0,0 +1,37 @@ + + + + + + flow_start_split + + + flow_start_split + flow_split_script + flow_split_service + + + flow_split_script + flow_script_join + true];]]> + + + flow_split_service + flow_service_join + + + flow_script_join + flow_service_join + flow_join_end + + + flow_join_end + + + + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineRevisionMismatch.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineRevisionMismatch.bpmn new file mode 100644 index 0000000000..0345456413 --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineRevisionMismatch.bpmn @@ -0,0 +1,29 @@ + + + + + + flow_start_user + + + flow_start_user + flow_user_service + + + flow_user_service + flow_service_script + + + flow_service_script + flow_script_end + true];]]> + + + flow_script_end + + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineSequentialScriptServiceTasks.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineSequentialScriptServiceTasks.bpmn new file mode 100644 index 0000000000..e131c2812a --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineSequentialScriptServiceTasks.bpmn @@ -0,0 +1,35 @@ + + + + + + flow_start_user + + + flow_start_user + flow_user_a + + + flow_user_a + flow_a_b + true];]]> + + + flow_a_b + flow_b_c + true];]]> + + + flow_b_c + flow_c_end + + + flow_c_end + + + + + + + + diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineSequentialServiceTasks.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineSequentialServiceTasks.bpmn new file mode 100644 index 0000000000..5664694f85 --- /dev/null +++ b/tests/Feature/Api/processes/inline-task-execution/InlineSequentialServiceTasks.bpmn @@ -0,0 +1,28 @@ + + + + + + flow_start_a + + + flow_start_a + flow_a_b + + + flow_a_b + flow_b_c + + + flow_b_c + flow_c_end + + + flow_c_end + + + + + + +