package jnpf.dmsService; import jnpf.dmsEntity.permission.entity.*; import jnpf.dmsEntity.storage.approval.*; import jnpf.dmsMapper.*; import jnpf.dmsService.search.DmsSearchIndexJobService; import jnpf.exception.DataException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.*; import org.mockito.junit.jupiter.MockitoExtension; import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class DmsApprovedFileActivationServiceTest { @Mock DmsApprovedBatchMapper receipts; @Mock DmsDocumentMapper documents; @Mock DmsFileVersionMapper versions; @Mock DmsVersionStateMapper states; @Mock DmsSearchIndexJobService search; @Mock DmsApprovedAuditSource source; @Mock DmsApprovedReadAccessService readAccess; DmsQianpiyeRenderSnapshotService qianpiyeSnapshots = new DmsQianpiyeRenderSnapshotService( null, null, null, null, new com.fasterxml.jackson.databind.ObjectMapper()); DmsApprovedFileActivationService service; @org.junit.jupiter.api.BeforeEach void setupCenter() { service = new DmsApprovedFileActivationService(receipts, new jnpf.dmsService.center.DmsCenterActivationService(org.mockito.Mockito.mock(jnpf.dmsIntegration.audit.DmsBusinessChangeAudit.class), documents, versions, states, search), source, qianpiyeSnapshots, readAccess,new DmsBusinessLifecycleGuard(null,false)); } private jnpf.dmsIntegration.audit.DmsVersionStateAuditRow auditState(String status) { var row = new jnpf.dmsIntegration.audit.DmsVersionStateAuditRow(); row.setLifecycleStatus(status); return row; } DmsApprovedBatch batch() { DmsApprovedBatch b = new DmsApprovedBatch(); b.setId(UUID.randomUUID()); b.setTenantId("tenant"); b.setStatus("WAITING"); b.setFlowId("flow"); b.setTaskId("task"); b.setAuditType("xiuding_shenhe"); b.setActorId("actor"); b.setSourceDigest("digest"); b.setApprovedAt(new Date()); return b; } DmsApprovedItem item(String type) { DmsApprovedItem i = new DmsApprovedItem(); i.setChangeType(type); i.setDocumentId(UUID.randomUUID()); i.setFileVersionId(UUID.randomUUID()); i.setBaseFileVersionId(UUID.randomUUID()); i.setChecksumSha256("hash"); return i; } void arrange(DmsApprovedBatch b, List items) { when(receipts.lock("tenant", b.getId())).thenReturn(b); when(receipts.activate("tenant", b.getId())).thenReturn(1); when(receipts.items("tenant", b.getId())).thenReturn(new ArrayList<>(items)); b.setActivatedAt(new Date()); b.setItems(items); when(source.load("tenant", "flow", "task", "xiuding_shenhe", "actor", b.getApprovedAt())) .thenReturn(new DmsApprovedAuditSource.Source(b, "author", new Date(), false)); } @Test void futureBatchDoesNotTouchVersions() { DmsApprovedBatch b = batch(); when(receipts.lock("tenant", b.getId())).thenReturn(b); assertEquals("WAITING", service.activate("tenant", b.getId()).getStatus()); verifyNoInteractions(versions, states, documents, source, search, readAccess); } @Test void effectiveRetryDoesNotGrantAgain() { DmsApprovedBatch b = batch(); b.setStatus("EFFECTIVE"); when(receipts.lock("tenant", b.getId())).thenReturn(b); assertSame(b, service.activate("tenant", b.getId())); verifyNoInteractions(versions, states, documents, source, search, readAccess); verify(receipts, never()).activate(any(), any()); } @Test void authorizationFailurePropagatesFromTheSameActivationTransaction() { DmsApprovedBatch b = batch(); DmsApprovedItem i = item("REUSE"); arrange(b, List.of(i)); when(documents.selectByTenantAndIdForUpdate("tenant", i.getDocumentId())).thenReturn(new DmsDocumentEntity()); DmsFileVersionEntity current = new DmsFileVersionEntity(); current.setId(i.getFileVersionId()); when(versions.selectCurrentEffective("tenant", i.getDocumentId())).thenReturn(current); doThrow(new DataException("invalid department")).when(readAccess).apply(eq(b), anyList(), anyList()); assertThrows(DataException.class, () -> service.activate("tenant", b.getId())); } @Test void reusedItemsNeverCreateOrSwitchVersions() { DmsApprovedBatch b = batch(); DmsApprovedItem i = item("REUSE"); arrange(b, List.of(i)); when(documents.selectByTenantAndIdForUpdate("tenant", i.getDocumentId())).thenReturn(new DmsDocumentEntity()); DmsFileVersionEntity current = new DmsFileVersionEntity(); current.setId(i.getFileVersionId()); when(versions.selectCurrentEffective("tenant", i.getDocumentId())).thenReturn(current); service.activate("tenant", b.getId()); verifyNoInteractions(states, search); verify(readAccess).apply(b, List.of(i), List.of(i)); } @Test void revisionChecksExactBaseBeforeSwitching() { DmsApprovedBatch b = batch(); DmsApprovedItem i = item("REVISE"); arrange(b, List.of(i)); when(documents.selectByTenantAndIdForUpdate("tenant", i.getDocumentId())).thenReturn(new DmsDocumentEntity()); DmsFileVersionEntity wrong = new DmsFileVersionEntity(); wrong.setId(UUID.randomUUID()); when(versions.selectCurrentEffective("tenant", i.getDocumentId())).thenReturn(wrong); assertThrows(DataException.class, () -> service.activate("tenant", b.getId())); verifyNoInteractions(states, search); } @Test void completeBatchActivatesMainAndRecordAfterValidation() { DmsApprovedBatch b = batch(); DmsApprovedItem main = item("CREATE"); DmsApprovedItem record = item("CREATE"); arrange(b, List.of(main, record)); // Historical registered DOCX files have no render snapshot; activation must keep their digest. main.setSourceKey("xiuding_shenhe:a:zhuwenjian"); main.setFileJson("[{\"name\":\"main.docx\",\"url\":\"main.docx\"}]"); for (DmsApprovedItem i : List.of(main, record)) { when(documents.selectByTenantAndIdForUpdate("tenant", i.getDocumentId())).thenReturn(new DmsDocumentEntity()); DmsFileVersionEntity registered = new DmsFileVersionEntity(); registered.setId(i.getFileVersionId()); registered.setDocumentId(i.getDocumentId()); when(versions.selectByTenantAndId("tenant", i.getFileVersionId())).thenReturn(registered); when(states.activateApproved("tenant", i.getFileVersionId(), "actor")).thenReturn(1); when(states.selectAuditState("tenant", i.getFileVersionId())).thenReturn(auditState("PENDING_EFFECTIVE"), auditState("EFFECTIVE")); } service.activate("tenant", b.getId()); for (DmsApprovedItem i : List.of(main, record)) { verify(states).activateApproved("tenant", i.getFileVersionId(), "actor"); verify(search).enqueueContentUpsert("tenant", i.getDocumentId(), i.getFileVersionId(), "hash"); } } @Test void approvalPageSnapshotIsIncludedWhenActivatingRegisteredFiles() { DmsApprovedBatch b = batch(); DmsApprovedItem main = item("CREATE"); main.setSourceKey("xiuding_shenhe:a:zhuwenjian"); main.setApprovalPageFileJson("[{\"fileId\":\"page.docx\"}]"); arrange(b, List.of(main)); String snapshot = "{\"protocolVersion\":1,\"taskId\":\"task\",\"flowId\":\"flow\"," + "\"valuesByTag\":{\"dms.qianpiye.flow.approve-1.approver\":\"审核人\"}}"; qianpiyeSnapshots.attach(b, snapshot, "author", new Date()); DmsApprovedBatch currentSource = batch(); currentSource.setItems(List.of(main)); when(source.load("tenant", "flow", "task", "xiuding_shenhe", "actor", b.getApprovedAt())) .thenReturn(new DmsApprovedAuditSource.Source(currentSource, "author", new Date(), false)); when(documents.selectByTenantAndIdForUpdate("tenant", main.getDocumentId())).thenReturn(new DmsDocumentEntity()); DmsFileVersionEntity registered = new DmsFileVersionEntity(); registered.setId(main.getFileVersionId()); registered.setDocumentId(main.getDocumentId()); when(versions.selectByTenantAndId("tenant", main.getFileVersionId())).thenReturn(registered); when(states.activateApproved("tenant", main.getFileVersionId(), "actor")).thenReturn(1); when(states.selectAuditState("tenant", main.getFileVersionId())).thenReturn(auditState("PENDING_EFFECTIVE"), auditState("EFFECTIVE")); service.activate("tenant", b.getId()); verify(states).activateApproved("tenant", main.getFileVersionId(), "actor"); } @Test void changedApprovedSourceIsRejectedBeforeTouchingCenterVersions() { DmsApprovedBatch b = batch(); when(receipts.lock("tenant", b.getId())).thenReturn(b); when(receipts.activate("tenant", b.getId())).thenReturn(1); DmsApprovedBatch changed = batch(); changed.setSourceDigest("changed-content"); when(source.load("tenant", "flow", "task", "xiuding_shenhe", "actor", b.getApprovedAt())) .thenReturn(new DmsApprovedAuditSource.Source(changed, "author", new Date(), false)); assertThrows(DataException.class, () -> service.activate("tenant", b.getId())); verifyNoInteractions(documents, versions, states, search); } }