[audit] P6.1 F11-4 台账路由挂起可观测:pageAdmin 增 hung 过滤 + 行级 routeHung 红标(口径=max attempt 路由行 outlet 仍 NULL,防误报)

This commit is contained in:
chenxing
2026-10-18 08:05:32 +08:00
parent f4121b2c1e
commit 448c8d36f4
6 changed files with 159 additions and 5 deletions
@@ -0,0 +1,104 @@
package com.yunhai.flowcenter.controller.api;
import com.yunhai.flowcenter.entity.FlwInstance;
import com.yunhai.flowcenter.repository.FlwInstanceRepository;
import com.yunhai.flowcenter.repository.FlwRouteLogRepository;
import com.yunhai.flowcenter.service.FlowAdminGuard;
import com.yunhai.platform.utils.model.BasePageResult;
import com.yunhai.platform.utils.model.BoaUser;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import java.util.Arrays;
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.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* F11-4 台账「路由挂起」可观测单测:hung 筛选透传 + 行级 routeHung 红标三态
* (在途+挂起 / 在途未挂 / 已终态)。挂起判定 JPQL 口径(最大 attempt 行 outlet 仍 NULL)
* 在 {@link FlwRouteLogRepository#findHungInstanceIds} 与 pageAdmin 的 exists 子查询里,
* 语义断言由 160 人工验收(V 过滤)与真库回归兜住;本测锁定控制器编排与红标填充。
*/
class FlowAdminInstanceHungTest {
private final FlowAdminGuard adminGuard = mock(FlowAdminGuard.class);
private final FlwInstanceRepository instanceRepository = mock(FlwInstanceRepository.class);
private final FlwRouteLogRepository routeLogRepository = mock(FlwRouteLogRepository.class);
private final FlowAdminController controller = new FlowAdminController(
adminGuard,
mock(com.yunhai.flowcenter.service.FlowDefAdminService.class),
mock(com.yunhai.flowcenter.service.FlowMapAdminService.class),
mock(com.yunhai.flowcenter.service.FlowCallbackAdminService.class),
mock(com.yunhai.flowcenter.service.FlowRouterAdminService.class),
instanceRepository,
mock(com.yunhai.flowcenter.service.FlowDiagramService.class),
mock(com.yunhai.flowcenter.service.DiagramImageService.class),
mock(com.fasterxml.jackson.databind.ObjectMapper.class),
mock(com.yunhai.flowcenter.service.FlowAppAdminService.class),
mock(com.yunhai.flowcenter.service.FlowDeptAdminService.class),
routeLogRepository);
private static FlwInstance instance(long id, int status) {
FlwInstance i = new FlwInstance();
i.setId(id);
i.setStatus(status);
return i;
}
@Test
void hungFilterPassedThroughToRepository() {
when(adminGuard.requireAdmin(any(), any())).thenReturn(990001L);
when(instanceRepository.pageAdmin(eq(990001L), any(), any(), eq(Boolean.TRUE), any()))
.thenReturn(new PageImpl<>(Arrays.asList(instance(1, 10))));
when(routeLogRepository.findHungInstanceIds(any())).thenReturn(Arrays.asList());
controller.instancePage(new BoaUser(), null, null, Boolean.TRUE, 1, 10);
ArgumentCaptor<Boolean> hung = ArgumentCaptor.forClass(Boolean.class);
verify(instanceRepository).pageAdmin(eq(990001L), isNull(), isNull(), hung.capture(), any());
assertEquals(Boolean.TRUE, hung.getValue(), "hung=true 必须原样透传到分页查询");
}
@Test
void routeHungBadgeThreeStates() {
when(adminGuard.requireAdmin(any(), any())).thenReturn(990001L);
// 三行:1=在途且在挂起集合;2=在途但不在集合;3=已终态(集合必然不含)
when(instanceRepository.pageAdmin(eq(990001L), any(), any(), any(), any()))
.thenReturn(new PageImpl<>(Arrays.asList(instance(1, 10), instance(2, 10), instance(3, 20))));
when(routeLogRepository.findHungInstanceIds(any()))
.thenReturn(Arrays.asList(1L));
BasePageResult<FlwInstance> result = controller.instancePage(new BoaUser(), null, null, null, 1, 10);
List<FlwInstance> rows = result.getContent();
assertEquals(3, rows.size());
assertTrue(rows.get(0).getRouteHung(), "在途且最新 attempt 路由行无出口 → 红标 true");
assertFalse(rows.get(1).getRouteHung(), "在途但无挂起路由行 → false");
assertFalse(rows.get(2).getRouteHung(), "已终态办件不参与挂起判定 → false");
}
@Test
void emptyPageSkipsHungLookup() {
when(adminGuard.requireAdmin(any(), any())).thenReturn(990001L);
when(instanceRepository.pageAdmin(eq(990001L), any(), any(), any(), any()))
.thenReturn(new PageImpl<>(Arrays.asList()));
controller.instancePage(new BoaUser(), 10, null, null, 1, 10);
verify(routeLogRepository, never()).findHungInstanceIds(any());
}
}
@@ -493,7 +493,8 @@ class FlowDiagramServiceTest {
mock(com.yunhai.flowcenter.service.DiagramImageService.class),
new ObjectMapper(),
mock(com.yunhai.flowcenter.service.FlowAppAdminService.class),
mock(com.yunhai.flowcenter.service.FlowDeptAdminService.class));
mock(com.yunhai.flowcenter.service.FlowDeptAdminService.class),
mock(com.yunhai.flowcenter.repository.FlwRouteLogRepository.class));
}
private BoaUser user(int id, Integer type, Integer schId, Integer... roleIds) {