package com.yunhai.flowcenter.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.yunhai.flowcenter.engine.FlowEngine; import com.yunhai.flowcenter.entity.FlwDef; import com.yunhai.flowcenter.repository.FlwAppRepository; import com.yunhai.flowcenter.repository.FlwDefRepository; import com.yunhai.flowcenter.repository.FlwRouterRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * updateMeta 原地编辑单测(2026-10-10 议题2,拍板 D4): * 三不变(version/flowGuid/status;contentJson 仅顶层 name)、appKey 白名单校验、 * contentJson 无顶层 name 时补写、name 非空校验。 */ class FlowDefAdminServiceUpdateMetaTest { private static final Long SCH = 3L; private FlwDefRepository defRepository; private FlwAppRepository appRepository; private FlowDefAdminService service; @BeforeEach void setUp() { defRepository = mock(FlwDefRepository.class); appRepository = mock(FlwAppRepository.class); service = new FlowDefAdminService(defRepository, mock(FlwRouterRepository.class), mock(com.yunhai.flowcenter.repository.FlwCallbackRegRepository.class), appRepository, mock(com.yunhai.flowcenter.repository.FlwInstanceRepository.class), mock(com.yunhai.flowcenter.service.FlowDeptAdminService.class), mock(FlowEngine.class), new ObjectMapper(), mock(com.yunhai.flowcenter.service.FormCenterSchemaClient.class)); when(defRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); when(appRepository.isEnabledApp(any(), any())).thenReturn(true); } private FlwDef published(String contentJson) { FlwDef d = new FlwDef(); d.setId(9L); d.setSchId(SCH); d.setFlowGuid("guid123"); d.setCode("work-leave"); d.setName("旧名"); d.setAppKey("100002"); d.setVersion(4); d.setStatus(1); d.setContentJson(contentJson); return d; } @Test void 已发布版本_contentJson封存_仅行内元数据可改() throws Exception { // T03(2026-10-25)发布封存:执行文档不可变——行 name 照改(目录元数据), // content_json 顶层 name 保持发布时字节(旧断言"同步改 JSON"已按新原则作废) String json = "{\"code\":\"work-leave\",\"name\":\"旧名\",\"start\":\"start\"," + "\"nodes\":[{\"id\":\"start\",\"type\":\"START\"}]," + "\"edges\":[{\"from\":\"start\",\"to\":\"end\"}]}"; FlwDef def = published(json); when(defRepository.findById(9L)).thenReturn(Optional.of(def)); FlwDef saved = service.updateMeta(SCH, 9L, "新名称", "100008"); assertEquals(4, saved.getVersion()); assertEquals("guid123", saved.getFlowGuid()); assertEquals(1, saved.getStatus()); assertEquals("work-leave", saved.getCode()); assertEquals("新名称", saved.getName()); assertEquals("100008", saved.getAppKey()); // 发布字节冻结:文档内 name 不随目录改名漂移 assertEquals("旧名", new ObjectMapper().readTree(saved.getContentJson()).path("name").asText()); assertEquals("work-leave", new ObjectMapper().readTree(saved.getContentJson()).path("code").asText()); assertTrue(saved.getContentJson().contains("\"type\":\"START\"")); } @Test void 已发布版本_无顶层name_不补写() throws Exception { // 发布封存:缺顶层 name 的已发布文档维持原样,不补写 FlwDef def = published("{\"code\":\"c\",\"start\":\"start\",\"nodes\":[],\"edges\":[]}"); when(defRepository.findById(9L)).thenReturn(Optional.of(def)); FlwDef saved = service.updateMeta(SCH, 9L, "补写名", null); assertFalse(new ObjectMapper().readTree(saved.getContentJson()).has("name")); assertNull(saved.getAppKey()); } @Test void 草稿版本_contentJson顶层name仍同步() throws Exception { // 草稿文档可变:顶层 name 同步保留(与发布封存互为边界) FlwDef def = published("{\"code\":\"c\",\"name\":\"旧名\",\"start\":\"start\",\"nodes\":[],\"edges\":[]}"); def.setStatus(0); // DRAFT when(defRepository.findById(9L)).thenReturn(Optional.of(def)); FlwDef saved = service.updateMeta(SCH, 9L, "草稿新名", null); assertEquals("草稿新名", new ObjectMapper().readTree(saved.getContentJson()).path("name").asText()); } @Test void appKey白名单外_被拦() { when(appRepository.isEnabledApp(SCH, "888888")).thenReturn(false); when(defRepository.findById(9L)).thenReturn(Optional.of(published("{}"))); Exception e = assertThrows(com.yunhai.platform.apistd.BizException.class, () -> service.updateMeta(SCH, 9L, "n", "888888")); assertTrue(e.getMessage().contains("应用设置")); } @Test void name空_被拦() { when(defRepository.findById(9L)).thenReturn(Optional.of(published("{}"))); assertThrows(com.yunhai.platform.apistd.BizException.class, () -> service.updateMeta(SCH, 9L, " ", null)); } @Test void 跨校_被拒() { FlwDef other = published("{}"); other.setSchId(2L); when(defRepository.findById(9L)).thenReturn(Optional.of(other)); assertThrows(com.yunhai.platform.apistd.BizException.class, () -> service.updateMeta(SCH, 9L, "n", null)); } }