package jnpf.dmsService;
|
|
import jnpf.base.UserInfo;
|
import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity;
|
import jnpf.dmsEntity.permission.enums.DmsPermissionAction;
|
import jnpf.dmsEntity.permission.enums.DmsResourceType;
|
import jnpf.dmsEntity.preview.enums.DmsPreviewFormat;
|
import jnpf.dmsEntity.preview.form.DmsFilePreviewTicketRequest;
|
import jnpf.dmsEntity.preview.model.DmsFilePreviewTicket;
|
import jnpf.dmsEntity.preview.vo.DmsFilePreviewTicketVO;
|
import jnpf.dmsEntity.storage.entity.DmsFileObjectEntity;
|
import jnpf.dmsMapper.DmsFileObjectMapper;
|
import jnpf.dmsMapper.DmsFileVersionMapper;
|
import jnpf.dmsPermission.DmsCurrentIdentity;
|
import jnpf.dmsPermission.DmsPermissionError;
|
import jnpf.dmsPermission.DmsPermissionException;
|
import jnpf.dmsPermission.DmsPermissionExecutionContext;
|
import jnpf.dmsPermission.DmsPermissionGuard;
|
import jnpf.dmsPermission.DmsPermissionOperation;
|
import jnpf.dmsService.pdf.DmsPdfGenerationTaskService;
|
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.provider.ValueSource;
|
import org.mockito.ArgumentCaptor;
|
|
import java.util.UUID;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.when;
|
|
class DmsFilePreviewServiceTest {
|
private final DmsCurrentIdentity currentIdentity = mock(DmsCurrentIdentity.class);
|
private final DmsPermissionGuard permissionGuard = mock(DmsPermissionGuard.class);
|
private final DmsFileVersionMapper versionMapper = mock(DmsFileVersionMapper.class);
|
private final DmsFileObjectMapper objectMapper = mock(DmsFileObjectMapper.class);
|
private final DmsFilePreviewTicketIssuer ticketIssuer = mock(DmsFilePreviewTicketIssuer.class);
|
private final DmsFileOperationLogService operationLogService = mock(DmsFileOperationLogService.class);
|
private final DmsWatermarkPreviewResolver watermarkPreviewResolver = mock(DmsWatermarkPreviewResolver.class);
|
private final DmsPreviewPreparationClient previewPreparationClient = mock(DmsPreviewPreparationClient.class);
|
private final DmsPdfGenerationTaskService pdfGenerationTaskService = mock(DmsPdfGenerationTaskService.class);
|
private final DmsFilePreviewService service = new DmsFilePreviewService(currentIdentity,
|
permissionGuard, versionMapper, objectMapper, ticketIssuer, operationLogService,
|
watermarkPreviewResolver, previewPreparationClient, pdfGenerationTaskService);
|
private final UUID documentId = UUID.fromString("018f0000-0000-7000-8000-000000000701");
|
private final UUID versionId = UUID.fromString("018f0000-0000-7000-8000-000000000702");
|
private final UUID operationId = UUID.fromString("018f0000-0000-7000-8000-000000000703");
|
|
@BeforeEach
|
void setUp() {
|
UserInfo user = new UserInfo();
|
user.setTenantId("tenant-1");
|
user.setUserId("user-1");
|
when(currentIdentity.requireUser()).thenReturn(user);
|
when(currentIdentity.tenantId(user)).thenReturn("tenant-1");
|
when(permissionGuard.execute(any(), any())).thenAnswer(invocation -> {
|
DmsPermissionOperation<?> operation = invocation.getArgument(1);
|
return operation.execute();
|
});
|
when(ticketIssuer.issue(any(DmsFilePreviewTicket.class),
|
org.mockito.ArgumentMatchers.eq(DmsFilePreviewService.TICKET_TTL_SECONDS)))
|
.thenReturn("preview-ticket");
|
when(watermarkPreviewResolver.resolve(any(), any(), any(), any()))
|
.thenReturn(java.util.Collections.emptyList());
|
when(previewPreparationClient.prepare(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
}
|
|
@Test
|
void documentPreviewResolvesCurrentVersionAndGuardsExactVersion() {
|
DmsFileVersionEntity version = version();
|
when(versionMapper.selectCurrentEffective("tenant-1", documentId)).thenReturn(version);
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(fileObject());
|
|
long before = System.currentTimeMillis();
|
DmsFilePreviewTicketVO result = service.createTicket(request(DmsResourceType.DOCUMENT, documentId));
|
|
assertEquals(versionId, result.getFileVersionId());
|
assertEquals("quality-manual.docx", result.getFileName());
|
assertEquals("preview-ticket", result.getAccessTicket());
|
assertTrue(result.getExpiresAt().getTime() >= before + 299_000L);
|
|
ArgumentCaptor<DmsPermissionExecutionContext> contextCaptor =
|
ArgumentCaptor.forClass(DmsPermissionExecutionContext.class);
|
verify(permissionGuard).execute(contextCaptor.capture(), any());
|
DmsPermissionExecutionContext context = contextCaptor.getValue();
|
assertEquals(DmsResourceType.FILE_VERSION, context.getResourceType());
|
assertEquals(versionId, context.getResourceId());
|
assertEquals(DmsPermissionAction.READ, context.getActionCode());
|
assertEquals(operationId, context.getOperationId());
|
assertNotNull(context.getSessionExpiresAt());
|
|
ArgumentCaptor<DmsFilePreviewTicket> payloadCaptor =
|
ArgumentCaptor.forClass(DmsFilePreviewTicket.class);
|
verify(ticketIssuer).issue(payloadCaptor.capture(),
|
org.mockito.ArgumentMatchers.eq(DmsFilePreviewService.TICKET_TTL_SECONDS));
|
DmsFilePreviewTicket payload = payloadCaptor.getValue();
|
assertEquals("tenant-1", payload.getTenantId());
|
assertEquals("user-1", payload.getUserId());
|
assertEquals(versionId, payload.getFileVersionId());
|
assertEquals("seaweedfs-1", payload.getStoragePlatform());
|
assertEquals(DmsPreviewFormat.PDF, payload.getPreviewFormat());
|
verify(operationLogService).recordView(any(), org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(documentId), org.mockito.ArgumentMatchers.eq(versionId),
|
org.mockito.ArgumentMatchers.eq("quality-manual.docx"),
|
org.mockito.ArgumentMatchers.eq(operationId));
|
verify(pdfGenerationTaskService).enqueueMissingEligible("tenant-1", versionId,
|
"quality-manual.docx", "a".repeat(64));
|
}
|
|
@ParameterizedTest
|
@ValueSource(strings = {"download", "print", "preview", "other", ""})
|
void previewTicketSelectsRequestedWatermarkScene(String watermarkType) {
|
DmsFileVersionEntity version = version();
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(fileObject());
|
DmsFilePreviewTicketRequest request = request(DmsResourceType.FILE_VERSION, versionId);
|
request.setWatermarkType(watermarkType);
|
|
service.createTicket(request);
|
|
if ("download".equals(watermarkType)) {
|
verify(watermarkPreviewResolver).resolveDownload(any(),
|
org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(version),
|
org.mockito.ArgumentMatchers.eq(request));
|
} else if ("print".equals(watermarkType)) {
|
verify(watermarkPreviewResolver).resolvePrint(any(),
|
org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(version),
|
org.mockito.ArgumentMatchers.eq(request));
|
} else {
|
verify(watermarkPreviewResolver).resolve(any(),
|
org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(version),
|
org.mockito.ArgumentMatchers.eq(request));
|
}
|
}
|
|
@Test
|
void historicalPreviewUsesSelectedFileVersionWithoutResolvingCurrentVersion() {
|
DmsFileVersionEntity version = version();
|
version.setLifecycleStatus("INVALIDATED");
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(fileObject());
|
|
DmsFilePreviewTicketVO result = service.createTicket(
|
request(DmsResourceType.FILE_VERSION, versionId));
|
|
assertEquals(versionId, result.getFileVersionId());
|
verify(versionMapper, never()).selectCurrentEffective(any(), any());
|
ArgumentCaptor<DmsFilePreviewTicket> historical = ArgumentCaptor.forClass(DmsFilePreviewTicket.class);
|
verify(ticketIssuer).issue(historical.capture(), anyLong());
|
assertTrue(historical.getValue().isHistoricalAccess());
|
}
|
|
@Test
|
void uncontrolledVersionSkipsReadPermissionButKeepsPreviewPipeline() {
|
DmsFileVersionEntity version = version();
|
version.setShifouShoukong("no");
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(fileObject());
|
doThrow(DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
|
"no read permission")).when(permissionGuard).execute(any(), any());
|
|
DmsFilePreviewTicketVO result = service.createTicket(
|
request(DmsResourceType.FILE_VERSION, versionId));
|
|
assertEquals("preview-ticket", result.getAccessTicket());
|
verify(permissionGuard, never()).execute(any(), any());
|
verify(previewPreparationClient).prepare(any());
|
verify(operationLogService).recordView(any(), org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(documentId), org.mockito.ArgumentMatchers.eq(versionId),
|
org.mockito.ArgumentMatchers.eq("quality-manual.docx"),
|
org.mockito.ArgumentMatchers.eq(operationId));
|
}
|
|
@Test
|
void controlledVersionWithoutReadPermissionIsRejected() {
|
DmsFileVersionEntity version = version();
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
doThrow(DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
|
"no read permission")).when(permissionGuard).execute(any(), any());
|
|
DmsPermissionException error = assertThrows(DmsPermissionException.class,
|
() -> service.createTicket(request(DmsResourceType.FILE_VERSION, versionId)));
|
|
assertEquals(DmsPermissionError.PERMISSION_DENIED, error.getError());
|
verify(ticketIssuer, never()).issue(any(), anyLong());
|
}
|
|
@Test
|
void returnsPreparedPdfMetadataInsteadOfOriginalDocxMetadata() {
|
DmsFileVersionEntity version = version();
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(fileObject());
|
when(previewPreparationClient.prepare(any())).thenAnswer(invocation -> {
|
DmsFilePreviewTicket prepared = invocation.getArgument(0);
|
prepared.setFileName("quality-manual.pdf");
|
prepared.setMimeType("application/pdf");
|
prepared.setSizeBytes(4096L);
|
return prepared;
|
});
|
|
DmsFilePreviewTicketVO result = service.createTicket(
|
request(DmsResourceType.FILE_VERSION, versionId));
|
|
assertEquals("quality-manual.pdf", result.getFileName());
|
assertEquals("application/pdf", result.getMimeType());
|
assertEquals(Long.valueOf(4096L), result.getSizeBytes());
|
}
|
|
@Test
|
void folderCannotBeUsedAsPreviewResource() {
|
DmsPermissionException error = assertThrows(DmsPermissionException.class,
|
() -> service.createTicket(request(DmsResourceType.FOLDER, documentId)));
|
|
assertEquals(DmsPermissionError.INVALID_REQUEST, error.getError());
|
verify(permissionGuard, never()).execute(any(), any());
|
verify(ticketIssuer, never()).issue(any(), anyLong());
|
}
|
|
@Test
|
void missingStoredObjectDoesNotIssueTicket() {
|
DmsFileVersionEntity version = version();
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
|
DmsPermissionException error = assertThrows(DmsPermissionException.class,
|
() -> service.createTicket(request(DmsResourceType.FILE_VERSION, versionId)));
|
|
assertEquals(DmsPermissionError.RESOURCE_NOT_FOUND, error.getError());
|
verify(ticketIssuer, never()).issue(any(), anyLong());
|
}
|
|
@Test
|
void pendingVersionCannotIssuePreviewTicket() {
|
DmsPermissionException error = assertThrows(DmsPermissionException.class,
|
() -> service.createTicket(request(DmsResourceType.FILE_VERSION, versionId)));
|
|
assertEquals(DmsPermissionError.RESOURCE_NOT_FOUND, error.getError());
|
verify(permissionGuard, never()).execute(any(), any());
|
verify(ticketIssuer, never()).issue(any(), anyLong());
|
}
|
|
private DmsFilePreviewTicketRequest request(DmsResourceType type, UUID id) {
|
DmsFilePreviewTicketRequest request = new DmsFilePreviewTicketRequest();
|
request.setResourceType(type);
|
request.setResourceId(id);
|
request.setOperationId(operationId);
|
request.setPreviewFormat(DmsPreviewFormat.PDF);
|
return request;
|
}
|
|
private DmsFileVersionEntity version() {
|
DmsFileVersionEntity version = new DmsFileVersionEntity();
|
version.setId(versionId);
|
version.setDocumentId(documentId);
|
version.setFileName("quality-manual.docx");
|
version.setMimeType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
version.setSizeBytes(2048L);
|
version.setChecksumSha256("a".repeat(64));
|
version.setShifouShoukong("yes");
|
return version;
|
}
|
|
private DmsFileObjectEntity fileObject() {
|
DmsFileObjectEntity object = new DmsFileObjectEntity();
|
object.setFileVersionId(versionId);
|
object.setStoragePlatform("seaweedfs-1");
|
object.setBucketName("jnpf-dms");
|
object.setBasePath("dms/");
|
object.setObjectPath("2026/08/24/document/");
|
object.setObjectName("quality-manual-v1.docx");
|
return object;
|
}
|
}
|