package jnpf.dmsService; import jnpf.base.ActionResult; import jnpf.dmsEntity.storage.approval.DmsApprovedBatch; import jnpf.dmsMapper.DmsApprovedBatchMapper; import jnpf.dmsService.impl.DmsWenjianBanbenServiceImpl; import jnpf.exception.DataException; import org.junit.jupiter.api.Test; import java.util.Date; import java.util.UUID; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; class DmsApprovedFileRetryServiceTest { DmsApprovedBatchMapper mapper = mock(DmsApprovedBatchMapper.class); DmsApprovedFileAcceptanceService acceptance = mock(DmsApprovedFileAcceptanceService.class); DmsApprovedContentService files = mock(DmsApprovedContentService.class); DmsApprovedFileActivationService activation = mock(DmsApprovedFileActivationService.class); DmsWenjianBanbenServiceImpl business = mock(DmsWenjianBanbenServiceImpl.class); DmsApprovedDisableProcessor disables = mock(DmsApprovedDisableProcessor.class); DmsInvalidationProperties invalidation = new DmsInvalidationProperties(); DmsApprovedFileRetryService service = new DmsApprovedFileRetryService(mapper,acceptance,files,activation,business,disables,invalidation); DmsApprovedBatch batch(String status) { DmsApprovedBatch b = new DmsApprovedBatch(); b.setId(UUID.randomUUID()); b.setTenantId("tenant"); b.setTaskId("task"); b.setFlowId("flow"); b.setAuditType("xinzeng_shenhe"); b.setActorId("actor"); b.setStatus(status); b.setApprovedAt(new Date()); b.setAttempts(1); when(mapper.claim(eq("tenant"),eq(b.getId()),any(),anyBoolean())).thenAnswer(i -> { b.setLeaseToken(i.getArgument(2)); return 1; }); when(mapper.find("tenant","task","xinzeng_shenhe")).thenReturn(b); return b; } @Test void unclaimedBatchHasNoSideEffects() { DmsApprovedBatch b = batch("REGISTERING"); when(mapper.claim(any(),any(),any(),anyBoolean())).thenReturn(0); service.process(b); verifyNoInteractions(acceptance,files,activation,business,disables); } @Test void sourceFailureLeavesApprovalQueuedAndSchedulesRetry() { DmsApprovedBatch b = batch("PREPARING"); when(acceptance.stage(any(),any(),any(),any(),any(),any())).thenThrow(new DataException("source unavailable")); assertThrows(DataException.class, () -> service.process(b)); verify(mapper).finish(eq("tenant"),eq(b.getId()),any(),eq("source unavailable"),eq(60)); verifyNoInteractions(files,activation,business,disables); } @Test void pushFailureCanBeRetriedWithoutLosingReservedBatch() { DmsApprovedBatch b = batch("REGISTERING"); when(acceptance.stage(any(),any(),any(),any(),any(),any())).thenReturn(b); when(files.register(any())).thenThrow(new DataException("storage unavailable")); assertThrows(DataException.class, () -> service.process(b)); verify(mapper).finish(eq("tenant"),eq(b.getId()),any(),eq("storage unavailable"),eq(60)); ActionResult ok = new ActionResult<>(); ok.setCode(200); DmsApprovedBatch result = new DmsApprovedBatch(); org.springframework.beans.BeanUtils.copyProperties(b,result); result.setStatus("APPLIED"); ok.setData(result); doReturn(result).when(files).register(any()); when(activation.activate("tenant", b.getId())).thenReturn(result); service.process(b); verify(mapper).finish(eq("tenant"),eq(b.getId()),any(),isNull(),eq(0)); } @Test void lostReplyAfterCommittedRegistrationResumesAtActivationWithoutResendingFiles() { DmsApprovedBatch b = batch("WAITING"); when(activation.activate("tenant",b.getId())).thenAnswer(i -> { b.setStatus("EFFECTIVE"); return b; }); service.process(b); verify(business).applyApproved(b); verifyNoInteractions(files,acceptance); } @Test void failedBusinessWriteRetriesProjectionOnly() { DmsApprovedBatch b = batch("EFFECTIVE"); when(activation.activate("tenant",b.getId())).thenReturn(b); doThrow(new DataException("business unavailable")).doNothing().when(business).applyApproved(b); assertThrows(DataException.class, () -> service.process(b)); service.process(b); verify(business,times(2)).applyApproved(b); verifyNoInteractions(files,acceptance); verify(mapper,never()).fail(any(),any()); } @Test void failedReceiptWriteStillPreservesOriginalFailureForLeaseRecovery() { DmsApprovedBatch b = batch("PREPARING"); when(acceptance.stage(any(),any(),any(),any(),any(),any())).thenThrow(new DataException("source")); when(mapper.finish(any(),any(),any(),any(),anyInt())).thenThrow(new DataException("database")); RuntimeException error=assertThrows(DataException.class,()->service.process(b)); assertEquals(1,error.getSuppressed().length); } DmsApprovedBatch stopBatch() { DmsApprovedBatch b=batch("WAITING");b.setAuditType("shixiao_shenqing");invalidation.setEnabled(true);invalidation.setAllBatches(true); when(mapper.find("tenant","task","shixiao_shenqing")).thenReturn(b); return b; } @Test void stopTasksUseSharedLeaseAndProcessorOnly() { DmsApprovedBatch b=stopBatch();when(disables.isEnabled()).thenReturn(true); service.process(b);verify(disables).process(b); verify(mapper).finish(eq("tenant"),eq(b.getId()),anyString(),isNull(),eq(0)); verifyNoInteractions(files,acceptance,activation,business); } @Test void stopFailureUsesSharedBackoffAndPreservesRowProgress() { DmsApprovedBatch b=stopBatch();when(disables.isEnabled()).thenReturn(true); doThrow(new DataException("row failed")).when(disables).process(b); assertThrows(DataException.class,()->service.process(b)); verify(mapper).finish(eq("tenant"),eq(b.getId()),anyString(),eq("row failed"),eq(60)); verify(mapper,never()).fail(any(),any());verifyNoInteractions(files,acceptance,activation,business); } @Test void disabledStopFeatureDoesNotClaimOrSendStopTask() { DmsApprovedBatch b=stopBatch();service.process(b); verify(mapper,never()).claim(any(),any(),any(),anyBoolean());verify(disables,never()).process(any()); } @Test void disabledBackoffRetriesExistingDelayedTaskAndLeavesFailureReadyForNextScan() { org.springframework.test.util.ReflectionTestUtils.setField(service,"retryBackoffEnabled",false); DmsApprovedBatch b = batch("REGISTERING"); b.setAttempts(7); b.setNextAttemptAt(new Date(System.currentTimeMillis() + 3600000)); when(acceptance.stage(any(),any(),any(),any(),any(),any())).thenThrow(new DataException("source unavailable")); assertThrows(DataException.class, () -> service.process(b)); verify(mapper).claim(eq("tenant"),eq(b.getId()),anyString(),eq(false)); verify(mapper).finish(eq("tenant"),eq(b.getId()),anyString(),eq("source unavailable"),eq(0)); verifyNoInteractions(files,activation,business,disables); } @Test void delayGrowsAndIsBounded() { assertEquals(60,DmsApprovedFileRetryService.retryDelay(1)); assertEquals(120,DmsApprovedFileRetryService.retryDelay(2)); assertEquals(240,DmsApprovedFileRetryService.retryDelay(3)); assertEquals(3600,DmsApprovedFileRetryService.retryDelay(1000)); } @Test void approvedRevocationUsesSharedProcessorWithoutRolloutConfiguration() { DmsApprovedBatch b=batch("WAITING");b.setAuditType("chexiao_shenqing"); when(mapper.find("tenant","task","chexiao_shenqing")).thenReturn(b); when(disables.isEnabled()).thenReturn(true); service.process(b); verify(mapper).claim(eq("tenant"),eq(b.getId()),anyString(),anyBoolean()); verify(disables).process(b); verify(mapper).finish(eq("tenant"),eq(b.getId()),anyString(),isNull(),eq(0)); verifyNoInteractions(files,acceptance,activation,business); } }