From 4e9a57651b521c96a76e3d08aa1ed93afb8d21b2 Mon Sep 17 00:00:00 2001
From: wsm <8454518+new-twice@user.noreply.gitee.com>
Date: Tue, 18 Aug 2026 08:04:26 +0000
Subject: [PATCH] =?UTF-8?q?!133=20fix(supervision):=20=E4=BF=AE=E6=AD=A3?=
=?UTF-8?q?=20InspectCloseoutFlowTest=20=E8=A7=92=E8=89=B2=E5=8F=82?=
=?UTF-8?q?=E6=95=B0=E5=8C=B9=E9=85=8D=E5=AE=9E=E9=99=85=E8=B0=83=E7=94=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix(supervision): 修正 InspectCloseoutFlowTest 角色参数匹配实际调用
* test(fixcontact): 新增流程表达式/监听器/Service 单测,fixcontact 可测代码接近全覆盖
* refactor(fixcontact): 提炼上两级解析与部门ID清洗纯函数,监听器依赖注入化
* chore(bpm): 安装 JaCoCo 覆盖率插件并修复构建链(补齐 base-core 依赖、排除环境依赖测试)
---
.../jeecg-boot-module-airag/pom.xml | 21 +
.../jeecg-boot-module-joa-flowable/pom.xml | 5 +
jeecg-module-supervision/pom.xml | 16 +
.../fixcontact/flow/FixContactFlow.java | 24 +-
.../flow/FixContactFlowSupport.java | 43 ++
.../listener/FixContactApprovalHelper.java | 32 +
.../FixContactDeptLeaderApproveListener.java | 15 +-
.../FixContactWorkerApproveListener.java | 3 +-
.../InspectCloseoutFlowTest.java | 6 +-
.../flow/FixContactFlowSupportTest.java | 103 +++
.../fixcontact/flow/FixContactFlowTest.java | 603 ++++++++++++++++++
.../FixContactApprovalHelperTest.java | 58 ++
...xContactDeptLeaderApproveListenerTest.java | 134 ++++
.../FixContactWorkerApproveListenerTest.java | 179 ++++++
.../FixedContact20260730ServiceImplTest.java | 178 ++++++
.../jeecg-system-cloud-api/pom.xml | 5 +
pom.xml | 23 +-
17 files changed, 1414 insertions(+), 34 deletions(-)
create mode 100644 jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowSupport.java
create mode 100644 jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactApprovalHelper.java
create mode 100644 jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowSupportTest.java
create mode 100644 jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowTest.java
create mode 100644 jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactApprovalHelperTest.java
create mode 100644 jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactDeptLeaderApproveListenerTest.java
create mode 100644 jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactWorkerApproveListenerTest.java
create mode 100644 jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/service/impl/FixedContact20260730ServiceImplTest.java
diff --git a/jeecg-boot-module/jeecg-boot-module-airag/pom.xml b/jeecg-boot-module/jeecg-boot-module-airag/pom.xml
index 9e5879d..e0bd813 100644
--- a/jeecg-boot-module/jeecg-boot-module-airag/pom.xml
+++ b/jeecg-boot-module/jeecg-boot-module-airag/pom.xml
@@ -31,6 +31,11 @@
+
+
+ org.jeecgframework.boot
+ jeecg-boot-base-core
+
org.jeecgframework.boot
@@ -189,4 +194,20 @@
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/airag/test/**
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jeecg-boot-module/jeecg-boot-module-joa-flowable/pom.xml b/jeecg-boot-module/jeecg-boot-module-joa-flowable/pom.xml
index 986fe5e..5da9799 100644
--- a/jeecg-boot-module/jeecg-boot-module-joa-flowable/pom.xml
+++ b/jeecg-boot-module/jeecg-boot-module-joa-flowable/pom.xml
@@ -57,6 +57,11 @@
+
+
+ org.jeecgframework.boot
+ jeecg-boot-base-core
+
org.jeecgframework.boot
diff --git a/jeecg-module-supervision/pom.xml b/jeecg-module-supervision/pom.xml
index e4a929f..9eea99c 100644
--- a/jeecg-module-supervision/pom.xml
+++ b/jeecg-module-supervision/pom.xml
@@ -30,5 +30,21 @@
jeecg-system-biz
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/flowregression/**
+
+
+
+
+
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlow.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlow.java
index 97e43b9..29147af 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlow.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlow.java
@@ -258,7 +258,7 @@ public class FixContactFlow {
execution.getProcessInstanceId());
return null;
}
- return deptVar.toString().replace("[", "").replace("]", "").trim();
+ return FixContactFlowSupport.cleanDeptId(deptVar);
}
// ${fixContactFlow.getCurrentDeptLeaderList(execution, deptId)}
@@ -357,14 +357,7 @@ public class FixContactFlow {
if (value == null) {
return Collections.emptyList();
}
- String str = value.toString();
- if (StringUtils.isBlank(str)) {
- return Collections.emptyList();
- }
- return Arrays.stream(str.split(","))
- .map(String::trim)
- .filter(StringUtils::isNotBlank)
- .collect(Collectors.toList());
+ return FixContactFlowSupport.splitToTrimmedList(value.toString());
}
// ${fixContactFlow.getVarAsListCount(execution, varName)}
@@ -374,13 +367,7 @@ public class FixContactFlow {
// ${fixContactFlow.getListSize(data)}
public int getListSize(String data) {
- if (StringUtils.isEmpty(data)) {
- return 0;
- }
- return (int) Arrays.stream(data.split(","))
- .map(String::trim)
- .filter(StringUtils::isNotEmpty)
- .count();
+ return FixContactFlowSupport.countTokens(data);
}
// ${fixContactFlow.getIsNeedLeaderApprove(execution)}
@@ -619,10 +606,7 @@ public class FixContactFlow {
if (StringUtils.isBlank(rawValue)) {
return Collections.emptyList();
}
- List result = Arrays.stream(rawValue.split(","))
- .map(String::trim)
- .filter(StringUtils::isNotBlank)
- .collect(Collectors.toList());
+ List result = FixContactFlowSupport.splitToTrimmedList(rawValue);
log.info("【定点联系-流程表达式】{} 解析结果: {}", logLabel, result);
return result;
} catch (Exception e) {
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowSupport.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowSupport.java
new file mode 100644
index 0000000..f2095c4
--- /dev/null
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowSupport.java
@@ -0,0 +1,43 @@
+package org.jeecg.modules.supervision.fixcontact.flow;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 定点联系流程表达式纯逻辑支撑(无外部依赖,可独立测试)。
+ *
+ * 规则(提炼自 FixContactFlow 内联重复逻辑):
+ * - splitToTrimmedList:逗号分割 → trim → 过滤空白元素
+ * - countTokens:分割后有效元素个数
+ * - cleanDeptId:流程变量部门 ID 清洗(去 [] 与首尾空白)
+ */
+public final class FixContactFlowSupport {
+
+ private FixContactFlowSupport() {
+ }
+
+ public static List splitToTrimmedList(String raw) {
+ if (StringUtils.isBlank(raw)) {
+ return Collections.emptyList();
+ }
+ return Arrays.stream(raw.split(","))
+ .map(String::trim)
+ .filter(StringUtils::isNotBlank)
+ .collect(Collectors.toList());
+ }
+
+ public static int countTokens(String raw) {
+ return splitToTrimmedList(raw).size();
+ }
+
+ public static String cleanDeptId(Object raw) {
+ if (raw == null) {
+ return null;
+ }
+ return raw.toString().replace("[", "").replace("]", "").trim();
+ }
+}
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactApprovalHelper.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactApprovalHelper.java
new file mode 100644
index 0000000..972741c
--- /dev/null
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactApprovalHelper.java
@@ -0,0 +1,32 @@
+package org.jeecg.modules.supervision.fixcontact.listener;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 定点联系流程审批辅助逻辑(纯函数,无外部依赖,可独立测试)。
+ *
+ * 规则:部门领导审批后,领导变量写入"上两级"执行实例;
+ * 存在第二层父级存第二层(大会签容器/外层作用域),否则存第一层。
+ */
+public final class FixContactApprovalHelper {
+
+ private FixContactApprovalHelper() {
+ }
+
+ /**
+ * 解析领导变量应写入的目标执行实例 ID。
+ *
+ * @param parentId 第一层父级执行实例 ID
+ * @param grandParentId 第二层父级执行实例 ID(可能为空)
+ * @return 有第二层返回 grandParentId;否则返回 parentId;parentId 为空返回 null(调用方跳过)
+ */
+ public static String resolveTargetExecutionId(String parentId, String grandParentId) {
+ if (StringUtils.isBlank(parentId)) {
+ return null;
+ }
+ if (StringUtils.isNotBlank(grandParentId)) {
+ return grandParentId;
+ }
+ return parentId;
+ }
+}
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactDeptLeaderApproveListener.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactDeptLeaderApproveListener.java
index 66a8fe9..4b3499d 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactDeptLeaderApproveListener.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactDeptLeaderApproveListener.java
@@ -1,13 +1,14 @@
package org.jeecg.modules.supervision.fixcontact.listener;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.engine.runtime.Execution;
import org.flowable.task.service.delegate.DelegateTask;
-import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
@@ -16,16 +17,13 @@ import org.springframework.stereotype.Component;
*/
@Slf4j
@Component("FixContactDeptLeaderApproveListener")
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class FixContactDeptLeaderApproveListener implements TaskListener {
private static final long serialVersionUID = 1L;
private static final String VARIABLE_NAME = FixContactConstant.FlowVarName.TEM_IMPL_LEADER;
- private static RuntimeService runtimeService;
-
- static {
- runtimeService = SpringContextUtils.getBean(RuntimeService.class);
- }
+ private final RuntimeService runtimeService;
@Override
public void notify(DelegateTask delegateTask) {
@@ -64,9 +62,8 @@ public class FixContactDeptLeaderApproveListener implements TaskListener {
if (parentExecution == null) {
return;
}
- String grandParentId = parentExecution.getParentId();
- // 有第二层父级则存入第二层(大会签容器/外层作用域),否则存入第一层
- String targetId = StringUtils.isNotBlank(grandParentId) ? grandParentId : parentId;
+ // 有第二层父级则存入第二层(大会签容器/外层作用域),否则存入第一层(规则见 FixContactApprovalHelper)
+ String targetId = FixContactApprovalHelper.resolveTargetExecutionId(parentId, parentExecution.getParentId());
try {
runtimeService.setVariableLocal(targetId, VARIABLE_NAME, assignee);
log.info("【定点联系-部门领导审批监听器】已存入上两级局部变量, targetExecutionId: {}, variableName: {}, assignee: {}",
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactWorkerApproveListener.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactWorkerApproveListener.java
index c0a87c1..fca6b51 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactWorkerApproveListener.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactWorkerApproveListener.java
@@ -8,6 +8,7 @@ import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
import org.jeecg.modules.supervision.fixcontact.entity.FixedContact20260730;
+import org.jeecg.modules.supervision.fixcontact.flow.FixContactFlowSupport;
import org.jeecg.modules.supervision.fixcontact.service.IFixedContact20260730Service;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
@@ -74,7 +75,7 @@ public class FixContactWorkerApproveListener implements TaskListener {
log.warn("【定点联系-经办人会签监听器】未获取到子流程当前部门ID,跳过记录经办人, taskId: {}", delegateTask.getId());
return;
}
- String deptId = rawDeptVar.toString().replace("[", "").replace("]", "").trim();
+ String deptId = FixContactFlowSupport.cleanDeptId(rawDeptVar);
FixedContact20260730 fixedContact = fixedContact20260730Service.getByIdForUpdate(rawBusinessKey.toString());
if (fixedContact == null) {
diff --git a/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/dqinspecttask/InspectCloseoutFlowTest.java b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/dqinspecttask/InspectCloseoutFlowTest.java
index 6362670..1f173b2 100644
--- a/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/dqinspecttask/InspectCloseoutFlowTest.java
+++ b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/dqinspecttask/InspectCloseoutFlowTest.java
@@ -124,7 +124,7 @@ class InspectCloseoutFlowTest {
@Test
void getDqDeptLdUserIdList_shouldReturnUserList() {
List users = List.of("dq_user1");
- when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-jj-worker"))
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-ld"))
.thenReturn(users);
assertEquals(users, flow.getDqDeptLdUserIdList());
@@ -132,7 +132,7 @@ class InspectCloseoutFlowTest {
@Test
void getDqDeptLdUserIdListLength_shouldReturnCount() {
- when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-jj-worker"))
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-ld"))
.thenReturn(List.of("user1", "user2"));
assertEquals(2, flow.getDqDeptLdUserIdListLength());
@@ -140,7 +140,7 @@ class InspectCloseoutFlowTest {
@Test
void getDqDeptLdUserIdListLength_shouldReturnZeroWhenEmpty() {
- when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-jj-worker"))
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-ld"))
.thenReturn(Collections.emptyList());
assertEquals(0, flow.getDqDeptLdUserIdListLength());
diff --git a/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowSupportTest.java b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowSupportTest.java
new file mode 100644
index 0000000..8dd6426
--- /dev/null
+++ b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowSupportTest.java
@@ -0,0 +1,103 @@
+package org.jeecg.modules.supervision.fixcontact.flow;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * FixContactFlowSupport 纯函数规则测试(TDD 红-绿-重构,阶段 A 示范)。
+ *
+ * 业务规则(来自 FixContactFlow 内联逻辑,3 处重复的逗号分割 + 部门 ID 清洗):
+ * - splitToTrimmedList:逗号分割 → 去首尾空白 → 过滤空白元素(null/空 → 空列表)
+ * - countTokens:分割后元素个数(等价于 splitToTrimmedList().size())
+ * - cleanDeptId:执行实例部门变量清洗(去掉 [] 与首尾空白,与 XiSpeak 监听器同款规则)
+ */
+class FixContactFlowSupportTest {
+
+ // ============ splitToTrimmedList:正常路径 ============
+
+ @Test
+ void should_split_multi_tokens() {
+ assertEquals(List.of("a", "b", "c"), FixContactFlowSupport.splitToTrimmedList("a,b,c"));
+ }
+
+ @Test
+ void should_trim_tokens_with_spaces() {
+ // 逗号前后有空格:逐 token trim
+ assertEquals(List.of("a", "b"), FixContactFlowSupport.splitToTrimmedList(" a , b "));
+ }
+
+ @Test
+ void should_return_single_token() {
+ assertEquals(List.of("a"), FixContactFlowSupport.splitToTrimmedList("a"));
+ }
+
+ // ============ splitToTrimmedList:边界路径 ============
+
+ @Test
+ void should_filter_empty_tokens() {
+ // 空元素(连续逗号/全空白元素)被过滤
+ assertEquals(List.of("a", "b"), FixContactFlowSupport.splitToTrimmedList("a,,b"));
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ @ValueSource(strings = {" ", " ", " , , "})
+ void should_return_empty_list_when_no_valid_token(String raw) {
+ assertEquals(List.of(), FixContactFlowSupport.splitToTrimmedList(raw));
+ }
+
+ // ============ countTokens:正常 + 边界 ============
+
+ @Test
+ void should_count_tokens() {
+ assertEquals(3, FixContactFlowSupport.countTokens("a,b,c"));
+ }
+
+ @Test
+ void should_count_trimmed_tokens() {
+ assertEquals(2, FixContactFlowSupport.countTokens(" a , b "));
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ @ValueSource(strings = {" ", " , , "})
+ void should_return_zero_when_no_valid_token(String raw) {
+ assertEquals(0, FixContactFlowSupport.countTokens(raw));
+ }
+
+ // ============ cleanDeptId:正常 + 边界 ============
+
+ @Test
+ void should_remove_square_brackets() {
+ assertEquals("dept-1", FixContactFlowSupport.cleanDeptId("[dept-1]"));
+ }
+
+ @Test
+ void should_remove_brackets_and_trim() {
+ // 多值列表变量场景:整体去括号 + trim
+ assertEquals("dept-1, dept-2", FixContactFlowSupport.cleanDeptId("[dept-1, dept-2]"));
+ }
+
+ @Test
+ void should_keep_plain_id_unchanged() {
+ assertEquals("dept-1", FixContactFlowSupport.cleanDeptId("dept-1"));
+ }
+
+ @Test
+ void should_handle_non_string_object() {
+ // 流程变量可能是任意对象,toString 后处理
+ assertEquals("123", FixContactFlowSupport.cleanDeptId(123L));
+ }
+
+ @Test
+ void should_return_null_when_raw_null() {
+ assertNull(FixContactFlowSupport.cleanDeptId(null));
+ }
+}
diff --git a/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowTest.java b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowTest.java
new file mode 100644
index 0000000..9d3ea62
--- /dev/null
+++ b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/flow/FixContactFlowTest.java
@@ -0,0 +1,603 @@
+package org.jeecg.modules.supervision.fixcontact.flow;
+
+import com.alibaba.fastjson.JSONObject;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.jeecg.common.system.api.ISysBaseAPI;
+import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
+import org.jeecg.modules.supervision.fixcontact.entity.FixedContact20260730;
+import org.jeecg.modules.supervision.fixcontact.entity.FixedContactFeedback20260730;
+import org.jeecg.modules.supervision.fixcontact.service.IFixedContact20260730Service;
+import org.jeecg.modules.supervision.fixcontact.service.IFixedContactFeedback20260730Service;
+import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
+import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+/**
+ * FixContactFlow 流程表达式方法测试(mock 外部依赖:service/iSysBaseAPI/runtimeService)。
+ * 覆盖:业务字段读取、部门角色查询、办结状态、当前部门、反馈、流程变量、JSON 解析、待办人汇总。
+ */
+@ExtendWith(MockitoExtension.class)
+class FixContactFlowTest {
+
+ @Mock private IFixedContact20260730Service fixedContactService;
+ @Mock private IFixedContactFeedback20260730Service feedbackService;
+ @Mock private ISysBaseAPI iSysBaseAPI;
+ @Mock private RuntimeService runtimeService;
+ @Mock private DelegateExecution execution;
+
+ private FixContactFlow flow;
+
+ @BeforeEach
+ void setUp() {
+ flow = new FixContactFlow(fixedContactService, feedbackService, null, iSysBaseAPI, runtimeService);
+ }
+
+ /** stub getFixedContact 读取业务表单的公共链路 */
+ private void mockEntity(FixedContact20260730 entity) {
+ when(execution.getVariable(FixContactConstant.FlowVarName.BUSINESS_KEY)).thenReturn("biz-1");
+ when(fixedContactService.getById("biz-1")).thenReturn(entity);
+ }
+
+ // ==================== 业务字段读取 ====================
+
+ @Nested
+ class BusinessFieldRead {
+
+ private FixedContact20260730 fullEntity() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setId("biz-1");
+ e.setContactLeader("sld_01");
+ e.setRelatedLeader("sz");
+ e.setApplicant("admin");
+ e.setHostDept("dept-host");
+ e.setCoDept("dept-co");
+ e.setIsNeedAppro("1");
+ e.setSupDeptleaderid("bg_ld_01");
+ e.setContactDept("外单位");
+ return e;
+ }
+
+ @Test
+ void should_return_contactLeader_when_entity_exists() {
+ mockEntity(fullEntity());
+ assertEquals("sld_01", flow.getContactLeader(execution));
+ }
+
+ @Test
+ void should_return_null_when_entity_not_found() {
+ mockEntity(null);
+ assertNull(flow.getContactLeader(execution));
+ }
+
+ @Test
+ void should_return_relatedLeader() {
+ mockEntity(fullEntity());
+ assertEquals("sz", flow.getRelatedLeader(execution));
+ }
+
+ @Test
+ void should_return_applicant() {
+ mockEntity(fullEntity());
+ assertEquals("admin", flow.getApplicant(execution));
+ }
+
+ @Test
+ void should_return_hostDept() {
+ mockEntity(fullEntity());
+ assertEquals("dept-host", flow.getHostDept(execution));
+ }
+
+ @Test
+ void should_return_coDept() {
+ mockEntity(fullEntity());
+ assertEquals("dept-co", flow.getCoDept(execution));
+ }
+
+ @Test
+ void should_return_isNeedAppro() {
+ mockEntity(fullEntity());
+ assertEquals("1", flow.getIsNeedBgLdApprove(execution));
+ }
+
+ @Test
+ void should_return_supDeptLeaderId() {
+ mockEntity(fullEntity());
+ assertEquals("bg_ld_01", flow.getBgLd(execution));
+ }
+
+ @Test
+ void should_return_contactDept() {
+ mockEntity(fullEntity());
+ assertEquals("外单位", flow.getContactDept(execution));
+ }
+
+ @Test
+ void should_return_null_when_contactLeader_blank() {
+ FixedContact20260730 e = fullEntity();
+ e.setContactLeader(" ");
+ mockEntity(e);
+ assertEquals(" ", flow.getContactLeader(execution));
+ }
+ }
+
+ // ==================== 部门+角色查询 ====================
+
+ @Nested
+ class DeptRoleQuery {
+
+ @Test
+ void should_return_office_leader_list() {
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(
+ FixContactConstant.DeptId.BG_DEPT_ID, FixContactConstant.RoleCode.SLD_ROLE_ID))
+ .thenReturn(List.of("sld_01", "sld_02"));
+ assertEquals(List.of("sld_01", "sld_02"), flow.getOfficeLeaderList(execution));
+ }
+
+ @Test
+ void should_return_office_leader_list_length() {
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(
+ FixContactConstant.DeptId.BG_DEPT_ID, FixContactConstant.RoleCode.SLD_ROLE_ID))
+ .thenReturn(List.of("sld_01", "sld_02"));
+ assertEquals(2, flow.getOfficeLeaderListLength(execution));
+ }
+
+ @Test
+ void should_return_users_by_dept_and_role() {
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-100", "ld"))
+ .thenReturn(List.of("u1", "u2"));
+ assertEquals(List.of("u1", "u2"), flow.getUsersByDeptAndRole("dept-100", "ld"));
+ }
+
+ @Test
+ void should_return_count_zero_when_no_users() {
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-100", "ld"))
+ .thenReturn(List.of());
+ assertEquals(0, flow.getUsersByDeptAndRoleCount("dept-100", "ld"));
+ }
+
+ @Test
+ void should_return_host_dept_leader_list() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setId("biz-1");
+ e.setHostDept("dept-host");
+ mockEntity(e);
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-host", "ld"))
+ .thenReturn(List.of("leader1", "leader2"));
+ assertEquals(List.of("leader1", "leader2"), flow.getHostDeptLeaderList(execution));
+ }
+
+ @Test
+ void should_return_empty_when_host_dept_has_no_leader() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setId("biz-1");
+ e.setHostDept("dept-host");
+ mockEntity(e);
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-host", "ld"))
+ .thenReturn(List.of());
+ assertEquals(List.of(), flow.getHostDeptLeaderList(execution));
+ }
+
+ @Test
+ void should_throw_when_host_dept_blank() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setId("biz-1");
+ mockEntity(e);
+ assertThrows(IllegalStateException.class, () -> flow.getHostDeptLeaderList(execution));
+ }
+
+ @Test
+ void should_throw_when_entity_not_found_for_host_leader() {
+ mockEntity(null);
+ assertThrows(IllegalStateException.class, () -> flow.getHostDeptLeaderList(execution));
+ }
+ }
+
+ // ==================== 办结状态 ====================
+
+ @Nested
+ class IsEndRead {
+
+ @Test
+ void should_return_null_when_dept_id_blank() {
+ // deptId 空白直接短路返回,不触达业务表查询
+ assertNull(flow.getIsEnd(execution, " "));
+ }
+
+ @Test
+ void should_return_null_when_entity_not_found() {
+ mockEntity(null);
+ assertNull(flow.getIsEnd(execution, "dept-1"));
+ }
+
+ @Test
+ void should_return_null_when_approve_info_empty() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setApproveInfo(new DeptApproveDetailMap());
+ mockEntity(e);
+ assertNull(flow.getIsEnd(execution, "dept-1"));
+ }
+
+ @Test
+ void should_return_isEnd_one_when_finished() {
+ DeptApproveDetail detail = new DeptApproveDetail();
+ detail.setIsEnd(1);
+ DeptApproveDetailMap map = new DeptApproveDetailMap();
+ map.put("dept-1", detail);
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setApproveInfo(map);
+ mockEntity(e);
+ assertEquals("1", flow.getIsEnd(execution, "dept-1"));
+ }
+
+ @Test
+ void should_return_isEnd_zero_when_unfinished() {
+ DeptApproveDetail detail = new DeptApproveDetail();
+ detail.setIsEnd(0);
+ DeptApproveDetailMap map = new DeptApproveDetailMap();
+ map.put("dept-1", detail);
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setApproveInfo(map);
+ mockEntity(e);
+ assertEquals("0", flow.getIsEnd(execution, "dept-1"));
+ }
+
+ @Test
+ void should_return_null_when_detail_missing() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setApproveInfo(new DeptApproveDetailMap());
+ mockEntity(e);
+ assertNull(flow.getIsEnd(execution, "no-such-dept"));
+ }
+ }
+
+ // ==================== 当前部门 ====================
+
+ @Nested
+ class CurrentDept {
+
+ @Test
+ void should_return_null_when_execution_null() {
+ assertNull(flow.getCurrentDeptId(null));
+ }
+
+ @Test
+ void should_clean_local_dept_var() {
+ when(execution.getVariableLocal(FixContactConstant.FlowVarName.IMPL_DEPT_ID)).thenReturn("[dept-1]");
+ assertEquals("dept-1", flow.getCurrentDeptId(execution));
+ }
+
+ @Test
+ void should_fallback_to_global_var() {
+ when(execution.getVariableLocal(FixContactConstant.FlowVarName.IMPL_DEPT_ID)).thenReturn(null);
+ when(execution.getVariable(FixContactConstant.FlowVarName.IMPL_DEPT_ID)).thenReturn("dept-2");
+ assertEquals("dept-2", flow.getCurrentDeptId(execution));
+ }
+
+ @Test
+ void should_return_null_when_no_dept_var() {
+ when(execution.getVariableLocal(FixContactConstant.FlowVarName.IMPL_DEPT_ID)).thenReturn(null);
+ when(execution.getVariable(FixContactConstant.FlowVarName.IMPL_DEPT_ID)).thenReturn(null);
+ assertNull(flow.getCurrentDeptId(execution));
+ }
+ }
+
+ // ==================== 反馈子表 ====================
+
+ @Nested
+ class Feedback {
+
+ private FixedContactFeedback20260730 feedback(String user, String dept, String finishStatus) {
+ FixedContactFeedback20260730 f = new FixedContactFeedback20260730();
+ f.setFeedbackUser(user);
+ f.setFeedbackDept(dept);
+ f.setFinishStatus(finishStatus);
+ return f;
+ }
+
+ private void mockEntityWithId() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setId("biz-1");
+ mockEntity(e);
+ }
+
+ @Test
+ void should_return_empty_when_entity_not_found() {
+ mockEntity(null);
+ assertEquals(List.of(), flow.getFeedbackList(execution));
+ }
+
+ @Test
+ void should_return_feedback_list() {
+ mockEntityWithId();
+ when(feedbackService.selectByMainId("biz-1")).thenReturn(List.of(feedback("u1", "d1", "1")));
+ assertEquals(1, flow.getFeedbackList(execution).size());
+ }
+
+ @Test
+ void should_return_null_latest_user_when_no_feedback() {
+ mockEntityWithId();
+ when(feedbackService.selectByMainId("biz-1")).thenReturn(List.of());
+ assertNull(flow.getLatestFeedbackUser(execution));
+ }
+
+ @Test
+ void should_return_latest_feedback_user() {
+ mockEntityWithId();
+ when(feedbackService.selectByMainId("biz-1"))
+ .thenReturn(List.of(feedback("u1", "d1", "1"), feedback("u2", "d2", "1")));
+ assertEquals("u2", flow.getLatestFeedbackUser(execution));
+ }
+
+ @Test
+ void should_return_latest_feedback_dept() {
+ mockEntityWithId();
+ when(feedbackService.selectByMainId("biz-1"))
+ .thenReturn(List.of(feedback("u1", "d1", "1"), feedback("u2", "d2", "1")));
+ assertEquals("d2", flow.getLatestFeedbackDept(execution));
+ }
+
+ @Test
+ void should_return_false_when_no_feedback() {
+ mockEntityWithId();
+ when(feedbackService.selectByMainId("biz-1")).thenReturn(List.of());
+ assertFalse(flow.isAllFeedbackFinished(execution));
+ }
+
+ @Test
+ void should_return_true_when_all_finished() {
+ mockEntityWithId();
+ when(feedbackService.selectByMainId("biz-1"))
+ .thenReturn(List.of(feedback("u1", "d1", "1"), feedback("u2", "d2", "1")));
+ assertTrue(flow.isAllFeedbackFinished(execution));
+ }
+
+ @Test
+ void should_return_false_when_any_feedback_unfinished() {
+ mockEntityWithId();
+ when(feedbackService.selectByMainId("biz-1"))
+ .thenReturn(List.of(feedback("u1", "d1", "1"), feedback("u2", "d2", " ")));
+ assertFalse(flow.isAllFeedbackFinished(execution));
+ }
+ }
+
+ // ==================== 流程变量 ====================
+
+ @Nested
+ class ProcessVariable {
+
+ @Test
+ void should_return_var_value() {
+ when(execution.getProcessInstanceId()).thenReturn("proc-1");
+ when(runtimeService.getVariable("proc-1", "name")).thenReturn("val");
+ assertEquals("val", flow.getVar(execution, "name"));
+ org.mockito.Mockito.verify(execution).getProcessInstanceId();
+ }
+
+ @Test
+ void should_return_null_when_var_absent() {
+ when(execution.getProcessInstanceId()).thenReturn("proc-1");
+ when(runtimeService.getVariable("proc-1", "name")).thenReturn(null);
+ assertNull(flow.getVar(execution, "name"));
+ }
+
+ @Test
+ void should_default_zero_when_need_leader_approve_blank() {
+ when(execution.getProcessInstanceId()).thenReturn("proc-1");
+ when(runtimeService.getVariable("proc-1", FixContactConstant.FlowVarName.IS_NEED_LEADER_APPROVE))
+ .thenReturn(null);
+ assertEquals("0", flow.getIsNeedLeaderApprove(execution));
+ }
+
+ @Test
+ void should_return_need_leader_approve_value() {
+ when(execution.getProcessInstanceId()).thenReturn("proc-1");
+ when(runtimeService.getVariable("proc-1", FixContactConstant.FlowVarName.IS_NEED_LEADER_APPROVE))
+ .thenReturn("1");
+ assertEquals("1", flow.getIsNeedLeaderApprove(execution));
+ }
+
+ @Test
+ void should_split_var_as_list() {
+ when(execution.getProcessInstanceId()).thenReturn("proc-1");
+ when(runtimeService.getVariable("proc-1", FixContactConstant.FlowVarName.LEADER_APPROVE_USER))
+ .thenReturn("sld_01, sld_02");
+ assertEquals(List.of("sld_01", "sld_02"), flow.getLeaderApproveUserList(execution));
+ }
+
+ @Test
+ void should_return_empty_list_when_var_null() {
+ when(execution.getProcessInstanceId()).thenReturn("proc-1");
+ when(runtimeService.getVariable("proc-1", FixContactConstant.FlowVarName.LEADER_APPROVE_USER))
+ .thenReturn(null);
+ assertEquals(List.of(), flow.getLeaderApproveUserList(execution));
+ }
+ }
+
+ // ==================== JSON 解析 ====================
+
+ @Nested
+ class JsonParse {
+
+ @Test
+ void should_parse_from_json_object() {
+ JSONObject data = new JSONObject().fluentPut("contactLeader", "sld_01");
+ assertEquals("sld_01", flow.getContactLeaderFromJson(data));
+ }
+
+ @Test
+ void should_parse_from_json_string() {
+ assertEquals("sld_01", flow.getContactLeaderFromJson("{\"contactLeader\":\"sld_01\"}"));
+ }
+
+ @Test
+ void should_return_null_when_json_null() {
+ assertNull(flow.getContactLeaderFromJson(null));
+ }
+
+ @Test
+ void should_return_null_when_invalid_json() {
+ assertNull(flow.getContactLeaderFromJson("not-a-json"));
+ }
+
+ @Test
+ void should_return_host_dept_leader_list_from_json() {
+ when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-host", "ld"))
+ .thenReturn(List.of("leader1"));
+ assertEquals(List.of("leader1"), flow.getHostDeptLeaderListFromJson(
+ new JSONObject().fluentPut("hostDept", "dept-host")));
+ }
+
+ @Test
+ void should_return_empty_when_host_dept_blank_in_json() {
+ assertEquals(List.of(), flow.getHostDeptLeaderListFromJson(
+ new JSONObject().fluentPut("hostDept", " ")));
+ }
+ }
+
+ // ==================== 部门列表组装 ====================
+
+ @Nested
+ class DeptIdList {
+
+ @Test
+ void should_compose_host_and_co_dept() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setId("biz-1");
+ e.setHostDept("dept-host");
+ e.setCoDept("dept-co");
+ mockEntity(e);
+ assertEquals(List.of("dept-host", "dept-co"), flow.getDeptIdList(execution));
+ }
+
+ @Test
+ void should_compose_only_host_dept() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setId("biz-1");
+ e.setHostDept("dept-host");
+ mockEntity(e);
+ assertEquals(List.of("dept-host"), flow.getDeptIdList(execution));
+ }
+
+ @Test
+ void should_return_empty_when_entity_not_found() {
+ mockEntity(null);
+ assertEquals(List.of(), flow.getDeptIdList(execution));
+ }
+ }
+
+ // ==================== 待办人汇总 ====================
+
+ @Nested
+ class TodoUser {
+
+ private FixedContact20260730 entityWithApproveInfo() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setId("biz-1");
+ e.setHostDept("dept-host");
+ e.setCoDept("dept-co");
+ mockEntity(e);
+ return e;
+ }
+
+ @Test
+ void should_return_empty_when_approve_info_empty() {
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setId("biz-1");
+ e.setHostDept("dept-host");
+ e.setCoDept("dept-co");
+ e.setApproveInfo(new DeptApproveDetailMap());
+ mockEntity(e);
+ assertEquals(List.of(), flow.getTodoUserList(execution));
+ }
+
+ @Test
+ void should_use_impl_user_name_list_when_not_finished() {
+ // isEnd=0:多选经办人 implUserNameList
+ DeptApproveDetail detail = new DeptApproveDetail();
+ detail.setIsEnd(0);
+ detail.setImplUserNameList(List.of("worker1", "worker2"));
+ DeptApproveDetailMap map = new DeptApproveDetailMap();
+ map.put("dept-host", detail);
+ entityWithApproveInfo().setApproveInfo(map);
+ assertEquals(List.of("worker1", "worker2"), flow.getTodoUserList(execution));
+ }
+
+ @Test
+ void should_use_impl_worker_when_finished() {
+ // isEnd=1:单选经办人 implWorker
+ DeptApproveDetail detail = new DeptApproveDetail();
+ detail.setIsEnd(1);
+ detail.setImplWorker("worker1");
+ DeptApproveDetailMap map = new DeptApproveDetailMap();
+ map.put("dept-host", detail);
+ entityWithApproveInfo().setApproveInfo(map);
+ assertEquals(List.of("worker1"), flow.getTodoUserList(execution));
+ }
+
+ @Test
+ void should_deduplicate_users_across_depts() {
+ // 两部门分配同一经办人:LinkedHashSet 去重保序
+ DeptApproveDetail d1 = new DeptApproveDetail();
+ d1.setIsEnd(0);
+ d1.setImplUserNameList(List.of("shared"));
+ DeptApproveDetail d2 = new DeptApproveDetail();
+ d2.setIsEnd(0);
+ d2.setImplUserNameList(List.of("shared", "worker2"));
+ DeptApproveDetailMap map = new DeptApproveDetailMap();
+ map.put("dept-host", d1);
+ map.put("dept-co", d2);
+ entityWithApproveInfo().setApproveInfo(map);
+ assertEquals(List.of("shared", "worker2"), flow.getTodoUserList(execution));
+ }
+ }
+
+ // ==================== 当前部门经办人 ====================
+
+ @Nested
+ class CurrentDeptWorker {
+
+ @Test
+ void should_return_empty_when_detail_null() {
+ assertEquals(List.of(), flow.getCurrentDeptWorkerList(execution, "dept-1"));
+ }
+
+ @Test
+ void should_return_impl_user_name_list() {
+ DeptApproveDetail detail = new DeptApproveDetail();
+ detail.setImplUserNameList(List.of("w1", "w2"));
+ when(execution.getVariable(FixContactConstant.FlowVarName.BUSINESS_KEY)).thenReturn("biz-1");
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setApproveInfo(new DeptApproveDetailMap());
+ e.getApproveInfo().put("dept-1", detail);
+ when(fixedContactService.getById("biz-1")).thenReturn(e);
+ assertEquals(List.of("w1", "w2"), flow.getCurrentDeptWorkerList(execution, "dept-1"));
+ }
+
+ @Test
+ void should_return_singleton_impl_worker() {
+ DeptApproveDetail detail = new DeptApproveDetail();
+ detail.setImplWorker("w1");
+ when(execution.getVariable(FixContactConstant.FlowVarName.BUSINESS_KEY)).thenReturn("biz-1");
+ FixedContact20260730 e = new FixedContact20260730();
+ e.setApproveInfo(new DeptApproveDetailMap());
+ e.getApproveInfo().put("dept-1", detail);
+ when(fixedContactService.getById("biz-1")).thenReturn(e);
+ assertEquals(List.of("w1"), flow.getCurrentDeptWorkerList(execution, "dept-1"));
+ }
+ }
+}
diff --git a/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactApprovalHelperTest.java b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactApprovalHelperTest.java
new file mode 100644
index 0000000..0a2ef01
--- /dev/null
+++ b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactApprovalHelperTest.java
@@ -0,0 +1,58 @@
+package org.jeecg.modules.supervision.fixcontact.listener;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * FixContactApprovalHelper.resolveTargetExecutionId 规则测试(TDD 红-绿-重构 演示)。
+ *
+ * 业务规则(来自 FixContactDeptLeaderApproveListener 第 53-69 行):
+ * 部门领导审批后,领导变量要写入"上两级"执行实例:
+ * - 存在第二层父级(大会签容器/外层作用域)→ 写入第二层 grandParentId
+ * - 不存在第二层父级 → 写入第一层 parentId
+ * - 第一层 parentId 为空 → 无目标,返回 null(调用方跳过写入)
+ */
+class FixContactApprovalHelperTest {
+
+ // ============ 正常路径 ============
+
+ @Test
+ void should_return_grandParent_when_both_levels_exist() {
+ // 两级父级都存在(子流程内会签容器场景)
+ String target = FixContactApprovalHelper.resolveTargetExecutionId("parent-1", "grand-1");
+ assertEquals("grand-1", target);
+ }
+
+ @Test
+ void should_return_parent_when_no_grand_parent() {
+ // 只有第一层父级(无外层容器)
+ String target = FixContactApprovalHelper.resolveTargetExecutionId("parent-1", null);
+ assertEquals("parent-1", target);
+ }
+
+ // ============ 边界路径 ============
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ @ValueSource(strings = {" ", " "})
+ void should_return_parent_when_grand_parent_blank(String grandParentId) {
+ // 第二层为空串/空白串:视为不存在,退回第一层(原逻辑 isNotBlank 判定)
+ String target = FixContactApprovalHelper.resolveTargetExecutionId("parent-1", grandParentId);
+ assertEquals("parent-1", target);
+ }
+
+ // ============ 异常路径 ============
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ @ValueSource(strings = {" ", " "})
+ void should_return_null_when_parent_blank(String parentId) {
+ // 第一层父级都没有:无目标执行实例,返回 null 供调用方跳过
+ assertNull(FixContactApprovalHelper.resolveTargetExecutionId(parentId, "grand-1"));
+ }
+}
diff --git a/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactDeptLeaderApproveListenerTest.java b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactDeptLeaderApproveListenerTest.java
new file mode 100644
index 0000000..e0807c0
--- /dev/null
+++ b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactDeptLeaderApproveListenerTest.java
@@ -0,0 +1,134 @@
+package org.jeecg.modules.supervision.fixcontact.listener;
+
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.runtime.Execution;
+import org.flowable.engine.runtime.ExecutionQuery;
+import org.flowable.task.service.delegate.DelegateTask;
+import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * FixContactDeptLeaderApproveListener 部门领导审批监听器测试(重构依赖注入后可测)。
+ * 规则:将 assignee 写入"上两级"执行实例局部变量——有第二层父级存第二层,否则存第一层。
+ */
+@ExtendWith(MockitoExtension.class)
+class FixContactDeptLeaderApproveListenerTest {
+
+ @Mock private RuntimeService runtimeService;
+ @Mock private DelegateTask delegateTask;
+ @Mock private Execution currentExecution;
+ @Mock private Execution parentExecution;
+ @Mock private ExecutionQuery query;
+ @Mock private ExecutionQuery parentQuery;
+
+ private FixContactDeptLeaderApproveListener listener;
+
+ @BeforeEach
+ void setUp() {
+ listener = new FixContactDeptLeaderApproveListener(runtimeService);
+ }
+
+ /** 链式 stub:第一次 createExecutionQuery 查当前执行,第二次查父级执行 */
+ private void mockExecutionChain(String parentId, String grandParentId) {
+ when(delegateTask.getAssignee()).thenReturn("leader1");
+ when(delegateTask.getExecutionId()).thenReturn("exec-1");
+ when(runtimeService.createExecutionQuery()).thenReturn(query, parentQuery);
+ when(query.executionId("exec-1")).thenReturn(query);
+ when(query.singleResult()).thenReturn(currentExecution);
+ when(currentExecution.getParentId()).thenReturn(parentId);
+ when(parentQuery.executionId(parentId)).thenReturn(parentQuery);
+ when(parentQuery.singleResult()).thenReturn(parentExecution);
+ when(parentExecution.getParentId()).thenReturn(grandParentId);
+ }
+
+ @Test
+ void should_return_when_task_null() {
+ listener.notify(null);
+ verify(runtimeService, never()).setVariableLocal(any(), any(), any());
+ }
+
+ @Test
+ void should_skip_when_assignee_blank() {
+ when(delegateTask.getAssignee()).thenReturn(" ");
+ listener.notify(delegateTask);
+ verify(runtimeService, never()).setVariableLocal(any(), any(), any());
+ }
+
+ @Test
+ void should_skip_when_execution_id_blank() {
+ when(delegateTask.getAssignee()).thenReturn("leader1");
+ when(delegateTask.getExecutionId()).thenReturn("");
+ listener.notify(delegateTask);
+ verify(runtimeService, never()).setVariableLocal(any(), any(), any());
+ }
+
+ @Test
+ void should_skip_when_current_execution_not_found() {
+ when(delegateTask.getAssignee()).thenReturn("leader1");
+ when(delegateTask.getExecutionId()).thenReturn("exec-1");
+ when(runtimeService.createExecutionQuery()).thenReturn(query);
+ when(query.executionId("exec-1")).thenReturn(query);
+ when(query.singleResult()).thenReturn(null);
+ listener.notify(delegateTask);
+ verify(runtimeService, never()).setVariableLocal(any(), any(), any());
+ }
+
+ @Test
+ void should_skip_when_parent_id_blank() {
+ when(delegateTask.getAssignee()).thenReturn("leader1");
+ when(delegateTask.getExecutionId()).thenReturn("exec-1");
+ when(runtimeService.createExecutionQuery()).thenReturn(query);
+ when(query.executionId("exec-1")).thenReturn(query);
+ when(query.singleResult()).thenReturn(currentExecution);
+ when(currentExecution.getParentId()).thenReturn(" ");
+ listener.notify(delegateTask);
+ verify(runtimeService, never()).setVariableLocal(any(), any(), any());
+ }
+
+ @Test
+ void should_skip_when_parent_execution_not_found() {
+ // 单独 stub(不复用 mockExecutionChain,避免无用 stub)
+ when(delegateTask.getAssignee()).thenReturn("leader1");
+ when(delegateTask.getExecutionId()).thenReturn("exec-1");
+ when(runtimeService.createExecutionQuery()).thenReturn(query, parentQuery);
+ when(query.executionId("exec-1")).thenReturn(query);
+ when(query.singleResult()).thenReturn(currentExecution);
+ when(currentExecution.getParentId()).thenReturn("parent-1");
+ when(parentQuery.executionId("parent-1")).thenReturn(parentQuery);
+ when(parentQuery.singleResult()).thenReturn(null);
+ listener.notify(delegateTask);
+ verify(runtimeService, never()).setVariableLocal(any(), any(), any());
+ }
+
+ @Test
+ void should_write_to_grand_parent_when_both_levels_exist() {
+ mockExecutionChain("parent-1", "grand-1");
+ listener.notify(delegateTask);
+ verify(runtimeService).setVariableLocal("grand-1", FixContactConstant.FlowVarName.TEM_IMPL_LEADER, "leader1");
+ }
+
+ @Test
+ void should_write_to_parent_when_no_grand_parent() {
+ mockExecutionChain("parent-1", null);
+ listener.notify(delegateTask);
+ verify(runtimeService).setVariableLocal("parent-1", FixContactConstant.FlowVarName.TEM_IMPL_LEADER, "leader1");
+ }
+
+ @Test
+ void should_not_throw_when_set_variable_fails() {
+ mockExecutionChain("parent-1", "grand-1");
+ doThrow(new RuntimeException("boom")).when(runtimeService).setVariableLocal("grand-1", FixContactConstant.FlowVarName.TEM_IMPL_LEADER, "leader1");
+ // 不抛异常即通过(异常被 catch)
+ listener.notify(delegateTask);
+ }
+}
diff --git a/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactWorkerApproveListenerTest.java b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactWorkerApproveListenerTest.java
new file mode 100644
index 0000000..1556c76
--- /dev/null
+++ b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/listener/FixContactWorkerApproveListenerTest.java
@@ -0,0 +1,179 @@
+package org.jeecg.modules.supervision.fixcontact.listener;
+
+import org.flowable.engine.RuntimeService;
+import org.flowable.task.service.delegate.DelegateTask;
+import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
+import org.jeecg.modules.supervision.fixcontact.entity.FixedContact20260730;
+import org.jeecg.modules.supervision.fixcontact.service.IFixedContact20260730Service;
+import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
+import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * FixContactWorkerApproveListener 经办人会签监听器测试。
+ * 规则:写入子流程局部变量 tem_impl_worker + 记录经办人到 approveInfo 对应部门(去重)。
+ */
+@ExtendWith(MockitoExtension.class)
+class FixContactWorkerApproveListenerTest {
+
+ @Mock private IFixedContact20260730Service fixedContactService;
+ @Mock private RuntimeService runtimeService;
+ @Mock private DelegateTask delegateTask;
+
+ private FixContactWorkerApproveListener listener;
+
+ @BeforeEach
+ void setUp() {
+ listener = new FixContactWorkerApproveListener(fixedContactService, runtimeService);
+ }
+
+ /** 正常办理链路:assignee + executionId + businessKey + deptVar 齐备 */
+ private void mockNormalTask(String deptVar) {
+ when(delegateTask.getAssignee()).thenReturn("worker1");
+ when(delegateTask.getExecutionId()).thenReturn("exec-1");
+ when(delegateTask.getVariable(FixContactConstant.FlowVarName.BUSINESS_KEY)).thenReturn("biz-1");
+ when(delegateTask.getVariableLocal(FixContactConstant.FlowVarName.IMPL_DEPT_ID)).thenReturn(deptVar);
+ }
+
+ @Test
+ void should_return_when_task_null() {
+ listener.notify(null);
+ verify(runtimeService, never()).setVariableLocal(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any());
+ }
+
+ @Test
+ void should_skip_when_assignee_blank() {
+ when(delegateTask.getAssignee()).thenReturn(" ");
+ listener.notify(delegateTask);
+ verify(runtimeService, never()).setVariableLocal(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any());
+ verify(fixedContactService, never()).updateById(org.mockito.ArgumentMatchers.any());
+ }
+
+ @Test
+ void should_write_worker_local_var() {
+ mockNormalTask("dept-1");
+ FixedContact20260730 entity = new FixedContact20260730();
+ entity.setId("biz-1");
+ entity.setApproveInfo(new DeptApproveDetailMap());
+ when(fixedContactService.getByIdForUpdate("biz-1")).thenReturn(entity);
+
+ listener.notify(delegateTask);
+
+ verify(runtimeService).setVariableLocal("exec-1", FixContactConstant.FlowVarName.TEM_IMPL_WORKER, "worker1");
+ verify(fixedContactService).updateById(entity);
+ }
+
+ @Test
+ void should_skip_record_when_business_key_missing() {
+ when(delegateTask.getAssignee()).thenReturn("worker1");
+ when(delegateTask.getExecutionId()).thenReturn("exec-1");
+ when(delegateTask.getVariable(FixContactConstant.FlowVarName.BUSINESS_KEY)).thenReturn(null);
+
+ listener.notify(delegateTask);
+
+ verify(fixedContactService, never()).updateById(org.mockito.ArgumentMatchers.any());
+ }
+
+ @Test
+ void should_skip_record_when_dept_var_missing() {
+ when(delegateTask.getAssignee()).thenReturn("worker1");
+ when(delegateTask.getExecutionId()).thenReturn("exec-1");
+ when(delegateTask.getVariable(FixContactConstant.FlowVarName.BUSINESS_KEY)).thenReturn("biz-1");
+ when(delegateTask.getVariableLocal(FixContactConstant.FlowVarName.IMPL_DEPT_ID)).thenReturn(null);
+ when(delegateTask.getVariable(FixContactConstant.FlowVarName.IMPL_DEPT_ID)).thenReturn(null);
+
+ listener.notify(delegateTask);
+
+ verify(fixedContactService, never()).updateById(org.mockito.ArgumentMatchers.any());
+ }
+
+ @Test
+ void should_skip_record_when_entity_not_found() {
+ mockNormalTask("dept-1");
+ when(fixedContactService.getByIdForUpdate("biz-1")).thenReturn(null);
+
+ listener.notify(delegateTask);
+
+ verify(fixedContactService, never()).updateById(org.mockito.ArgumentMatchers.any());
+ }
+
+ @Test
+ void should_append_worker_to_existing_detail() {
+ mockNormalTask("dept-1");
+ DeptApproveDetail detail = new DeptApproveDetail();
+ detail.setDeptId("dept-1");
+ detail.setImplUserNameList(new ArrayList<>(List.of("old_worker")));
+ DeptApproveDetailMap map = new DeptApproveDetailMap();
+ map.put("dept-1", detail);
+ FixedContact20260730 entity = new FixedContact20260730();
+ entity.setId("biz-1");
+ entity.setApproveInfo(map);
+ when(fixedContactService.getByIdForUpdate("biz-1")).thenReturn(entity);
+
+ listener.notify(delegateTask);
+
+ assertEquals(List.of("old_worker", "worker1"), detail.getImplUserNameList());
+ verify(fixedContactService).updateById(entity);
+ }
+
+ @Test
+ void should_create_detail_when_dept_not_in_map() {
+ // approveInfo 为 null + dept 无 detail:新建 map + detail
+ mockNormalTask("dept-1");
+ FixedContact20260730 entity = new FixedContact20260730();
+ entity.setId("biz-1");
+ when(fixedContactService.getByIdForUpdate("biz-1")).thenReturn(entity);
+
+ listener.notify(delegateTask);
+
+ assertEquals(List.of("worker1"), entity.getApproveInfo().get("dept-1").getImplUserNameList());
+ verify(fixedContactService).updateById(entity);
+ }
+
+ @Test
+ void should_not_update_when_worker_already_in_list() {
+ mockNormalTask("dept-1");
+ DeptApproveDetail detail = new DeptApproveDetail();
+ detail.setDeptId("dept-1");
+ detail.setImplUserNameList(new ArrayList<>(List.of("worker1")));
+ DeptApproveDetailMap map = new DeptApproveDetailMap();
+ map.put("dept-1", detail);
+ FixedContact20260730 entity = new FixedContact20260730();
+ entity.setId("biz-1");
+ entity.setApproveInfo(map);
+ when(fixedContactService.getByIdForUpdate("biz-1")).thenReturn(entity);
+
+ listener.notify(delegateTask);
+
+ assertEquals(1, detail.getImplUserNameList().size());
+ verify(fixedContactService, never()).updateById(org.mockito.ArgumentMatchers.any());
+ }
+
+ @Test
+ void should_clean_dept_var_with_brackets() {
+ // 部门变量带 []:记录到清洗后的 dept key
+ mockNormalTask("[dept-1]");
+ FixedContact20260730 entity = new FixedContact20260730();
+ entity.setId("biz-1");
+ entity.setApproveInfo(new DeptApproveDetailMap());
+ when(fixedContactService.getByIdForUpdate("biz-1")).thenReturn(entity);
+
+ listener.notify(delegateTask);
+
+ assertTrue(entity.getApproveInfo().containsKey("dept-1"));
+ assertEquals("dept-1", entity.getApproveInfo().get("dept-1").getDeptId());
+ }
+}
diff --git a/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/service/impl/FixedContact20260730ServiceImplTest.java b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/service/impl/FixedContact20260730ServiceImplTest.java
new file mode 100644
index 0000000..72caebb
--- /dev/null
+++ b/jeecg-module-supervision/src/test/java/org/jeecg/modules/supervision/fixcontact/service/impl/FixedContact20260730ServiceImplTest.java
@@ -0,0 +1,178 @@
+package org.jeecg.modules.supervision.fixcontact.service.impl;
+
+import org.flowable.engine.RuntimeService;
+import org.jeecg.common.exception.JeecgBootException;
+import org.jeecg.modules.supervision.fixcontact.entity.FixedContact20260730;
+import org.jeecg.modules.supervision.fixcontact.entity.FixedContactFeedback20260730;
+import org.jeecg.modules.supervision.fixcontact.mapper.FixedContact20260730Mapper;
+import org.jeecg.modules.supervision.fixcontact.mapper.FixedContactFeedback20260730Mapper;
+import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * FixedContact20260730ServiceImpl 测试(mock mapper + runtimeService)。
+ * 核心:saveSubApprove 部门审批保存规则(isEnd=1 存 implWorker / isEnd=0 存 implUserNameList)+ 主从 CRUD。
+ */
+@ExtendWith(MockitoExtension.class)
+class FixedContact20260730ServiceImplTest {
+
+ @Mock private FixedContact20260730Mapper baseMapper;
+ @Mock private FixedContactFeedback20260730Mapper feedbackMapper;
+ @Mock private RuntimeService runtimeService;
+
+ private FixedContact20260730ServiceImpl service;
+
+ @BeforeEach
+ void setUp() {
+ service = new FixedContact20260730ServiceImpl();
+ ReflectionTestUtils.setField(service, "baseMapper", baseMapper);
+ ReflectionTestUtils.setField(service, "fixedContactFeedback20260730Mapper", feedbackMapper);
+ ReflectionTestUtils.setField(service, "runtimeService", runtimeService);
+ }
+
+ // ==================== saveSubApprove(核心业务规则) ====================
+
+ @Test
+ void should_throw_when_main_not_exists() {
+ when(baseMapper.selectOne(any(), anyBoolean())).thenReturn(null);
+ assertThrows(JeecgBootException.class, () -> service.saveSubApprove("no-such", "dept-1", 1, "w1", null));
+ }
+
+ @Test
+ void should_store_impl_worker_when_is_end_one() {
+ // isEnd=1(办结):单选经办人 implWorker,清空 implUserNameList
+ FixedContact20260730 entity = new FixedContact20260730();
+ entity.setId("biz-1");
+ when(baseMapper.selectOne(any(), anyBoolean())).thenReturn(entity);
+
+ service.saveSubApprove("biz-1", "dept-1", 1, "w1", List.of("w2"));
+
+ DeptApproveDetail detail = entity.getApproveInfo().get("dept-1");
+ assertEquals("w1", detail.getImplWorker());
+ assertNull(detail.getImplUserNameList());
+ verify(baseMapper).updateById(entity);
+ }
+
+ @Test
+ void should_store_impl_user_name_list_when_is_end_zero() {
+ // isEnd=0(分配经办人):多选 implUserNameList,清空 implWorker
+ FixedContact20260730 entity = new FixedContact20260730();
+ entity.setId("biz-1");
+ when(baseMapper.selectOne(any(), anyBoolean())).thenReturn(entity);
+
+ service.saveSubApprove("biz-1", "dept-1", 0, "w1", List.of("w2", "w3"));
+
+ DeptApproveDetail detail = entity.getApproveInfo().get("dept-1");
+ assertNull(detail.getImplWorker());
+ assertEquals(List.of("w2", "w3"), detail.getImplUserNameList());
+ verify(baseMapper).updateById(entity);
+ }
+
+ @Test
+ void should_merge_into_existing_detail() {
+ // 同一部门二次保存:覆盖原有 detail 而不重建
+ FixedContact20260730 entity = new FixedContact20260730();
+ entity.setId("biz-1");
+ entity.setApproveInfo(new org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap());
+ DeptApproveDetail existing = new DeptApproveDetail();
+ existing.setDeptId("dept-1");
+ entity.getApproveInfo().put("dept-1", existing);
+ when(baseMapper.selectOne(any(), anyBoolean())).thenReturn(entity);
+
+ service.saveSubApprove("biz-1", "dept-1", 0, null, List.of("w9"));
+
+ DeptApproveDetail detail = entity.getApproveInfo().get("dept-1");
+ assertEquals(List.of("w9"), detail.getImplUserNameList());
+ verify(baseMapper).updateById(entity);
+ }
+
+ @Test
+ void should_create_map_when_approve_info_null() {
+ FixedContact20260730 entity = new FixedContact20260730();
+ entity.setId("biz-1");
+ when(baseMapper.selectOne(any(), anyBoolean())).thenReturn(entity);
+
+ service.saveSubApprove("biz-1", "dept-9", 0, null, List.of("w1"));
+
+ assertEquals(List.of("w1"), entity.getApproveInfo().get("dept-9").getImplUserNameList());
+ }
+
+ // ==================== 主从 CRUD ====================
+
+ @Test
+ void should_save_main_and_sub_list() {
+ FixedContact20260730 main = new FixedContact20260730();
+ main.setId("biz-1");
+ FixedContactFeedback20260730 sub = new FixedContactFeedback20260730();
+
+ service.saveMain(main, List.of(sub));
+
+ verify(baseMapper).insert(main);
+ assertEquals("biz-1", sub.getFixedContact20260730Id());
+ verify(feedbackMapper).insert(sub);
+ }
+
+ @Test
+ void should_save_main_without_sub_when_list_null() {
+ FixedContact20260730 main = new FixedContact20260730();
+ service.saveMain(main, null);
+ verify(baseMapper).insert(main);
+ verify(feedbackMapper, never()).insert(any());
+ }
+
+ @Test
+ void should_update_main_and_replace_sub() {
+ FixedContact20260730 main = new FixedContact20260730();
+ main.setId("biz-1");
+ FixedContactFeedback20260730 sub = new FixedContactFeedback20260730();
+
+ service.updateMain(main, List.of(sub));
+
+ verify(baseMapper).updateById(main);
+ verify(feedbackMapper).deleteByMainId("biz-1");
+ assertEquals("biz-1", sub.getFixedContact20260730Id());
+ verify(feedbackMapper).insert(sub);
+ }
+
+ @Test
+ void should_delete_main_with_sub() {
+ service.delMain("biz-1");
+ verify(feedbackMapper).deleteByMainId("biz-1");
+ verify(baseMapper).deleteById("biz-1");
+ }
+
+ @Test
+ void should_delete_batch_with_sub() {
+ service.delBatchMain(List.of("biz-1", "biz-2"));
+ verify(feedbackMapper).deleteByMainId("biz-1");
+ verify(baseMapper).deleteById("biz-1");
+ verify(feedbackMapper).deleteByMainId("biz-2");
+ verify(baseMapper).deleteById("biz-2");
+ }
+
+ // ==================== 流程变量同步 ====================
+
+ @Test
+ void should_update_form_and_sync_variable() {
+ FixedContact20260730 form = new FixedContact20260730();
+ service.saveBpmFormAndSyncVariable(form, "proc-1", "json_data", "{\"a\":1}");
+ verify(baseMapper).updateById(form);
+ verify(runtimeService).setVariable("proc-1", "json_data", "{\"a\":1}");
+ }
+}
diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/pom.xml b/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/pom.xml
index db1f54d..9d29c85 100644
--- a/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/pom.xml
+++ b/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/pom.xml
@@ -12,6 +12,11 @@
jeecg-system-cloud-api
+
+
+ org.jeecgframework.boot
+ jeecg-boot-base-core
+
org.springframework.cloud
diff --git a/pom.xml b/pom.xml
index c1bc6a5..e0a273c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,7 +40,7 @@
3.8.0
- true
+ false
17
UTF-8
@@ -556,6 +556,27 @@
${skipTests}
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+
+ jacoco-prepare-agent
+
+ prepare-agent
+
+
+
+ jacoco-report
+ test
+
+ report
+
+
+
+
org.apache.maven.plugins