package com.zy.core.utils; import com.zy.asrs.entity.BasStationOpt; import com.zy.asrs.entity.BasDevp; import com.zy.asrs.entity.BasStation; import com.zy.asrs.entity.WrkMast; import com.zy.asrs.service.BasDevpService; import com.zy.asrs.service.BasStationService; import com.zy.asrs.service.BasStationOptService; import com.zy.asrs.service.WrkMastService; import com.zy.asrs.service.WrkAnalysisService; import com.zy.asrs.utils.NotifyUtils; import com.zy.common.entity.FindCrnNoResult; import com.zy.common.service.CommonService; import com.zy.core.cache.MessageQueue; import com.zy.core.cache.SlaveConnection; import com.zy.core.enums.SlaveType; import com.zy.core.enums.WrkStsType; import com.zy.core.enums.WrkIoType; import com.zy.core.enums.RedisKeyType; import com.zy.core.move.StationMoveCoordinator; import com.zy.core.move.StationMoveDispatchMode; import com.zy.core.move.StationMoveSession; import com.zy.core.dispatch.StationCommandDispatchResult; import com.zy.core.dispatch.StationCommandDispatcher; import com.zy.core.model.command.StationCommand; import com.zy.core.model.protocol.StationProtocol; import com.zy.core.model.protocol.StationTaskBufferItem; import com.zy.core.thread.StationThread; import com.zy.common.utils.RedisUtil; import com.zy.core.utils.station.StationDispatchLoadSupport; import com.zy.core.utils.station.StationDispatchRuntimeStateSupport; import com.zy.core.utils.station.StationOutboundDecisionSupport; import com.zy.core.utils.station.StationOutboundDispatchProcessor; import com.zy.core.utils.station.StationRegularDispatchProcessor; import com.zy.core.utils.station.StationRerouteProcessor; import com.zy.core.utils.station.model.DispatchLimitConfig; import com.zy.core.utils.station.model.LoadGuardState; import com.zy.core.utils.station.model.LoopHitResult; import com.zy.core.utils.station.model.RerouteCommandPlan; import com.zy.core.utils.station.model.RerouteContext; import com.zy.core.utils.station.model.RerouteDecision; import com.zy.core.utils.station.model.RerouteExecutionResult; import com.zy.core.utils.station.model.RerouteSceneType; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; import java.util.Date; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.times; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class StationOperateProcessUtilsReroutePipelineTest { private final Map> dependencyOverrides = new IdentityHashMap<>(); private StationOperateProcessUtils newUtils() { StationOperateProcessUtils utils = new StationOperateProcessUtils(); stash(utils, "stationOutboundDecisionSupport", new StationOutboundDecisionSupport()); wireRerouteProcessor(utils); return utils; } private void wireOutboundSupport(StationOperateProcessUtils utils) { StationOutboundDecisionSupport support = new StationOutboundDecisionSupport(); copyIfPresent(utils, support, "wrkMastService"); copyIfPresent(utils, support, "basDevpService"); copyIfPresent(utils, support, "basStationService"); copyIfPresent(utils, support, "navigateUtils"); copyIfPresent(utils, support, "redisUtil"); copyIfPresent(utils, support, "stationTaskLoopService"); copyIfPresent(utils, support, "stationMoveCoordinator"); StationDispatchRuntimeStateSupport runtimeStateSupport = new StationDispatchRuntimeStateSupport(); copyIfPresent(utils, runtimeStateSupport, "redisUtil"); copyIfPresent(utils, runtimeStateSupport, "basStationOptService"); ReflectionTestUtils.setField(support, "stationDispatchRuntimeStateSupport", runtimeStateSupport); stash(utils, "stationOutboundDecisionSupport", support); } private void wireRerouteProcessor(StationOperateProcessUtils utils) { StationRerouteProcessor processor = new StationRerouteProcessor(); copyIfPresent(utils, processor, "basDevpService"); copyIfPresent(utils, processor, "wrkMastService"); copyIfPresent(utils, processor, "commonService"); copyIfPresent(utils, processor, "redisUtil"); copyIfPresent(utils, processor, "locMastService"); copyIfPresent(utils, processor, "wmsOperateUtils"); copyIfPresent(utils, processor, "basStationOptService"); copyIfPresent(utils, processor, "stationMoveCoordinator"); Object dispatcher = readIfPresent(utils, "stationCommandDispatcher"); if (dispatcher == null) { dispatcher = new StationCommandDispatcher( (RedisUtil) readIfPresent(utils, "redisUtil"), (StationMoveCoordinator) readIfPresent(utils, "stationMoveCoordinator") ); } ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher); Object outboundSupport = readIfPresent(utils, "stationOutboundDecisionSupport"); if (outboundSupport == null) { wireOutboundSupport(utils); outboundSupport = readIfPresent(utils, "stationOutboundDecisionSupport"); } if (outboundSupport != null) { ReflectionTestUtils.setField(processor, "stationOutboundDecisionSupport", outboundSupport); } StationDispatchRuntimeStateSupport runtimeStateSupport = new StationDispatchRuntimeStateSupport(); copyIfPresent(utils, runtimeStateSupport, "redisUtil"); copyIfPresent(utils, runtimeStateSupport, "basStationOptService"); ReflectionTestUtils.setField(processor, "stationDispatchRuntimeStateSupport", runtimeStateSupport); ReflectionTestUtils.setField(utils, "stationRerouteProcessor", processor); } private void stash(StationOperateProcessUtils utils, String fieldName, Object value) { dependencyOverrides.computeIfAbsent(utils, key -> new HashMap<>()).put(fieldName, value); } private Object readIfPresent(Object source, String fieldName) { try { return ReflectionTestUtils.getField(source, fieldName); } catch (IllegalArgumentException ignore) { if (source instanceof StationOperateProcessUtils) { Map values = dependencyOverrides.get(source); return values == null ? null : values.get(fieldName); } return null; } } private void copyIfPresent(Object source, Object target, String fieldName) { Object value = readIfPresent(source, fieldName); if (value != null) { try { ReflectionTestUtils.setField(target, fieldName, value); } catch (IllegalArgumentException ignore) { } } } @SuppressWarnings("unchecked") private void stubTaskDispatchLock(StationMoveCoordinator coordinator) { when(coordinator.withTaskDispatchLock(any(), any())).thenAnswer(invocation -> { java.util.function.Supplier supplier = invocation.getArgument(1); return supplier == null ? null : supplier.get(); }); } @Test void choosesRunBlockCommandBuilderForRunBlockRerouteScene() { RerouteSceneType scene = RerouteSceneType.RUN_BLOCK_REROUTE; assertSame(RerouteSceneType.RUN_BLOCK_REROUTE, scene); } @Test void resolveExecutionTarget_skipsWhenTargetEqualsCurrentStation() { RerouteDecision decision = RerouteDecision.skip("same-station"); assertTrue(decision.skip()); assertEquals("same-station", decision.skipReason()); } @Test void buildCommandPlan_usesRunBlockCommandBuilderForRunBlockScene() { StationOperateProcessUtils utils = newUtils(); StationThread stationThread = mock(StationThread.class); StationCommand command = new StationCommand(); command.setTaskNo(100); command.setStationId(10); command.setTargetStaNo(20); when(stationThread.getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d)).thenReturn(command); RerouteContext context = RerouteContext.create( RerouteSceneType.RUN_BLOCK_REROUTE, buildBasDevp(1), stationThread, buildStationProtocol(10, 100, 10), buildWrkMast(100, 99), Collections.emptyList(), 0.25d, "checkStationRunBlock_reroute" ).withRunBlockCommand() .withCancelSessionBeforeDispatch() .withResetSegmentCommandsBeforeDispatch(); RerouteCommandPlan plan = utils.buildRerouteCommandPlan( context, RerouteDecision.proceed(20) ); verify(stationThread).getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d); assertSame(command, plan.command()); } @Test void executePlan_skipsWhenCurrentTaskStillExistsInBuffer() { StationOperateProcessUtils utils = newUtils(); StationCommand command = new StationCommand(); command.setTaskNo(100); command.setStationId(10); command.setTargetStaNo(20); StationTaskBufferItem bufferItem = new StationTaskBufferItem(); bufferItem.setTaskNo(100); RerouteContext context = RerouteContext.create( RerouteSceneType.OUT_ORDER, buildBasDevp(1), mock(StationThread.class), buildStationProtocol(10, 100, 10, Collections.singletonList(bufferItem)), buildWrkMast(100, 20), List.of(10, 20), 0.0d, "checkStationOutOrder" ); RerouteExecutionResult result = utils.executeReroutePlan( context, RerouteCommandPlan.dispatch( command, RerouteDecision.proceed(20), "checkStationOutOrder" ) ); assertTrue(result.skipped()); assertEquals("buffer-has-current-task", result.skipReason()); } @Test void outOrderAndWatchCircle_shareDecisionFlow() { StationOperateProcessUtils utils = newUtils(); WrkMast wrkMast = buildWrkMast(100, 20); RerouteContext outOrderContext = RerouteContext.create( RerouteSceneType.OUT_ORDER, buildBasDevp(1), mock(StationThread.class), buildStationProtocol(10, 100, 10), wrkMast, Collections.emptyList(), 0.0d, "checkStationOutOrder" ); RerouteContext watchCircleContext = RerouteContext.create( RerouteSceneType.WATCH_CIRCLE, buildBasDevp(1), mock(StationThread.class), buildStationProtocol(10, 100, 10), wrkMast, Collections.emptyList(), 0.0d, "watchCircleStation" ); RerouteDecision outOrderDecision = utils.resolveSharedRerouteDecision(outOrderContext); RerouteDecision watchCircleDecision = utils.resolveSharedRerouteDecision(watchCircleContext); assertEquals(20, outOrderDecision.targetStationId()); assertEquals(20, watchCircleDecision.targetStationId()); } @Test void runBlockReroute_keepsDirectReassignAndNormalRerouteSeparate() { StationOperateProcessUtils utils = newUtils(); WrkMast inboundWrkMast = buildWrkMast(100, 20); inboundWrkMast.setIoType(WrkIoType.IN.id); assertTrue(utils.shouldUseRunBlockDirectReassign(inboundWrkMast, 10, List.of(10))); assertTrue(!utils.shouldUseRunBlockDirectReassign(inboundWrkMast, 11, List.of(10))); } @Test void idleRecover_skipsWhenLastDispatchIsTooRecent() { StationOperateProcessUtils utils = newUtils(); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); RedisUtil redisUtil = mock(RedisUtil.class); stubTaskDispatchLock(coordinator); stash(utils, "stationMoveCoordinator", coordinator); stash(utils, "redisUtil", redisUtil); wireRerouteProcessor(utils); StationMoveSession session = new StationMoveSession(); session.setStatus(StationMoveSession.STATUS_RUNNING); session.setCurrentStationId(10); session.setDispatchStationId(10); session.setLastIssuedAt(System.currentTimeMillis()); when(coordinator.loadSession(100)).thenReturn(session); StationCommand command = new StationCommand(); command.setTaskNo(100); command.setStationId(10); command.setTargetStaNo(20); RerouteContext context = RerouteContext.create( RerouteSceneType.IDLE_RECOVER, buildBasDevp(1), mock(StationThread.class), buildStationProtocol(10, 100, 10), buildWrkMast(100, 20), Collections.emptyList(), 0.0d, "checkStationIdleRecover" ).withRecentDispatchGuard(); RerouteExecutionResult result = utils.executeReroutePlan( context, RerouteCommandPlan.dispatch( command, RerouteDecision.proceed(20), "checkStationIdleRecover" ) ); assertTrue(result.skipped()); assertEquals("recent-dispatch", result.skipReason()); verify(coordinator, never()).cancelSession(100); } @Test void idleRecover_skipsWhenCurrentStationIsStillInsideRecentlyIssuedActiveRoute() { StationOperateProcessUtils utils = newUtils(); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); RedisUtil redisUtil = mock(RedisUtil.class); stubTaskDispatchLock(coordinator); stash(utils, "stationMoveCoordinator", coordinator); stash(utils, "redisUtil", redisUtil); wireRerouteProcessor(utils); StationMoveSession session = new StationMoveSession(); session.setStatus(StationMoveSession.STATUS_RUNNING); session.setDispatchStationId(196); session.setCurrentStationId(189); session.setFullPathStationIds(List.of(196, 195, 189, 121, 124)); session.setLastIssuedAt(System.currentTimeMillis()); when(coordinator.loadSession(10510)).thenReturn(session); StationCommand command = new StationCommand(); command.setTaskNo(10510); command.setStationId(121); command.setTargetStaNo(124); RerouteContext context = RerouteContext.create( RerouteSceneType.IDLE_RECOVER, buildBasDevp(1), mock(StationThread.class), buildStationProtocol(121, 10510, 121), buildWrkMast(10510, 124), Collections.emptyList(), 0.0d, "checkStationIdleRecover" ).withRecentDispatchGuard(); RerouteExecutionResult result = utils.executeReroutePlan( context, RerouteCommandPlan.dispatch( command, RerouteDecision.proceed(124), "checkStationIdleRecover" ) ); assertTrue(result.skipped()); assertEquals("recent-dispatch", result.skipReason()); verify(coordinator, never()).cancelSession(10510); } @Test void idleRecover_skipsWhenStationCommandLogShowsRecentIssuedMove() { StationOperateProcessUtils utils = newUtils(); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); BasStationOptService basStationOptService = mock(BasStationOptService.class); RedisUtil redisUtil = mock(RedisUtil.class); stubTaskDispatchLock(coordinator); stash(utils, "stationMoveCoordinator", coordinator); stash(utils, "basStationOptService", basStationOptService); stash(utils, "redisUtil", redisUtil); wireRerouteProcessor(utils); StationMoveSession session = new StationMoveSession(); session.setStatus(StationMoveSession.STATUS_RUNNING); session.setDispatchStationId(136); session.setCurrentStationId(145); session.setFullPathStationIds(List.of(136, 137, 139, 142, 143, 145, 148, 149, 151)); session.setLastIssuedAt(System.currentTimeMillis() - 20_000L); when(coordinator.loadSession(10493)).thenReturn(session); BasStationOpt recentOpt = new BasStationOpt(); recentOpt.setTaskNo(10493); recentOpt.setStationId(148); recentOpt.setSend(1); recentOpt.setMode(String.valueOf(com.zy.core.enums.StationCommandType.MOVE)); recentOpt.setSendTime(new Date()); when(basStationOptService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) .thenReturn(Collections.singletonList(recentOpt)); Boolean skipped = ReflectionTestUtils.invokeMethod(utils, "shouldSkipIdleRecoverForRecentDispatch", 10493, 148); assertTrue(Boolean.TRUE.equals(skipped)); } @Test void checkStationOutOrder_skipsWhenActiveSessionAlreadyOwnsCurrentStation() { StationOperateProcessUtils utils = newUtils(); BasDevpService basDevpService = mock(BasDevpService.class); WrkMastService wrkMastService = mock(WrkMastService.class); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); RedisUtil redisUtil = mock(RedisUtil.class); StationThread stationThread = mock(StationThread.class); stubTaskDispatchLock(coordinator); stash(utils, "basDevpService", basDevpService); ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); stash(utils, "stationMoveCoordinator", coordinator); stash(utils, "redisUtil", redisUtil); wireOutboundSupport(utils); wireRerouteProcessor(utils); BasDevp basDevp = buildBasDevp(1); basDevp.setIsOutOrderList("[{\"deviceNo\":1,\"stationId\":145}]"); when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) .thenReturn(Collections.singletonList(basDevp)); StationProtocol stationProtocol = buildStationProtocol(145, 10337, 145); stationProtocol.setAutoing(true); stationProtocol.setLoading(true); when(stationThread.getStatusMap()).thenReturn(Map.of(145, stationProtocol)); StationCommand command = new StationCommand(); command.setTaskNo(10337); command.setStationId(145); command.setTargetStaNo(111); when(stationThread.getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10337), eq(145), eq(111), eq(0), eq(0.0d))) .thenReturn(command); WrkMast wrkMast = buildWrkMast(10337, 111); wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); wrkMast.setIoType(WrkIoType.OUT.id); when(wrkMastService.selectByWorkNo(10337)).thenReturn(wrkMast); StationMoveSession session = new StationMoveSession(); session.setTaskNo(10337); session.setStatus(StationMoveSession.STATUS_RUNNING); session.setDispatchMode(StationMoveDispatchMode.CIRCLE); session.setCurrentStationId(145); session.setCurrentRouteTargetStationId(148); session.setNextDecisionStationId(148); session.setFullPathStationIds(List.of(145, 148, 149, 151, 119, 118, 117, 115, 114, 111)); when(coordinator.loadSession(10337)).thenReturn(session); when(redisUtil.get(anyString())).thenReturn(null); MessageQueue.init(SlaveType.Devp, 1); SlaveConnection.put(SlaveType.Devp, 1, stationThread); try { utils.checkStationOutOrder(); assertNull(MessageQueue.peek(SlaveType.Devp, 1)); verify(stationThread, never()).getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10337), eq(145), eq(111), eq(0), eq(0.0d)); } finally { MessageQueue.clear(SlaveType.Devp, 1); SlaveConnection.remove(SlaveType.Devp, 1); } } @Test void checkStationOutOrder_restartsWhenBlockedSessionExistsButStationNoLongerRunBlock() { StationOperateProcessUtils utils = newUtils(); BasDevpService basDevpService = mock(BasDevpService.class); WrkMastService wrkMastService = mock(WrkMastService.class); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); RedisUtil redisUtil = mock(RedisUtil.class); StationThread stationThread = mock(StationThread.class); stubTaskDispatchLock(coordinator); stash(utils, "basDevpService", basDevpService); ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); stash(utils, "stationMoveCoordinator", coordinator); stash(utils, "redisUtil", redisUtil); wireOutboundSupport(utils); wireRerouteProcessor(utils); BasDevp basDevp = buildBasDevp(1); basDevp.setIsOutOrderList("[{\"deviceNo\":1,\"stationId\":145}]"); when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) .thenReturn(Collections.singletonList(basDevp)); StationProtocol stationProtocol = buildStationProtocol(145, 10337, 145); stationProtocol.setAutoing(true); stationProtocol.setLoading(true); stationProtocol.setRunBlock(false); when(stationThread.getStatusMap()).thenReturn(Map.of(145, stationProtocol)); StationCommand command = new StationCommand(); command.setTaskNo(10337); command.setStationId(145); command.setTargetStaNo(111); when(stationThread.getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10337), eq(145), eq(111), eq(0), eq(0.0d))) .thenReturn(command); WrkMast wrkMast = buildWrkMast(10337, 111); wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); wrkMast.setIoType(WrkIoType.OUT.id); when(wrkMastService.selectByWorkNo(10337)).thenReturn(wrkMast); StationMoveSession session = new StationMoveSession(); session.setTaskNo(10337); session.setStatus(StationMoveSession.STATUS_BLOCKED); session.setDispatchMode(StationMoveDispatchMode.CIRCLE); session.setCurrentStationId(145); session.setCurrentRouteTargetStationId(148); session.setNextDecisionStationId(148); session.setFullPathStationIds(List.of(145, 148, 149, 151, 119, 118, 117, 115, 114, 111)); when(coordinator.loadSession(10337)).thenReturn(session); when(redisUtil.get(anyString())).thenReturn(null); MessageQueue.init(SlaveType.Devp, 1); SlaveConnection.put(SlaveType.Devp, 1, stationThread); try { utils.checkStationOutOrder(); assertTrue(MessageQueue.peek(SlaveType.Devp, 1) != null); verify(stationThread, times(1)).getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10337), eq(145), eq(111), eq(0), eq(0.0d)); } finally { MessageQueue.clear(SlaveType.Devp, 1); SlaveConnection.remove(SlaveType.Devp, 1); } } @Test void checkStationOutOrder_skipsRunBlockStationBeforePlanning() { StationOperateProcessUtils utils = newUtils(); BasDevpService basDevpService = mock(BasDevpService.class); WrkMastService wrkMastService = mock(WrkMastService.class); RedisUtil redisUtil = mock(RedisUtil.class); StationThread stationThread = mock(StationThread.class); stash(utils, "basDevpService", basDevpService); ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); stash(utils, "redisUtil", redisUtil); wireOutboundSupport(utils); wireRerouteProcessor(utils); BasDevp basDevp = buildBasDevp(1); basDevp.setIsOutOrderList("[{\"deviceNo\":1,\"stationId\":145}]"); when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) .thenReturn(Collections.singletonList(basDevp)); StationProtocol stationProtocol = buildStationProtocol(145, 10337, 145); stationProtocol.setAutoing(true); stationProtocol.setLoading(true); stationProtocol.setRunBlock(true); when(stationThread.getStatusMap()).thenReturn(Map.of(145, stationProtocol)); StationCommand command = new StationCommand(); command.setTaskNo(10337); command.setStationId(145); command.setTargetStaNo(111); when(stationThread.getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10337), eq(145), eq(111), eq(0), eq(0.0d))) .thenReturn(command); WrkMast wrkMast = buildWrkMast(10337, 111); wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); wrkMast.setIoType(WrkIoType.OUT.id); when(wrkMastService.selectByWorkNo(10337)).thenReturn(wrkMast); when(redisUtil.get(anyString())).thenReturn(null); MessageQueue.init(SlaveType.Devp, 1); SlaveConnection.put(SlaveType.Devp, 1, stationThread); try { utils.checkStationOutOrder(); assertNull(MessageQueue.peek(SlaveType.Devp, 1)); verify(stationThread, never()).getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10337), eq(145), eq(111), eq(0), eq(0.0d)); } finally { MessageQueue.clear(SlaveType.Devp, 1); SlaveConnection.remove(SlaveType.Devp, 1); } } @Test void executePlan_runBlockReroute_reissuesWhenBlockedSessionMatchesCandidatePath() { StationOperateProcessUtils utils = newUtils(); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); RedisUtil redisUtil = mock(RedisUtil.class); stubTaskDispatchLock(coordinator); stash(utils, "stationMoveCoordinator", coordinator); stash(utils, "redisUtil", redisUtil); wireRerouteProcessor(utils); StationCommand command = new StationCommand(); command.setTaskNo(100); command.setStationId(145); command.setTargetStaNo(111); command.setNavigatePath(List.of(145, 113, 112, 111)); command.setOriginalNavigatePath(List.of(145, 113, 112, 111)); StationMoveSession session = new StationMoveSession(); session.setTaskNo(100); session.setStatus(StationMoveSession.STATUS_BLOCKED); session.setCurrentStationId(145); session.setDispatchStationId(145); session.setPathSignature("same-path"); when(coordinator.loadSession(100)).thenReturn(session); when(coordinator.buildPathSignature(command)).thenReturn("same-path"); RerouteContext context = RerouteContext.create( RerouteSceneType.RUN_BLOCK_REROUTE, buildBasDevp(1), mock(StationThread.class), buildStationProtocol(145, 100, 145), buildWrkMast(100, 111), Collections.emptyList(), 0.0d, "checkStationRunBlock_reroute" ).withCancelSessionBeforeDispatch() .withResetSegmentCommandsBeforeDispatch(); MessageQueue.init(SlaveType.Devp, 1); try { RerouteExecutionResult result = utils.executeReroutePlan( context, RerouteCommandPlan.dispatch( command, RerouteDecision.proceed(111), "checkStationRunBlock_reroute" ) ); assertTrue(!result.skipped()); org.mockito.InOrder inOrder = inOrder(coordinator); inOrder.verify(coordinator).markCancelPending(100, "reroute_pending"); inOrder.verify(coordinator).cancelSession(100); verify(coordinator, times(1)).cancelSession(100); } finally { MessageQueue.clear(SlaveType.Devp, 1); } } @Test void executePlan_runBlockReroute_ignoresCurrentTaskBufferAfterReset() { StationOperateProcessUtils utils = newUtils(); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); RedisUtil redisUtil = mock(RedisUtil.class); stubTaskDispatchLock(coordinator); stash(utils, "stationMoveCoordinator", coordinator); stash(utils, "redisUtil", redisUtil); wireRerouteProcessor(utils); StationCommand command = new StationCommand(); command.setTaskNo(10388); command.setStationId(186); command.setTargetStaNo(124); command.setNavigatePath(List.of(186, 189, 121, 124)); command.setOriginalNavigatePath(List.of(186, 189, 121, 124)); StationTaskBufferItem bufferItem = new StationTaskBufferItem(); bufferItem.setTaskNo(10388); when(redisUtil.get(anyString())).thenReturn(null); RerouteContext context = RerouteContext.create( RerouteSceneType.RUN_BLOCK_REROUTE, buildBasDevp(1), mock(StationThread.class), buildStationProtocol(186, 10388, 189, Collections.singletonList(bufferItem)), buildWrkMast(10388, 124), Collections.emptyList(), 0.0d, "checkStationRunBlock_reroute" ).withCancelSessionBeforeDispatch() .withResetSegmentCommandsBeforeDispatch(); MessageQueue.init(SlaveType.Devp, 1); try { RerouteExecutionResult result = utils.executeReroutePlan( context, RerouteCommandPlan.dispatch( command, RerouteDecision.proceed(124), "checkStationRunBlock_reroute" ) ); assertTrue(!result.skipped()); verify(coordinator, times(1)).cancelSession(10388); } finally { MessageQueue.clear(SlaveType.Devp, 1); } } @Test void executePlan_runBlockReroute_bypassesSuppressDispatchAfterReset() { StationOperateProcessUtils utils = newUtils(); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); RedisUtil redisUtil = mock(RedisUtil.class); stubTaskDispatchLock(coordinator); stash(utils, "stationMoveCoordinator", coordinator); stash(utils, "redisUtil", redisUtil); wireRerouteProcessor(utils); StationCommand command = new StationCommand(); command.setTaskNo(10388); command.setStationId(186); command.setTargetStaNo(124); command.setNavigatePath(List.of(186, 124)); command.setOriginalNavigatePath(List.of(186, 124)); when(redisUtil.get(anyString())).thenReturn(null); when(coordinator.shouldSuppressDispatch(10388, 186, command)).thenReturn(true); RerouteContext context = RerouteContext.create( RerouteSceneType.RUN_BLOCK_REROUTE, buildBasDevp(1), mock(StationThread.class), buildStationProtocol(186, 10388, 186), buildWrkMast(10388, 124), Collections.emptyList(), 0.0d, "checkStationRunBlock_reroute" ).withSuppressDispatchGuard() .withCancelSessionBeforeDispatch() .withResetSegmentCommandsBeforeDispatch(); MessageQueue.init(SlaveType.Devp, 1); try { RerouteExecutionResult result = utils.executeReroutePlan( context, RerouteCommandPlan.dispatch( command, RerouteDecision.proceed(124), "checkStationRunBlock_reroute" ) ); assertTrue(!result.skipped()); verify(coordinator, times(1)).cancelSession(10388); } finally { MessageQueue.clear(SlaveType.Devp, 1); } } @Test void stationInExecute_recordsDispatchSessionAfterIssuingMoveCommand() { StationOperateProcessUtils utils = newUtils(); BasDevpService basDevpService = mock(BasDevpService.class); WrkMastService wrkMastService = mock(WrkMastService.class); CommonService commonService = mock(CommonService.class); RedisUtil redisUtil = mock(RedisUtil.class); WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class); StationDispatchLoadSupport loadSupport = mock(StationDispatchLoadSupport.class); StationThread stationThread = mock(StationThread.class); stubTaskDispatchLock(coordinator); StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor(); ReflectionTestUtils.setField(processor, "basDevpService", basDevpService); ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService); ReflectionTestUtils.setField(processor, "commonService", commonService); ReflectionTestUtils.setField(processor, "redisUtil", redisUtil); ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService); ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator); ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher); ReflectionTestUtils.setField(processor, "stationDispatchLoadSupport", loadSupport); ReflectionTestUtils.setField(utils, "stationRegularDispatchProcessor", processor); BasDevp basDevp = buildBasDevp(1); basDevp.setBarcodeStationList("[{\"deviceNo\":1,\"stationId\":101}]"); when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) .thenReturn(Collections.singletonList(basDevp)); StationProtocol stationProtocol = buildStationProtocol(101, 500670, 101); stationProtocol.setAutoing(true); stationProtocol.setLoading(true); stationProtocol.setBarcode("GSL110005"); when(stationThread.getStatusMap()).thenReturn(Map.of(101, stationProtocol)); when(stationThread.getStatus()).thenReturn(Collections.singletonList(stationProtocol)); WrkMast wrkMast = buildWrkMast(500670, 102); wrkMast.setWrkSts(WrkStsType.NEW_INBOUND.sts); wrkMast.setLocNo("8-4-1"); when(wrkMastService.getOne(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))).thenReturn(wrkMast); when(wrkMastService.updateById(wrkMast)).thenReturn(true); FindCrnNoResult findCrnNoResult = new FindCrnNoResult(); findCrnNoResult.setCrnNo(4); findCrnNoResult.setCrnType(SlaveType.Crn); when(commonService.findCrnNoByLocNo("8-4-1")).thenReturn(findCrnNoResult); when(commonService.findInStationId(findCrnNoResult, 101)).thenReturn(102); StationCommand command = new StationCommand(); command.setTaskNo(500670); command.setStationId(101); command.setTargetStaNo(102); when(stationThread.getCommand(com.zy.core.enums.StationCommandType.MOVE, 500670, 101, 102, 0)) .thenReturn(command); when(redisUtil.get(anyString())).thenReturn(null); when(dispatcher.dispatch(1, command, "station-operate-process", "stationInExecute")) .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-operate-process", "stationInExecute")); DispatchLimitConfig baseConfig = new DispatchLimitConfig(); LoadGuardState loadGuardState = new LoadGuardState(); LoopHitResult noHit = LoopHitResult.noHit(); when(loadSupport.getDispatchLimitConfig(null, null)).thenReturn(baseConfig); when(loadSupport.countCurrentStationTask()).thenReturn(0); when(loadSupport.buildLoadGuardState(baseConfig)).thenReturn(loadGuardState); when(loadSupport.getDispatchLimitConfig(101, 102)).thenReturn(baseConfig); when(loadSupport.findPathLoopHit(baseConfig, 101, 102, loadGuardState)).thenReturn(noHit); when(loadSupport.isDispatchBlocked(baseConfig, 0, loadGuardState, false)).thenReturn(false); MessageQueue.init(SlaveType.Devp, 1); SlaveConnection.put(SlaveType.Devp, 1, stationThread); try { utils.stationInExecute(); verify(coordinator, times(1)).recordDispatch(eq(500670), eq(101), eq("stationInExecute"), same(command), eq(false)); } finally { MessageQueue.clear(SlaveType.Devp, 1); SlaveConnection.remove(SlaveType.Devp, 1); } } @Test void dualCrnStationOutExecute_delegatesToOutboundProcessor() { StationOperateProcessUtils utils = newUtils(); StationOutboundDispatchProcessor processor = mock(StationOutboundDispatchProcessor.class); ReflectionTestUtils.setField(utils, "stationOutboundDispatchProcessor", processor); utils.dualCrnStationOutExecute(); verify(processor, times(1)).dualCrnStationOutExecute(); } @Test void stationOutExecuteFinish_attemptsClearPathBeforeCompletingTask() { StationOperateProcessUtils utils = newUtils(); WrkMastService wrkMastService = mock(WrkMastService.class); BasStationService basStationService = mock(BasStationService.class); WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class); NotifyUtils notifyUtils = mock(NotifyUtils.class); RedisUtil redisUtil = mock(RedisUtil.class); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); StationThread stationThread = mock(StationThread.class); StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor(); ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService); ReflectionTestUtils.setField(processor, "basStationService", basStationService); ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService); ReflectionTestUtils.setField(processor, "notifyUtils", notifyUtils); ReflectionTestUtils.setField(processor, "redisUtil", redisUtil); ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator); ReflectionTestUtils.setField(utils, "stationRegularDispatchProcessor", processor); WrkMast wrkMast = buildWrkMast(10335, 145); wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) .thenReturn(Collections.singletonList(wrkMast)); when(wrkMastService.updateById(wrkMast)).thenReturn(true); BasStation basStation = new BasStation(); basStation.setStationId(145); basStation.setDeviceNo(1); when(basStationService.getOne(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) .thenReturn(basStation); StationProtocol stationProtocol = buildStationProtocol(145, 10335, 145); when(stationThread.getStatusMap()).thenReturn(Map.of(145, stationProtocol)); when(stationThread.clearPath(10335)).thenReturn(true); SlaveConnection.put(SlaveType.Devp, 1, stationThread); try { utils.stationOutExecuteFinish(); verify(stationThread, times(1)).clearPath(10335); verify(coordinator, times(1)).finishSession(10335); verify(wrkMastService, times(1)).updateById(wrkMast); assertEquals(WrkStsType.STATION_RUN_COMPLETE.sts, wrkMast.getWrkSts()); } finally { SlaveConnection.remove(SlaveType.Devp, 1); } } @Test void watchCircleStation_usesSessionArrivalStateWhenLegacyCommandMissing() { StationOperateProcessUtils utils = newUtils(); BasDevpService basDevpService = mock(BasDevpService.class); WrkMastService wrkMastService = mock(WrkMastService.class); StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); RedisUtil redisUtil = mock(RedisUtil.class); StationThread stationThread = mock(StationThread.class); stubTaskDispatchLock(coordinator); stash(utils, "basDevpService", basDevpService); ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); stash(utils, "stationMoveCoordinator", coordinator); stash(utils, "redisUtil", redisUtil); wireOutboundSupport(utils); wireRerouteProcessor(utils); BasDevp basDevp = buildBasDevp(1); when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) .thenReturn(Collections.singletonList(basDevp)); StationProtocol stationProtocol = buildStationProtocol(145, 10337, 148); stationProtocol.setAutoing(true); stationProtocol.setLoading(true); when(stationThread.getStatus()).thenReturn(Collections.singletonList(stationProtocol)); StationCommand command = new StationCommand(); command.setTaskNo(10337); command.setStationId(145); command.setTargetStaNo(111); when(stationThread.getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10337), eq(145), eq(111), eq(0), eq(0.0d))) .thenReturn(command); WrkMast wrkMast = buildWrkMast(10337, 111); wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); wrkMast.setIoType(WrkIoType.OUT.id); when(wrkMastService.selectByWorkNo(10337)).thenReturn(wrkMast); StationMoveSession session = new StationMoveSession(); session.setTaskNo(10337); session.setStatus(StationMoveSession.STATUS_RUNNING); session.setDispatchMode(StationMoveDispatchMode.CIRCLE); session.setCurrentStationId(145); session.setDispatchStationId(145); session.setNextDecisionStationId(145); session.setFullPathStationIds(List.of(145, 148, 149, 151, 119, 118, 117, 115, 114, 111)); when(coordinator.loadSession(10337)).thenReturn(session); when(redisUtil.get(anyString())).thenReturn(null); MessageQueue.init(SlaveType.Devp, 1); SlaveConnection.put(SlaveType.Devp, 1, stationThread); try { utils.watchCircleStation(); verify(stationThread, times(1)).getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10337), eq(145), eq(111), eq(0), eq(0.0d)); } finally { MessageQueue.clear(SlaveType.Devp, 1); SlaveConnection.remove(SlaveType.Devp, 1); } } private static BasDevp buildBasDevp(int devpNo) { BasDevp basDevp = new BasDevp(); basDevp.setDevpNo(devpNo); return basDevp; } private static WrkMast buildWrkMast(int wrkNo, int targetStationId) { WrkMast wrkMast = new WrkMast(); wrkMast.setWrkNo(wrkNo); wrkMast.setStaNo(targetStationId); return wrkMast; } private static StationProtocol buildStationProtocol(int stationId, int taskNo, int targetStationId) { return buildStationProtocol(stationId, taskNo, targetStationId, Collections.emptyList()); } private static StationProtocol buildStationProtocol(int stationId, int taskNo, int targetStationId, List taskBufferItems) { StationProtocol stationProtocol = new StationProtocol(); stationProtocol.setStationId(stationId); stationProtocol.setTaskNo(taskNo); stationProtocol.setTargetStaNo(targetStationId); stationProtocol.setTaskBufferItems(taskBufferItems); return stationProtocol; } }