按《FL最终代码审计与上线阻断清单》194 项已审计工作树形成候选,基座 = origin/main 11e0778。
- 源码/测试/资源:沿用已审计工作树内容(96 项相对远端为新增或更新),含未跟踪实现
CallbackFactStore、CallbackSourcePolicy、VariableContract、FormalApplyCommandService、
FlowActorContextGate 等;C01 续验夹具修正一并入内。
- CI:保留远端 11e0778 的门禁强化(CI_SOURCE_SHA 核对、容器回收验证、空 schema 反向门禁、
main ref 才推镜像);ci/flow-test-schema.sql 按当前实体重新生成(14→20 表,补 6 新表与
6 新列,回调日志复合唯一键 uk_flw_log_event_reg),workflow 表数断言同步 14→20。
- 依据:docs/tasks/evidence/FL-final-audit-20261027(194 项哈希/版本核对)。
141 lines
6.0 KiB
Java
141 lines
6.0 KiB
Java
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));
|
||
}
|
||
}
|