package jnpf.elnService.template; import feign.Request; import feign.Response; import jnpf.base.vo.DownloadVO; import jnpf.elnEntity.ElnDocumentFileVo; import jnpf.exception.DataException; import jnpf.file.FileApi; import jnpf.file.FileUploadApi; import jnpf.model.ExportModel; import jnpf.model.FileListVO; import jnpf.model.upload.UploadFileModel; import jnpf.util.JsonUtil; import org.dromara.x.file.storage.core.FileInfo; import org.junit.Test; import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class ElnTemplateFileServiceTest { @Test public void copiesUniqueAuthoritativeDocxAndClosesResponses() { Fixture fixture = new Fixture(); String attachments = fixture.service.copyDocx(sourceAttachments(), "target-template"); List files = JsonUtil.getJsonToList(attachments, ElnDocumentFileVo.class); assertEquals(1, files.size()); assertEquals("target-template.docx", files.get(0).getFileId()); assertEquals("docx", files.get(0).getFileExtension()); assertNotEquals("source.docx", files.get(0).getFileId()); assertArrayEquals(Fixture.DOCX, fixture.uploadApi.files.get("target-template.docx")); assertEquals(Collections.singletonList("target-template.docx"), fixture.uploadApi.replacedIds); assertEquals(2, fixture.uploadApi.closedBodies); assertEquals("annex", fixture.uploadApi.downloads.get(0).getFolderName()); assertEquals("source.docx", fixture.uploadApi.downloads.get(0).getObjectName()); assertEquals("WebAnnexFile/", fixture.uploadApi.lastFolder); assertEquals(0, fixture.uploadApi.uploadCalls); } @Test public void retriesByOverwritingTheSameDeterministicTarget() { Fixture fixture = new Fixture(); fixture.service.copyDocx(sourceAttachments(), "target-template"); fixture.service.copyDocx(sourceAttachments(), "target-template"); assertEquals(2, fixture.uploadApi.replacedIds.size()); assertEquals("target-template.docx", fixture.uploadApi.replacedIds.get(0)); assertEquals("target-template.docx", fixture.uploadApi.replacedIds.get(1)); assertEquals(2, fixture.uploadApi.files.size()); assertTrue(fixture.uploadApi.files.containsKey("target-template.docx")); } @Test public void rejectsReplaceFalseWithoutReturningAttachmentJson() { Fixture fixture = new Fixture(); fixture.uploadApi.replaceResult = false; expectDataException(() -> fixture.service.copyDocx(sourceAttachments(), "target-template")); assertEquals(1, fixture.uploadApi.replacedIds.size()); } @Test public void rejectsFailedTargetReadback() { Fixture fixture = new Fixture(); fixture.uploadApi.failTargetReadback = true; expectDataException(() -> fixture.service.copyDocx(sourceAttachments(), "target-template")); } @Test public void rejectsTargetDigestMismatch() { Fixture fixture = new Fixture(); fixture.uploadApi.corruptTargetReadback = true; expectDataException(() -> fixture.service.copyDocx(sourceAttachments(), "target-template")); } @Test public void rejectsAmbiguousEmptyAndOversizedSourceDocuments() { Fixture fixture = new Fixture(); expectDataException(() -> fixture.service.copyDocx( "[{\"fileId\":\"a.docx\",\"fileExtension\":\"docx\"}," + "{\"fileId\":\"b.docx\",\"fileExtension\":\"docx\"}]", "target")); fixture.uploadApi.files.put("source.docx", new byte[0]); expectDataException(() -> fixture.service.copyDocx(sourceAttachments(), "target")); fixture.uploadApi.reportedLength = 50 * 1024 * 1024 + 1; expectDataException(() -> fixture.service.copyDocx(sourceAttachments(), "target")); } @Test public void acceptsUnknownContentLengthAfterReadingActualBytes() { Fixture fixture = new Fixture(); fixture.uploadApi.reportedLength = -1; String attachments = fixture.service.copyDocx(sourceAttachments(), "target-template"); assertTrue(attachments.contains("target-template.docx")); } private static String sourceAttachments() { return "[{\"fileId\":\"source.docx\",\"name\":\"source.docx\"," + "\"fileExtension\":\"docx\"}]"; } private static void expectDataException(Runnable action) { try { action.run(); fail("预期抛出 DataException"); } catch (DataException expected) { assertFalse(expected.getMessage().trim().isEmpty()); } } private static final class Fixture { private static final byte[] DOCX = "docx-content".getBytes(StandardCharsets.UTF_8); private final StubFileApi fileApi = new StubFileApi(); private final StubFileUploadApi uploadApi = new StubFileUploadApi(); private final ElnTemplateFileService service = new ElnTemplateFileService(fileApi, uploadApi); private Fixture() { uploadApi.files.put("source.docx", DOCX); } } private static final class StubFileApi implements FileApi { @Override public String getPath(String type) { assertEquals("annex", type); return "WebAnnexFile/"; } @Override public DownloadVO exportFile(ExportModel exportModel) { return null; } @Override public String getLocalBasePath() { return null; } @Override public String getAllowPreviewFileType() { return null; } @Override public Boolean fileExists(String type, String fileName) { return null; } } private static final class StubFileUploadApi implements FileUploadApi { private final Map files = new LinkedHashMap<>(); private final List replacedIds = new ArrayList<>(); private final List downloads = new ArrayList<>(); private boolean replaceResult = true; private boolean failTargetReadback; private boolean corruptTargetReadback; private Integer reportedLength; private int uploadCalls; private int closedBodies; private String lastFolder; @Override public FileInfo uploadFile(MultipartFile multipartFile, String folderName, String fileName) { uploadCalls++; return null; } @Override public boolean replaceFile(MultipartFile multipartFile, String folderName, String fileName) { replacedIds.add(fileName); lastFolder = folderName; if (replaceResult) { try { files.put(fileName, multipartFile.getBytes()); } catch (IOException e) { throw new IllegalStateException(e); } } return replaceResult; } @Override public byte[] getByte(String fileName, String folderName) { return null; } @Override public List getFileList(String folderName) { return Collections.emptyList(); } @Override public Response downFile(UploadFileModel model) { downloads.add(model); boolean target = model.getObjectName().startsWith("target-"); if (target && failTargetReadback) { return response(404, null); } byte[] bytes = files.get(model.getObjectName()); if (target && corruptTargetReadback && bytes != null) { bytes = "different-content".getBytes(StandardCharsets.UTF_8); } return response(bytes == null ? 404 : 200, bytes); } private Response response(int status, byte[] bytes) { return Response.builder() .status(status) .reason(status == 200 ? "OK" : "Not Found") .request(Request.create(Request.HttpMethod.GET, "http://file/download", Collections.>emptyMap(), null, Charset.defaultCharset())) .body(bytes == null ? null : new TrackingBody(bytes, reportedLength == null ? bytes.length : reportedLength, this)) .build(); } @Override public FileInfo getFile(String fileId) { return null; } @Override public String getDefaultPlatform() { return null; } @Override public List getVisualList(String folderName) { return Collections.emptyList(); } } private static final class TrackingBody implements Response.Body { private final byte[] bytes; private final int length; private final StubFileUploadApi owner; private TrackingBody(byte[] bytes, int length, StubFileUploadApi owner) { this.bytes = bytes; this.length = length; this.owner = owner; } @Override public Integer length() { return length; } @Override public boolean isRepeatable() { return true; } @Override public InputStream asInputStream() { return new ByteArrayInputStream(bytes); } @Override public Reader asReader(Charset charset) { throw new UnsupportedOperationException(); } @Override public void close() { owner.closedBodies++; } } }