fix: 审批附件落盘改到上传根目录,修复预览/下载 404(#44)
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package com.gxwebsoft.approval;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.MvcResult;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 附件「上传 → 文件端点取回」往返集成测试(回归 #44)。
|
||||
*
|
||||
* <p>附件上传成功后,H5 详情页按返回的 url 直接打开原文件;该 url 依赖
|
||||
* sys_file_record.path 与落盘物理路径一致。路径错位时预览/下载全部 404,
|
||||
* 且只有一次真实往返能发现。</p>
|
||||
*
|
||||
* <p>不加 {@code @Transactional}:下载端点经 {@code @DS("server")} 的
|
||||
* FileRecordService 查文件记录,测试事务会把连接钉死在 master(该表在 core 库),
|
||||
* 与生产无事务的调用形态不符;数据隔离由 {@link ApprovalTestData#cleanup()} 负责。</p>
|
||||
*/
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles({"gxtyzx-test", "test"})
|
||||
class ApprovalAttachmentFileRoundTripIT {
|
||||
|
||||
private static final int TENANT = TestAuthentication.TENANT_ID;
|
||||
private static final String SUBMIT = "approval:instance:submit";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
private ApprovalTestData data;
|
||||
private ApprovalTestData.TestUser reporter;
|
||||
private Integer organizationId;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
data = new ApprovalTestData(dataSource);
|
||||
organizationId = data.createOrganization("测试部门" + data.uniqueSuffix(), 136);
|
||||
reporter = data.createUser("报送人" + data.uniqueSuffix(), organizationId);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
data.cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
void uploadedAttachmentIsFetchableThroughPreviewAndDownloadEndpoints() throws Exception {
|
||||
long instanceId = createCompletionDraft("附件往返");
|
||||
JsonNode attachment = uploadAttachment(instanceId);
|
||||
String url = attachment.path("url").asText();
|
||||
assertThat(url).contains("/file/");
|
||||
|
||||
// 前端拿到的 url 去掉 scheme/host 后必须能直接 GET 到原文件(浏览器 window.open 无登录态)
|
||||
String fetchPath = "/api" + url.substring(url.indexOf("/file/"));
|
||||
byte[] payload = pngBytes();
|
||||
|
||||
MvcResult preview = mockMvc.perform(get(fetchPath))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
assertThat(preview.getResponse().getContentAsByteArray()).isEqualTo(payload);
|
||||
|
||||
// 下载端点走同一条物理路径解析,一并回归
|
||||
String downloadPath = fetchPath.replace("/api/file/", "/api/file/download/");
|
||||
MvcResult download = mockMvc.perform(get(downloadPath))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
assertThat(download.getResponse().getContentAsByteArray()).isEqualTo(payload);
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
private long createCompletionDraft(String name) throws Exception {
|
||||
String body = "{\"name\":\"" + name + "\",\"implementOrg\":\"实施单位\",\"fee\":\"1.00\","
|
||||
+ "\"serviceContent\":\"服务内容\"}";
|
||||
String response = mockMvc.perform(authed(post("/api/approval/instance/completion"))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
long id = objectMapper.readTree(response).path("data").asLong();
|
||||
data.trackInstance(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private JsonNode uploadAttachment(long instanceId) throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile("file", "sign.png", "image/png", pngBytes());
|
||||
String response = mockMvc.perform(multipart("/api/approval/attachment/upload")
|
||||
.file(file)
|
||||
.param("instanceId", String.valueOf(instanceId))
|
||||
.param("category", "SIGNED")
|
||||
.header("tenantId", TENANT)
|
||||
.with(authentication(TestAuthentication.user(reporter.userId, reporter.username, organizationId, TENANT, SUBMIT))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
return objectMapper.readTree(response).path("data");
|
||||
}
|
||||
|
||||
private byte[] pngBytes() {
|
||||
return new byte[]{(byte) 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A};
|
||||
}
|
||||
|
||||
private org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder authed(
|
||||
org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder builder) {
|
||||
return builder.header("tenantId", TENANT)
|
||||
.with(authentication(TestAuthentication.user(reporter.userId, reporter.username, organizationId, TENANT, SUBMIT)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user