package jnpf.dmsService.expiry; import jnpf.dmsEntity.storage.expiry.*; import jnpf.dmsMapper.*; import jnpf.dmsService.DmsFileLifecycleService; import jnpf.dmsService.DmsFileValidityProjection; import jnpf.dmsService.search.DmsSearchProperties; import jnpf.dmsService.search.meili.DmsMeilisearchClient; import jnpf.exception.DataException; import org.junit.jupiter.api.Test; import org.mockito.InOrder; import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; class DmsFileExpiryTest { private final DmsFileExpiryMapper mapper = mock(DmsFileExpiryMapper.class); private final DmsFileExpiryProjectionMapper resources = mock(DmsFileExpiryProjectionMapper.class); private final DmsFileExpiryProjection projection = new DmsFileExpiryProjection(resources, mapper); private final UUID version = UUID.randomUUID(); private final Date end = new Date(1700000000000L); @Test void expiresBusinessVersionResourceAndRelationsButRetainsPointer() { DmsFileExpiryResource resource = resource("effective", null); when(resources.lockResources("t", version)).thenReturn(List.of(resource)); when(resources.disable("t", resource, end, "actor")).thenReturn(1); when(resources.expireBusinessVersion("t", resource, "actor")).thenReturn(1); projection.apply("t", version, end, "actor"); InOrder ordered = inOrder(resources, mapper); ordered.verify(resources).hasPendingProjection("t", version); ordered.verify(resources).lockResources("t", version); ordered.verify(resources).disable("t", resource, end, "actor"); ordered.verify(resources).expireBusinessVersion("t", resource, "actor"); ordered.verify(resources).closeRelations("t", "file", end, "actor"); ordered.verify(mapper).enqueueCleanup("t", version); assertEquals("business-version", resource.getCurrentVersionId()); } @Test void repairsExpiredProjectionAndEnqueuesCenterOnlyVersions() { when(resources.lockResources("t", version)).thenReturn(List.of()); projection.apply("t", version, end, "actor"); verify(mapper).enqueueCleanup("t", version); verify(resources, never()).disable(any(), any(), any(), any()); } @Test void refusesToOverwriteOtherDisableReasonsOrAmbiguousMappings() { when(resources.lockResources("t", version)).thenReturn(List.of(resource("disabled", "yichexiao"))); assertThrows(DataException.class, () -> projection.apply("t", version, end, "actor")); when(resources.lockResources("t", version)).thenReturn(List.of(resource("effective", null), resource("effective", null))); assertThrows(DataException.class, () -> projection.apply("t", version, end, "actor")); verifyNoInteractions(mapper); } @Test void defersExpiryUntilApprovedBusinessProjectionFinishes() { when(resources.hasPendingProjection("t", version)).thenReturn(true); assertThrows(DataException.class, () -> projection.apply("t", version, end, "actor")); verify(resources, never()).lockResources(any(), any()); verifyNoInteractions(mapper); } @Test void validityProjectionRejectsDisabledBusinessResources() { when(resources.lockResources("t", version)).thenReturn(List.of(resource("disabled", "yidaoqi"))); assertThrows(DataException.class, () -> new DmsFileValidityProjection(resources).apply("t", version, end, "actor")); verify(resources, never()).extendValidity(any(), any(), any(), any()); } @Test void executorSkipsLockedDocumentsAndCandidatesExtendedAfterDiscovery() { DmsFileLifecycleService lifecycle = mock(DmsFileLifecycleService.class); DmsFileExpiryExecutor executor = new DmsFileExpiryExecutor(mapper, lifecycle, new jnpf.dmsService.DmsBusinessLifecycleGuard(null, false)); DmsFileExpiryItem item = item(); assertFalse(executor.process(item)); when(mapper.tryLockDocument("t", item.getDocumentId())).thenReturn(item.getDocumentId()); assertFalse(executor.process(item)); verifyNoInteractions(lifecycle); when(mapper.dueState("t", version)).thenReturn(item); assertTrue(executor.process(item)); verify(lifecycle).updateExpired(argThat(form -> version.equals(form.getFileVersionId())), eq("dms-expiry-job")); } @Test void occupiedFileSkipsBeforeDocumentLockAndDoesNotReportApplied() { DmsFileLifecycleService lifecycle = mock(DmsFileLifecycleService.class); jnpf.dmsService.DmsBusinessLifecycleGuard guard = mock(jnpf.dmsService.DmsBusinessLifecycleGuard.class); DmsFileExpiryExecutor executor = new DmsFileExpiryExecutor(mapper, lifecycle, guard); assertFalse(executor.process(item())); verifyNoInteractions(mapper, lifecycle); } @Test void defaultJobProcessesDueVersionsAndContinuesAfterFailure() { DmsFileExpiryProperties properties = new DmsFileExpiryProperties(); properties.setBatchSize(1); DmsFileExpiryExecutor executor = mock(DmsFileExpiryExecutor.class); DmsFileExpirySearchCleanup cleanup = mock(DmsFileExpirySearchCleanup.class); DmsFileExpiryItem first = item(), second = item(); second.setFileVersionId(UUID.randomUUID()); when(mapper.candidates(null, 1)).thenReturn(List.of(first)); when(mapper.candidates(first, 1)).thenReturn(List.of(second)); when(mapper.candidates(second, 1)).thenReturn(List.of()); when(executor.process(first)).thenThrow(new DataException("conflict")); when(executor.process(second)).thenReturn(true); DmsFileExpiryJob job = new DmsFileExpiryJob(properties, mapper, executor, cleanup, new DmsSearchProperties()); DmsFileExpiryJob.Result result = job.run(); assertEquals(2, result.getScanned()); assertEquals(1, result.getFailed()); assertEquals(1, result.getApplied()); verify(executor).process(first); verify(executor).process(second); verifyNoInteractions(cleanup); } @Test void sharedCleanupRunsEvenWhenNoVersionsAreDue() { when(mapper.candidates(null, 100)).thenReturn(List.of()); DmsFileExpiryItem receipt = item(); when(mapper.cleanupCandidates(100)).thenReturn(List.of(receipt)); DmsFileExpiryExecutor executor = mock(DmsFileExpiryExecutor.class); DmsFileExpirySearchCleanup cleanup = mock(DmsFileExpirySearchCleanup.class); DmsSearchProperties search = new DmsSearchProperties(); search.setEnabled(true); DmsFileExpiryJob.Result result = new DmsFileExpiryJob(new DmsFileExpiryProperties(), mapper, executor, cleanup, search).run(); assertEquals(0, result.getScanned()); verify(cleanup).process(receipt); verifyNoInteractions(executor); } @Test void cleanupPersistsAcceptedTaskAndWaitsForConfirmedSuccess() throws Exception { DmsMeilisearchClient client = mock(DmsMeilisearchClient.class); DmsFileExpirySearchCleanup worker = new DmsFileExpirySearchCleanup(mapper, client, new DmsFileExpiryProperties()); DmsFileExpiryItem item = item(); when(mapper.lockCleanup("t", version)).thenReturn(item); when(client.deleteDocument("t", version)).thenReturn(42L); worker.process(item); verify(mapper).updateCleanup(item, false, null, 60); when(client.taskStatus(42L)).thenReturn("succeeded"); worker.process(item); verify(mapper).updateCleanup(item, true, null, 60); verify(client, times(1)).deleteDocument("t", version); } @Test void cleanupRetriesFailedOrLostTasksAndRetainsNetworkFailures() throws Exception { DmsMeilisearchClient client = mock(DmsMeilisearchClient.class); DmsFileExpirySearchCleanup worker = new DmsFileExpirySearchCleanup(mapper, client, new DmsFileExpiryProperties()); DmsFileExpiryItem item = item(); item.setSearchTaskId(42L); when(mapper.lockCleanup("t", version)).thenReturn(item); when(client.taskStatus(42L)).thenReturn("failed"); worker.process(item); assertNull(item.getSearchTaskId()); when(client.deleteDocument("t", version)).thenThrow(new java.io.IOException("offline")); worker.process(item); verify(mapper).updateCleanup(item, false, "IOException: offline", 60); } private DmsFileExpiryResource resource(String status, String reason) { DmsFileExpiryResource value = new DmsFileExpiryResource(); value.setId("file"); value.setCurrentVersionId("business-version"); value.setCenterVersionId(version.toString()); value.setVersionCenterId(version.toString()); value.setBizStatus(status); value.setDisabledSource(reason); return value; } private DmsFileExpiryItem item() { DmsFileExpiryItem value = new DmsFileExpiryItem(); value.setTenantId("t"); value.setDocumentId(UUID.randomUUID()); value.setFileVersionId(version); value.setLifecycleStatus("EFFECTIVE"); value.setEffectiveFrom(new Date(1600000000000L)); value.setEffectiveTo(end); return value; } }