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.compensate;
  
  import static java.util.Collections.singletonMap;
  import static org.assertj.core.api.Assertions.assertThat;
  import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
  
  import org.activiti.engine.history.HistoricActivityInstance;
  import org.activiti.engine.history.HistoricActivityInstanceQuery;
  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;
  import org.activiti.engine.test.EnableVerboseExecutionTreeLogging;
  import org.activiti.engine.test.bpmn.event.compensate.helper.SetVariablesDelegate;
  
  /**
   */
  @EnableVerboseExecutionTreeLogging
  public class CompensateEventTest extends PluggableActivitiTestCase {
  
      @Deployment
      public void testCompensateSubprocess() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
  
          assertThat(runtimeService.getVariable(processInstance.getId(), "undoBookHotel")).isEqualTo(5);
  
          Execution execution = runtimeService.createExecutionQuery().activityId("beforeEnd").singleResult();
          runtimeService.trigger(execution.getId());
          assertProcessEnded(processInstance.getId());
      }
  
      @Deployment
      public void testCompensateSubprocessWithoutActivityRef() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
  
          assertThat(runtimeService.getVariable(processInstance.getId(), "undoBookHotel")).isEqualTo(5);
  
          Execution execution = runtimeService.createExecutionQuery().activityId("beforeEnd").singleResult();
          runtimeService.trigger(execution.getId());
          assertProcessEnded(processInstance.getId());
      }
  
      @Deployment
      public void testCompensateSubprocessWithUserTask() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
  
          Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
          assertThat(task.getName()).isEqualTo("Manually undo book hotel");
          taskService.complete(task.getId());
  
          Execution execution = runtimeService.createExecutionQuery().activityId("beforeEnd").singleResult();
          runtimeService.trigger(execution.getId());
          assertProcessEnded(processInstance.getId());
      }
  
      @Deployment
      public void testCompensateSubprocessWithUserTask2() {
          // Same process as testCompensateSubprocessWithUserTask, but now the end event is reached first
          // (giving an exception before)
  
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
          Execution execution = runtimeService.createExecutionQuery().activityId("beforeEnd").singleResult();
          runtimeService.trigger(execution.getId());
  
          Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
          assertThat(task.getName()).isEqualTo("Manually undo book hotel");
          taskService.complete(task.getId());
  
          assertProcessEnded(processInstance.getId());
      }
  
      @Deployment
      public void testCompensateMiSubprocess() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
  
          assertThat(runtimeService.getVariable(processInstance.getId(), "undoBookHotel")).isEqualTo(5);
  
          Execution execution = runtimeService.createExecutionQuery().activityId("beforeEnd").singleResult();
          runtimeService.trigger(execution.getId());
          assertProcessEnded(processInstance.getId());
      }
  
      @Deployment
      public void testCompensateScope() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
  
          assertThat(runtimeService.getVariable(processInstance.getId(), "undoBookHotel")).isEqualTo(5);
          assertThat(runtimeService.getVariable(processInstance.getId(), "undoBookFlight")).isEqualTo(5);
  
          Execution execution = runtimeService.createExecutionQuery().activityId("beforeEnd").singleResult();
          runtimeService.trigger(execution.getId());
          assertProcessEnded(processInstance.getId());
      }
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/compensate/CompensateEventTest.testCallActivityCompensationHandler.bpmn20.xml",
              "org/activiti/engine/test/bpmn/event/compensate/CompensationHandler.bpmn20.xml",
          }
      )
      public void testCallActivityCompensationHandler() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              assertThat(
                  historyService.createHistoricActivityInstanceQuery().activityId("undoBookHotel").count()
              ).isEqualTo(5);
          }
  
          Execution execution = runtimeService.createExecutionQuery().activityId("beforeEnd").singleResult();
          runtimeService.trigger(execution.getId());
          assertProcessEnded(processInstance.getId());
  
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              assertThat(historyService.createHistoricProcessInstanceQuery().count()).isEqualTo(6);
          }
      }
  
      @Deployment
      public void testCompensateMiSubprocessVariableSnapshots() {
          // see referenced java delegates in the process definition.
  
          SetVariablesDelegate.variablesMap.clear();
  
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              assertThat(
                  historyService.createHistoricActivityInstanceQuery().activityId("undoBookHotel").count()
              ).isEqualTo(5);
          }
  
          assertProcessEnded(processInstance.getId());
      }
  
      public void testMultipleCompensationCatchEventsFails() {
          assertThatExceptionOfType(Exception.class).isThrownBy(() ->
              repositoryService
                  .createDeployment()
                  .addClasspathResource(
                      "org/activiti/engine/test/bpmn/event/compensate/CompensateEventTest.testMultipleCompensationCatchEventsFails.bpmn20.xml"
                  )
                  .deploy()
          );
      }
  
      public void testInvalidActivityRefFails() {
          assertThatExceptionOfType(Exception.class)
              .isThrownBy(() ->
                  repositoryService
                      .createDeployment()
                      .addClasspathResource(
                          "org/activiti/engine/test/bpmn/event/compensate/CompensateEventTest.testInvalidActivityRefFails.bpmn20.xml"
                      )
                      .deploy()
              )
              .withMessageContaining("Invalid attribute value for 'activityRef':");
      }
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/compensate/CompensateEventTest.testCompensationStepEndRecorded.bpmn20.xml",
          }
      )
      public void testCompensationStepEndTimeRecorded() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
              "compensationStepEndRecordedProcess"
          );
          assertProcessEnded(processInstance.getId());
          assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
              final HistoricActivityInstanceQuery query = historyService
                  .createHistoricActivityInstanceQuery()
                  .activityId("compensationScriptTask");
              assertThat(query.count()).isEqualTo(1);
              final HistoricActivityInstance compensationScriptTask = query.singleResult();
              assertThat(compensationScriptTask).isNotNull();
              assertThat(compensationScriptTask.getEndTime()).isNotNull();
              assertThat(compensationScriptTask.getDurationInMillis()).isNotNull();
          }
      }
  
      @Deployment
      public void testCompensateWithSubprocess() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
  
          if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.AUDIT)) {
              HistoricActivityInstance historicActivityInstance = historyService
                  .createHistoricActivityInstanceQuery()
                  .processInstanceId(processInstance.getId())
                  .activityId("bookHotel")
                  .singleResult();
              assertThat(historicActivityInstance.getEndTime()).isNotNull();
          }
  
          // Triggering the task will trigger the compensation subprocess
          Task afterBookHotelTask = taskService
              .createTaskQuery()
              .processInstanceId(processInstance.getId())
              .taskDefinitionKey("afterBookHotel")
              .singleResult();
          taskService.complete(afterBookHotelTask.getId());
  
          Task compensationTask1 = taskService
              .createTaskQuery()
              .processInstanceId(processInstance.getId())
              .taskDefinitionKey("compensateTask1")
              .singleResult();
          assertThat(compensationTask1).isNotNull();
  
          Task compensationTask2 = taskService
              .createTaskQuery()
              .processInstanceId(processInstance.getId())
              .taskDefinitionKey("compensateTask2")
              .singleResult();
          assertThat(compensationTask2).isNotNull();
  
          taskService.complete(compensationTask1.getId());
          taskService.complete(compensationTask2.getId());
  
          Task compensationTask3 = taskService
              .createTaskQuery()
              .processInstanceId(processInstance.getId())
              .taskDefinitionKey("compensateTask3")
              .singleResult();
          assertThat(compensationTask3).isNotNull();
          taskService.complete(compensationTask3.getId());
  
          assertProcessEnded(processInstance.getId());
      }
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/compensate/CompensateEventTest.testCompensateWithSubprocess.bpmn20.xml",
          }
      )
      public void testCompensateWithSubprocess2() {
          // Same as testCompensateWithSubprocess, but without throwing the compensation event
          // As such, to verify that the extra compensation executions have no effect on the regular process execution
  
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
              "compensateProcess",
              singletonMap("doCompensation", false)
          );
  
          Task afterBookHotelTask = taskService
              .createTaskQuery()
              .processInstanceId(processInstance.getId())
              .taskDefinitionKey("afterBookHotel")
              .singleResult();
          taskService.complete(afterBookHotelTask.getId());
  
          assertProcessEnded(processInstance.getId());
      }
  
      @Deployment
      public void testCompensateNestedSubprocess() {
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("compensateProcess");
  
          // Completing should trigger the compensations
          Task task = taskService
              .createTaskQuery()
              .processInstanceId(processInstance.getId())
              .taskDefinitionKey("afterNestedSubProcess")
              .singleResult();
          assertThat(task).isNotNull();
          taskService.complete(task.getId());
  
          Task compensationTask = taskService
              .createTaskQuery()
              .processInstanceId(processInstance.getId())
              .taskDefinitionKey("undoBookHotel")
              .singleResult();
          assertThat(compensationTask).isNotNull();
          taskService.complete(compensationTask.getId());
  
          assertProcessEnded(processInstance.getId());
      }
  }
  