This commit is contained in:
@@ -169,6 +169,60 @@ class OapiFlowContractIT extends FlowTestSupport {
|
||||
assertNotEquals(0, badResp.getBody().get("code").asInt(), "不可达回调地址应被探活拦截");
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回托管件契约(2026-09-16 补:此前只有 CAS 用户面端点,业务应用只能浏览器直调):
|
||||
* 提交人本人 → 200 + 办件 40 + 待办失效;非提交人 → 业务码拒绝(应用签名不替代身份)。
|
||||
*/
|
||||
@Test
|
||||
void withdraw_bySubmitter_whenApproving() {
|
||||
mapDept("ybb", 101, "甲");
|
||||
FlwDef def = saveAndPublishDef("t15", "撤回", linearDef("ybb", "审核"));
|
||||
String body = "{"
|
||||
+ "\"flowGuid\":\"" + def.getFlowGuid() + "\","
|
||||
+ "\"bizType\":\"svc_item\",\"bizId\":\"apply-w1\",\"bizTitle\":\"张三的撤回件\","
|
||||
+ "\"submitterId\":201,\"submitterName\":\"张三\"}";
|
||||
Long instanceId = unwrap(client.post("/oapi/flow/apply?schId=1", body, JsonNode.class)
|
||||
.getBody()).get("instanceId").asLong();
|
||||
assertEquals(1, taskRepository.findByInstanceIdAndStatus(instanceId, 0).size());
|
||||
|
||||
// 非提交人(202)代撤 → 拒绝
|
||||
ResponseEntity<JsonNode> forged = client.post(
|
||||
"/oapi/flow/instance/" + instanceId + "/withdraw?schId=1&submitterId=202", null, JsonNode.class);
|
||||
assertNotEquals(0, forged.getBody().get("code").asInt(), "非提交人撤回应被拒绝");
|
||||
assertEquals(10, instanceRepository.findById(instanceId).get().getStatus());
|
||||
|
||||
// 提交人本人(201)→ 撤回成功
|
||||
ResponseEntity<JsonNode> ok = client.post(
|
||||
"/oapi/flow/instance/" + instanceId + "/withdraw?schId=1&submitterId=201", null, JsonNode.class);
|
||||
assertEquals(200, ok.getStatusCodeValue());
|
||||
assertEquals(0, ok.getBody().get("code").asInt(), "撤回成功须返回业务成功码");
|
||||
assertEquals(40, instanceRepository.findById(instanceId).get().getStatus());
|
||||
assertEquals(0, taskRepository.findByInstanceIdAndStatus(instanceId, 0).size(), "待办任务须全部失效");
|
||||
|
||||
// 重复撤回(已 40)→ 拒绝
|
||||
ResponseEntity<JsonNode> again = client.post(
|
||||
"/oapi/flow/instance/" + instanceId + "/withdraw?schId=1&submitterId=201", null, JsonNode.class);
|
||||
assertNotEquals(0, again.getBody().get("code").asInt(), "非审批中状态不可撤回");
|
||||
}
|
||||
|
||||
/** 跨租户撤回:schId 不符 → 拒绝,且不得改动办件 */
|
||||
@Test
|
||||
void withdraw_crossTenant_rejected() {
|
||||
mapDept("ybb", 101, "甲");
|
||||
FlwDef def = saveAndPublishDef("t16", "跨租户撤回", linearDef("ybb", "审核"));
|
||||
String body = "{"
|
||||
+ "\"flowGuid\":\"" + def.getFlowGuid() + "\","
|
||||
+ "\"bizType\":\"svc_item\",\"bizId\":\"apply-w2\",\"bizTitle\":\"跨租户\","
|
||||
+ "\"submitterId\":201,\"submitterName\":\"张三\"}";
|
||||
Long instanceId = unwrap(client.post("/oapi/flow/apply?schId=1", body, JsonNode.class)
|
||||
.getBody()).get("instanceId").asLong();
|
||||
|
||||
ResponseEntity<JsonNode> resp = client.post(
|
||||
"/oapi/flow/instance/" + instanceId + "/withdraw?schId=2&submitterId=201", null, JsonNode.class);
|
||||
assertNotEquals(0, resp.getBody().get("code").asInt(), "跨租户撤回应被拒绝");
|
||||
assertEquals(10, instanceRepository.findById(instanceId).get().getStatus());
|
||||
}
|
||||
|
||||
private JsonNode unwrap(JsonNode body) {
|
||||
return body.get("data");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.yunhai.flowcenter.service;
|
||||
|
||||
import com.yunhai.flowcenter.model.FlowModels;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* 流程图服务端出图(2026-09-16 "CAS 验证码式嵌入"):
|
||||
* renderPng 恒出非空 PNG;布局对环/孤儿/缺失端点防御出图(外部 img 场景无错误页可跳)。
|
||||
*/
|
||||
class DiagramImageServiceTest {
|
||||
|
||||
private final DiagramImageService service = new DiagramImageService();
|
||||
|
||||
private FlowModels.NodeVo node(String id, String type, String label, String sub, String state) {
|
||||
FlowModels.NodeVo n = new FlowModels.NodeVo();
|
||||
n.setId(id);
|
||||
n.setType(type);
|
||||
n.setLabel(label);
|
||||
n.setSubLabel(sub);
|
||||
n.setState(state);
|
||||
return n;
|
||||
}
|
||||
|
||||
private FlowModels.EdgeVo edge(String from, String to, String name) {
|
||||
FlowModels.EdgeVo e = new FlowModels.EdgeVo();
|
||||
e.setFrom(from);
|
||||
e.setTo(to);
|
||||
e.setName(name);
|
||||
return e;
|
||||
}
|
||||
|
||||
private FlowModels.DiagramVo vo(List<FlowModels.NodeVo> nodes, List<FlowModels.EdgeVo> edges) {
|
||||
FlowModels.DiagramVo vo = new FlowModels.DiagramVo();
|
||||
vo.setName("教师入职审批");
|
||||
vo.setMode("auto");
|
||||
vo.setNodes(nodes);
|
||||
vo.setEdges(edges);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Test
|
||||
void render_instanceVo_producesPngWithStateColors() {
|
||||
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(
|
||||
node("n1", "START", "开始", null, null),
|
||||
node("n2", "APPROVAL", "审核节点", "张三、李四 · 任一人通过", null),
|
||||
node("n3", "APPROVAL", "科室审批", "教学处 · 3人 · 时限24h", "active"),
|
||||
node("n4", "END", "结束", null, null)));
|
||||
List<FlowModels.EdgeVo> edges = new ArrayList<>(Arrays.asList(
|
||||
edge("n1", "n2", null), edge("n2", "n3", null), edge("n3", "n4", null)));
|
||||
byte[] png = service.renderPng(vo(nodes, edges));
|
||||
// PNG 头 89 50 4E 47
|
||||
assertEquals(0x89, png[0] & 0xFF);
|
||||
assertEquals(0x50, png[1] & 0xFF);
|
||||
assertTrue(png.length > 2000, "渲染结果过小,疑似空图");
|
||||
// 状态无关的纯定义图也恒出图(flowGuid 口径)
|
||||
for (FlowModels.NodeVo n : nodes) {
|
||||
n.setState(null);
|
||||
}
|
||||
assertNotEquals(0, service.renderPng(vo(nodes, edges)).length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void render_defensive_missingEndpointAndCycle_stillProducesImage() {
|
||||
// 成环 + 指向不存在端点的边:不抛,仍出图(外部 img 场景防御)
|
||||
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(
|
||||
node("n1", "START", "开始", null, null),
|
||||
node("n2", "APPROVAL", "审核节点", null, "done"),
|
||||
node("n3", "APPROVAL", "孤儿节点", null, null)));
|
||||
List<FlowModels.EdgeVo> edges = new ArrayList<>(Arrays.asList(
|
||||
edge("n1", "n2", null), edge("n2", "n1", null), edge("n2", "ghost", null)));
|
||||
assertDoesNotThrow(() -> service.renderPng(vo(nodes, edges)));
|
||||
}
|
||||
}
|
||||
@@ -366,7 +366,7 @@ class FlowDiagramServiceTest {
|
||||
// 真守卫(同 FlowAccessGuardTest 口径):提交人/办理人/本校管理员
|
||||
FlowAccessGuard accessGuard = new FlowAccessGuard(taskRepository);
|
||||
controller = new FlowUserController(queryService, taskService, instanceService,
|
||||
accessGuard, diagramService);
|
||||
accessGuard, diagramService, mock(com.yunhai.flowcenter.service.DiagramImageService.class));
|
||||
}
|
||||
|
||||
private BoaUser user(int id, Integer type, Integer schId, Integer... roleIds) {
|
||||
|
||||
Reference in New Issue
Block a user