498 lines
23 KiB
Java
498 lines
23 KiB
Java
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, null), "n1");
|
||
assertEquals("人事处", n1.getLabel());
|
||
assertEquals("科室 · 2人 · 时限48h", n1.getSubLabel());
|
||
assertEquals("auto", service.build(def(DEPT_DEF_JSON), null, null).getMode());
|
||
}
|
||
|
||
// ==================== 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());
|
||
assertEquals("auto", 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);
|
||
}
|
||
|
||
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());
|
||
}
|
||
}
|
||
}
|