package jnpf.dmsService;
|
|
import jnpf.base.UserInfo;
|
import jnpf.dmsEntity.permission.entity.DmsDocumentEntity;
|
import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity;
|
import jnpf.dmsEntity.permission.enums.DmsPermissionAction;
|
import jnpf.dmsEntity.permission.enums.DmsResourceType;
|
import jnpf.dmsEntity.preview.model.DmsFilePreviewTicket;
|
import jnpf.dmsEntity.preview.model.DmsWatermarkSnapshot;
|
import jnpf.dmsEntity.print.form.DmsFilePrintTicketRequest;
|
import jnpf.dmsEntity.print.form.DmsPrintOperationCreateForm;
|
import jnpf.dmsEntity.print.vo.DmsFilePrintTicketVO;
|
import jnpf.dmsEntity.storage.entity.DmsFileObjectEntity;
|
import jnpf.dmsEntity.watermark.DmsWatermarkParameters;
|
import jnpf.dmsMapper.DmsDocumentMapper;
|
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.mockito.ArgumentCaptor;
|
|
import java.util.Collections;
|
import java.util.List;
|
import java.util.UUID;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
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 DmsFilePrintServiceTest {
|
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 DmsDocumentMapper documentMapper = mock(DmsDocumentMapper.class);
|
private final DmsFilePreviewTicketIssuer ticketIssuer = mock(DmsFilePreviewTicketIssuer.class);
|
private final DmsPrintOperationLogService logService = mock(DmsPrintOperationLogService.class);
|
private final DmsWatermarkPreviewResolver watermarkResolver = mock(DmsWatermarkPreviewResolver.class);
|
private final DmsPdfGenerationTaskService pdfGenerationTaskService = mock(DmsPdfGenerationTaskService.class);
|
private final DmsFilePrintService service = new DmsFilePrintService(currentIdentity,
|
permissionGuard, versionMapper, objectMapper, documentMapper, ticketIssuer,
|
logService, watermarkResolver, pdfGenerationTaskService);
|
private final UUID documentId = UUID.fromString("018f0000-0000-7000-8000-000000000821");
|
private final UUID versionId = UUID.fromString("018f0000-0000-7000-8000-000000000822");
|
private final UUID operationId = UUID.fromString("018f0000-0000-7000-8000-000000000823");
|
|
@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(), org.mockito.ArgumentMatchers.eq(
|
DmsFilePrintService.TICKET_TTL_SECONDS))).thenReturn("print-ticket");
|
when(watermarkResolver.resolvePrint(any(), any(), any(), any())).thenReturn(Collections.emptyList());
|
}
|
|
@Test
|
void issuesPrintScopedTicketAfterPrintPermissionAndRecordsInitiation() {
|
DmsFileVersionEntity version = version();
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(object());
|
when(documentMapper.selectByTenantAndId("tenant-1", documentId)).thenReturn(document());
|
|
DmsFilePrintTicketVO result = service.createTicket(request());
|
|
assertEquals("print-ticket", result.getAccessTicket());
|
assertEquals(2, result.getCopies());
|
assertEquals("1-3", result.getPrintPages());
|
|
ArgumentCaptor<DmsPermissionExecutionContext> context =
|
ArgumentCaptor.forClass(DmsPermissionExecutionContext.class);
|
verify(permissionGuard).execute(context.capture(), any());
|
assertEquals(DmsPermissionAction.PRINT, context.getValue().getActionCode());
|
assertEquals(DmsResourceType.FILE_VERSION, context.getValue().getResourceType());
|
assertEquals(versionId, context.getValue().getResourceId());
|
assertEquals(operationId, context.getValue().getOperationId());
|
assertNull(context.getValue().getSessionExpiresAt());
|
|
ArgumentCaptor<DmsFilePreviewTicket> ticket = ArgumentCaptor.forClass(DmsFilePreviewTicket.class);
|
verify(ticketIssuer).issue(ticket.capture(), org.mockito.ArgumentMatchers.eq(
|
DmsFilePrintService.TICKET_TTL_SECONDS));
|
assertEquals(DmsFilePreviewTicket.PRINT_SCOPE, ticket.getValue().getScope());
|
|
ArgumentCaptor<DmsPrintOperationCreateForm> log =
|
ArgumentCaptor.forClass(DmsPrintOperationCreateForm.class);
|
verify(logService).create(log.capture());
|
assertEquals(operationId.toString(), log.getValue().getClientEventId());
|
assertEquals("CONTROLLED_DOCUMENT", log.getValue().getPrintType());
|
assertEquals("FILE_PREVIEW", log.getValue().getSourceType());
|
assertEquals("打印页码:1-3", log.getValue().getTargets().get(0).getRemark());
|
verify(pdfGenerationTaskService).enqueueMissingEligible("tenant-1", versionId,
|
"quality-manual.docx", "a".repeat(64));
|
}
|
|
@Test
|
void managedPrintCannotIssueTicketWithoutSerialWatermark() {
|
var request = request(); request.setRequireSerialWatermark(true);
|
var barcode = new DmsWatermarkParameters.CustomBarcode();
|
barcode.setBarValue("unique-1"); barcode.setBarText("打印流水号 unique-1 计数流水号 20260001");
|
request.setCustomBarcode(List.of(barcode));
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version());
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(object());
|
when(documentMapper.selectByTenantAndId("tenant-1", documentId)).thenReturn(document());
|
assertThrows(jnpf.exception.DataException.class, () -> service.createTicket(request));
|
verify(ticketIssuer, never()).issue(any(), anyLong());
|
}
|
|
@Test
|
void requiredSerialAcceptsMatchingExistingCustomBarcodeSnapshot() {
|
var request = request();
|
request.setRequireSerialWatermark(true);
|
var barcode = new DmsWatermarkParameters.CustomBarcode();
|
barcode.setBarValue("20261003000001");
|
barcode.setBarText("打印流水号: 20261003000001 计数流水号: 20260001");
|
request.setCustomBarcode(List.of(barcode));
|
var snapshot = new DmsWatermarkSnapshot();
|
snapshot.setWatermarkType("BARCODE");
|
snapshot.setCustomBarcode(true);
|
snapshot.setContent(barcode.getBarValue());
|
snapshot.setBarcodeText(barcode.getBarText());
|
snapshot.setPositions(List.of(new jnpf.dmsEntity.preview.model.DmsWatermarkPositionSnapshot()));
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version());
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(object());
|
when(documentMapper.selectByTenantAndId("tenant-1", documentId)).thenReturn(document());
|
when(watermarkResolver.resolvePrint(any(), org.mockito.ArgumentMatchers.eq("tenant-1"),
|
any(), org.mockito.ArgumentMatchers.eq(request))).thenReturn(List.of(snapshot));
|
|
service.createTicket(request);
|
|
ArgumentCaptor<DmsFilePreviewTicket> ticket = ArgumentCaptor.forClass(DmsFilePreviewTicket.class);
|
verify(ticketIssuer).issue(ticket.capture(), org.mockito.ArgumentMatchers.eq(
|
DmsFilePrintService.TICKET_TTL_SECONDS));
|
assertEquals(List.of(snapshot), ticket.getValue().getWatermarks());
|
}
|
|
@Test
|
void resolvesRuntimeParametersAgainstPrintConfigurationIntoTicketSnapshot() {
|
DmsFileVersionEntity version = version();
|
DmsFilePrintTicketRequest request = request();
|
DmsWatermarkParameters.CustomBarcode barcode = new DmsWatermarkParameters.CustomBarcode();
|
barcode.setPage(2);
|
barcode.setBarValue("COPY-002");
|
barcode.setBarText("第二份");
|
request.setCustomBarcode(List.of(barcode));
|
|
DmsWatermarkSnapshot snapshot = new DmsWatermarkSnapshot();
|
snapshot.setWatermarkType("BARCODE");
|
snapshot.setContent("COPY-002");
|
snapshot.setBarcodeText("第二份");
|
snapshot.setRenderPages(List.of(2));
|
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(object());
|
when(documentMapper.selectByTenantAndId("tenant-1", documentId)).thenReturn(document());
|
when(watermarkResolver.resolvePrint(any(), org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(version), org.mockito.ArgumentMatchers.eq(request)))
|
.thenReturn(List.of(snapshot));
|
|
service.createTicket(request);
|
|
verify(watermarkResolver).resolvePrint(any(), org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(version), org.mockito.ArgumentMatchers.eq(request));
|
ArgumentCaptor<DmsFilePreviewTicket> ticket = ArgumentCaptor.forClass(DmsFilePreviewTicket.class);
|
verify(ticketIssuer).issue(ticket.capture(), org.mockito.ArgumentMatchers.eq(
|
DmsFilePrintService.TICKET_TTL_SECONDS));
|
assertEquals(List.of(snapshot), ticket.getValue().getWatermarks());
|
assertEquals("1-3", ticket.getValue().getPrintPages());
|
}
|
|
@Test
|
void uncontrolledVersionSkipsPrintPermissionButKeepsPrintPipeline() {
|
DmsFileVersionEntity version = version();
|
version.setShifouShoukong("no");
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
when(objectMapper.selectByTenantAndFileVersionId("tenant-1", versionId)).thenReturn(object());
|
when(documentMapper.selectByTenantAndId("tenant-1", documentId)).thenReturn(document());
|
doThrow(DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
|
"no print permission")).when(permissionGuard).execute(any(), any());
|
|
DmsFilePrintTicketVO result = service.createTicket(request());
|
|
assertEquals("print-ticket", result.getAccessTicket());
|
verify(permissionGuard, never()).execute(any(), any());
|
verify(watermarkResolver).resolvePrint(any(), org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(version), any());
|
verify(logService).create(any());
|
}
|
|
@Test
|
void controlledVersionWithoutPrintPermissionIsRejected() {
|
DmsFileVersionEntity version = version();
|
when(versionMapper.selectReadableByTenantAndId("tenant-1", versionId)).thenReturn(version);
|
doThrow(DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
|
"no print permission")).when(permissionGuard).execute(any(), any());
|
|
DmsPermissionException error = assertThrows(
|
DmsPermissionException.class,
|
() -> service.createTicket(request()));
|
|
assertEquals(DmsPermissionError.PERMISSION_DENIED, error.getError());
|
verify(ticketIssuer, never()).issue(any(), anyLong());
|
}
|
|
private DmsFilePrintTicketRequest request() {
|
DmsFilePrintTicketRequest request = new DmsFilePrintTicketRequest();
|
request.setResourceType(DmsResourceType.FILE_VERSION);
|
request.setResourceId(versionId);
|
request.setOperationId(operationId);
|
request.setCopies(2);
|
request.setPrintPages("1-3");
|
return request;
|
}
|
|
private DmsFileVersionEntity version() {
|
DmsFileVersionEntity version = new DmsFileVersionEntity();
|
version.setId(versionId);
|
version.setDocumentId(documentId);
|
version.setVersionLabel("V1.0");
|
version.setFileName("quality-manual.docx");
|
version.setSizeBytes(2048L);
|
version.setChecksumSha256("a".repeat(64));
|
version.setShifouShoukong("yes");
|
return version;
|
}
|
|
private DmsFileObjectEntity object() {
|
DmsFileObjectEntity object = new DmsFileObjectEntity();
|
object.setStoragePlatform("seaweedfs-1");
|
object.setBucketName("jnpf-dms");
|
object.setObjectPath("2026/09/document/");
|
object.setObjectName("manual-v1.docx");
|
return object;
|
}
|
|
private DmsDocumentEntity document() {
|
DmsDocumentEntity document = new DmsDocumentEntity();
|
document.setId(documentId);
|
document.setDocumentNo("QM-001");
|
document.setName("质量手册");
|
return document;
|
}
|
}
|