package jnpf.fileStorage.dms; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import jnpf.dmsEntity.storage.enums.DmsWorkflowFileType; import jnpf.dmsEntity.storage.form.DmsWorkflowFileForm; import jnpf.dmsEntity.storage.vo.DmsFileUploadVO; import jnpf.dmsEntity.storage.vo.DmsWorkflowFileUploadVO; import jnpf.exception.DataException; import org.junit.Test; import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class DmsCommonFileReaderTest { private final ObjectMapper objectMapper = new ObjectMapper(); private final DmsCommonFileStorage commonFileStorage = mock(DmsCommonFileStorage.class); private final DmsCommonFileReader reader = new DmsCommonFileReader( objectMapper, commonFileStorage); @Test public void parsesFileCommonRelativeAndAbsoluteUrls() { DmsCommonFileReader.SourceFile relative = reader.parse( "/api/file/Image/annex/stored%20file.docx?s=ticket"); DmsCommonFileReader.SourceFile absolute = reader.parse( "https://files.example.com/api/file/Image/temporary/id.pdf?fullfilename=a.pdf"); assertEquals("annex", relative.getType()); assertEquals("stored file.docx", relative.getFileName()); assertEquals("temporary", absolute.getType()); assertEquals("id.pdf", absolute.getFileName()); } @Test public void rejectsUrlsOutsideFileCommonAndEncodedPathTraversal() { assertThrows(DataException.class, () -> reader.parse("https://example.com/file.docx")); assertThrows(DataException.class, () -> reader.parse("/api/file/Image/annex/%2E%2E%2Fsecret.docx")); } @Test @SuppressWarnings("unchecked") public void readsStoredContentAndRestoresOriginalNameFromFileJson() { byte[] content = "controlled document".getBytes(StandardCharsets.UTF_8); Map fileJson = new LinkedHashMap<>(); fileJson.put("fileId", "stored-file.docx"); fileJson.put("name", "质量手册.docx"); when(commonFileStorage.exists("annex", "stored-file.docx")).thenReturn(true); doAnswer(invocation -> { Consumer consumer = invocation.getArgument(2); consumer.accept(new ByteArrayInputStream(content)); return null; }).when(commonFileStorage).download(eq("annex"), eq("stored-file.docx"), any()); String result = reader.withFile( "/api/file/Image/annex/stored-file.docx", fileJson, "质量手册", file -> verifyFile(file, content)); assertEquals("stored", result); } @Test @SuppressWarnings("unchecked") public void readsDownloadUrlUsingFileIdFromFileJson() { byte[] content = "approval page".getBytes(StandardCharsets.UTF_8); String url = "/api/file/Download?encryption=ticket&name=%E7%AD%BE%E6%89%B9%E9%A1%B52.0.docx"; Map fileJson = new LinkedHashMap<>(); fileJson.put("fileId", "6aad3081cdb2f4d33f7ad915.docx"); fileJson.put("name", "签批页2.0.docx"); fileJson.put("url", url); when(commonFileStorage.exists("annex", "6aad3081cdb2f4d33f7ad915.docx")) .thenReturn(true); doAnswer(invocation -> { Consumer consumer = invocation.getArgument(2); consumer.accept(new ByteArrayInputStream(content)); return null; }).when(commonFileStorage).download( eq("annex"), eq("6aad3081cdb2f4d33f7ad915.docx"), any()); String result = reader.withFile(url, fileJson, "签批页", file -> verifyFile(file, content, "签批页2.0.docx")); assertEquals("stored", result); } @Test public void rejectsDownloadUrlWithoutSafeFileId() { String url = "/api/file/Download?encryption=ticket"; assertThrows(DataException.class, () -> reader.withFile(url, new LinkedHashMap<>(), "签批页", file -> null)); Map unsafe = new LinkedHashMap<>(); unsafe.put("fileId", "../secret.docx"); unsafe.put("url", url); assertThrows(DataException.class, () -> reader.withFile(url, unsafe, "签批页", file -> null)); } @Test public void acceptsFrontendAliasesAndSerializesFlatResponseWithFileJson() throws Exception { DmsWorkflowFileForm form = objectMapper.readValue( "{\"filetTpe\":\"修定\",\"fileJson\":{\"name\":\"a.docx\"}}", DmsWorkflowFileForm.class); assertEquals(DmsWorkflowFileType.REVISE, form.getFileType()); assertEquals("a.docx", ((Map) form.getFileJson()).get("name")); DmsFileUploadVO file = new DmsFileUploadVO(); file.setDocumentNo("DOC-001"); Map fileJson = new LinkedHashMap<>(); fileJson.put("name", "a.docx"); DmsWorkflowFileUploadVO response = new DmsWorkflowFileUploadVO(file, fileJson); JsonNode json = objectMapper.readTree(objectMapper.writeValueAsString(response)); assertEquals("DOC-001", json.get("documentNo").asText()); assertEquals("a.docx", json.get("file_json").get("name").asText()); assertFalse(json.has("file")); assertSame(fileJson, response.getFileJson()); } private String verifyFile(MultipartFile file, byte[] expected) { return verifyFile(file, expected, "质量手册.docx"); } private String verifyFile(MultipartFile file, byte[] expected, String expectedName) { try { assertEquals(expectedName, file.getOriginalFilename()); assertArrayEquals(expected, file.getBytes()); return "stored"; } catch (Exception exception) { throw new AssertionError(exception); } } }