package com.intrasoft.ermis.transit.officeofexitfortransit.core.service;

import com.intrasoft.ermis.common.bpmn.core.BpmnEngine;
import com.intrasoft.ermis.common.bpmn.core.CorrelateBpmnMessageCommand;
import com.intrasoft.ermis.common.logging.ErmisLogger;
import com.intrasoft.ermis.transit.officeofexitfortransit.domain.enums.BpmnFlowVariablesEnum;
import com.intrasoft.ermis.transit.officeofexitfortransit.domain.enums.ProcessDefinitionEnum;
import java.util.Map;
import java.util.Objects;
import lombok.AllArgsConstructor;
import org.apache.commons.collections.map.HashedMap;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@AllArgsConstructor(onConstructor_ = {@Autowired})
public class BpmnFlowServiceImpl implements BpmnFlowService {

    private final ErmisLogger log;

    private final BpmnEngine bpmnEngine;

    private final RuntimeService runtimeService;

    @Override
    public void terminateProcess(String businessKey, String processDefinitionKey, String reason) {
        log.info("terminateProcess() on processDefinitionKey: [{}] and businessKey: [{}]", processDefinitionKey, businessKey);
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                                                        .processInstanceBusinessKey(businessKey)
                                                        .processDefinitionKey(processDefinitionKey)
                                                        .singleResult();

        if (Objects.isNull(processInstance) || processInstance.isEnded()) {
            log.warn("terminateProcess() with businessKey: [{}] --> NO ACTIVE PROCESS FOUND", businessKey);
            return;
        }

        runtimeService.deleteProcessInstanceIfExists(processInstance.getProcessInstanceId(), reason, false, false, false, false);
    }

    @Override
    public void correlateMessage(String businessKey, String messageName) {
        log.info("correlateMessage() on messageType: [{}] and businessKey: [{}]", messageName, businessKey);
        CorrelateBpmnMessageCommand correlateBpmnMessageCommand = CorrelateBpmnMessageCommand
                .builder()
                .messageName(messageName)
                .businessKey(businessKey)
                .build();
        bpmnEngine.correlateMessage(correlateBpmnMessageCommand);
    }

    @Override
    public void startProcess(ProcessDefinitionEnum processDefinition, String businessKey, Map<String, Object> processVariables) {
        log.info("startProcess() on processDefinitionKey: [{}] and businessKey: [{}]", processDefinition.getProcessDefinitionKey(), businessKey);
        Map<String, Object> finalVars = new HashedMap();
        finalVars.put(BpmnFlowVariablesEnum.PROCESS_BUSINESS_KEY.getValue(), businessKey);
        finalVars.putAll(processVariables);
        bpmnEngine.createProcessInstanceByProcessId(processDefinition.getProcessDefinitionKey(), finalVars);
    }

    @Override
    public void correlateMessageWithValues(String businessKey, String messageName, Map<String, Object> values) {
        log.info("correlateMessageWithValues() on messageType : [{}] and businessKey: [{}]", messageName, businessKey);
        CorrelateBpmnMessageCommand correlateBpmnMessageCommand = CorrelateBpmnMessageCommand
                .builder()
                .messageName(messageName)
                .businessKey(businessKey)
                .values(values)
                .build();
        bpmnEngine.correlateMessage(correlateBpmnMessageCommand);
    }
}

package com.intrasoft.ermis.transit.officeofexitfortransit.service;

import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.intrasoft.ermis.common.bpmn.core.BpmnEngine;
import com.intrasoft.ermis.common.bpmn.core.CorrelateBpmnMessageCommand;
import com.intrasoft.ermis.common.logging.ErmisLogger;
import com.intrasoft.ermis.transit.officeofexitfortransit.core.service.BpmnFlowServiceImpl;
import com.intrasoft.ermis.transit.officeofexitfortransit.domain.enums.ProcessDefinitionEnum;
import java.util.Map;
import java.util.UUID;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.impl.ProcessInstanceQueryImpl;
import org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity;
import org.camunda.bpm.engine.runtime.ProcessInstanceQuery;
import org.camunda.bpm.extension.mockito.CamundaMockito;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class BpmnFlowServiceUT {

    @Mock
    private ErmisLogger ermisLogger;

    @Mock
    private BpmnEngine bpmnEngine;

    @Mock
    private RuntimeService runtimeService;

    @InjectMocks
    private BpmnFlowServiceImpl bpmnFlowService;

    @Captor
    private ArgumentCaptor<CorrelateBpmnMessageCommand> correlateBpmnMessageCommandArgumentCaptor;

    @Test
    @DisplayName("Test - Correlate Message with values")
    void testCorrelateMessageWithValues() {

        String businessKey = UUID.randomUUID().toString();

        String messageName = "TEST_NAME";

        Map<String, Object> values = Map.ofEntries(
                entry("TEST_KEY_1", "TEST_VALUE"),
                entry("TEST_KEY_2", true)
        );

        try {
            // Call BpmnFlowService method:
            bpmnFlowService.correlateMessageWithValues(businessKey, messageName, values);
            // Verification & Assertions:
            verify(bpmnEngine, times(1)).correlateMessage(correlateBpmnMessageCommandArgumentCaptor.capture());
            assertEquals(businessKey, correlateBpmnMessageCommandArgumentCaptor.getValue().getBusinessKey());
            assertEquals(messageName, correlateBpmnMessageCommandArgumentCaptor.getValue().getMessageName());
            assertEquals(values.keySet(), correlateBpmnMessageCommandArgumentCaptor.getValue().getValues().keySet());
            assertEquals(values.get("TEST_KEY_1"), correlateBpmnMessageCommandArgumentCaptor.getValue().getValues().get("TEST_KEY_1"));
            assertEquals(values.get("TEST_KEY_2"), correlateBpmnMessageCommandArgumentCaptor.getValue().getValues().get("TEST_KEY_2"));
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Cancel Process Instance - EXISTS")
    void testCancelProcessInstanceExists() {

        String businessKey = UUID.randomUUID().toString();

        String reason = "TEST_REASON";

        // Stubs:
        // (Use ExecutionEntity to be able to set ProcessInstanceID, valid object as ProcessInstance extends it.)
        ExecutionEntity processInstance = new ExecutionEntity();
        processInstance.setProcessInstanceId(UUID.randomUUID().toString());

        when(runtimeService.createProcessInstanceQuery()).thenReturn(new ProcessInstanceQueryImpl());
        final ProcessInstanceQuery processInstanceQuery = CamundaMockito.mockProcessInstanceQuery(runtimeService)
                                                                        .singleResult(processInstance);
        doNothing().when(runtimeService)
                   .deleteProcessInstanceIfExists(anyString(), eq(reason), eq(false), eq(false), eq(false), eq(false));

        try {
            // Call BpmnFlowService method:
            bpmnFlowService.terminateProcess(businessKey,
                    ProcessDefinitionEnum.OOEXIT_FOR_TRANSIT_MAIN_PROCESS.getProcessDefinitionKey(), reason);
            // Verifications:
            verify(runtimeService, times(1)).createProcessInstanceQuery();
            verify(processInstanceQuery).singleResult();
            verify(runtimeService, times(1))
                    .deleteProcessInstanceIfExists(anyString(), eq(reason), eq(false), eq(false), eq(false), eq(false));
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Cancel Process Instance - NOT EXISTS")
    void testCancelProcessInstanceNotExists() {

        String businessKey = UUID.randomUUID().toString();

        String reason = "TEST_REASON";

        // Stubs:
        when(runtimeService.createProcessInstanceQuery()).thenReturn(new ProcessInstanceQueryImpl());
        final ProcessInstanceQuery processInstanceQuery = CamundaMockito.mockProcessInstanceQuery(runtimeService)
                                                                        .singleResult(null);

        try {
            // Call BpmnFlowService method:
            bpmnFlowService.terminateProcess(businessKey,
                    ProcessDefinitionEnum.OOEXIT_FOR_TRANSIT_MAIN_PROCESS.getProcessDefinitionKey(), reason);
            // Verifications:
            verify(runtimeService, times(1)).createProcessInstanceQuery();
            verify(processInstanceQuery).singleResult();
            verify(runtimeService, times(0))
                    .deleteProcessInstanceIfExists(anyString(), eq(reason), eq(false), eq(false), eq(false), eq(false));
        } catch (Exception e) {

            fail();
        }
    }
}
