package jnpf.flowable.util;
|
|
import jnpf.exception.WorkFlowException;
|
import jnpf.base.ActionResult;
|
import jnpf.flowable.entity.OperatorEntity;
|
import jnpf.flowable.entity.TemplateNodeEntity;
|
import jnpf.flowable.enums.EventEnum;
|
import jnpf.flowable.enums.NodeEnum;
|
import jnpf.flowable.enums.OperatorStateEnum;
|
import jnpf.flowable.model.flowable.FlowAbleUrl;
|
import jnpf.flowable.model.flowable.NextOrPrevFo;
|
import jnpf.flowable.model.task.FlowModel;
|
import jnpf.flowable.model.templatenode.nodejson.NodeModel;
|
import jnpf.flowable.model.util.FlowContextHolder;
|
import jnpf.flowable.model.util.FlowEventHolder;
|
import jnpf.util.JsonUtil;
|
import org.junit.After;
|
import org.junit.Before;
|
import org.junit.Test;
|
import org.junit.runner.RunWith;
|
import org.mockito.InjectMocks;
|
import org.mockito.Mock;
|
import org.mockito.junit.MockitoJUnitRunner;
|
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.stream.Collectors;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertTrue;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
import static org.mockito.Mockito.when;
|
|
@RunWith(MockitoJUnitRunner.class)
|
public class BackEventsTest {
|
|
@Mock private FlowAbleUrl engine;
|
@Mock private FlowUtil flowUtil;
|
@InjectMocks private OperatorUtil operatorUtil;
|
|
private FlowModel model;
|
private List<OperatorEntity> operators;
|
|
@Before
|
public void setUp() {
|
FlowEventHolder.clear();
|
FlowContextHolder.clearAll();
|
model = new FlowModel();
|
model.setDeploymentId("deployment");
|
model.getTaskEntity().setId("task");
|
operators = new ArrayList<>();
|
for (String code : Arrays.asList("A", "B", "C", "D")) {
|
addNode(code, NodeEnum.APPROVER, OperatorStateEnum.RUNING);
|
}
|
model.setNodeEntity(model.getNodeEntityList().get(3));
|
}
|
|
@After
|
public void cleanUp() {
|
FlowEventHolder.clear();
|
FlowContextHolder.clearAll();
|
}
|
|
@Test
|
public void sendsBackInReverseOrderExcludingTarget() throws Exception {
|
graph(Map.of("D", List.of("C"), "C", List.of("B"), "B", List.of("A")));
|
assertEquals(List.of("D", "C", "B"), resolve(List.of("A"), List.of("B", "C", "D")));
|
}
|
|
@Test
|
public void disabledNodeDoesNotPreventEarlierConfiguredEvent() throws Exception {
|
model.getNodes().get("C").getBackFuncConfig().setOn(false);
|
graph(Map.of("D", List.of("C"), "C", List.of("B"), "B", List.of("A")));
|
assertEquals(List.of("D", "B"), resolve(List.of("A"), List.of("B", "C", "D")));
|
}
|
|
@Test
|
public void excludesInvalidUnvisitedFutureAndUnrelatedBranchNodes() throws Exception {
|
operators.get(1).setStatus(OperatorStateEnum.FUTILITY.getCode());
|
addNode("X", NodeEnum.APPROVER, OperatorStateEnum.RUNING);
|
addNode("future", NodeEnum.APPROVER, OperatorStateEnum.RUNING);
|
addNode("unvisited", NodeEnum.APPROVER, OperatorStateEnum.WAITING);
|
graph(Map.of("D", List.of("C", "unvisited"), "C", List.of("B"), "B", List.of("A"),
|
"unvisited", List.of("A")));
|
assertEquals(List.of("D", "C"), resolve(List.of("A"), List.of("B", "C", "D", "X", "future", "unvisited")));
|
}
|
|
@Test
|
public void processingNodesParticipateAndDuplicateOperatorsRunOnce() throws Exception {
|
model.getNodes().get("C").setType(NodeEnum.PROCESSING.getType());
|
operators.add(operators.get(2));
|
graph(Map.of("D", List.of("C"), "C", List.of("B"), "B", List.of("A")));
|
assertEquals(List.of("D", "C", "B"), resolve(List.of("A"), List.of("B", "C", "D")));
|
}
|
|
@Test
|
public void mergeWaitsForBothBranchesBeforeSharedPredecessor() throws Exception {
|
addNode("X", NodeEnum.PROCESSING, OperatorStateEnum.RUNING);
|
graph(Map.of("D", List.of("C", "X"), "C", List.of("B"), "X", List.of("B"), "B", List.of("A")));
|
List<String> events = resolve(List.of("A"), List.of("B", "C", "X", "D"));
|
assertEquals("D", events.get(0));
|
assertEquals(4, events.size());
|
assertTrue(events.indexOf("C") < events.indexOf("B"));
|
assertTrue(events.indexOf("X") < events.indexOf("B"));
|
}
|
|
@Test
|
public void stopsAtEverySelectedTarget() throws Exception {
|
graph(Map.of("D", List.of("C", "B"), "C", List.of("A")));
|
assertEquals(List.of("D", "C"), resolve(List.of("A", "B"), List.of("B", "C", "D")));
|
}
|
|
@Test
|
public void noConfiguredIntermediateEventPreservesCurrentOnlyWithoutEngineCalls() throws Exception {
|
model.getNodes().get("B").getBackFuncConfig().setInterfaceId("");
|
model.getNodes().get("C").getBackFuncConfig().setOn(false);
|
assertEquals(List.of("D"), resolve(List.of("A"), List.of("B", "C", "D")));
|
verifyNoInteractions(engine);
|
}
|
|
@Test
|
public void cycleTerminatesAndEachNodeRunsOnce() throws Exception {
|
graph(Map.of("D", List.of("C"), "C", List.of("B", "D"), "B", List.of("A")));
|
assertEquals(List.of("D", "C", "B"), resolve(List.of("A"), List.of("B", "C", "D")));
|
}
|
|
@Test
|
public void queueDispatchesEachNodesOwnConfigurationAndFormInOrder() throws Exception {
|
ServiceUtil service = org.mockito.Mockito.mock(ServiceUtil.class);
|
FlowUtil eventUtil = new FlowUtil(null, null, service, null, null, null, null, null, null,
|
null, null, null, null, null, null, null, null);
|
List<String> calls = new ArrayList<>();
|
doAnswer(invocation -> {
|
FlowModel event = invocation.getArgument(0);
|
eventUtil.event(event, invocation.getArgument(1), invocation.getArgument(2));
|
return null;
|
}).when(flowUtil).event(any(FlowModel.class), any(Integer.class), any(Map.class));
|
doAnswer(invocation -> {
|
calls.add(invocation.getArgument(0));
|
ActionResult<Object> result = new ActionResult<>();
|
result.setCode(200);
|
return result;
|
}).when(service).infoToId(any(), any());
|
// 校验真实事件处理器按各节点表单取数,而不是复用 D 的表单。
|
List<String> forms = new ArrayList<>();
|
when(service.infoData(any(), any())).thenAnswer(invocation -> {
|
forms.add(invocation.getArgument(0));
|
assertEquals("task", invocation.getArgument(1));
|
return Map.of("node", invocation.getArgument(0));
|
});
|
TemplateNodeEntity original = model.getNodeEntity();
|
operatorUtil.addBackEvents(model, List.of("D", "C", "B"));
|
assertEquals(List.of("D", "C", "B"), FlowEventHolder.getAllEvent().stream()
|
.map(e -> e.getFlowModel().getNodeEntity().getNodeCode()).collect(Collectors.toList()));
|
assertTrue(FlowEventHolder.getAllEvent().stream().allMatch(e -> EventEnum.BACK.getStatus().equals(e.getStatus())));
|
assertSame(original, model.getNodeEntity());
|
assertEquals(Collections.emptyList(), calls);
|
operatorUtil.handleEvent();
|
assertEquals(List.of("back-D", "back-C", "back-B"), calls);
|
assertEquals(List.of("form-D", "form-C", "form-B"), forms);
|
assertTrue(FlowEventHolder.getAllEvent().isEmpty());
|
}
|
|
private void graph(Map<String, List<String>> previous) throws WorkFlowException {
|
when(engine.getPrev(any(NextOrPrevFo.class))).thenAnswer(invocation -> {
|
NextOrPrevFo fo = invocation.getArgument(0);
|
assertEquals("deployment", fo.getDeploymentId());
|
return previous.getOrDefault(fo.getTaskKey(), Collections.emptyList());
|
});
|
}
|
|
private List<String> resolve(List<String> targets, List<String> after) throws WorkFlowException {
|
return operatorUtil.getBackEventNodeCodes(model, "D", targets, after, operators);
|
}
|
|
private void addNode(String code, NodeEnum type, OperatorStateEnum state) {
|
NodeModel node = new NodeModel();
|
node.setNodeId(code);
|
node.setType(type.getType());
|
node.getBackFuncConfig().setOn(true);
|
node.getBackFuncConfig().setInterfaceId("back-" + code);
|
model.getNodes().put(code, node);
|
TemplateNodeEntity entity = new TemplateNodeEntity();
|
entity.setNodeCode(code);
|
entity.setNodeType(type.getType());
|
entity.setFormId("form-" + code);
|
entity.setNodeJson(JsonUtil.getObjectToString(node));
|
model.getNodeEntityList().add(entity);
|
OperatorEntity operator = new OperatorEntity();
|
operator.setNodeCode(code);
|
operator.setStatus(state.getCode());
|
operators.add(operator);
|
}
|
}
|