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.history;
  
  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.List;
  import org.activiti.engine.ActivitiIllegalArgumentException;
  import org.activiti.engine.history.HistoricActivityInstance;
  import org.activiti.engine.history.HistoricActivityInstanceQuery;
  import org.activiti.engine.history.HistoricProcessInstance;
  import org.activiti.engine.impl.history.HistoryLevel;
  import org.activiti.engine.impl.test.PluggableActivitiTestCase;
  import org.activiti.engine.runtime.Execution;
  import org.activiti.engine.runtime.ProcessInstance;
  import org.activiti.engine.task.Task;
  import org.activiti.engine.test.Deployment;
  
  /**
  
  
   */
  public class HistoricActivityInstanceTest extends PluggableActivitiTestCase {
  
      @Deployment
      public void testHistoricActivityInstanceNoop() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("noopProcess");
  
          HistoricActivityInstance historicActivityInstance = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("noop")
              .singleResult();
  
          assertThat(historicActivityInstance.getActivityId()).isEqualTo("noop");
          assertThat(historicActivityInstance.getActivityType()).isEqualTo("serviceTask");
          assertThat(historicActivityInstance.getProcessDefinitionId()).isNotNull();
          assertThat(historicActivityInstance.getProcessInstanceId()).isEqualTo(processInstance.getId());
          assertThat(historicActivityInstance.getStartTime()).isNotNull();
          assertThat(historicActivityInstance.getEndTime()).isNotNull();
          assertThat(historicActivityInstance.getDurationInMillis() >= 0).isTrue();
      }
  
      @Deployment
      public void testHistoricActivityInstanceReceive() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("receiveProcess");
  
          HistoricActivityInstance historicActivityInstance = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("receive")
              .singleResult();
  
          assertThat(historicActivityInstance.getActivityId()).isEqualTo("receive");
          assertThat(historicActivityInstance.getActivityType()).isEqualTo("receiveTask");
          assertThat(historicActivityInstance.getEndTime()).isNull();
          assertThat(historicActivityInstance.getDurationInMillis()).isNull();
          assertThat(historicActivityInstance.getProcessDefinitionId()).isNotNull();
          assertThat(historicActivityInstance.getProcessInstanceId()).isEqualTo(processInstance.getId());
          assertThat(historicActivityInstance.getStartTime()).isNotNull();
  
          Execution execution = runtimeService
              .createExecutionQuery()
              .onlyChildExecutions()
              .processInstanceId(processInstance.getId())
              .singleResult();
          runtimeService.trigger(execution.getId());
  
          historicActivityInstance = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("receive")
              .singleResult();
  
          assertThat(historicActivityInstance.getActivityId()).isEqualTo("receive");
          assertThat(historicActivityInstance.getActivityType()).isEqualTo("receiveTask");
          assertThat(historicActivityInstance.getEndTime()).isNotNull();
          assertThat(historicActivityInstance.getDurationInMillis() >= 0).isTrue();
          assertThat(historicActivityInstance.getProcessDefinitionId()).isNotNull();
          assertThat(historicActivityInstance.getProcessInstanceId()).isEqualTo(processInstance.getId());
          assertThat(historicActivityInstance.getStartTime()).isNotNull();
      }
  
      @Deployment
      public void testHistoricActivityInstanceUnfinished() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess");
          assertThat(processInstance).isNotNull();
  
          HistoricActivityInstanceQuery historicActivityInstanceQuery =
              historyService.createHistoricActivityInstanceQuery();
  
          long finishedActivityInstanceCount = historicActivityInstanceQuery.finished().count();
          assertThat(finishedActivityInstanceCount).as("The Start event is completed").isEqualTo(1);
  
          long unfinishedActivityInstanceCount = historicActivityInstanceQuery.unfinished().count();
          assertThat(unfinishedActivityInstanceCount).as("One active (unfinished) User Task").isEqualTo(1);
      }
  
      @Deployment
      public void testHistoricActivityInstanceQuery() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("noopProcess");
  
          assertThat(
              historyService.createHistoricActivityInstanceQuery().activityId("nonExistingActivityId").list()
          ).hasSize(0);
          assertThat(historyService.createHistoricActivityInstanceQuery().activityId("noop").list()).hasSize(1);
  
          assertThat(
              historyService.createHistoricActivityInstanceQuery().activityType("nonExistingActivityType").list()
          ).hasSize(0);
          assertThat(historyService.createHistoricActivityInstanceQuery().activityType("serviceTask").list()).hasSize(1);
  
          assertThat(
              historyService.createHistoricActivityInstanceQuery().activityName("nonExistingActivityName").list()
          ).hasSize(0);
          assertThat(historyService.createHistoricActivityInstanceQuery().activityName("No operation").list()).hasSize(1);
  
          assertThat(
              historyService.createHistoricActivityInstanceQuery().taskAssignee("nonExistingAssignee").list()
          ).hasSize(0);
  
          assertThat(
              historyService.createHistoricActivityInstanceQuery().executionId("nonExistingExecutionId").list()
          ).hasSize(0);
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              assertThat(
                  historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstance.getId()).list()
              ).hasSize(3);
          } else {
              assertThat(
                  historyService.createHistoricActivityInstanceQuery().executionId(processInstance.getId()).list()
              ).hasSize(0);
          }
  
          assertThat(
              historyService
                  .createHistoricActivityInstanceQuery()
                  .processInstanceId("nonExistingProcessInstanceId")
                  .list()
          ).hasSize(0);
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              assertThat(
                  historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstance.getId()).list()
              ).hasSize(3);
          } else {
              assertThat(
                  historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstance.getId()).list()
              ).hasSize(0);
          }
  
          assertThat(
              historyService
                  .createHistoricActivityInstanceQuery()
                  .processDefinitionId("nonExistingProcessDefinitionId")
                  .list()
          ).hasSize(0);
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              assertThat(
                  historyService
                      .createHistoricActivityInstanceQuery()
                      .processDefinitionId(processInstance.getProcessDefinitionId())
                      .list()
              ).hasSize(3);
          } else {
              assertThat(
                  historyService
                      .createHistoricActivityInstanceQuery()
                      .processDefinitionId(processInstance.getProcessDefinitionId())
                      .list()
              ).hasSize(0);
          }
  
          assertThat(historyService.createHistoricActivityInstanceQuery().unfinished().list()).hasSize(0);
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              assertThat(historyService.createHistoricActivityInstanceQuery().finished().list()).hasSize(3);
          } else {
              assertThat(historyService.createHistoricActivityInstanceQuery().finished().list()).hasSize(0);
          }
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              HistoricActivityInstance historicActivityInstance = historyService
                  .createHistoricActivityInstanceQuery()
                  .list()
                  .get(0);
              assertThat(
                  historyService
                      .createHistoricActivityInstanceQuery()
                      .activityInstanceId(historicActivityInstance.getId())
                      .list()
              ).hasSize(1);
          }
      }
  
      @Deployment
      public void testHistoricActivityInstanceForEventsQuery() {
          ProcessInstance pi = runtimeService.startProcessInstanceByKey("eventProcess");
          assertThat(taskService.createTaskQuery().count()).isEqualTo(1);
          runtimeService.signalEventReceived("signal");
          assertProcessEnded(pi.getId());
  
          assertThat(historyService.createHistoricActivityInstanceQuery().activityId("noop").list()).hasSize(1);
          assertThat(historyService.createHistoricActivityInstanceQuery().activityId("userTask").list()).hasSize(1);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().activityId("intermediate-event").list()
          ).hasSize(1);
          assertThat(historyService.createHistoricActivityInstanceQuery().activityId("start").list()).hasSize(1);
          assertThat(historyService.createHistoricActivityInstanceQuery().activityId("end").list()).hasSize(1);
  
          // TODO: Discuss if boundary events will occur in the log!
          // assertThat(1,
          // historyService.createHistoricActivityInstanceQuery().activityId("boundaryEvent").list().size());
  
          HistoricActivityInstance intermediateEvent = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("intermediate-event")
              .singleResult();
          assertThat(intermediateEvent.getStartTime()).isNotNull();
          assertThat(intermediateEvent.getEndTime()).isNotNull();
  
          HistoricActivityInstance startEvent = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("start")
              .singleResult();
          assertThat(startEvent.getStartTime()).isNotNull();
          assertThat(startEvent.getEndTime()).isNotNull();
  
          HistoricActivityInstance endEvent = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("end")
              .singleResult();
          assertThat(endEvent.getStartTime()).isNotNull();
          assertThat(endEvent.getEndTime()).isNotNull();
      }
  
      @Deployment
      public void testHistoricActivityInstanceProperties() {
          // Start process instance
          runtimeService.startProcessInstanceByKey("taskAssigneeProcess");
  
          // Get task list
          HistoricActivityInstance historicActivityInstance = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("theTask")
              .singleResult();
  
          Task task = taskService.createTaskQuery().singleResult();
          assertThat(historicActivityInstance.getTaskId()).isEqualTo(task.getId());
          assertThat(historicActivityInstance.getAssignee()).isEqualTo("kermit");
      }
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/history/calledProcess.bpmn20.xml",
              "org/activiti/engine/test/history/HistoricActivityInstanceTest.testCallSimpleSubProcess.bpmn20.xml",
          }
      )
      public void testHistoricActivityInstanceCalledProcessId() {
          runtimeService.startProcessInstanceByKey("callSimpleSubProcess");
  
          HistoricActivityInstance historicActivityInstance = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("callSubProcess")
              .singleResult();
  
          HistoricProcessInstance oldInstance = historyService
              .createHistoricProcessInstanceQuery()
              .processDefinitionKey("calledProcess")
              .singleResult();
  
          assertThat(historicActivityInstance.getCalledProcessInstanceId()).isEqualTo(oldInstance.getId());
      }
  
      @Deployment
      public void testSorting() {
          runtimeService.startProcessInstanceByKey("process");
  
          int expectedActivityInstances;
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              expectedActivityInstances = 2;
          } else {
              expectedActivityInstances = 0;
          }
  
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceId().asc().list()
          ).hasSize(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceStartTime().asc().list()
          ).hasSize(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceEndTime().asc().list()
          ).hasSize(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceDuration().asc().list()
          ).hasSize(expectedActivityInstances);
          assertThat(historyService.createHistoricActivityInstanceQuery().orderByExecutionId().asc().list()).hasSize(
              expectedActivityInstances
          );
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByProcessDefinitionId().asc().list()
          ).hasSize(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByProcessInstanceId().asc().list()
          ).hasSize(expectedActivityInstances);
  
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceId().desc().list()
          ).hasSize(expectedActivityInstances);
          assertThat(
              historyService
                  .createHistoricActivityInstanceQuery()
                  .orderByHistoricActivityInstanceStartTime()
                  .desc()
                  .list()
          ).hasSize(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceEndTime().desc().list()
          ).hasSize(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceDuration().desc().list()
          ).hasSize(expectedActivityInstances);
          assertThat(historyService.createHistoricActivityInstanceQuery().orderByExecutionId().desc().list()).hasSize(
              expectedActivityInstances
          );
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByProcessDefinitionId().desc().list()
          ).hasSize(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByProcessInstanceId().desc().list()
          ).hasSize(expectedActivityInstances);
  
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceId().asc().count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(
              historyService
                  .createHistoricActivityInstanceQuery()
                  .orderByHistoricActivityInstanceStartTime()
                  .asc()
                  .count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceEndTime().asc().count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceDuration().asc().count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(historyService.createHistoricActivityInstanceQuery().orderByExecutionId().asc().count()).isEqualTo(
              expectedActivityInstances
          );
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByProcessDefinitionId().asc().count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByProcessInstanceId().asc().count()
          ).isEqualTo(expectedActivityInstances);
  
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceId().desc().count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(
              historyService
                  .createHistoricActivityInstanceQuery()
                  .orderByHistoricActivityInstanceStartTime()
                  .desc()
                  .count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceEndTime().desc().count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(
              historyService
                  .createHistoricActivityInstanceQuery()
                  .orderByHistoricActivityInstanceDuration()
                  .desc()
                  .count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(historyService.createHistoricActivityInstanceQuery().orderByExecutionId().desc().count()).isEqualTo(
              expectedActivityInstances
          );
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByProcessDefinitionId().desc().count()
          ).isEqualTo(expectedActivityInstances);
          assertThat(
              historyService.createHistoricActivityInstanceQuery().orderByProcessInstanceId().desc().count()
          ).isEqualTo(expectedActivityInstances);
      }
  
      public void testInvalidSorting() {
          assertThatExceptionOfType(ActivitiIllegalArgumentException.class).isThrownBy(() ->
              historyService.createHistoricActivityInstanceQuery().asc().list()
          );
  
          assertThatExceptionOfType(ActivitiIllegalArgumentException.class).isThrownBy(() ->
              historyService.createHistoricActivityInstanceQuery().desc().list()
          );
  
          assertThatExceptionOfType(ActivitiIllegalArgumentException.class).isThrownBy(() ->
              historyService.createHistoricActivityInstanceQuery().orderByHistoricActivityInstanceDuration().list()
          );
      }
  
      /**
       * Test to validate fix for ACT-1399: Boundary-event and event-based auditing
       */
      @Deployment
      public void testBoundaryEvent() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("boundaryEventProcess");
          // Complete the task with the boundary-event on it
          Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
          assertThat(task).isNotNull();
          taskService.complete(task.getId());
  
          assertThat(
              runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId()).count()
          ).isEqualTo(0L);
  
          // Check if there is NO historic activity instance for a boundary-event
          // that has not triggered
          HistoricActivityInstance historicActivityInstance = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("boundary")
              .processInstanceId(processInstance.getId())
              .singleResult();
  
          assertThat(historicActivityInstance).isNull();
  
          // Now check the history when the boundary-event is fired
          processInstance = runtimeService.startProcessInstanceByKey("boundaryEventProcess");
  
          task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
  
          Execution signalExecution = runtimeService
              .createExecutionQuery()
              .signalEventSubscriptionName("alert")
              .singleResult();
          runtimeService.signalEventReceived("alert", signalExecution.getId());
          assertThat(
              runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId()).count()
          ).isEqualTo(0L);
  
          historicActivityInstance = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("boundary")
              .processInstanceId(processInstance.getId())
              .singleResult();
  
          assertThat(historicActivityInstance).isNotNull();
          assertThat(historicActivityInstance.getStartTime()).isNotNull();
          assertThat(historicActivityInstance.getEndTime()).isNotNull();
      }
  
      /**
       * Test to validate fix for ACT-1399: Boundary-event and event-based auditing
       */
      @Deployment
      public void testEventBasedGateway() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("catchSignal");
          Execution waitingExecution = runtimeService
              .createExecutionQuery()
              .signalEventSubscriptionName("alert")
              .singleResult();
          assertThat(waitingExecution).isNotNull();
          runtimeService.signalEventReceived("alert", waitingExecution.getId());
  
          assertThat(
              runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId()).count()
          ).isEqualTo(0L);
  
          HistoricActivityInstance historicActivityInstance = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("eventBasedgateway")
              .processInstanceId(processInstance.getId())
              .singleResult();
  
          assertThat(historicActivityInstance).isNotNull();
      }
  
      /**
       * Test to validate fix for ACT-1549: endTime of joining parallel gateway is not set
       */
      @Deployment
      public void testParallelJoinEndTime() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("forkJoin");
  
          List<Task> tasksToComplete = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
          assertThat(tasksToComplete).hasSize(2);
  
          // Complete both tasks, second task-complete should end the fork-gateway
          // and set time
          taskService.complete(tasksToComplete.get(0).getId());
          taskService.complete(tasksToComplete.get(1).getId());
  
          List<HistoricActivityInstance> historicActivityInstance = historyService
              .createHistoricActivityInstanceQuery()
              .activityId("join")
              .processInstanceId(processInstance.getId())
              .list();
  
          assertThat(historicActivityInstance).isNotNull();
  
          // History contains 2 entries for parallel join (one for each path
          // arriving in the join), should contain end-time
          assertThat(historicActivityInstance).hasSize(2);
          assertThat(historicActivityInstance.get(0).getEndTime()).isNotNull();
          assertThat(historicActivityInstance.get(1).getEndTime()).isNotNull();
      }
  
      @Deployment
      public void testLoop() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
              "historic-activity-loops",
              singletonMap("input", 0)
          );
  
          // completing 10 user tasks
          // 15 service tasks should have passed
  
          for (int i = 0; i < 10; i++) {
              Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
              Number inputNumber = (Number) taskService.getVariable(task.getId(), "input");
              int input = inputNumber.intValue();
              assertThat(input).isEqualTo(i);
              taskService.complete(task.getId(), singletonMap("input", input + 1));
              task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
          }
  
          // Verify history
          List<HistoricActivityInstance> taskActivityInstances = historyService
              .createHistoricActivityInstanceQuery()
              .activityType("userTask")
              .list();
          assertThat(taskActivityInstances).hasSize(10);
          for (HistoricActivityInstance historicActivityInstance : taskActivityInstances) {
              assertThat(historicActivityInstance.getStartTime()).isNotNull();
              assertThat(historicActivityInstance.getEndTime()).isNotNull();
          }
  
          List<HistoricActivityInstance> serviceTaskInstances = historyService
              .createHistoricActivityInstanceQuery()
              .activityType("serviceTask")
              .list();
          assertThat(serviceTaskInstances).hasSize(15);
          for (HistoricActivityInstance historicActivityInstance : serviceTaskInstances) {
              assertThat(historicActivityInstance.getStartTime()).isNotNull();
              assertThat(historicActivityInstance.getEndTime()).isNotNull();
          }
      }
  }
  