package com.zy.ai.service; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.zy.ai.domain.autotune.AutoTuneApplyRequest; import com.zy.ai.domain.autotune.AutoTuneApplyResult; import com.zy.ai.domain.autotune.AutoTuneChangeCommand; import com.zy.ai.entity.AiAutoTuneChange; import com.zy.ai.entity.AiAutoTuneJob; import com.zy.ai.service.impl.AutoTuneApplyServiceImpl; import com.zy.asrs.entity.BasCrnp; import com.zy.asrs.entity.BasDualCrnp; import com.zy.asrs.entity.BasStation; import com.zy.asrs.entity.StationFlowCapacity; import com.zy.asrs.service.BasCrnpService; import com.zy.asrs.service.BasDualCrnpService; import com.zy.asrs.service.BasStationService; import com.zy.asrs.service.StationFlowCapacityService; import com.zy.system.entity.Config; import com.zy.system.service.ConfigService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.springframework.test.util.ReflectionTestUtils; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class AutoTuneApplyServiceImplTest { private AutoTuneApplyServiceImpl service; @Mock private AiAutoTuneJobService aiAutoTuneJobService; @Mock private AiAutoTuneChangeService aiAutoTuneChangeService; @Mock private ConfigService configService; @Mock private BasStationService basStationService; @Mock private BasCrnpService basCrnpService; @Mock private BasDualCrnpService basDualCrnpService; @Mock private StationFlowCapacityService stationFlowCapacityService; @BeforeEach void setUp() { service = new AutoTuneApplyServiceImpl(); ReflectionTestUtils.setField(service, "aiAutoTuneJobService", aiAutoTuneJobService); ReflectionTestUtils.setField(service, "aiAutoTuneChangeService", aiAutoTuneChangeService); ReflectionTestUtils.setField(service, "configService", configService); ReflectionTestUtils.setField(service, "basStationService", basStationService); ReflectionTestUtils.setField(service, "basCrnpService", basCrnpService); ReflectionTestUtils.setField(service, "basDualCrnpService", basDualCrnpService); ReflectionTestUtils.setField(service, "stationFlowCapacityService", stationFlowCapacityService); AtomicLong jobId = new AtomicLong(100); when(aiAutoTuneJobService.save(any(AiAutoTuneJob.class))).thenAnswer(invocation -> { AiAutoTuneJob job = invocation.getArgument(0); job.setId(jobId.incrementAndGet()); return true; }); when(aiAutoTuneJobService.updateById(any(AiAutoTuneJob.class))).thenReturn(true); when(aiAutoTuneChangeService.saveBatch(any(Collection.class))).thenReturn(true); when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(Collections.emptyList()); when(configService.getConfigValue(eq("aiAutoTuneIntervalMinutes"), any())).thenReturn("10"); when(configService.saveConfigValue(any(), any())).thenReturn(true); when(basStationService.update(any(Wrapper.class))).thenReturn(true); when(basCrnpService.update(any(Wrapper.class))).thenReturn(true); when(basDualCrnpService.update(any(Wrapper.class))).thenReturn(true); } @Test void rejectNonWhitelistedKey() { AutoTuneApplyResult result = service.apply(request(true, command("sys_config", null, "badKey", "10"))); List changes = savedChanges(); assertFalse(result.getSuccess()); assertEquals("rejected", changes.get(0).getResultStatus()); assertTrue(changes.get(0).getRejectReason().contains("不支持的调参目标")); verify(configService, never()).saveConfigValue(any(), any()); } @Test void rejectOutOfRangeInterval() { when(configService.getOne(any(Wrapper.class))).thenReturn(config("aiAutoTuneIntervalMinutes", "10")); AutoTuneApplyResult result = service.apply(request(true, command("sys_config", null, "aiAutoTuneIntervalMinutes", "100"))); List changes = savedChanges(); assertFalse(result.getSuccess()); assertEquals("rejected", changes.get(0).getResultStatus()); assertTrue(changes.get(0).getRejectReason().contains("5~60")); } @Test void rejectOverStepConveyorLimitChange() { when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10")); service.apply(request(true, command("sys_config", null, "conveyorStationTaskLimit", "16"))); List changes = savedChanges(); assertEquals("rejected", changes.get(0).getResultStatus()); assertTrue(changes.get(0).getRejectReason().contains("步长不能超过 5")); } @Test void rejectStationOutTaskLimitAboveDirectionalBufferCapacity() { when(basStationService.getById(101)).thenReturn(station(101, 1)); when(stationFlowCapacityService.getOne(any(Wrapper.class))).thenReturn(capacity(101, "OUT", 2)); service.apply(request(true, command("station", "101", "outTaskLimit", "3"))); List changes = savedChanges(); assertEquals("rejected", changes.get(0).getResultStatus()); assertTrue(changes.get(0).getRejectReason().contains("0~2")); } @Test void rejectCooldownHit() { when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10")); AiAutoTuneChange cooldownChange = new AiAutoTuneChange(); cooldownChange.setResultStatus("success"); cooldownChange.setCooldownExpireTime(new Date(System.currentTimeMillis() + 60_000L)); when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(cooldownChange)); service.apply(request(true, command("sys_config", null, "conveyorStationTaskLimit", "15"))); List changes = savedChanges(); assertEquals("rejected", changes.get(0).getResultStatus()); assertTrue(changes.get(0).getRejectReason().contains("冷却期")); } @Test void applyMixedBatchSuccessfully() { when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10")); when(basStationService.getById(101)).thenReturn(station(101, 1)); when(stationFlowCapacityService.getOne(any(Wrapper.class))).thenReturn(capacity(101, "OUT", 2)); when(basCrnpService.getById(1)).thenReturn(crn(1, 1, 1)); when(basDualCrnpService.getById(2)).thenReturn(dualCrn(2, 1, 1)); AutoTuneApplyResult result = service.apply(request(false, command("sys_config", null, "conveyorStationTaskLimit", "15"), command("station", "101", "outTaskLimit", "2"), command("crn", "1", "maxOutTask", "2"), command("dual_crn", "2", "maxInTask", "2") )); List changes = savedChanges(); assertTrue(result.getSuccess()); assertEquals(4, changes.size()); assertEquals(4, result.getSuccessCount()); assertEquals(0, result.getRejectCount()); assertTrue(changes.stream().allMatch(change -> "success".equals(change.getResultStatus()))); verify(configService).saveConfigValue("conveyorStationTaskLimit", "15"); verify(configService).refreshSystemConfigCache(); verify(basStationService).update(any(Wrapper.class)); verify(basCrnpService).update(any(Wrapper.class)); verify(basDualCrnpService).update(any(Wrapper.class)); } @Test void rollbackLastJobSuccessfully() { AiAutoTuneJob latestJob = new AiAutoTuneJob(); latestJob.setId(10L); when(aiAutoTuneJobService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(latestJob)); AiAutoTuneChange configChange = successChange(10L, "sys_config", "", "conveyorStationTaskLimit", "10", "15"); AiAutoTuneChange stationChange = successChange(10L, "station", "101", "outTaskLimit", "1", "2"); when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(List.of(configChange, stationChange)); when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "15")); when(basStationService.getById(101)).thenReturn(station(101, 2)); AutoTuneApplyResult result = service.rollbackLastSuccessfulJob("manual rollback"); List changes = savedChanges(); assertTrue(result.getSuccess()); assertEquals(2, changes.size()); assertTrue(changes.stream().allMatch(change -> "success".equals(change.getResultStatus()))); verify(configService).saveConfigValue("conveyorStationTaskLimit", "10"); verify(configService).refreshSystemConfigCache(); verify(basStationService).update(any(Wrapper.class)); } private AutoTuneApplyRequest request(boolean dryRun, AutoTuneChangeCommand... commands) { AutoTuneApplyRequest request = new AutoTuneApplyRequest(); request.setDryRun(dryRun); request.setReason("test"); request.setTriggerType("manual"); request.setChanges(List.of(commands)); return request; } private AutoTuneChangeCommand command(String targetType, String targetId, String targetKey, String newValue) { AutoTuneChangeCommand command = new AutoTuneChangeCommand(); command.setTargetType(targetType); command.setTargetId(targetId); command.setTargetKey(targetKey); command.setNewValue(newValue); return command; } private Config config(String code, String value) { Config config = new Config(); config.setCode(code); config.setValue(value); config.setStatus((short) 1); return config; } private BasStation station(Integer stationId, Integer outTaskLimit) { BasStation station = new BasStation(); station.setStationId(stationId); station.setOutTaskLimit(outTaskLimit); return station; } private BasCrnp crn(Integer crnNo, Integer maxOutTask, Integer maxInTask) { BasCrnp crnp = new BasCrnp(); crnp.setCrnNo(crnNo); crnp.setMaxOutTask(maxOutTask); crnp.setMaxInTask(maxInTask); return crnp; } private BasDualCrnp dualCrn(Integer crnNo, Integer maxOutTask, Integer maxInTask) { BasDualCrnp dualCrnp = new BasDualCrnp(); dualCrnp.setCrnNo(crnNo); dualCrnp.setMaxOutTask(maxOutTask); dualCrnp.setMaxInTask(maxInTask); return dualCrnp; } private StationFlowCapacity capacity(Integer stationId, String directionCode, Integer bufferCapacity) { StationFlowCapacity capacity = new StationFlowCapacity(); capacity.setStationId(stationId); capacity.setDirectionCode(directionCode); capacity.setBufferCapacity(bufferCapacity); return capacity; } private AiAutoTuneChange successChange(Long jobId, String targetType, String targetId, String targetKey, String oldValue, String appliedValue) { AiAutoTuneChange change = new AiAutoTuneChange(); change.setJobId(jobId); change.setTargetType(targetType); change.setTargetId(targetId); change.setTargetKey(targetKey); change.setOldValue(oldValue); change.setAppliedValue(appliedValue); change.setResultStatus("success"); return change; } private List savedChanges() { ArgumentCaptor> captor = ArgumentCaptor.forClass(Collection.class); verify(aiAutoTuneChangeService).saveBatch(captor.capture()); return new ArrayList<>(captor.getValue()); } }