package jnpf.fileStorage.dms;
|
|
import jakarta.servlet.http.HttpServletResponse;
|
import jnpf.dmsEntity.preview.model.DmsFilePreviewTicket;
|
import jnpf.dmsEntity.preview.model.DmsWatermarkSnapshot;
|
import jnpf.dmsEntity.storage.model.DmsPersistentPdfSource;
|
import jnpf.exception.DataException;
|
import jnpf.util.JsonUtil;
|
import org.dromara.x.file.storage.core.FileInfo;
|
import org.dromara.x.file.storage.core.FileStorageService;
|
import org.dromara.x.file.storage.core.platform.MinioFileStorage;
|
import org.junit.Test;
|
|
import java.io.OutputStream;
|
import java.util.Collections;
|
import java.util.UUID;
|
|
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertNull;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.junit.Assert.fail;
|
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.when;
|
|
public class DmsOnlyOfficeFileServiceTest {
|
private static final UUID VERSION_ID =
|
UUID.fromString("018f0000-0000-7000-8000-000000000702");
|
|
@Test
|
public void parsesValidBoundPreviewPayload() {
|
DmsFilePreviewTicket payload = payload(60_000L);
|
DmsOnlyOfficeFileService service = service(payload);
|
|
assertEquals(VERSION_ID, service.parse("ticket").getFileVersionId());
|
}
|
|
@Test
|
public void parsesValidPrintScopedPayload() {
|
DmsFilePreviewTicket payload = payload(60_000L);
|
payload.setScope(DmsFilePreviewTicket.PRINT_SCOPE);
|
|
assertEquals(DmsFilePreviewTicket.PRINT_SCOPE,
|
service(payload).parse("ticket").getScope());
|
}
|
|
@Test(expected = DataException.class)
|
public void rejectsExpiredPreviewPayload() {
|
DmsOnlyOfficeFileService service = service(payload(-1L));
|
|
service.parse("ticket");
|
}
|
|
@Test
|
public void rejectsVersionMismatchBeforeAccessingStorage() {
|
FileStorageService storageService = mock(FileStorageService.class);
|
DmsFilePreviewTicket payload = payload(60_000L);
|
String json = JsonUtil.getObjectToString(payload);
|
DmsOnlyOfficeFileService service = new DmsOnlyOfficeFileService(storageService, ticket -> json,
|
mock(DmsFileWatermarkService.class));
|
|
try {
|
service.stream(UUID.fromString("018f0000-0000-7000-8000-000000000799"),
|
"ticket", mock(HttpServletResponse.class));
|
fail("expected version mismatch");
|
} catch (DataException expected) {
|
assertEquals("DMS 预览票据与文件版本不匹配", expected.getMessage());
|
}
|
|
verify(storageService, never()).getFileStorageVerify("seaweedfs-1");
|
}
|
|
@Test
|
public void rejectsClientPrintTicketForInternalPrintBaseStream() {
|
FileStorageService storageService = mock(FileStorageService.class);
|
DmsFilePreviewTicket payload = payload(60_000L);
|
payload.setScope(DmsFilePreviewTicket.PRINT_SCOPE);
|
DmsOnlyOfficeFileService service = new DmsOnlyOfficeFileService(storageService,
|
ticket -> JsonUtil.getObjectToString(payload), mock(DmsFileWatermarkService.class));
|
|
try {
|
service.stream(VERSION_ID, "ticket", true, mock(HttpServletResponse.class));
|
fail("expected internal print-base scope rejection");
|
} catch (DataException expected) {
|
assertEquals("只有内部打印基础票据可以读取基础源文件", expected.getMessage());
|
}
|
|
verify(storageService, never()).getFileStorageVerify("seaweedfs-1");
|
}
|
|
@Test
|
public void dynamicallyWatermarksPngAndReturnsInlinePreviewHeaders() throws Exception {
|
byte[] sourceBytes = new byte[]{(byte) 0x89, 0x50, 0x4e, 0x47, 1};
|
byte[] watermarkedBytes = new byte[]{(byte) 0x89, 0x50, 0x4e, 0x47, 2, 3};
|
DmsFilePreviewTicket payload = imagePayload(sourceBytes.length);
|
FileStorageService storageService = storage(payload, sourceBytes);
|
DmsFileWatermarkService watermarkService = mock(DmsFileWatermarkService.class);
|
when(watermarkService.loadAndApply(any(), any(DmsFilePreviewTicket.class)))
|
.thenAnswer(invocation -> {
|
DmsImageWatermarkService.SourceReader reader = invocation.getArgument(0);
|
DmsFilePreviewTicket parsed = invocation.getArgument(1);
|
assertEquals(VERSION_ID, parsed.getFileVersionId());
|
assertArrayEquals(sourceBytes, reader.read());
|
return watermarkedBytes;
|
});
|
DmsOnlyOfficeFileService service = new DmsOnlyOfficeFileService(storageService,
|
ticket -> JsonUtil.getObjectToString(payload), watermarkService);
|
org.springframework.mock.web.MockHttpServletResponse response =
|
new org.springframework.mock.web.MockHttpServletResponse();
|
|
service.stream(VERSION_ID, "ticket", response);
|
|
assertArrayEquals(watermarkedBytes, response.getContentAsByteArray());
|
assertEquals("image/png", response.getContentType());
|
assertEquals(watermarkedBytes.length, response.getContentLength());
|
assertEquals("private, no-store", response.getHeader("Cache-Control"));
|
assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
|
org.springframework.http.ContentDisposition disposition = org.springframework.http.ContentDisposition
|
.parse(response.getHeader("Content-Disposition"));
|
assertEquals("inline", disposition.getType());
|
assertEquals("diagram.png", disposition.getFilename());
|
verify(watermarkService).loadAndApply(any(), any(DmsFilePreviewTicket.class));
|
}
|
|
@Test
|
public void watermarkFailureDoesNotWriteOriginalPngToPreviewResponse() throws Exception {
|
byte[] sourceBytes = new byte[]{(byte) 0x89, 0x50, 0x4e, 0x47, 1};
|
DmsFilePreviewTicket payload = imagePayload(sourceBytes.length);
|
FileStorageService storageService = storage(payload, sourceBytes);
|
DmsFileWatermarkService watermarkService = mock(DmsFileWatermarkService.class);
|
when(watermarkService.loadAndApply(any(), any(DmsFilePreviewTicket.class)))
|
.thenAnswer(invocation -> {
|
DmsImageWatermarkService.SourceReader reader = invocation.getArgument(0);
|
DmsFilePreviewTicket parsed = invocation.getArgument(1);
|
assertEquals(VERSION_ID, parsed.getFileVersionId());
|
assertArrayEquals(sourceBytes, reader.read());
|
throw new DataException("图片水印生成失败");
|
});
|
DmsOnlyOfficeFileService service = new DmsOnlyOfficeFileService(storageService,
|
ticket -> JsonUtil.getObjectToString(payload), watermarkService);
|
org.springframework.mock.web.MockHttpServletResponse response =
|
new org.springframework.mock.web.MockHttpServletResponse();
|
|
try {
|
service.stream(VERSION_ID, "ticket", response);
|
fail("expected image watermark failure");
|
} catch (DataException expected) {
|
assertEquals("图片水印生成失败", expected.getMessage());
|
}
|
|
assertEquals(0, response.getContentAsByteArray().length);
|
assertNull(response.getContentType());
|
assertNull(response.getHeader("Content-Disposition"));
|
verify(watermarkService).loadAndApply(any(), any(DmsFilePreviewTicket.class));
|
}
|
|
@Test
|
public void missingPersistentPdfFallsBackToOriginalWordForPrintConversion() throws Exception {
|
DmsFilePreviewTicket payload = payload(60_000L);
|
payload.setScope(DmsFilePreviewTicket.PRINT_BASE_SCOPE);
|
DmsPersistentPdfSource pdf = new DmsPersistentPdfSource();
|
pdf.setObjectKey("dms-derived/pdf/version/missing.pdf");
|
pdf.setSizeBytes(128L);
|
pdf.setSourceChecksumSha256("a".repeat(64));
|
payload.setPdfSource(pdf);
|
FileStorageService storageService = mock(FileStorageService.class, RETURNS_DEEP_STUBS);
|
MinioFileStorage storage = mock(MinioFileStorage.class);
|
when(storage.getBucketName()).thenReturn("jnpf-dms");
|
when(storageService.getFileStorageVerify("seaweedfs-1")).thenReturn(storage);
|
when(storageService.exists(any(FileInfo.class))).thenReturn(false);
|
Class<?> downloaderType = FileStorageService.class
|
.getMethod("download", FileInfo.class).getReturnType();
|
Object downloader = mock(downloaderType, invocation -> null);
|
when(storageService.download(any(FileInfo.class))).thenAnswer(invocation -> downloader);
|
DmsPersistentPdfRecoveryReporter reporter = mock(DmsPersistentPdfRecoveryReporter.class);
|
DmsOnlyOfficeFileService service = new DmsOnlyOfficeFileService(storageService,
|
ticket -> JsonUtil.getObjectToString(payload), mock(DmsFileWatermarkService.class), reporter);
|
|
service.stream(VERSION_ID, "ticket", true,
|
new org.springframework.mock.web.MockHttpServletResponse());
|
|
org.mockito.ArgumentCaptor<FileInfo> source =
|
org.mockito.ArgumentCaptor.forClass(FileInfo.class);
|
verify(storageService).download(source.capture());
|
assertEquals("2026/08/24/document/", source.getValue().getPath());
|
assertEquals("quality-manual-v1.docx", source.getValue().getFilename());
|
verify(reporter).report(payload, "持久化 PDF 对象不存在或检查失败");
|
}
|
|
private DmsOnlyOfficeFileService service(DmsFilePreviewTicket payload) {
|
String json = JsonUtil.getObjectToString(payload);
|
return new DmsOnlyOfficeFileService(mock(FileStorageService.class), ticket -> json,
|
mock(DmsFileWatermarkService.class));
|
}
|
|
private FileStorageService storage(DmsFilePreviewTicket payload, byte[] sourceBytes) throws Exception {
|
FileStorageService storageService = mock(FileStorageService.class);
|
MinioFileStorage storage = mock(MinioFileStorage.class);
|
when(storage.getBucketName()).thenReturn(payload.getBucketName());
|
when(storageService.getFileStorageVerify(payload.getStoragePlatform())).thenReturn(storage);
|
Class<?> downloaderType = FileStorageService.class
|
.getMethod("download", FileInfo.class).getReturnType();
|
Object downloader = mock(downloaderType, invocation -> {
|
if ("outputStream".equals(invocation.getMethod().getName())) {
|
((OutputStream) invocation.getArgument(0)).write(sourceBytes);
|
}
|
return null;
|
});
|
when(storageService.download(any(FileInfo.class))).thenAnswer(invocation -> downloader);
|
return storageService;
|
}
|
|
private DmsFilePreviewTicket imagePayload(long sizeBytes) {
|
DmsFilePreviewTicket payload = payload(60_000L);
|
payload.setFileName("diagram.png");
|
payload.setMimeType("image/png");
|
payload.setSizeBytes(sizeBytes);
|
payload.setObjectPath("2026/08/24/image/");
|
payload.setObjectName("diagram-v1.png");
|
payload.setWatermarks(Collections.singletonList(new DmsWatermarkSnapshot()));
|
return payload;
|
}
|
|
private DmsFilePreviewTicket payload(long expiresInMillis) {
|
DmsFilePreviewTicket payload = new DmsFilePreviewTicket();
|
payload.setScope(DmsFilePreviewTicket.SCOPE);
|
payload.setTenantId("tenant-1");
|
payload.setUserId("user-1");
|
payload.setFileVersionId(VERSION_ID);
|
payload.setFileName("quality-manual.docx");
|
payload.setMimeType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
payload.setSizeBytes(2048L);
|
payload.setStoragePlatform("seaweedfs-1");
|
payload.setBucketName("jnpf-dms");
|
payload.setBasePath("dms/");
|
payload.setObjectPath("2026/08/24/document/");
|
payload.setObjectName("quality-manual-v1.docx");
|
payload.setExpiresAt(System.currentTimeMillis() + expiresInMillis);
|
return payload;
|
}
|
}
|