package jnpf.dmsService; import jnpf.dmsEntity.center.*; import jnpf.dmsEntity.storage.approval.DmsDisableRequest; import jnpf.dmsEntity.storage.expiry.DmsFileExpiryResource; import jnpf.dmsMapper.*; import jnpf.dmsService.center.DmsCenterDisableService; import jnpf.exception.DataException; import org.junit.jupiter.api.*; import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; class DmsApprovedRevocationExecutorTest { DmsRevocationMapper mapper=mock(DmsRevocationMapper.class); DmsCenterDisableService center=mock(DmsCenterDisableService.class); DmsFileExpiryProjectionMapper resources=mock(DmsFileExpiryProjectionMapper.class); DmsFileExpiryMapper cleanup=mock(DmsFileExpiryMapper.class); DmsDisableApplicationMapper occupancy=mock(DmsDisableApplicationMapper.class); DmsApprovedRevocationExecutor executor=new DmsApprovedRevocationExecutor(mapper,center,resources,cleanup,occupancy,new DmsBusinessLifecycleGuard(null,false)); DmsCenterDisableCommand command=new DmsCenterDisableCommand(); DmsDisableRequest row=new DmsDisableRequest(); Date at=new Date(); @BeforeEach void setup() { command.setTenantId("t");command.setRequestId(UUID.randomUUID());command.setMainDocumentId(UUID.randomUUID()); command.setExpectedMainVersionId(UUID.randomUUID());command.setReason("yichexiao");command.setActorId("actor"); command.setEffectiveAt(new Date(at.getTime()-10000));command.setLeaseToken("lease"); row.setId(command.getRequestId());row.setTenantId("t");row.setBatchId(UUID.randomUUID());row.setDocumentId(command.getMainDocumentId()); row.setFileVersionId(command.getExpectedMainVersionId());row.setActorId("actor");row.setScheduledAt(command.getEffectiveAt()); row.setFileId("file");row.setBusinessVersionId("business-version");row.setTaskId("task");row.setOperation("yichexiao");row.setStatus("WAITING"); when(mapper.lockRequest(command)).thenReturn(row);when(mapper.validLease(row,"lease")).thenReturn(true); when(mapper.approvedSource(row)).thenReturn(true);when(mapper.executionTime()).thenReturn(at); } void target() { DmsInvalidationTarget t=new DmsInvalidationTarget();t.setDocumentId(row.getDocumentId());t.setFileVersionId(row.getFileVersionId()); when(center.revoke(command,at)).thenReturn(List.of(t)); DmsFileExpiryResource r=new DmsFileExpiryResource();r.setId("file");r.setCurrentVersionId("business-version"); r.setCenterVersionId(row.getFileVersionId().toString());r.setVersionCenterId(r.getCenterVersionId());r.setBizStatus("effective"); when(resources.lockResources("t",row.getFileVersionId())).thenReturn(List.of(r)); when(mapper.disableResource(row,r,at)).thenReturn(1);when(mapper.revokeBusinessVersion(row,r,at)).thenReturn(1);when(mapper.recordExecution(row,at)).thenReturn(1); when(occupancy.releaseFile("t","task","yichexiao","file")).thenReturn(1);when(mapper.complete(row,at,"lease")).thenReturn(1); } @Test void approvedBatchExecutesWithoutRolloutConfigurationAndCompletesAfterEveryWrite() { target();assertEquals(200,executor.execute(command).getCode()); var order=inOrder(center,mapper,occupancy,cleanup); order.verify(center).revoke(command,at);order.verify(mapper).disableResource(eq(row),any(),eq(at)); order.verify(cleanup).enqueueCleanup("t",row.getFileVersionId());order.verify(mapper).recordExecution(row,at); order.verify(occupancy).releaseFile("t","task","yichexiao","file");order.verify(mapper).complete(row,at,"lease"); } @Test void independentReceiptReleasesAfterDurableCompletion() { target(); var guard=mock(DmsBusinessLifecycleGuard.class); var receipt=new jnpf.dmsBusinessOccupancy.BusinessFileOccupancy(); var receipts=List.of(receipt); when(guard.requireExecution("t","task",null,"REVOCATION",List.of("file"))).thenReturn(receipts); var enabledExecutor=new DmsApprovedRevocationExecutor(mapper,center,resources,cleanup,occupancy,guard); assertEquals(200,enabledExecutor.execute(command).getCode()); var order=inOrder(guard,center,mapper); order.verify(guard).requireExecution("t","task",null,"REVOCATION",List.of("file")); order.verify(center).revoke(command,at); order.verify(mapper).complete(row,at,"lease"); order.verify(guard).completeExecution(receipts); } @Test void changedIndependentReceiptStopsBeforeCenterMutation() { var guard=mock(DmsBusinessLifecycleGuard.class); when(guard.requireExecution("t","task",null,"REVOCATION",List.of("file"))) .thenThrow(new IllegalStateException("receipt changed")); var enabledExecutor=new DmsApprovedRevocationExecutor(mapper,center,resources,cleanup,occupancy,guard); assertThrows(IllegalStateException.class,()->enabledExecutor.execute(command)); verifyNoInteractions(center); verify(guard,never()).completeExecution(any()); } @Test void committedReplayDoesNotTouchVersionsOrOccupancy() { row.setStatus("APPLIED");assertEquals(200,executor.execute(command).getCode()); verifyNoInteractions(center,resources,cleanup,occupancy); } @Test void changedSnapshotAndForeignTenantAreRejected() { command.setExpectedMainVersionId(UUID.randomUUID());assertThrows(DataException.class,()->executor.execute(command)); verifyNoInteractions(center); } @Test void lostLeaseOrUnapprovedSourceNeverExecutes() { when(mapper.validLease(row,"lease")).thenReturn(false);assertThrows(DataException.class,()->executor.execute(command)); when(mapper.validLease(row,"lease")).thenReturn(true);when(mapper.approvedSource(row)).thenReturn(false); assertThrows(DataException.class,()->executor.execute(command));verifyNoInteractions(center); } @Test void legacyMarkerDoesNotPretendToBeSuccess() { row.setStatus("CENTER_APPLIED");assertThrows(DataException.class,()->executor.execute(command));verifyNoInteractions(center); } @Test void businessConflictPreventsCompletionAndRelease() { target();when(resources.lockResources("t",row.getFileVersionId())).thenReturn(List.of()); assertThrows(DataException.class,()->executor.execute(command));verifyNoInteractions(occupancy,cleanup); verify(mapper,never()).complete(any(),any(),any()); } @Test void invalidationCannotExecuteThroughRevocation() { command.setReason("yishixiao");assertThrows(DataException.class,()->executor.execute(command));verifyNoInteractions(center); } @Test void finalLeaseLossFailsTheTransaction() { target();when(mapper.complete(row,at,"lease")).thenReturn(0);assertThrows(DataException.class,()->executor.execute(command)); } @Test void alreadyIneffectiveCompletesWithoutRewritingFileAndReleasesOwnReceipt() { target(); when(center.alreadyIneffective(command,row,at)).thenReturn(true); var guard=mock(DmsBusinessLifecycleGuard.class); var receipts=List.of(new jnpf.dmsBusinessOccupancy.BusinessFileOccupancy()); when(guard.requireExecution("t","task",null,"REVOCATION",List.of("file"))).thenReturn(receipts); var checked=new DmsApprovedRevocationExecutor(mapper,center,resources,cleanup,occupancy,guard); assertEquals(200,checked.execute(command).getCode()); verify(center,never()).revoke(any(),any()); verifyNoInteractions(resources,cleanup); verify(mapper,never()).disableResource(any(),any(),any()); var order=inOrder(center,mapper,occupancy,guard); order.verify(center).alreadyIneffective(command,row,at); order.verify(mapper).recordExecution(row,at); order.verify(occupancy).releaseFile("t","task",row.getOperation(),"file"); order.verify(mapper).complete(row,at,"lease"); order.verify(guard).completeExecution(receipts); } @Test void ineffectiveCompletionLeaseLossDoesNotReleaseIndependentReceipt() { target();when(center.alreadyIneffective(command,row,at)).thenReturn(true); when(mapper.complete(row,at,"lease")).thenReturn(0); var guard=mock(DmsBusinessLifecycleGuard.class); var checked=new DmsApprovedRevocationExecutor(mapper,center,resources,cleanup,occupancy,guard); assertThrows(DataException.class,()->checked.execute(command)); verify(guard,never()).completeExecution(any()); verify(center,never()).revoke(any(),any()); verifyNoInteractions(resources,cleanup); } @Test void ineffectiveCheckFailureCannotBeReportedAsSuccess() { when(center.alreadyIneffective(command,row,at)).thenThrow(new DataException("projection changed")); assertThrows(DataException.class,()->executor.execute(command)); verify(mapper,never()).recordExecution(any(),any()); verify(mapper,never()).complete(any(),any(),any()); verifyNoInteractions(occupancy,resources,cleanup); } }