content: /*
   * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.activiti.engine.test.bpmn.event.signal;
  
  import static java.util.Arrays.asList;
  import static java.util.Collections.singletonMap;
  import static org.assertj.core.api.Assertions.assertThat;
  import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
  
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import org.activiti.engine.ActivitiException;
  import org.activiti.engine.impl.EventSubscriptionQueryImpl;
  import org.activiti.engine.impl.test.PluggableActivitiTestCase;
  import org.activiti.engine.runtime.Execution;
  import org.activiti.engine.runtime.Job;
  import org.activiti.engine.runtime.ProcessInstance;
  import org.activiti.engine.task.Task;
  import org.activiti.engine.test.Deployment;
  import org.activiti.validation.validator.Problems;
  
  /**
   */
  public class SignalEventTest extends PluggableActivitiTestCase {
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.catchAlertSignal.bpmn20.xml",
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.throwAlertSignal.bpmn20.xml",
          }
      )
      public void testSignalCatchIntermediate() {
          runtimeService.startProcessInstanceByKey("catchSignal");
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(1);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(1);
  
          runtimeService.startProcessInstanceByKey("throwSignal");
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(0);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
      }
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.catchAlertSignalExpression.bpmn20.xml",
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.throwAlertSignalExpression.bpmn20.xml",
          }
      )
      public void testSignalCatchIntermediateExpression() {
          Map<String, Object> variableMap = new HashMap<String, Object>();
          variableMap.put("mySignalName", "testSignal");
          runtimeService.startProcessInstanceByKey("catchSignal", variableMap);
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(1);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(1);
  
          runtimeService.startProcessInstanceByKey("throwSignal", variableMap);
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(0);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
      }
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.catchAlertSignalBoundary.bpmn20.xml",
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.throwAlertSignal.bpmn20.xml",
          }
      )
      public void testSignalCatchBoundary() {
          runtimeService.startProcessInstanceByKey("catchSignal");
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(1);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(1);
  
          runtimeService.startProcessInstanceByKey("throwSignal");
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(0);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
      }
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.catchAlertSignalBoundaryWithReceiveTask.bpmn20.xml",
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.throwAlertSignal.bpmn20.xml",
          }
      )
      public void testSignalCatchBoundaryWithVariables() {
          HashMap<String, Object> variables1 = new HashMap<String, Object>();
          variables1.put("processName", "catchSignal");
          ProcessInstance pi = runtimeService.startProcessInstanceByKey("catchSignal", variables1);
  
          HashMap<String, Object> variables2 = new HashMap<String, Object>();
          variables2.put("processName", "throwSignal");
          runtimeService.startProcessInstanceByKey("throwSignal", variables2);
  
          assertThat(runtimeService.getVariable(pi.getId(), "processName")).isEqualTo("throwSignal");
      }
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.catchAlertSignal.bpmn20.xml",
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.throwAlertSignalAsynch.bpmn20.xml",
          }
      )
      public void testSignalCatchIntermediateAsynch() {
          runtimeService.startProcessInstanceByKey("catchSignal");
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(1);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(1);
  
          runtimeService.startProcessInstanceByKey("throwSignal");
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(1);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(1);
  
          // there is a job:
          assertThat(managementService.createJobQuery().count()).isEqualTo(1);
  
          try {
              processEngineConfiguration.getClock().setCurrentTime(new Date(System.currentTimeMillis() + 1000));
              waitForJobExecutorToProcessAllJobs(10000, 100l);
  
              assertThat(createEventSubscriptionQuery().count()).isEqualTo(0);
              assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
              assertThat(managementService.createJobQuery().count()).isEqualTo(0);
          } finally {
              processEngineConfiguration.getClock().setCurrentTime(new Date());
          }
      }
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.catchMultipleSignals.bpmn20.xml",
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.throwAlertSignal.bpmn20.xml",
              "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.throwAbortSignal.bpmn20.xml",
          }
      )
      public void testSignalCatchDifferentSignals() {
          runtimeService.startProcessInstanceByKey("catchSignal");
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(2);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(1);
  
          runtimeService.startProcessInstanceByKey("throwAbort");
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(1);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(1);
  
          Task taskAfterAbort = taskService.createTaskQuery().taskAssignee("gonzo").singleResult();
          assertThat(taskAfterAbort).isNotNull();
          taskService.complete(taskAfterAbort.getId());
  
          runtimeService.startProcessInstanceByKey("throwSignal");
  
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(0);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
      }
  
      /**
       * Verifies the solution of https://jira.codehaus.org/browse/ACT-1309
       */
      @Deployment
      public void testSignalBoundaryOnSubProcess() {
          ProcessInstance pi = runtimeService.startProcessInstanceByKey("signalEventOnSubprocess");
          runtimeService.signalEventReceived("stopSignal");
          assertProcessEnded(pi.getProcessInstanceId());
      }
  
      public void testDuplicateSignalNames() {
          assertThatExceptionOfType(Exception.class)
              .isThrownBy(() ->
                  repositoryService
                      .createDeployment()
                      .addClasspathResource(
                          "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.duplicateSignalNames.bpmn20.xml"
                      )
                      .deploy()
              )
              .withMessageContaining(Problems.SIGNAL_DUPLICATE_NAME);
      }
  
      public void testNoSignalName() {
          assertThatExceptionOfType(Exception.class)
              .isThrownBy(() ->
                  repositoryService
                      .createDeployment()
                      .addClasspathResource(
                          "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.noSignalName.bpmn20.xml"
                      )
                      .deploy()
              )
              .withMessageContaining(Problems.SIGNAL_MISSING_NAME);
      }
  
      public void testSignalNoId() {
          assertThatExceptionOfType(Exception.class)
              .isThrownBy(() ->
                  repositoryService
                      .createDeployment()
                      .addClasspathResource(
                          "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.signalNoId.bpmn20.xml"
                      )
                      .deploy()
              )
              .withMessageContaining(Problems.SIGNAL_MISSING_ID);
      }
  
      public void testSignalNoRef() {
          assertThatExceptionOfType(Exception.class)
              .isThrownBy(() ->
                  repositoryService
                      .createDeployment()
                      .addClasspathResource(
                          "org/activiti/engine/test/bpmn/event/signal/SignalEventTests.signalNoRef.bpmn20.xml"
                      )
                      .deploy()
              )
              .withMessageContaining(Problems.SIGNAL_EVENT_MISSING_SIGNAL_REF);
      }
  
      private EventSubscriptionQueryImpl createEventSubscriptionQuery() {
          return new EventSubscriptionQueryImpl(processEngineConfiguration.getCommandExecutor());
      }
  
      /**
       * TestCase to reproduce Issue ACT-1344
       */
      @Deployment
      public void testNonInterruptingSignal() {
          ProcessInstance pi = runtimeService.startProcessInstanceByKey("nonInterruptingSignalEvent");
  
          List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi.getProcessInstanceId()).list();
          assertThat(tasks).hasSize(1);
          Task currentTask = tasks.get(0);
          assertThat(currentTask.getName()).isEqualTo("My User Task");
  
          runtimeService.signalEventReceived("alert");
  
          tasks = taskService.createTaskQuery().processInstanceId(pi.getProcessInstanceId()).list();
          assertThat(tasks).hasSize(2);
  
          for (Task task : tasks) {
              assertThat(task.getName()).isIn("My User Task", "My Second User Task");
          }
  
          taskService.complete(taskService.createTaskQuery().taskName("My User Task").singleResult().getId());
  
          tasks = taskService.createTaskQuery().processInstanceId(pi.getProcessInstanceId()).list();
          assertThat(tasks).hasSize(1);
          currentTask = tasks.get(0);
          assertThat(currentTask.getName()).isEqualTo("My Second User Task");
      }
  
      /**
       * TestCase to reproduce Issue ACT-1344
       */
      @Deployment
      public void testNonInterruptingSignalWithSubProcess() {
          ProcessInstance pi = runtimeService.startProcessInstanceByKey("nonInterruptingSignalWithSubProcess");
          List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi.getProcessInstanceId()).list();
          assertThat(tasks).hasSize(1);
  
          Task currentTask = tasks.get(0);
          assertThat(currentTask.getName()).isEqualTo("Approve");
  
          runtimeService.signalEventReceived("alert");
  
          tasks = taskService.createTaskQuery().processInstanceId(pi.getProcessInstanceId()).list();
          assertThat(tasks).hasSize(2);
  
          for (Task task : tasks) {
              assertThat(task.getName()).isIn("Approve", "Review");
          }
  
          taskService.complete(taskService.createTaskQuery().taskName("Approve").singleResult().getId());
  
          tasks = taskService.createTaskQuery().processInstanceId(pi.getProcessInstanceId()).list();
          assertThat(tasks).hasSize(1);
  
          currentTask = tasks.get(0);
          assertThat(currentTask.getName()).isEqualTo("Review");
  
          taskService.complete(taskService.createTaskQuery().taskName("Review").singleResult().getId());
  
          tasks = taskService.createTaskQuery().processInstanceId(pi.getProcessInstanceId()).list();
          assertThat(tasks).hasSize(1);
      }
  
      @Deployment
      public void testUseSignalForExceptionsBetweenParallelPaths() {
          runtimeService.startProcessInstanceByKey("processWithSignal");
  
          // First task should be to select the developers
          Task task = taskService.createTaskQuery().singleResult();
          assertThat(task.getName()).isEqualTo("Enter developers");
          taskService.complete(
              task.getId(),
              singletonMap("developers", asList("developerOne", "developerTwo", "developerThree"))
          );
  
          // Should be three distinct tasks for each developer
          assertThat(taskService.createTaskQuery().taskAssignee("developerOne").singleResult().getName()).isEqualTo(
              "Develop specifications"
          );
          assertThat(taskService.createTaskQuery().taskAssignee("developerTwo").singleResult().getName()).isEqualTo(
              "Develop specifications"
          );
          assertThat(taskService.createTaskQuery().taskAssignee("developerThree").singleResult().getName()).isEqualTo(
              "Develop specifications"
          );
  
          // Negotiate with client is a task for kermit
          task = taskService.createTaskQuery().taskAssignee("kermit").singleResult();
          assertThat(task.getName()).isEqualTo("Negotiate with client");
  
          // When the kermit task is completed, it throws a signal which should
          // cancel the multi instance
          taskService.complete(task.getId(), singletonMap("negotationFailed", true));
  
          // No tasks should be open then and process should have ended
          assertThat(taskService.createTaskQuery().count()).isEqualTo(0);
          assertThat(runtimeService.createExecutionQuery().count()).isEqualTo(0);
      }
  
      @Deployment
      public void testSignalWithProcessInstanceScope() {
          // Start the process that catches the signal
          ProcessInstance processInstanceCatch = runtimeService.startProcessInstanceByKey("processWithSignalCatch");
          assertThat(
              taskService.createTaskQuery().processInstanceId(processInstanceCatch.getId()).singleResult().getName()
          ).isEqualTo("userTaskWithSignalCatch");
  
          // Then start the process that will throw the signal
          runtimeService.startProcessInstanceByKey("processWithSignalThrow");
  
          // Since the signal is process instance scoped, the second process
          // shouldn't have proceeded in any way
          assertThat(
              taskService.createTaskQuery().processInstanceId(processInstanceCatch.getId()).singleResult().getName()
          ).isEqualTo("userTaskWithSignalCatch");
  
          // Let's try to trigger the catch using the API, that should also fail
          runtimeService.signalEventReceived("The Signal");
          assertThat(
              taskService.createTaskQuery().processInstanceId(processInstanceCatch.getId()).singleResult().getName()
          ).isEqualTo("userTaskWithSignalCatch");
      }
  
      @Deployment
      public void testSignalWithGlobalScope() {
          // Start the process that catches the signal
          ProcessInstance processInstanceCatch = runtimeService.startProcessInstanceByKey("processWithSignalCatch");
          assertThat(
              taskService.createTaskQuery().processInstanceId(processInstanceCatch.getId()).singleResult().getName()
          ).isEqualTo("userTaskWithSignalCatch");
  
          // Then start the process that will throw thee signal
          runtimeService.startProcessInstanceByKey("processWithSignalThrow");
  
          // Since the signal is process instance scoped, the second process
          // shouldn't have proceeded in any way
          assertThat(
              taskService.createTaskQuery().processInstanceId(processInstanceCatch.getId()).singleResult().getName()
          ).isEqualTo("userTaskAfterSignalCatch");
      }
  
      @Deployment
      public void testAsyncTriggeredSignalEvent() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("processWithSignalCatch");
  
          assertThat(processInstance).isNotNull();
          Execution execution = runtimeService
              .createExecutionQuery()
              .processInstanceId(processInstance.getId())
              .signalEventSubscriptionName("The Signal")
              .singleResult();
          assertThat(execution).isNotNull();
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(1);
          assertThat(runtimeService.createExecutionQuery().count()).isEqualTo(2);
  
          runtimeService.signalEventReceivedAsync("The Signal", execution.getId());
  
          assertThat(managementService.createJobQuery().messages().count()).isEqualTo(1);
  
          waitForJobExecutorToProcessAllJobs(8000L, 200L);
          assertThat(createEventSubscriptionQuery().count()).isEqualTo(0);
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
          assertThat(managementService.createJobQuery().count()).isEqualTo(0);
      }
  
      @Deployment
      public void testSignalUserTask() {
          runtimeService.startProcessInstanceByKey("catchSignal");
          Execution execution = runtimeService
              .createExecutionQuery()
              .onlyChildExecutions()
              .activityId("waitState")
              .singleResult();
  
          assertThat(execution).isNotNull();
  
          assertThatExceptionOfType(ActivitiException.class).isThrownBy(() -> runtimeService.trigger(execution.getId()));
      }
  
      public void testSignalStartEventFromProcess() {
          // Deploy test processes
          repositoryService
              .createDeployment()
              .addClasspathResource(
                  "org/activiti/engine/test/bpmn/event/signal/SignalEventTest.testSignalStartEvent.bpmn20.xml"
              )
              .deploy();
  
          // Starting the process that fires the signal should start three process instances that are listening on that signal
          runtimeService.startProcessInstanceByKey("processWithSignalThrow");
  
          // Verify
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(3);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(3);
  
          List<Task> tasks = taskService.createTaskQuery().orderByTaskName().asc().list();
          List<String> names = asList("A", "B", "C");
          for (int i = 0; i < tasks.size(); i++) {
              assertThat(tasks.get(i).getName()).isEqualTo("Task in process " + names.get(i));
          }
  
          // Start a process with a signal boundary event
          runtimeService.startProcessInstanceByKey("processWithSignalCatch");
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(4);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(4);
          assertThat(taskService.createTaskQuery().taskName("Task in process D").count()).isEqualTo(1);
  
          // Firing the signal should now trigger the one with the boundary event
          // too
          runtimeService.startProcessInstanceByKey("processWithSignalThrow");
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(7);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(7);
          assertThat(taskService.createTaskQuery().taskName("Task after signal").count()).isEqualTo(1);
  
          // Cleanup
          for (org.activiti.engine.repository.Deployment deployment : repositoryService.createDeploymentQuery().list()) {
              repositoryService.deleteDeployment(deployment.getId(), true);
          }
      }
  
      public void testSignalStartEventFromProcesAsync() {
          // Deploy test processes
          repositoryService
              .createDeployment()
              .addClasspathResource(
                  "org/activiti/engine/test/bpmn/event/signal/SignalEventTest.testSignalStartEventAsync.bpmn20.xml"
              )
              .deploy();
  
          // Starting the process that fires the signal should start 1 process
          // instance that are listening on that signal, the others are done async
          runtimeService.startProcessInstanceByKey("processWithSignalThrow");
  
          // Verify
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(0);
  
          assertThat(managementService.createJobQuery().count()).isEqualTo(3);
          for (Job job : managementService.createJobQuery().list()) {
              managementService.executeJob(job.getId());
          }
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(3);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(3);
  
          List<Task> tasks = taskService.createTaskQuery().orderByTaskName().asc().list();
          List<String> names = asList("A", "B", "C");
          for (int i = 0; i < tasks.size(); i++) {
              assertThat(tasks.get(i).getName()).isEqualTo("Task in process " + names.get(i));
          }
  
          // Start a process with a signal boundary event
          runtimeService.startProcessInstanceByKey("processWithSignalCatch");
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(4);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(4);
          assertThat(taskService.createTaskQuery().taskName("Task in process D").count()).isEqualTo(1);
  
          // Firing again
          runtimeService.startProcessInstanceByKey("processWithSignalThrow");
  
          assertThat(managementService.createJobQuery().count()).isEqualTo(4);
          for (Job job : managementService.createJobQuery().list()) {
              managementService.executeJob(job.getId());
          }
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(7);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(7);
          assertThat(taskService.createTaskQuery().taskName("Task after signal").count()).isEqualTo(1);
  
          // Cleanup
          for (org.activiti.engine.repository.Deployment deployment : repositoryService.createDeploymentQuery().list()) {
              repositoryService.deleteDeployment(deployment.getId(), true);
          }
      }
  
      public void testSignalStartEventFromAPI() {
          // Deploy test processes
          repositoryService
              .createDeployment()
              .addClasspathResource(
                  "org/activiti/engine/test/bpmn/event/signal/SignalEventTest.testSignalStartEvent.bpmn20.xml"
              )
              .deploy();
  
          runtimeService.signalEventReceived("The Signal");
  
          // Verify
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(3);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(3);
  
          List<Task> tasks = taskService.createTaskQuery().orderByTaskName().asc().list();
          List<String> names = asList("A", "B", "C");
          for (int i = 0; i < tasks.size(); i++) {
              assertThat(tasks.get(i).getName()).isEqualTo("Task in process " + names.get(i));
          }
  
          // Start a process with a signal boundary event
          runtimeService.startProcessInstanceByKey("processWithSignalCatch");
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(4);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(4);
          assertThat(taskService.createTaskQuery().taskName("Task in process D").count()).isEqualTo(1);
  
          // Firing the signal should now trigger the one with the boundary event
          // too
          runtimeService.signalEventReceived("The Signal");
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(7);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(7);
          assertThat(taskService.createTaskQuery().taskName("Task after signal").count()).isEqualTo(1);
  
          // Cleanup
          for (org.activiti.engine.repository.Deployment deployment : repositoryService.createDeploymentQuery().list()) {
              repositoryService.deleteDeployment(deployment.getId(), true);
          }
      }
  
      public void testSignalStartEventFromAPIAsync() {
          // Deploy test processes
          repositoryService
              .createDeployment()
              .addClasspathResource(
                  "org/activiti/engine/test/bpmn/event/signal/SignalEventTest.testSignalStartEventAsync.bpmn20.xml"
              )
              .deploy();
  
          runtimeService.signalEventReceivedAsync("The Signal");
  
          assertThat(managementService.createJobQuery().count()).isEqualTo(3);
          for (Job job : managementService.createJobQuery().list()) {
              managementService.executeJob(job.getId());
          }
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(3);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(3);
  
          List<Task> tasks = taskService.createTaskQuery().orderByTaskName().asc().list();
          List<String> names = asList("A", "B", "C");
          for (int i = 0; i < tasks.size(); i++) {
              assertThat(tasks.get(i).getName()).isEqualTo("Task in process " + names.get(i));
          }
  
          // Start a process with a signal boundary event
          runtimeService.startProcessInstanceByKey("processWithSignalCatch");
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(4);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(4);
          assertThat(taskService.createTaskQuery().taskName("Task in process D").count()).isEqualTo(1);
  
          // Firing again
          runtimeService.signalEventReceivedAsync("The Signal");
  
          assertThat(managementService.createJobQuery().count()).isEqualTo(4);
          for (Job job : managementService.createJobQuery().list()) {
              managementService.executeJob(job.getId());
          }
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(7);
          assertThat(taskService.createTaskQuery().count()).isEqualTo(7);
          assertThat(taskService.createTaskQuery().taskName("Task after signal").count()).isEqualTo(1);
  
          // Cleanup
          for (org.activiti.engine.repository.Deployment deployment : repositoryService.createDeploymentQuery().list()) {
              repositoryService.deleteDeployment(deployment.getId(), true);
          }
      }
  
      public void testSignalStartEventSuspendedProcessDefinition() {
          // Deploy test processes
          repositoryService
              .createDeployment()
              .addClasspathResource(
                  "org/activiti/engine/test/bpmn/event/signal/SignalEventTest.testSignalStartEvent.bpmn20.xml"
              )
              .deploy();
  
          repositoryService.suspendProcessDefinitionByKey("processWithSignalStart1");
  
          assertThatExceptionOfType(ActivitiException.class)
              .as("ActivitiException expected. Process definition is suspended")
              .isThrownBy(() -> runtimeService.signalEventReceived("The Signal"));
  
          // Cleanup
          for (org.activiti.engine.repository.Deployment deployment : repositoryService.createDeploymentQuery().list()) {
              repositoryService.deleteDeployment(deployment.getId(), true);
          }
      }
  
      @Deployment
      public void testEarlyFinishedProcess() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("callerProcess");
          assertThat(processInstance.getId()).isNotNull();
      }
  
      @Deployment
      public void testNoneEndEventAfterSignalInConcurrentProcess() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("my-process");
          assertThat(processInstance).isNotNull();
  
          Task task = taskService.createTaskQuery().taskDefinitionKey("usertask1").singleResult();
          taskService.claim(task.getId(), "user");
          taskService.complete(task.getId());
  
          task = taskService.createTaskQuery().singleResult();
  
          assertThat(task.getTaskDefinitionKey()).isEqualTo("usertask2");
      }
  
      /**
       * Test case for https://activiti.atlassian.net/browse/ACT-1978
       */
      public void testSignalDeleteOnRedeploy() {
          // Deploy test processes
          repositoryService
              .createDeployment()
              .addClasspathResource(
                  "org/activiti/engine/test/bpmn/event/signal/SignalEventTest.testSignalStartEvent.bpmn20.xml"
              )
              .deploy();
  
          // Deploy new versions
          repositoryService
              .createDeployment()
              .addClasspathResource(
                  "org/activiti/engine/test/bpmn/event/signal/SignalEventTest.testSignalStartEvent.bpmn20.xml"
              )
              .deploy();
          repositoryService
              .createDeployment()
              .addClasspathResource(
                  "org/activiti/engine/test/bpmn/event/signal/SignalEventTest.testSignalStartEvent.bpmn20.xml"
              )
              .deploy();
  
          // Firing a signal start event should only start ONE process instance
          // This used to be two, due to subscriptions not being cleaned up
          runtimeService.signalEventReceived("The Signal");
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(3);
  
          // Cleanup
          for (org.activiti.engine.repository.Deployment deployment : repositoryService.createDeploymentQuery().list()) {
              repositoryService.deleteDeployment(deployment.getId(), true);
          }
      }
  
      @Deployment
      public void testSignalWaitOnUserTaskBoundaryEvent() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("signal-wait");
          Execution execution = runtimeService
              .createExecutionQuery()
              .processInstanceId(processInstance.getId())
              .signalEventSubscriptionName("waitsig")
              .singleResult();
          assertThat(execution).isNotNull();
          runtimeService.signalEventReceived("waitsig", execution.getId());
          execution = runtimeService
              .createExecutionQuery()
              .processInstanceId(processInstance.getId())
              .signalEventSubscriptionName("waitsig")
              .singleResult();
          assertThat(execution).isNull();
          Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
          assertThat(task).isNotNull();
          assertThat(task.getName()).isEqualTo("Wait2");
      }
  
      /**
       * From https://forums.activiti.org/content/boundary-signal-causes-already-taking-transition
       */
      @Deployment
      public void testSignalThrowAndCatchInSameTransaction() {
          String fileExistsVar = "fileexists";
  
          // remove mock file
          FileExistsMock.getInstance().removeFile();
  
          // create first instance
          ProcessInstance firstProcessInstance = runtimeService.startProcessInstanceByKey("signalBoundaryProcess");
          assertThat(firstProcessInstance).isNotNull();
  
          // task should be "add a file"
          Task firstTask = taskService.createTaskQuery().singleResult();
          assertThat(firstTask.getName()).isEqualTo("Add a file");
  
          Map<String, Object> vars = runtimeService.getVariables(firstTask.getExecutionId());
          // file does not exists
          assertThat(vars.get(fileExistsVar)).isEqualTo(false);
  
          // create second instance
          ProcessInstance secondProcessInstance = runtimeService.startProcessInstanceByKey("signalBoundaryProcess");
          assertThat(secondProcessInstance).isNotNull();
  
          // there should be two open tasks
          List<Task> tasks = taskService.createTaskQuery().list();
          assertThat(tasks).hasSize(2);
  
          // get current second task
          Task secondTask = taskService
              .createTaskQuery()
              .processInstanceId(secondProcessInstance.getProcessInstanceId())
              .singleResult();
          // must be also in "add a file"
          assertThat(secondTask.getName()).isEqualTo("Add a file");
  
          // file does not exists yet
          vars = runtimeService.getVariables(secondTask.getExecutionId());
          assertThat(vars.get(fileExistsVar)).isEqualTo(false);
  
          // now, we "add a file"
          taskService.claim(firstTask.getId(), "user");
          // create the file
          FileExistsMock.getInstance().touchFile();
          // complete the task - this should cancel all tasks waiting in "Add a file"
          // using the "fileAddedSignal"
          // FIXME: this causes the exception:
          taskService.complete(firstTask.getId());
  
          List<Task> usingTask = taskService.createTaskQuery().taskName("Use the file").list();
          assertThat(usingTask).hasSize(1);
      }
  
      @Deployment
      public void testMultipleSignalStartEvents() {
          runtimeService.signalEventReceived("signal1");
          validateTaskCounts(1, 0, 0);
  
          runtimeService.signalEventReceived("signal2");
          validateTaskCounts(1, 1, 0);
  
          runtimeService.signalEventReceived("signal3");
          validateTaskCounts(1, 1, 1);
  
          runtimeService.signalEventReceived("signal1");
          validateTaskCounts(2, 1, 1);
  
          runtimeService.signalEventReceived("signal1");
          validateTaskCounts(3, 1, 1);
  
          runtimeService.signalEventReceived("signal3");
          validateTaskCounts(3, 1, 2);
      }
  
      private void validateTaskCounts(long taskACount, long taskBCount, long taskCCount) {
          assertThat(taskService.createTaskQuery().taskName("Task A").count()).isEqualTo(taskACount);
          assertThat(taskService.createTaskQuery().taskName("Task B").count()).isEqualTo(taskBCount);
          assertThat(taskService.createTaskQuery().taskName("Task C").count()).isEqualTo(taskCCount);
      }
  }
  