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.impl.test;
  
  import static java.util.Collections.singletonList;
  import static org.assertj.core.api.Assertions.assertThat;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.Callable;
  import junit.framework.AssertionFailedError;
  import org.activiti.bpmn.model.BpmnModel;
  import org.activiti.bpmn.model.EndEvent;
  import org.activiti.bpmn.model.SequenceFlow;
  import org.activiti.bpmn.model.StartEvent;
  import org.activiti.bpmn.model.UserTask;
  import org.activiti.engine.DynamicBpmnService;
  import org.activiti.engine.HistoryService;
  import org.activiti.engine.ManagementService;
  import org.activiti.engine.ProcessEngine;
  import org.activiti.engine.RepositoryService;
  import org.activiti.engine.RuntimeService;
  import org.activiti.engine.TaskService;
  import org.activiti.engine.history.HistoricActivityInstance;
  import org.activiti.engine.history.HistoricProcessInstance;
  import org.activiti.engine.history.HistoricTaskInstance;
  import org.activiti.engine.impl.ProcessEngineImpl;
  import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
  import org.activiti.engine.impl.db.DbSqlSession;
  import org.activiti.engine.impl.history.HistoryLevel;
  import org.activiti.engine.impl.identity.Authentication;
  import org.activiti.engine.impl.interceptor.Command;
  import org.activiti.engine.impl.interceptor.CommandConfig;
  import org.activiti.engine.impl.interceptor.CommandContext;
  import org.activiti.engine.impl.interceptor.CommandExecutor;
  import org.activiti.engine.repository.Deployment;
  import org.activiti.engine.repository.ProcessDefinition;
  import org.activiti.engine.runtime.ProcessInstance;
  
  public abstract class AbstractActivitiTestCase extends AbstractTestCase {
  
      private static final List<String> TABLENAMES_EXCLUDED_FROM_DB_CLEAN_CHECK = singletonList("ACT_GE_PROPERTY");
  
      protected ProcessEngine processEngine;
  
      protected String deploymentIdFromDeploymentAnnotation;
      protected List<String> deploymentIdsForAutoCleanup = new ArrayList<String>();
      protected Throwable exception;
  
      protected ProcessEngineConfigurationImpl processEngineConfiguration;
      protected RepositoryService repositoryService;
      protected RuntimeService runtimeService;
      protected TaskService taskService;
      protected HistoryService historyService;
      protected ManagementService managementService;
      protected DynamicBpmnService dynamicBpmnService;
  
      @Override
      protected void setUp() throws Exception {
          super.setUp();
  
          // Always reset authenticated user to avoid any mistakes
          Authentication.setAuthenticatedUserId(null);
      }
  
      protected abstract void initializeProcessEngine();
  
      // Default: do nothing
      protected void closeDownProcessEngine() {}
  
      @Override
      public void runBare() throws Throwable {
          initializeProcessEngine();
          if (repositoryService == null) {
              initializeServices();
          }
  
          try {
              deploymentIdFromDeploymentAnnotation = TestHelper.annotationDeploymentSetUp(
                  processEngine,
                  getClass(),
                  getName()
              );
  
              super.runBare();
  
              validateHistoryData();
          } catch (AssertionFailedError e) {
              log.error(EMPTY_LINE);
              log.error("ASSERTION FAILED: {}", e, e);
              exception = e;
              throw e;
          } catch (Throwable e) {
              log.error(EMPTY_LINE);
              log.error("EXCEPTION: {}", e, e);
              exception = e;
              throw e;
          } finally {
              if (deploymentIdFromDeploymentAnnotation != null) {
                  TestHelper.annotationDeploymentTearDown(
                      processEngine,
                      deploymentIdFromDeploymentAnnotation,
                      getClass(),
                      getName()
                  );
                  deploymentIdFromDeploymentAnnotation = null;
              }
  
              for (String autoDeletedDeploymentId : deploymentIdsForAutoCleanup) {
                  repositoryService.deleteDeployment(autoDeletedDeploymentId, true);
              }
              deploymentIdsForAutoCleanup.clear();
  
              assertAndEnsureCleanDb();
              processEngineConfiguration.getClock().reset();
  
              // Can't do this in the teardown, as the teardown will be called as part of the super.runBare
              closeDownProcessEngine();
          }
      }
  
      protected void validateHistoryData() {
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.AUDIT)) {
              List<HistoricProcessInstance> historicProcessInstances = historyService
                  .createHistoricProcessInstanceQuery()
                  .finished()
                  .list();
  
              for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
                  assertThat(historicProcessInstance.getProcessDefinitionId())
                      .as("Historic process instance has no process definition id")
                      .isNotNull();
                  assertThat(historicProcessInstance.getProcessDefinitionKey())
                      .as("Historic process instance has no process definition key")
                      .isNotNull();
                  assertThat(historicProcessInstance.getProcessDefinitionVersion())
                      .as("Historic process instance has no process definition version")
                      .isNotNull();
                  assertThat(historicProcessInstance.getDeploymentId())
                      .as("Historic process instance has no process definition key")
                      .isNotNull();
                  assertThat(historicProcessInstance.getStartActivityId())
                      .as("Historic process instance has no start activiti id")
                      .isNotNull();
                  assertThat(historicProcessInstance.getStartTime())
                      .as("Historic process instance has no start time")
                      .isNotNull();
                  assertThat(historicProcessInstance.getEndTime())
                      .as("Historic process instance has no end time")
                      .isNotNull();
  
                  String processInstanceId = historicProcessInstance.getId();
  
                  // tasks
                  List<HistoricTaskInstance> historicTaskInstances = historyService
                      .createHistoricTaskInstanceQuery()
                      .processInstanceId(processInstanceId)
                      .list();
                  if (historicTaskInstances != null && historicTaskInstances.size() > 0) {
                      for (HistoricTaskInstance historicTaskInstance : historicTaskInstances) {
                          assertThat(historicTaskInstance.getProcessInstanceId()).isEqualTo(processInstanceId);
                          if (historicTaskInstance.getClaimTime() != null) {
                              assertThat(historicTaskInstance.getWorkTimeInMillis())
                                  .as(
                                      "Historic task " + historicTaskInstance.getTaskDefinitionKey() + " has no work time"
                                  )
                                  .isNotNull();
                          }
                          assertThat(historicTaskInstance.getId())
                              .as("Historic task " + historicTaskInstance.getTaskDefinitionKey() + " has no id")
                              .isNotNull();
                          assertThat(historicTaskInstance.getProcessInstanceId())
                              .as(
                                  "Historic task " +
                                  historicTaskInstance.getTaskDefinitionKey() +
                                  " has no process instance id"
                              )
                              .isNotNull();
                          assertThat(historicTaskInstance.getExecutionId())
                              .as("Historic task " + historicTaskInstance.getTaskDefinitionKey() + " has no execution id")
                              .isNotNull();
                          assertThat(historicTaskInstance.getProcessDefinitionId())
                              .as(
                                  "Historic task " +
                                  historicTaskInstance.getTaskDefinitionKey() +
                                  " has no process definition id"
                              )
                              .isNotNull();
                          assertThat(historicTaskInstance.getTaskDefinitionKey())
                              .as(
                                  "Historic task " +
                                  historicTaskInstance.getTaskDefinitionKey() +
                                  " has no task definition key"
                              )
                              .isNotNull();
                          assertThat(historicTaskInstance.getCreateTime())
                              .as("Historic task " + historicTaskInstance.getTaskDefinitionKey() + " has no create time")
                              .isNotNull();
                          assertThat(historicTaskInstance.getStartTime())
                              .as("Historic task " + historicTaskInstance.getTaskDefinitionKey() + " has no start time")
                              .isNotNull();
                          assertThat(historicTaskInstance.getEndTime())
                              .as("Historic task " + historicTaskInstance.getTaskDefinitionKey() + " has no end time")
                              .isNotNull();
                      }
                  }
  
                  // activities
                  List<HistoricActivityInstance> historicActivityInstances = historyService
                      .createHistoricActivityInstanceQuery()
                      .processInstanceId(processInstanceId)
                      .list();
                  if (historicActivityInstances != null && historicActivityInstances.size() > 0) {
                      for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                          assertThat(historicActivityInstance.getProcessInstanceId()).isEqualTo(processInstanceId);
                          assertThat(historicActivityInstance.getActivityId())
                              .as(
                                  "Historic activity instance " +
                                  historicActivityInstance.getActivityId() +
                                  " has no activity id"
                              )
                              .isNotNull();
                          assertThat(historicActivityInstance.getActivityType())
                              .as(
                                  "Historic activity instance " +
                                  historicActivityInstance.getActivityId() +
                                  " has no activity type"
                              )
                              .isNotNull();
                          assertThat(historicActivityInstance.getProcessDefinitionId())
                              .as(
                                  "Historic activity instance " +
                                  historicActivityInstance.getActivityId() +
                                  " has no process definition id"
                              )
                              .isNotNull();
                          assertThat(historicActivityInstance.getProcessInstanceId())
                              .as(
                                  "Historic activity instance " +
                                  historicActivityInstance.getActivityId() +
                                  " has no process instance id"
                              )
                              .isNotNull();
                          assertThat(historicActivityInstance.getExecutionId())
                              .as(
                                  "Historic activity instance " +
                                  historicActivityInstance.getActivityId() +
                                  " has no execution id"
                              )
                              .isNotNull();
                          assertThat(historicActivityInstance.getStartTime())
                              .as(
                                  "Historic activity instance " +
                                  historicActivityInstance.getActivityId() +
                                  " has no start time"
                              )
                              .isNotNull();
                          assertThat(historicActivityInstance.getEndTime())
                              .as(
                                  "Historic activity instance " +
                                  historicActivityInstance.getActivityId() +
                                  " has no end time"
                              )
                              .isNotNull();
                      }
                  }
              }
          }
      }
  
      /**
       * Each test is assumed to clean up all DB content it entered. After a test method executed, this method scans all tables to see if the DB is completely clean. It throws AssertionFailed in case the
       * DB is not clean. If the DB is not clean, it is cleaned by performing a create a drop.
       */
      protected void assertAndEnsureCleanDb() throws Throwable {
          log.debug("verifying that db is clean after test");
          Map<String, Long> tableCounts = managementService.getTableCount();
          StringBuilder outputMessage = new StringBuilder();
          for (String tableName : tableCounts.keySet()) {
              String tableNameWithoutPrefix = tableName.replace(processEngineConfiguration.getDatabaseTablePrefix(), "");
              if (!TABLENAMES_EXCLUDED_FROM_DB_CLEAN_CHECK.contains(tableNameWithoutPrefix)) {
                  Long count = tableCounts.get(tableName);
                  if (count != 0L) {
                      outputMessage.append("  ").append(tableName).append(": ").append(count).append(" record(s) ");
                  }
              }
          }
          if (outputMessage.length() > 0) {
              outputMessage.insert(0, "DB NOT CLEAN: \n");
              log.error(EMPTY_LINE);
              log.error(outputMessage.toString());
  
              log.info("dropping and recreating db");
  
              CommandExecutor commandExecutor =
                  ((ProcessEngineImpl) processEngine).getProcessEngineConfiguration().getCommandExecutor();
              CommandConfig config = new CommandConfig().transactionNotSupported();
              commandExecutor.execute(
                  config,
                  new Command<Object>() {
                      public Object execute(CommandContext commandContext) {
                          DbSqlSession session = commandContext.getDbSqlSession();
                          session.dbSchemaDrop();
                          session.dbSchemaCreate();
                          return null;
                      }
                  }
              );
  
              if (exception != null) {
                  throw exception;
              } else {
                  fail(outputMessage.toString());
              }
          } else {
              log.info("database was clean");
          }
      }
  
      protected void initializeServices() {
          processEngineConfiguration = ((ProcessEngineImpl) processEngine).getProcessEngineConfiguration();
          repositoryService = processEngine.getRepositoryService();
          runtimeService = processEngine.getRuntimeService();
          taskService = processEngine.getTaskService();
          historyService = processEngine.getHistoryService();
          managementService = processEngine.getManagementService();
          dynamicBpmnService = processEngine.getDynamicBpmnService();
      }
  
      public void assertProcessNotEnded(final String processInstanceId) {
          ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
          assertThat(processInstance).isNotNull();
      }
  
      public void assertProcessEnded(final String processInstanceId) {
          ProcessInstance processInstance = processEngine
              .getRuntimeService()
              .createProcessInstanceQuery()
              .processInstanceId(processInstanceId)
              .singleResult();
  
          if (processInstance != null) {
              throw new AssertionFailedError(
                  "Expected finished process instance '" + processInstanceId + "' but it was still in the db"
              );
          }
  
          // Verify historical data if end times are correctly set
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.AUDIT)) {
              // process instance
              HistoricProcessInstance historicProcessInstance = historyService
                  .createHistoricProcessInstanceQuery()
                  .processInstanceId(processInstanceId)
                  .singleResult();
              assertThat(historicProcessInstance.getId()).isEqualTo(processInstanceId);
              assertThat(historicProcessInstance.getStartTime())
                  .as("Historic process instance has no start time")
                  .isNotNull();
              assertThat(historicProcessInstance.getEndTime())
                  .as("Historic process instance has no end time")
                  .isNotNull();
  
              // tasks
              List<HistoricTaskInstance> historicTaskInstances = historyService
                  .createHistoricTaskInstanceQuery()
                  .processInstanceId(processInstanceId)
                  .list();
              if (historicTaskInstances != null && historicTaskInstances.size() > 0) {
                  for (HistoricTaskInstance historicTaskInstance : historicTaskInstances) {
                      assertThat(historicTaskInstance.getProcessInstanceId()).isEqualTo(processInstanceId);
                      assertThat(historicTaskInstance.getStartTime())
                          .as("Historic task " + historicTaskInstance.getTaskDefinitionKey() + " has no start time")
                          .isNotNull();
                      assertThat(historicTaskInstance.getEndTime())
                          .as("Historic task " + historicTaskInstance.getTaskDefinitionKey() + " has no end time")
                          .isNotNull();
                  }
              }
  
              // activities
              List<HistoricActivityInstance> historicActivityInstances = historyService
                  .createHistoricActivityInstanceQuery()
                  .processInstanceId(processInstanceId)
                  .list();
              if (historicActivityInstances != null && historicActivityInstances.size() > 0) {
                  for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                      assertThat(historicActivityInstance.getProcessInstanceId()).isEqualTo(processInstanceId);
                      assertThat(historicActivityInstance.getStartTime())
                          .as(
                              "Historic activity instance " +
                              historicActivityInstance.getActivityId() +
                              " has no start time"
                          )
                          .isNotNull();
                      assertThat(historicActivityInstance.getEndTime())
                          .as(
                              "Historic activity instance " +
                              historicActivityInstance.getActivityId() +
                              " has no end time"
                          )
                          .isNotNull();
                  }
              }
          }
      }
  
      public void waitForJobExecutorToProcessAllJobs(long maxMillisToWait, long intervalMillis) {
          JobTestHelper.waitForJobExecutorToProcessAllJobs(
              processEngineConfiguration,
              managementService,
              maxMillisToWait,
              intervalMillis
          );
      }
  
      public void waitForJobExecutorOnCondition(long maxMillisToWait, long intervalMillis, Callable<Boolean> condition) {
          JobTestHelper.waitForJobExecutorOnCondition(
              processEngineConfiguration,
              maxMillisToWait,
              intervalMillis,
              condition
          );
      }
  
      public void executeJobExecutorForTime(long maxMillisToWait, long intervalMillis) {
          JobTestHelper.executeJobExecutorForTime(processEngineConfiguration, maxMillisToWait, intervalMillis);
      }
  
      public void waitForJobExecutorToProcessAllJobsAndExecutableTimerJobs(long maxMillisToWait, long intervalMillis) {
          JobTestHelper.waitForJobExecutorToProcessAllJobsAndExecutableTimerJobs(
              processEngineConfiguration,
              managementService,
              maxMillisToWait,
              intervalMillis
          );
      }
  
      /**
       * Since the 'one task process' is used everywhere the actual process content doesn't matter, instead of copying around the BPMN 2.0 xml one could use this methodwhich gives a {@link BpmnModel}
       * version of the same process back.
       */
      public BpmnModel createOneTaskTestProcess() {
          BpmnModel model = new BpmnModel();
          org.activiti.bpmn.model.Process process = new org.activiti.bpmn.model.Process();
          model.addProcess(process);
          process.setId("oneTaskProcess");
          process.setName("The one task process");
  
          StartEvent startEvent = new StartEvent();
          startEvent.setId("start");
          process.addFlowElement(startEvent);
  
          UserTask userTask = new UserTask();
          userTask.setName("The Task");
          userTask.setId("theTask");
          userTask.setAssignee("kermit");
          process.addFlowElement(userTask);
  
          EndEvent endEvent = new EndEvent();
          endEvent.setId("theEnd");
          process.addFlowElement(endEvent);
  
          process.addFlowElement(new SequenceFlow("start", "theTask"));
          process.addFlowElement(new SequenceFlow("theTask", "theEnd"));
  
          return model;
      }
  
      public BpmnModel createTwoTasksTestProcess() {
          BpmnModel model = new BpmnModel();
          org.activiti.bpmn.model.Process process = new org.activiti.bpmn.model.Process();
          model.addProcess(process);
          process.setId("twoTasksProcess");
          process.setName("The two tasks process");
  
          StartEvent startEvent = new StartEvent();
          startEvent.setId("start");
          process.addFlowElement(startEvent);
  
          UserTask userTask = new UserTask();
          userTask.setName("The First Task");
          userTask.setId("task1");
          userTask.setAssignee("kermit");
          process.addFlowElement(userTask);
  
          UserTask userTask2 = new UserTask();
          userTask2.setName("The Second Task");
          userTask2.setId("task2");
          userTask2.setAssignee("kermit");
          process.addFlowElement(userTask2);
  
          EndEvent endEvent = new EndEvent();
          endEvent.setId("theEnd");
          process.addFlowElement(endEvent);
  
          process.addFlowElement(new SequenceFlow("start", "task1"));
          process.addFlowElement(new SequenceFlow("start", "task2"));
          process.addFlowElement(new SequenceFlow("task1", "theEnd"));
          process.addFlowElement(new SequenceFlow("task2", "theEnd"));
  
          return model;
      }
  
      public BpmnModel createOneTaskAndStartEventWithFormKeyProcess() {
          BpmnModel model = new BpmnModel();
          org.activiti.bpmn.model.Process process = new org.activiti.bpmn.model.Process();
          model.addProcess(process);
          process.setId("testProcess");
          process.setName("Test process");
  
          StartEvent startEvent = new StartEvent();
          startEvent.setId("start");
          startEvent.setFormKey("testFormKey");
          process.addFlowElement(startEvent);
  
          UserTask userTask = new UserTask();
          userTask.setName("The Task");
          userTask.setId("theTask");
          userTask.setAssignee("kermit");
          process.addFlowElement(userTask);
  
          EndEvent endEvent = new EndEvent();
          endEvent.setId("theEnd");
          process.addFlowElement(endEvent);
  
          process.addFlowElement(new SequenceFlow("start", "theTask"));
          process.addFlowElement(new SequenceFlow("theTask", "theEnd"));
  
          return model;
      }
  
      /**
       * Creates and deploys the one task process. See {@link #createOneTaskTestProcess()}.
       *
       * @return The process definition id (NOT the process definition key) of deployed one task process.
       */
      public String deployOneTaskTestProcess() {
          BpmnModel bpmnModel = createOneTaskTestProcess();
          Deployment deployment = repositoryService
              .createDeployment()
              .addBpmnModel("oneTasktest.bpmn20.xml", bpmnModel)
              .deploy();
  
          deploymentIdsForAutoCleanup.add(deployment.getId()); // For auto-cleanup
  
          ProcessDefinition processDefinition = repositoryService
              .createProcessDefinitionQuery()
              .deploymentId(deployment.getId())
              .singleResult();
          return processDefinition.getId();
      }
  
      public String deployTwoTasksTestProcess() {
          BpmnModel bpmnModel = createTwoTasksTestProcess();
          Deployment deployment = repositoryService
              .createDeployment()
              .addBpmnModel("twoTasksTestProcess.bpmn20.xml", bpmnModel)
              .deploy();
  
          deploymentIdsForAutoCleanup.add(deployment.getId()); // For auto-cleanup
  
          ProcessDefinition processDefinition = repositoryService
              .createProcessDefinitionQuery()
              .deploymentId(deployment.getId())
              .singleResult();
          return processDefinition.getId();
      }
  
      //
      // HELPERS
      //
  
      protected void assertHistoricTasksDeleteReason(
          ProcessInstance processInstance,
          String expectedDeleteReason,
          String... taskNames
      ) {
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.AUDIT)) {
              for (String taskName : taskNames) {
                  List<HistoricTaskInstance> historicTaskInstances = historyService
                      .createHistoricTaskInstanceQuery()
                      .processInstanceId(processInstance.getId())
                      .taskName(taskName)
                      .list();
                  assertThat(historicTaskInstances.size() > 0).isTrue();
                  for (HistoricTaskInstance historicTaskInstance : historicTaskInstances) {
                      assertThat(historicTaskInstance.getEndTime()).isNotNull();
                      if (expectedDeleteReason == null) {
                          assertThat(historicTaskInstance.getDeleteReason()).isNull();
                      } else {
                          assertThat(historicTaskInstance.getDeleteReason().startsWith(expectedDeleteReason)).isTrue();
                      }
                  }
              }
          }
      }
  
      protected void assertHistoricActivitiesDeleteReason(
          ProcessInstance processInstance,
          String expectedDeleteReason,
          String... activityIds
      ) {
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.AUDIT)) {
              for (String activityId : activityIds) {
                  List<HistoricActivityInstance> historicActivityInstances = historyService
                      .createHistoricActivityInstanceQuery()
                      .activityId(activityId)
                      .processInstanceId(processInstance.getId())
                      .list();
                  assertThat(historicActivityInstances).as("Could not find historic activities").isNotEmpty();
                  for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                      assertThat(historicActivityInstance.getEndTime()).isNotNull();
                      if (expectedDeleteReason == null) {
                          assertThat(historicActivityInstance.getDeleteReason()).isNull();
                      } else {
                          assertThat(
                              historicActivityInstance.getDeleteReason().startsWith(expectedDeleteReason)
                          ).isTrue();
                      }
                  }
              }
          }
      }
  }
  