feat(def): updateMeta 元数据原地编辑——已发布可改 name/appKey 不升版本(议题2)

- 独立于 save 的升版本路径:不碰 code/flow_guid/version/status/contentJson 结构
- contentJson 顶层 name 同步(ObjectNode 保序;消除行 name 与 JSON name 脏窗口,拍板 D4)
- appKey 校验同 save 口径(显式传入须 flw_app 启用集合;可置空)
- 单测 5 例(三不变断言/白名单/补写/跨校)+ E2E 14/14(含升版本路径回归与 oapi 摘要)
This commit is contained in:
chenxing
2026-10-10 19:23:44 +08:00
parent d6b7a68ec3
commit 4293c9628a
3 changed files with 178 additions and 0 deletions
@@ -0,0 +1,123 @@
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.FlwAssigneeMapRepository;
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.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(FlwAssigneeMapRepository.class),
mock(FlwRouterRepository.class),
mock(com.yunhai.flowcenter.repository.FlwCallbackRegRepository.class),
appRepository,
mock(FlowEngine.class),
new ObjectMapper());
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仅顶层name() throws Exception {
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());
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 contentJson无顶层name_补写() throws Exception {
FlwDef def = published("{\"code\":\"c\",\"start\":\"start\",\"nodes\":[],\"edges\":[]}");
when(defRepository.findById(9L)).thenReturn(Optional.of(def));
FlwDef saved = service.updateMeta(SCH, 9L, "补写名", null);
assertEquals("补写名", new ObjectMapper().readTree(saved.getContentJson()).path("name").asText());
assertNull(saved.getAppKey());
}
@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));
}
}