feat: 外部协调单流程与端到端用例(#51);查询列表返回 flowCode
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
package com.gxwebsoft.approval;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.gxwebsoft.approval.constant.ApprovalConstants;
|
||||
import com.gxwebsoft.support.ApprovalTestData;
|
||||
import com.gxwebsoft.support.TestAuthentication;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* 外部协调单(#51):与内部协调单共用一张表单与业务表,节点链多一个受理部门分管领导。
|
||||
*
|
||||
* <p>关键断言:doc_type 仍是 coordination、flow_code 是 coordination_external、
|
||||
* 分管领导按**受理部门**定位、单据类型显示名取流程定义名称。</p>
|
||||
*/
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles({"gxtyzx-test", "test"})
|
||||
class ApprovalCoordinationExternalIT {
|
||||
|
||||
private static final int TENANT = TestAuthentication.TENANT_ID;
|
||||
private static final String LIST = "approval:instance:list";
|
||||
private static final String SUBMIT = "approval:instance:submit";
|
||||
private static final String APPROVE = "approval:instance:approve";
|
||||
private static final String ARCHIVE = "approval:instance:archive";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
private ApprovalTestData data;
|
||||
private ApprovalTestData.TestUser reporter;
|
||||
private Integer reportOrgId;
|
||||
private Integer acceptOrgId;
|
||||
|
||||
private ApprovalTestData.TestUser reportLeader;
|
||||
private ApprovalTestData.TestUser acceptLeader;
|
||||
private ApprovalTestData.TestUser acceptDeputy;
|
||||
private ApprovalTestData.TestUser dispatchTarget;
|
||||
private ApprovalTestData.TestUser archivist;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
data = new ApprovalTestData(dataSource);
|
||||
String suffix = data.uniqueSuffix();
|
||||
reportOrgId = data.createOrganization("外部报送部门" + suffix, 136);
|
||||
acceptOrgId = data.createOrganization("外部受理部门" + suffix, 136);
|
||||
reporter = data.createUser("外部报送人" + suffix, reportOrgId);
|
||||
|
||||
ApprovalTestData.TestRole reportLeaderRole =
|
||||
data.createRole(data.leaderRoleCode(reportOrgId), "报送部门负责人");
|
||||
reportLeader = data.createUser("外部报送负责人" + suffix, reportOrgId);
|
||||
data.bind(reportLeader.userId, reportLeaderRole.roleId);
|
||||
|
||||
ApprovalTestData.TestRole acceptLeaderRole =
|
||||
data.createRole(data.leaderRoleCode(acceptOrgId), "受理部门负责人");
|
||||
acceptLeader = data.createUser("外部受理负责人" + suffix, acceptOrgId);
|
||||
data.bind(acceptLeader.userId, acceptLeaderRole.roleId);
|
||||
|
||||
// 分管领导按受理部门定位:只有受理部门的分管角色有人
|
||||
ApprovalTestData.TestRole acceptDeputyRole = data.createRole(
|
||||
ApprovalConstants.departmentDeputyRoleCode(data.organizationCode(acceptOrgId)), "受理部门分管领导");
|
||||
acceptDeputy = data.createUser("外部受理分管" + suffix, acceptOrgId);
|
||||
data.bind(acceptDeputy.userId, acceptDeputyRole.roleId);
|
||||
|
||||
ApprovalTestData.TestRole staffRole = data.createRole(ApprovalConstants.ROLE_STAFF, "员工");
|
||||
data.bind(reporter.userId, staffRole.roleId);
|
||||
dispatchTarget = data.createUser("外部派单对象" + suffix, acceptOrgId);
|
||||
data.bind(dispatchTarget.userId, staffRole.roleId);
|
||||
|
||||
ApprovalTestData.TestRole archivistRole =
|
||||
data.createRole(ApprovalConstants.ROLE_ARCHIVIST, "收文员");
|
||||
archivist = data.createUser("外部收文员" + suffix, reportOrgId);
|
||||
data.bind(archivist.userId, archivistRole.roleId);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
data.cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sharesCoordinationFormButRunsExternalNodeChain() throws Exception {
|
||||
long instanceId = saveExternalDraft("外部协调:设备进场");
|
||||
uploadAttachment(instanceId);
|
||||
|
||||
assertThat(data.queryMaster("SELECT doc_type FROM approval_instance WHERE id = ?",
|
||||
String.class, instanceId)).isEqualTo(ApprovalConstants.DOC_COORDINATION);
|
||||
assertThat(data.queryMaster("SELECT flow_code FROM approval_instance WHERE id = ?",
|
||||
String.class, instanceId)).isEqualTo(ApprovalConstants.FLOW_COORDINATION_EXTERNAL);
|
||||
// 业务表共用,未新建表
|
||||
assertThat(data.queryMaster("SELECT COUNT(*) FROM approval_coordination_form WHERE instance_id = ?",
|
||||
Integer.class, instanceId)).isEqualTo(1);
|
||||
|
||||
mockMvc.perform(authed(post("/api/approval/instance/" + instanceId + "/submit"), reporter, SUBMIT))
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.docTypeName").value("外部协调单"))
|
||||
.andExpect(jsonPath("$.data.currentNodeName").value("报送部门负责人"));
|
||||
|
||||
approveFor(instanceId, reportLeader)
|
||||
.andExpect(jsonPath("$.data.currentNodeName").value("受理部门负责人"));
|
||||
approveFor(instanceId, acceptLeader)
|
||||
.andExpect(jsonPath("$.data.currentNodeName").value("分管领导"));
|
||||
approveFor(instanceId, acceptDeputy)
|
||||
.andExpect(jsonPath("$.data.currentNodeName").value("派单"));
|
||||
|
||||
// 派单:受理部门审批参与人
|
||||
mockMvc.perform(authed(post("/api/approval/task/" + pendingTaskId(instanceId, acceptLeader.userId)
|
||||
+ "/dispatch"), acceptLeader, APPROVE)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"targetUserId\":" + dispatchTarget.userId + "}"))
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.currentNodeName").value("存档"));
|
||||
|
||||
// 双人存档:报送人 + 被派单人
|
||||
mockMvc.perform(authed(post("/api/approval/task/" + pendingTaskId(instanceId, reporter.userId)
|
||||
+ "/confirm"), reporter, APPROVE))
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.status").value("PENDING"));
|
||||
mockMvc.perform(authed(post("/api/approval/task/" + pendingTaskId(instanceId, dispatchTarget.userId)
|
||||
+ "/confirm"), dispatchTarget, APPROVE))
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.status").value("APPROVED"));
|
||||
|
||||
mockMvc.perform(authed(post("/api/approval/task/" + pendingTaskId(instanceId, archivist.userId)
|
||||
+ "/archive"), archivist, ARCHIVE))
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.status").value("ARCHIVED"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deputyIsResolvedFromAcceptOrganizationNotReportOrganization() throws Exception {
|
||||
// 报送部门没有分管领导,只有受理部门有:外部流程应能正常提交
|
||||
long instanceId = saveExternalDraft("外部协调:分管归属");
|
||||
uploadAttachment(instanceId);
|
||||
mockMvc.perform(authed(post("/api/approval/instance/" + instanceId + "/submit"), reporter, SUBMIT))
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
approveFor(instanceId, reportLeader);
|
||||
approveFor(instanceId, acceptLeader);
|
||||
// 若按报送部门解析分管领导,这里会缺配
|
||||
approveFor(instanceId, acceptDeputy)
|
||||
.andExpect(jsonPath("$.data.currentNodeName").value("派单"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalFlowIsUnaffectedAndBothAppearInTypeFilter() throws Exception {
|
||||
long external = saveExternalDraft("外部协调:并存");
|
||||
uploadAttachment(external);
|
||||
mockMvc.perform(authed(post("/api/approval/instance/" + external + "/submit"), reporter, SUBMIT))
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
|
||||
String internalResponse = mockMvc.perform(authed(post("/api/approval/instance/coordination"),
|
||||
reporter, SUBMIT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"acceptOrgId\":" + acceptOrgId + ",\"title\":\"内部协调:并存\","
|
||||
+ "\"content\":\"内部内容\"}"))
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
long internal = objectMapper.readTree(internalResponse).path("data").asLong();
|
||||
data.trackInstance(internal);
|
||||
uploadAttachment(internal);
|
||||
mockMvc.perform(authed(post("/api/approval/instance/" + internal + "/submit"), reporter, SUBMIT))
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
|
||||
// 按流程类型筛选:两类单据显示名取流程定义名称
|
||||
mockMvc.perform(authed(get("/api/approval/instance/page?keywords=并存&flowCode=coordination_external"),
|
||||
archivist, LIST))
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.list.length()").value(1))
|
||||
.andExpect(jsonPath("$.data.list[0].flowCode").value("coordination_external"))
|
||||
.andExpect(jsonPath("$.data.list[0].docTypeName").value("外部协调单"));
|
||||
|
||||
mockMvc.perform(authed(get("/api/approval/instance/page?keywords=并存&flowCode=coordination_internal"),
|
||||
archivist, LIST))
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.list.length()").value(1))
|
||||
.andExpect(jsonPath("$.data.list[0].docTypeName").value("内部协调单"));
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
private long saveExternalDraft(String title) throws Exception {
|
||||
String response = mockMvc.perform(authed(post("/api/approval/instance/coordination_external"),
|
||||
reporter, SUBMIT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"acceptOrgId\":" + acceptOrgId + ",\"title\":" + quote(title)
|
||||
+ ",\"content\":\"外部协调内容\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
long instanceId = objectMapper.readTree(response).path("data").asLong();
|
||||
data.trackInstance(instanceId);
|
||||
return instanceId;
|
||||
}
|
||||
|
||||
private org.springframework.test.web.servlet.ResultActions approveFor(long instanceId,
|
||||
ApprovalTestData.TestUser approver)
|
||||
throws Exception {
|
||||
return mockMvc.perform(authed(post("/api/approval/task/" + pendingTaskId(instanceId, approver.userId)
|
||||
+ "/approve"), approver, APPROVE)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"opinion\":\"同意\"}"))
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
}
|
||||
|
||||
private long pendingTaskId(long instanceId, int userId) {
|
||||
Long taskId = data.queryMaster("SELECT id FROM approval_task WHERE instance_id = ? "
|
||||
+ "AND assignee_id = ? AND status = 'PENDING' ORDER BY id LIMIT 1", Long.class, instanceId, userId);
|
||||
assertThat(taskId).as("待办任务应存在: " + userId).isNotNull();
|
||||
return taskId;
|
||||
}
|
||||
|
||||
private void uploadAttachment(long instanceId) throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile("file", "sign.png", "image/png",
|
||||
new byte[]{(byte) 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A});
|
||||
mockMvc.perform(multipart("/api/approval/attachment/upload")
|
||||
.file(file)
|
||||
.param("instanceId", String.valueOf(instanceId))
|
||||
.param("category", ApprovalConstants.CATEGORY_SIGNED)
|
||||
.header("tenantId", TENANT)
|
||||
.with(authentication(TestAuthentication.user(reporter.userId, reporter.username,
|
||||
reportOrgId, TENANT, SUBMIT))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
}
|
||||
|
||||
private MockHttpServletRequestBuilder authed(MockHttpServletRequestBuilder builder,
|
||||
ApprovalTestData.TestUser user,
|
||||
String... authorities) {
|
||||
return builder.header("tenantId", TENANT)
|
||||
.with(authentication(TestAuthentication.user(user.userId, user.username,
|
||||
user.userId == reporter.userId ? reportOrgId : acceptOrgId, TENANT, authorities)));
|
||||
}
|
||||
|
||||
private String quote(String value) {
|
||||
return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user