Files
flow-center-service/src/test/java/com/yunhai/flowcenter/service/FlowDiagramServiceTest.java
T
chenxing 9cc0b06c29
build / build-push (push) Successful in 1m30s
feat(flow): 工作台与轨迹优化 + 管理端启用回路 + 本地验签配置修复
- 已办去重: 同一办件一行 + doneCount 累计次数(FlwTaskRepository 查询去重)
- 发起/撤回/重提留痕事件行(状态码 6/7, 无需 DDL); /oapi/flow/progress steps 含撤回/重提步骤
- 管理端新增启用回路: POST /admin/map/enable/{id}、POST /admin/def/enable/{id}; GET /api/flow/def/source 源码查询
- 修复 findEnabledByOrdId 未过滤 status 的隐性缺陷; 新增 DeptLabelSupport 科室标签支撑
- 测试: FlowEngineIT 撤回留痕/已办去重断言、OapiFlowContractIT 撤回步骤断言(94 tests 全过)
- 本地验签修复: 本地 UserBase 无 context-path, yh.open.services 显式覆盖服务目录枚举兜底的 /userbase basePath, 否则出站签名与服务端剥离不对齐 → GetUserInfo 等全部「验签失败」; 生产经 Caddy 挂 /userbase 时删除 services 覆盖
2026-09-18 16:56:01 +08:00

512 lines
24 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.flowcenter.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhai.flowcenter.controller.api.FlowAdminController;
import com.yunhai.flowcenter.controller.api.FlowUserController;
import com.yunhai.flowcenter.entity.FlwAssigneeMap;
import com.yunhai.flowcenter.entity.FlwDef;
import com.yunhai.flowcenter.entity.FlwInstance;
import com.yunhai.flowcenter.entity.FlwTask;
import com.yunhai.flowcenter.engine.AssigneeResolver;
import com.yunhai.flowcenter.engine.FlowEngine;
import com.yunhai.flowcenter.enums.FlwEnums;
import com.yunhai.flowcenter.model.FlowModels;
import com.yunhai.flowcenter.repository.FlwAssigneeMapRepository;
import com.yunhai.flowcenter.repository.FlwBranchRepository;
import com.yunhai.flowcenter.repository.FlwDefRepository;
import com.yunhai.flowcenter.repository.FlwInstanceRepository;
import com.yunhai.flowcenter.repository.FlwTaskRepository;
import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.openapi.base.OpenApiUserClient;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.model.BoaUser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationEventPublisher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
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.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* 流程示意图出图(方案 §3.2 标签解析规则 + §5 TDD C1-C6):
* C1/CC3 视图分支与兜底链、C2 UserBase 补名降级、C4 flowGuid 取版、C5/C6 控制器守卫接线。
* 科室视图不泄人员名单(USERS 只显"指定办理人"且不调 UserBase)是硬口径。
*/
class FlowDiagramServiceTest {
private static final Long SCH = 990001L;
/** 并行链:start→sp1→n1(DEPT_KEY rs)→j1→end(教师入职缩样) */
private static final String DEPT_DEF_JSON = "{"
+ "\"code\":\"teacher_in\",\"name\":\"教师入职\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\"},"
+ "{\"id\":\"sp1\",\"type\":\"PARALLEL_SPLIT\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"人事处审批\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"rs\"},\"countersign\":\"ALL\",\"deadlineHours\":48},"
+ "{\"id\":\"j1\",\"type\":\"PARALLEL_JOIN\"},"
+ "{\"id\":\"end\",\"type\":\"END\"}],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"sp1\"},{\"from\":\"sp1\",\"to\":\"n1\"},"
+ "{\"from\":\"n1\",\"to\":\"j1\"},{\"from\":\"j1\",\"to\":\"end\"}]}";
/** ANY 会签变体(无时限) */
private static final String ANY_DEF_JSON = "{"
+ "\"code\":\"c2\",\"name\":\"任一通过\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"甲审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"rs\"},\"countersign\":\"ANY\"},"
+ "{\"id\":\"end\",\"type\":\"END\"}],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"end\"}]}";
/** USERS 直配变体(Notice 先审后发缩样) */
private static final String USERS_DEF_JSON = "{"
+ "\"code\":\"notice_audit\",\"name\":\"Notice先审后发\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\"},"
+ "{\"id\":\"n2\",\"type\":\"APPROVAL\",\"name\":\"甲乙会签\",\"assignee\":{\"type\":\"USERS\",\"userIds\":[11,12,13]}},"
+ "{\"id\":\"end\",\"type\":\"END\"}],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n2\"},{\"from\":\"n2\",\"to\":\"end\"}]}";
private FlwDefRepository defRepository;
private FlwAssigneeMapRepository mapRepository;
private FlwTaskRepository taskRepository;
private OpenApiUserClient userClient;
private FlowDiagramService service;
@BeforeEach
void setUp() {
defRepository = mock(FlwDefRepository.class);
mapRepository = mock(FlwAssigneeMapRepository.class);
taskRepository = mock(FlwTaskRepository.class);
userClient = mock(OpenApiUserClient.class);
when(taskRepository.findByInstanceIdOrderByIdAsc(anyLong()))
.thenReturn(Collections.emptyList());
// 真 FlowEngine(真实 JSON 解析通道)+ mock 依赖
FlowEngine engine = new FlowEngine(new ObjectMapper(),
taskRepository, mock(FlwBranchRepository.class), mock(AssigneeResolver.class),
mock(ApplicationEventPublisher.class));
service = new FlowDiagramService(defRepository, mapRepository,
mock(com.yunhai.flowcenter.repository.FlwInstanceRepository.class),
taskRepository, engine, userClient);
}
private FlwDef def(String json) {
FlwDef def = new FlwDef();
def.setId(12L);
def.setSchId(SCH);
def.setFlowGuid("g1");
def.setCode("c1");
def.setName("教师入职");
def.setVersion(2);
def.setStatus(FlwEnums.DefStatus.PUBLISHED.getCode());
def.setContentJson(json);
return def;
}
private FlwAssigneeMap mapRow(String deptName, int userId, String userName) {
FlwAssigneeMap m = new FlwAssigneeMap();
m.setSchId(SCH);
m.setDeptKey("rs");
m.setDeptName(deptName);
m.setUserId(userId);
m.setUserName(userName);
m.setStatus(0);
return m;
}
private BoaUser boaUser(String name) {
BoaUser u = mock(BoaUser.class);
when(u.getName()).thenReturn(name);
return u;
}
private FlowModels.NodeVo node(FlowModels.DiagramVo vo, String id) {
return vo.getNodes().stream().filter(n -> id.equals(n.getId())).findFirst()
.orElseThrow(() -> new AssertionError("节点缺失:" + id));
}
// ==================== C1:DEPT_KEY 节点科室视图 ====================
@Test
void c1_deptView_deptKeyNode_labelDeptName_subLabelCountAndDeadline() {
when(mapRepository.findEnabled(SCH, "rs")).thenReturn(Arrays.asList(
mapRow("人事处", 11, "张三"), mapRow("人事处", 12, "李四")));
FlowModels.DiagramVo vo = service.build(def(DEPT_DEF_JSON), null, "dept");
FlowModels.NodeVo n1 = node(vo, "n1");
assertEquals("人事处", n1.getLabel());
assertEquals("科室 · 2人 · 时限48h", n1.getSubLabel());
assertEquals("rs", n1.getDeptKey());
assertEquals("DEPT_KEY", n1.getAssigneeType());
assertEquals("ALL", n1.getCountersign());
assertEquals(Integer.valueOf(48), n1.getDeadlineHours());
// 科室视图不出现映射成员姓名
assertFalse(n1.getSubLabel().contains("张三"));
assertFalse(n1.getSubLabel().contains("李四"));
}
@Test
void c1_deptView_fallbackChain_deptKeyThenNodeName() {
// 映射行全无名 → deptKey 兜底
when(mapRepository.findEnabled(SCH, "rs"))
.thenReturn(Collections.singletonList(mapRow("", 11, "张三")));
FlowModels.NodeVo n1 = node(service.build(def(DEPT_DEF_JSON), null, "dept"), "n1");
assertEquals("rs", n1.getLabel());
assertEquals("科室 · 1人 · 时限48h", n1.getSubLabel());
// 无映射行 → 节点名兜底(人数照实为 0)
when(mapRepository.findEnabled(SCH, "rs")).thenReturn(Collections.emptyList());
n1 = node(service.build(def(DEPT_DEF_JSON), null, "dept"), "n1");
assertEquals("人事处审批", n1.getLabel());
assertEquals("科室 · 0人 · 时限48h", n1.getSubLabel());
}
@Test
void c1_anyCountersign_appendsSuffix() {
when(mapRepository.findEnabled(SCH, "rs"))
.thenReturn(Collections.singletonList(mapRow("人事处", 11, "张三")));
FlowModels.NodeVo n1 = node(service.build(def(ANY_DEF_JSON), null, "dept"), "n1");
assertEquals("科室 · 1人 · 任一人通过", n1.getSubLabel());
}
@Test
void c1_fixedLabels_andEdgesCarried() {
when(mapRepository.findEnabled(anyLong(), anyString())).thenReturn(Collections.emptyList());
FlowModels.DiagramVo vo = service.build(def(DEPT_DEF_JSON), null, "dept");
assertEquals("开始", node(vo, "start").getLabel());
assertEquals("并行分发", node(vo, "sp1").getLabel());
assertEquals("汇聚等待", node(vo, "j1").getLabel());
assertEquals("结束", node(vo, "end").getLabel());
assertNull(node(vo, "start").getSubLabel());
assertNull(node(vo, "end").getSubLabel());
assertEquals(4, vo.getEdges().size());
assertEquals("start", vo.getEdges().get(0).getFrom());
assertEquals("sp1", vo.getEdges().get(0).getTo());
// 出图元信息(前端"按流程定义 vN 生成"与工具条 Tag 数据源)
assertEquals("g1", vo.getFlowGuid());
assertEquals(Long.valueOf(12L), vo.getDefId());
assertEquals("教师入职", vo.getName());
assertEquals(Integer.valueOf(2), vo.getVersion());
assertEquals(Integer.valueOf(1), vo.getStatus());
assertEquals("dept", vo.getMode());
}
@Test
void c1_autoView_deptKeyNode_rendersDeptStyle() {
when(mapRepository.findEnabled(SCH, "rs")).thenReturn(Arrays.asList(
mapRow("人事处", 11, "张三"), mapRow("人事处", 12, "李四")));
FlowModels.NodeVo n1 = node(service.build(def(DEPT_DEF_JSON), null, "auto"), "n1");
assertEquals("人事处", n1.getLabel());
assertEquals("科室 · 2人 · 时限48h", n1.getSubLabel());
assertEquals("auto", service.build(def(DEPT_DEF_JSON), null, "auto").getMode());
}
/** 2026-09-18:mode 缺省改为人员视图(原 auto 自适应)——老深链显式带 auto 仍按自适应出图 */
@Test
void c1_defaultMode_isUserView() {
when(mapRepository.findEnabled(SCH, "rs")).thenReturn(Arrays.asList(
mapRow("人事处", 11, "张三"), mapRow("人事处", 12, "李四")));
FlowModels.DiagramVo vo = service.build(def(DEPT_DEF_JSON), null, null);
assertEquals("user", vo.getMode());
FlowModels.NodeVo n1 = node(vo, "n1");
assertEquals("人事处审批", n1.getLabel());
assertEquals("张三、李四 · 时限48h", n1.getSubLabel());
}
// ==================== C2:USERS 节点人员视图(UserBase 补名降级) ====================
@Test
void c2_userView_usersNode_namesFromUserBase_degradeToId() {
BoaUser jia = boaUser("甲老师");
BoaUser yi = boaUser("乙老师");
when(userClient.getUserById("11")).thenReturn(jia);
when(userClient.getUserById("12")).thenReturn(yi);
when(userClient.getUserById("13")).thenThrow(new RuntimeException("UserBase 不可达"));
FlowModels.NodeVo n2 = node(service.build(def(USERS_DEF_JSON), null, "user"), "n2");
assertEquals("甲乙会签", n2.getLabel());
assertEquals("甲老师、乙老师、#13", n2.getSubLabel());
assertEquals("USERS", n2.getAssigneeType());
assertNull(n2.getDeptKey());
}
@Test
void c2_userBaseReturnsNullUser_degradesToId() {
when(userClient.getUserById(anyString())).thenReturn(null);
FlowModels.NodeVo n2 = node(service.build(def(USERS_DEF_JSON), null, "user"), "n2");
assertEquals("#11、#12、#13", n2.getSubLabel());
}
@Test
void c2_autoView_usersNode_rendersUserStyle() {
BoaUser jia = boaUser("甲老师");
BoaUser yi = boaUser("乙老师");
when(userClient.getUserById("11")).thenReturn(jia);
when(userClient.getUserById("12")).thenReturn(yi);
when(userClient.getUserById("13")).thenReturn(null);
FlowModels.NodeVo n2 = node(service.build(def(USERS_DEF_JSON), null, "auto"), "n2");
assertEquals("甲老师、乙老师、#13", n2.getSubLabel());
}
// ==================== C3:视图分支防泄名单 / DEPT_KEY 人员视图 ====================
@Test
void c3_deptView_usersNode_subLabelOnlyAssigneeHint_neverCallsUserBase() {
BoaUser jia = boaUser("甲老师");
BoaUser yi = boaUser("乙老师");
when(userClient.getUserById("11")).thenReturn(jia);
when(userClient.getUserById("12")).thenReturn(yi);
FlowModels.NodeVo n2 = node(service.build(def(USERS_DEF_JSON), null, "dept"), "n2");
assertEquals("甲乙会签", n2.getLabel());
assertEquals("指定办理人", n2.getSubLabel());
assertFalse(n2.getSubLabel().contains("甲老师"));
assertFalse(n2.getSubLabel().contains("乙老师"));
// 防泄名单硬保证:科室视图连 UserBase 都不查
verify(userClient, never()).getUserById(anyString());
}
@Test
void c3_userView_deptKeyNode_listsMemberNames_blankNameDegradesToId() {
when(mapRepository.findEnabled(SCH, "rs")).thenReturn(Arrays.asList(
mapRow("人事处", 11, "张三"), mapRow("人事处", 12, null)));
FlowModels.NodeVo n1 = node(service.build(def(DEPT_DEF_JSON), null, "user"), "n1");
// 人员视图:label=节点名,subLabel=映射成员名(空名降级 #id)+ 会签/时限后缀
assertEquals("人事处审批", n1.getLabel());
assertEquals("张三、#12 · 时限48h", n1.getSubLabel());
}
// ==================== C4:flowGuid 分支取版与参数校验 ====================
@Test
void c4_byGuid_returnsHighestPublishedDef() {
FlwDef def = def(DEPT_DEF_JSON);
when(defRepository.findFirstBySchIdAndFlowGuidAndStatusOrderByVersionDesc(
SCH, "g1", FlwEnums.DefStatus.PUBLISHED.getCode()))
.thenReturn(Optional.of(def));
when(mapRepository.findEnabled(anyLong(), anyString())).thenReturn(Collections.emptyList());
FlowModels.DiagramVo vo = service.byGuid(SCH, "g1", null);
assertEquals("教师入职", vo.getName());
assertEquals(Integer.valueOf(2), vo.getVersion());
// 2026-09-18:缺省口径=人员视图
assertEquals("user", vo.getMode());
assertNull(vo.getInstanceId());
}
@Test
void c4_byGuid_noPublished_throwsBizException() {
when(defRepository.findFirstBySchIdAndFlowGuidAndStatusOrderByVersionDesc(
anyLong(), anyString(), anyInt()))
.thenReturn(Optional.empty());
BizException e = assertThrows(BizException.class, () -> service.byGuid(SCH, "g1", "dept"));
assertEquals("流程不存在或未发布", e.getMessage());
assertEquals(ErrorCode.PARAM_ERROR, e.getCode());
}
@Test
void c4_byGuid_crossSchoolDef_forbidden() {
FlwDef other = def(DEPT_DEF_JSON);
other.setSchId(888888L);
when(defRepository.findFirstBySchIdAndFlowGuidAndStatusOrderByVersionDesc(
anyLong(), anyString(), anyInt()))
.thenReturn(Optional.of(other));
BizException e = assertThrows(BizException.class, () -> service.byGuid(SCH, "g1", "dept"));
assertEquals("跨校不可查看", e.getMessage());
assertEquals(ErrorCode.FORBIDDEN, e.getCode());
}
@Test
void c4_invalidMode_rejected() {
BizException e = assertThrows(BizException.class,
() -> service.build(def(DEPT_DEF_JSON), null, "xxx"));
assertEquals("mode 仅支持 dept/user", e.getMessage());
}
@Test
void c4_byInstance_usesDefSnapshotOfInstance() {
FlwInstance instance = new FlwInstance();
instance.setId(100L);
instance.setSchId(SCH);
instance.setDefId(12L);
when(defRepository.findById(12L)).thenReturn(Optional.of(def(DEPT_DEF_JSON)));
when(mapRepository.findEnabled(anyLong(), anyString())).thenReturn(Collections.emptyList());
FlowModels.DiagramVo vo = service.byInstance(instance, "dept");
assertEquals(Long.valueOf(100L), vo.getInstanceId());
assertEquals(Long.valueOf(12L), vo.getDefId());
assertEquals(Integer.valueOf(2), vo.getVersion());
}
@Test
void c6_build_draftDef_ok() {
FlwDef draft = def(DEPT_DEF_JSON);
draft.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
when(mapRepository.findEnabled(anyLong(), anyString())).thenReturn(Collections.emptyList());
FlowModels.DiagramVo vo = service.build(draft, null, "auto");
assertEquals(Integer.valueOf(0), vo.getStatus());
assertEquals(5, vo.getNodes().size());
}
// ==================== C5:用户面 /api/flow/diagram 控制器守卫 ====================
@Nested
class UserDiagramEndpoint {
private FlowQueryService queryService;
private FlowTaskService taskService;
private FlowInstanceService instanceService;
private FlowUserController controller;
private FlowDiagramService diagramService;
@BeforeEach
void setUpController() {
queryService = mock(FlowQueryService.class);
taskService = mock(FlowTaskService.class);
instanceService = mock(FlowInstanceService.class);
diagramService = mock(FlowDiagramService.class);
// 真守卫(同 FlowAccessGuardTest 口径):提交人/办理人/本校管理员
FlowAccessGuard accessGuard = new FlowAccessGuard(taskRepository);
controller = new FlowUserController(queryService, taskService, instanceService,
accessGuard, diagramService, mock(com.yunhai.flowcenter.service.DiagramImageService.class));
}
private BoaUser user(int id, Integer type, Integer schId, Integer... roleIds) {
BoaUser u = mock(BoaUser.class, RETURNS_DEEP_STUBS);
when(u.getId()).thenReturn(id);
when(u.getType()).thenReturn(type);
when(u.getRoleIds()).thenReturn(roleIds.length == 0
? null : new ArrayList<>(Arrays.asList(roleIds)));
when(u.getCurrentRole().getSchId()).thenReturn(schId);
return u;
}
@Test
void c5_flowGuid_sameSchoolUser_ok() {
FlowModels.DiagramVo vo = new FlowModels.DiagramVo();
when(diagramService.byGuid(SCH, "g1", "dept")).thenReturn(vo);
assertSame(vo, controller.diagram(user(11, 2, SCH.intValue(), 9), "g1", null, "dept"));
verify(diagramService).byGuid(SCH, "g1", "dept");
}
@Test
void c5_flowGuid_notLoggedIn_forbidden() {
BizException e = assertThrows(BizException.class,
() -> controller.diagram(null, "g1", null, null));
assertEquals("未登录", e.getMessage());
assertEquals(ErrorCode.FORBIDDEN, e.getCode());
}
@Test
void c5_noParam_paramError() {
assertThrows(BizException.class,
() -> controller.diagram(user(11, 2, SCH.intValue(), 9), null, null, null));
}
@Test
void c5_instance_submitter_ok() {
FlwInstance instance = new FlwInstance();
instance.setId(100L);
instance.setSchId(SCH);
instance.setSubmitterId(11);
when(instanceService.requireInstance(SCH, 100L)).thenReturn(instance);
FlowModels.DiagramVo vo = new FlowModels.DiagramVo();
when(diagramService.byInstance(instance, "dept")).thenReturn(vo);
assertSame(vo, controller.diagram(user(11, 2, SCH.intValue(), 9), null, 100L, "dept"));
verify(diagramService).byInstance(instance, "dept");
}
@Test
void c5_instance_assignee_ok() {
FlwInstance instance = new FlwInstance();
instance.setId(100L);
instance.setSchId(SCH);
instance.setSubmitterId(11);
FlwTask task = new FlwTask();
task.setAssigneeId(22);
when(instanceService.requireInstance(SCH, 100L)).thenReturn(instance);
when(taskRepository.findByInstanceIdOrderByIdAsc(100L))
.thenReturn(Collections.singletonList(task));
FlowModels.DiagramVo vo = new FlowModels.DiagramVo();
when(diagramService.byInstance(instance, "dept")).thenReturn(vo);
assertSame(vo, controller.diagram(user(22, 2, SCH.intValue(), 9), null, 100L, "dept"));
}
@Test
void c5_instance_unrelatedUser_forbidden() {
FlwInstance instance = new FlwInstance();
instance.setId(100L);
instance.setSchId(SCH);
instance.setSubmitterId(11);
when(instanceService.requireInstance(SCH, 100L)).thenReturn(instance);
BizException e = assertThrows(BizException.class,
() -> controller.diagram(user(36, 2, SCH.intValue(), 9), null, 100L, "dept"));
assertEquals("仅提交人/办理人/本校管理员可查看该办件", e.getMessage());
verify(diagramService, never()).byInstance(any(), any());
}
}
// ==================== C6:管理面 /api/flow/admin/def/diagram ====================
@Nested
class AdminDefDiagramEndpoint {
private FlowAdminController controller;
private FlowDefAdminService defAdminService;
private FlowDiagramService diagramService;
@BeforeEach
void setUpController() {
defAdminService = mock(FlowDefAdminService.class);
diagramService = mock(FlowDiagramService.class);
// 真管理守卫(纯逻辑无依赖)+ 其余依赖 mock
controller = new FlowAdminController(new FlowAdminGuard(), defAdminService,
mock(FlowMapAdminService.class), mock(FlowCallbackAdminService.class),
mock(FlwInstanceRepository.class), diagramService,
mock(com.yunhai.flowcenter.service.DiagramImageService.class));
}
private BoaUser user(int id, Integer type, Integer schId, Integer... roleIds) {
BoaUser u = mock(BoaUser.class, RETURNS_DEEP_STUBS);
when(u.getId()).thenReturn(id);
when(u.getType()).thenReturn(type);
when(u.getRoleIds()).thenReturn(roleIds.length == 0
? null : new ArrayList<>(Arrays.asList(roleIds)));
when(u.getCurrentRole().getSchId()).thenReturn(schId);
return u;
}
@Test
void c6_admin_draftDef_ok() {
FlwDef draft = def(DEPT_DEF_JSON);
draft.setId(55L);
draft.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
when(defAdminService.get(SCH, 55L)).thenReturn(draft);
FlowModels.DiagramVo vo = new FlowModels.DiagramVo();
when(diagramService.build(draft, null, "auto")).thenReturn(vo);
assertSame(vo, controller.defDiagram(user(1, 1, SCH.intValue()), 55L, "auto"));
verify(diagramService).build(draft, null, "auto");
}
@Test
void c6_nonAdmin_forbidden() {
BoaUser ordinary = user(77, 2, SCH.intValue(), 9);
BizException e = assertThrows(BizException.class,
() -> controller.defDiagram(ordinary, 55L, "auto"));
assertEquals("无流程管理权限", e.getMessage());
verify(defAdminService, never()).get(any(), anyLong());
}
}
}