From dc3f9cc91759823ce59486f19b138be4b296a0f1 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期二, 28 四月 2026 09:43:28 +0800
Subject: [PATCH] #
---
src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java | 542 +++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 510 insertions(+), 32 deletions(-)
diff --git a/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java b/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
index 2d538bb..1620f70 100644
--- a/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
+++ b/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
@@ -10,17 +10,20 @@
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.entity.WrkMast;
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.asrs.service.WrkMastService;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
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.InOrder;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
@@ -36,14 +39,22 @@
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,7 +77,9 @@
@Mock
private BasDualCrnpService basDualCrnpService;
@Mock
- private StationFlowCapacityService stationFlowCapacityService;
+ private WrkMastService wrkMastService;
+ @Mock
+ private RedisUtil redisUtil;
private RecordingTransactionManager transactionManager;
@BeforeEach
@@ -79,23 +92,30 @@
ReflectionTestUtils.setField(service, "basStationService", basStationService);
ReflectionTestUtils.setField(service, "basCrnpService", basCrnpService);
ReflectionTestUtils.setField(service, "basDualCrnpService", basDualCrnpService);
- ReflectionTestUtils.setField(service, "stationFlowCapacityService", stationFlowCapacityService);
+ ReflectionTestUtils.setField(service, "wrkMastService", wrkMastService);
ReflectionTestUtils.setField(service, "transactionManager", transactionManager);
+ ReflectionTestUtils.setField(service, "redisUtil", redisUtil);
AtomicLong jobId = new AtomicLong(100);
when(aiAutoTuneJobService.save(any(AiAutoTuneJob.class))).thenAnswer(invocation -> {
+ transactionManager.recordJobSaveCall();
AiAutoTuneJob job = invocation.getArgument(0);
job.setId(jobId.incrementAndGet());
return true;
});
- when(aiAutoTuneJobService.updateById(any(AiAutoTuneJob.class))).thenReturn(true);
+ when(aiAutoTuneJobService.updateById(any(AiAutoTuneJob.class))).thenAnswer(invocation -> {
+ transactionManager.recordJobUpdateCall();
+ return true;
+ });
when(aiAutoTuneChangeService.saveBatch(any(Collection.class))).thenReturn(true);
when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(Collections.emptyList());
+ when(wrkMastService.count(any(Wrapper.class))).thenReturn(0L);
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);
+ when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(true);
}
@Test
@@ -143,47 +163,130 @@
}
@Test
- void rejectCrnOutBatchRunningLimitRangeAndStepCases() {
+ void crnOutBatchRunningLimitAllowsStepThreeAndRejectsRangeAndStepCases() {
when(configService.getOne(any(Wrapper.class))).thenReturn(config("crnOutBatchRunningLimit", "10"));
service.apply(request(true,
command("sys_config", null, "crnOutBatchRunningLimit", "13"),
+ command("sys_config", null, "crnOutBatchRunningLimit", "14"),
command("sys_config", null, "crnOutBatchRunningLimit", "21")
));
List<AiAutoTuneChange> changes = savedChanges();
- assertEquals("rejected", changes.get(0).getResultStatus());
- assertTrue(changes.get(0).getRejectReason().contains("姝ラ暱涓嶈兘瓒呰繃 2"));
+ assertEquals("dry_run", changes.get(0).getResultStatus());
+ assertEquals("13", changes.get(0).getRequestedValue());
assertEquals("rejected", changes.get(1).getResultStatus());
- assertTrue(changes.get(1).getRejectReason().contains("1~20"));
+ assertTrue(changes.get(1).getRejectReason().contains("姝ラ暱涓嶈兘瓒呰繃 3"));
+ assertEquals("rejected", changes.get(2).getResultStatus());
+ assertTrue(changes.get(2).getRejectReason().contains("1~20"));
}
@Test
- void rejectMaxInTaskRangeAndStepCases() {
+ void maxInTaskAllowsStepThreeAndRejectsRangeAndStepCases() {
when(basCrnpService.getById(1)).thenReturn(crn(1, 1, 5));
service.apply(request(true,
- command("crn", "1", "maxInTask", "7"),
+ command("crn", "1", "maxInTask", "8"),
+ command("crn", "1", "maxInTask", "9"),
command("crn", "1", "maxInTask", "11")
));
List<AiAutoTuneChange> changes = savedChanges();
- assertEquals("rejected", changes.get(0).getResultStatus());
- assertTrue(changes.get(0).getRejectReason().contains("姝ラ暱涓嶈兘瓒呰繃 1"));
+ assertEquals("dry_run", changes.get(0).getResultStatus());
+ assertEquals("8", changes.get(0).getRequestedValue());
assertEquals("rejected", changes.get(1).getResultStatus());
- assertTrue(changes.get(1).getRejectReason().contains("0~10"));
+ assertTrue(changes.get(1).getRejectReason().contains("姝ラ暱涓嶈兘瓒呰繃 3"));
+ assertEquals("rejected", changes.get(2).getResultStatus());
+ assertTrue(changes.get(2).getRejectReason().contains("0~10"));
+ }
+
+ @Test
+ void maxOutTaskAllowsStepThreeAndRejectsStepFour() {
+ when(basCrnpService.getById(1)).thenReturn(crn(1, 1, 1));
+
+ service.apply(request(true,
+ command("crn", "1", "maxOutTask", "4"),
+ command("crn", "1", "maxOutTask", "5")
+ ));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ assertEquals("dry_run", changes.get(0).getResultStatus());
+ assertEquals("4", changes.get(0).getRequestedValue());
+ assertEquals("rejected", changes.get(1).getResultStatus());
+ assertTrue(changes.get(1).getRejectReason().contains("姝ラ暱涓嶈兘瓒呰繃 3"));
+ }
+
+ @Test
+ void dualCrnMaxOutTaskAllowsStepThreeAndRejectsStepFour() {
+ when(basDualCrnpService.getById(2)).thenReturn(dualCrn(2, 1, 1));
+
+ service.apply(request(true,
+ command("dual_crn", "2", "maxOutTask", "4"),
+ command("dual_crn", "2", "maxOutTask", "5")
+ ));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ assertEquals("dry_run", changes.get(0).getResultStatus());
+ assertEquals("4", changes.get(0).getRequestedValue());
+ assertEquals("rejected", changes.get(1).getResultStatus());
+ assertTrue(changes.get(1).getRejectReason().contains("姝ラ暱涓嶈兘瓒呰繃 3"));
}
@Test
void rejectStationOutTaskLimitAboveDirectionalBufferCapacity() {
- when(basStationService.getById(101)).thenReturn(station(101, 1));
- when(stationFlowCapacityService.getOne(any(Wrapper.class))).thenReturn(capacity(101, "OUT", 2));
+ when(basStationService.getById(101)).thenReturn(station(101, 1, 2));
service.apply(request(true, command("station", "101", "outTaskLimit", "3")));
List<AiAutoTuneChange> changes = savedChanges();
assertEquals("rejected", changes.get(0).getResultStatus());
assertTrue(changes.get(0).getRejectReason().contains("0~2"));
+ }
+
+ @Test
+ void rejectStationOutTaskLimitNullUnlimitedCurrentValue() {
+ when(basStationService.getById(101)).thenReturn(station(101, null));
+
+ service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ assertEquals("rejected", changes.get(0).getResultStatus());
+ assertTrue(changes.get(0).getRejectReason().contains("闇�瑕佷汉宸ュ厛鍒濆鍖栦负鏈夐檺鍊�"));
+ }
+
+ @Test
+ void rejectStationOutTaskLimitNegativeUnlimitedCurrentValue() {
+ when(basStationService.getById(101)).thenReturn(station(101, -1));
+
+ service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ assertEquals("rejected", changes.get(0).getResultStatus());
+ assertTrue(changes.get(0).getRejectReason().contains("闇�瑕佷汉宸ュ厛鍒濆鍖栦负鏈夐檺鍊�"));
+ }
+
+ @Test
+ void allowStationOutTaskLimitZeroToOneAsFiniteStep() {
+ when(basStationService.getById(101)).thenReturn(station(101, 0, 1));
+
+ AutoTuneApplyResult result = service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ assertTrue(result.getSuccess());
+ assertEquals("dry_run", changes.get(0).getResultStatus());
+ assertEquals("0", changes.get(0).getOldValue());
+ assertEquals("1", changes.get(0).getRequestedValue());
+ }
+
+ @Test
+ void rejectStationOutTaskLimitWithoutOutBufferCapacity() {
+ when(basStationService.getById(101)).thenReturn(station(101, 0));
+
+ service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ assertEquals("rejected", changes.get(0).getResultStatus());
+ assertTrue(changes.get(0).getRejectReason().contains("缂哄皯 outBufferCapacity"));
}
@Test
@@ -204,8 +307,7 @@
@Test
void realMixedValidAndInvalidBatchRejectsAndDoesNotWriteTargets() {
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(basStationService.getById(101)).thenReturn(station(101, 1, 2));
AutoTuneApplyResult result = service.apply(request(false,
command("sys_config", null, "conveyorStationTaskLimit", "15"),
@@ -233,6 +335,46 @@
verify(basStationService, never()).update(any(Wrapper.class));
verify(basCrnpService, never()).update(any(Wrapper.class));
verify(basDualCrnpService, never()).update(any(Wrapper.class));
+ }
+
+ @Test
+ void applyJobRecordsActiveTasksWhenCountIsPositive() {
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+ when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L);
+
+ service.apply(request(true, command("sys_config", null, "conveyorStationTaskLimit", "15")));
+
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertEquals(1, updatedJob.getHasActiveTasks());
+
+ ArgumentCaptor<Wrapper<WrkMast>> captor = ArgumentCaptor.forClass(Wrapper.class);
+ verify(wrkMastService).count(captor.capture());
+ String sqlSegment = captor.getValue().getSqlSegment();
+ assertTrue(sqlSegment.contains("NOT IN"));
+ assertTrue(sqlSegment.contains("OR"));
+ assertTrue(sqlSegment.contains("wrk_sts IS NULL"));
+ }
+
+ @Test
+ void applyJobRecordsNoActiveTasksWhenCountIsZero() {
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+ when(wrkMastService.count(any(Wrapper.class))).thenReturn(0L);
+
+ service.apply(request(true, command("sys_config", null, "conveyorStationTaskLimit", "15")));
+
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertEquals(0, updatedJob.getHasActiveTasks());
+ }
+
+ @Test
+ void applyJobFallsBackToNoActiveTasksWhenCountFails() {
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+ when(wrkMastService.count(any(Wrapper.class))).thenThrow(new IllegalStateException("count failed"));
+
+ service.apply(request(true, command("sys_config", null, "conveyorStationTaskLimit", "15")));
+
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertEquals(0, updatedJob.getHasActiveTasks());
}
@Test
@@ -273,8 +415,7 @@
@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(basStationService.getById(101)).thenReturn(station(101, 1, 2));
when(basCrnpService.getById(1)).thenReturn(crn(1, 1, 1));
when(basDualCrnpService.getById(2)).thenReturn(dualCrn(2, 1, 1));
@@ -300,6 +441,27 @@
}
@Test
+ void applyCacheRefreshFailureDoesNotAppendFailedAuditOrJob() {
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+ doThrow(new IllegalStateException("cache refresh failed"))
+ .when(configService).refreshSystemConfigCache();
+
+ AutoTuneApplyResult result = service.apply(request(false,
+ command("sys_config", null, "conveyorStationTaskLimit", "15")
+ ));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertTrue(result.getSuccess());
+ assertEquals("success", updatedJob.getStatus());
+ assertEquals(1, changes.size());
+ assertEquals("success", changes.get(0).getResultStatus());
+ verify(aiAutoTuneChangeService, times(1)).saveBatch(any(Collection.class));
+ verify(aiAutoTuneJobService, times(1)).updateById(any(AiAutoTuneJob.class));
+ verify(configService).refreshSystemConfigCache();
+ }
+
+ @Test
void writeFailureReturnsFailedAuditWithoutThrowing() {
when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
when(configService.saveConfigValue("conveyorStationTaskLimit", "15")).thenThrow(new IllegalStateException("db write failed"));
@@ -311,6 +473,113 @@
assertEquals("failed", changes.get(0).getResultStatus());
assertTrue(changes.get(0).getRejectReason().contains("db write failed"));
assertEquals(1, transactionManager.getRollbackCount());
+ assertTrue(transactionManager.getJobSaveInsideTransactionCount() > 0);
+ assertTrue(transactionManager.getJobUpdateInsideTransactionCount() > 0);
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ verify(redisUtil).compareAndDelete(eq(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key), anyString());
+ }
+
+ @Test
+ void auditSaveBatchFailureRollsBackTargetWriteAndReturnsFailedAudit() {
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+ when(aiAutoTuneChangeService.saveBatch(any(Collection.class)))
+ .thenThrow(new IllegalStateException("audit failed"))
+ .thenReturn(true);
+
+ AutoTuneApplyResult result = service.apply(request(false, command("sys_config", null, "conveyorStationTaskLimit", "15")));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertFalse(result.getSuccess());
+ assertEquals("failed", updatedJob.getStatus());
+ assertEquals("failed", changes.get(0).getResultStatus());
+ assertTrue(changes.get(0).getRejectReason().contains("audit failed"));
+ assertEquals(1, transactionManager.getRollbackCount());
+ assertTrue(transactionManager.getJobSaveInsideTransactionCount() > 0);
+ assertTrue(transactionManager.getJobUpdateInsideTransactionCount() > 0);
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ verify(configService).saveConfigValue("conveyorStationTaskLimit", "15");
+ verify(configService, never()).refreshSystemConfigCache();
+ verify(redisUtil).compareAndDelete(eq(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key), anyString());
+ }
+
+ @Test
+ void jobUpdateFailureRollsBackTargetWriteTransaction() {
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+ when(aiAutoTuneJobService.updateById(any(AiAutoTuneJob.class))).thenAnswer(invocation -> {
+ transactionManager.recordJobUpdateCall();
+ return false;
+ });
+
+ IllegalStateException exception = assertThrows(IllegalStateException.class,
+ () -> service.apply(request(false, command("sys_config", null, "conveyorStationTaskLimit", "15"))));
+
+ assertTrue(exception.getMessage().contains("鏇存柊璋冨弬浠诲姟鐘舵�佸け璐�"));
+ assertEquals(2, transactionManager.getRollbackCount());
+ assertEquals(0, transactionManager.getCommitCount());
+ assertTrue(transactionManager.getJobSaveInsideTransactionCount() > 0);
+ assertTrue(transactionManager.getJobUpdateInsideTransactionCount() > 0);
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ verify(configService).saveConfigValue("conveyorStationTaskLimit", "15");
+ verify(configService, never()).refreshSystemConfigCache();
+ verify(redisUtil).compareAndDelete(eq(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key), anyString());
+ }
+
+ @Test
+ void jobUpdateFailureRecordsFailureJobWhenRecoveryUpdateSucceeds() {
+ AtomicInteger updateAttempts = new AtomicInteger();
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+ when(aiAutoTuneJobService.updateById(any(AiAutoTuneJob.class))).thenAnswer(invocation -> {
+ transactionManager.recordJobUpdateCall();
+ return updateAttempts.incrementAndGet() > 1;
+ });
+
+ AutoTuneApplyResult result = service.apply(request(false, command("sys_config", null, "conveyorStationTaskLimit", "15")));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertFalse(result.getSuccess());
+ assertEquals("failed", updatedJob.getStatus());
+ assertEquals(1, changes.size());
+ assertEquals("failed", changes.get(0).getResultStatus());
+ assertTrue(changes.get(0).getRejectReason().contains("鏇存柊璋冨弬浠诲姟鐘舵�佸け璐�"));
+ assertEquals(1, transactionManager.getRollbackCount());
+ assertTrue(transactionManager.getCommitCount() >= 1);
+ assertTrue(transactionManager.getJobSaveInsideTransactionCount() > 0);
+ assertTrue(transactionManager.getJobUpdateInsideTransactionCount() > 0);
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ verify(configService).saveConfigValue("conveyorStationTaskLimit", "15");
+ verify(configService, never()).refreshSystemConfigCache();
+ verify(aiAutoTuneChangeService, times(2)).saveBatch(any(Collection.class));
+ verify(aiAutoTuneJobService, times(2)).updateById(any(AiAutoTuneJob.class));
+ verify(redisUtil).compareAndDelete(eq(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key), anyString());
+ }
+
+ @Test
+ void realApplyLockNotAcquiredRejectsWithoutTargetWrite() {
+ when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(false);
+
+ AutoTuneApplyResult result = service.apply(request(false, command("sys_config", null, "conveyorStationTaskLimit", "15")));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertFalse(result.getSuccess());
+ assertEquals("failed", updatedJob.getStatus());
+ assertEquals("failed", changes.get(0).getResultStatus());
+ assertTrue(changes.get(0).getRejectReason().contains("閿佷笉鍙敤"));
+ assertTrue(changes.get(0).getRejectReason().contains("Redis"));
+ assertTrue(transactionManager.getJobSaveInsideTransactionCount() > 0);
+ assertTrue(transactionManager.getJobUpdateInsideTransactionCount() > 0);
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ verify(configService, never()).getOne(any(Wrapper.class));
+ verify(configService, never()).saveConfigValue(any(), any());
+ verify(redisUtil).hasKey(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key);
+ verify(redisUtil, never()).compareAndDelete(anyString(), anyString());
}
@Test
@@ -337,6 +606,176 @@
verify(configService).saveConfigValue("conveyorStationTaskLimit", "10");
verify(configService).refreshSystemConfigCache();
verify(basStationService).update(any(Wrapper.class));
+ verify(redisUtil).compareAndDelete(eq(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key), anyString());
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ InOrder rollbackOrder = inOrder(redisUtil, aiAutoTuneChangeService);
+ rollbackOrder.verify(redisUtil).trySetStringIfAbsent(anyString(), anyString(), anyLong());
+ rollbackOrder.verify(aiAutoTuneChangeService, atLeastOnce()).list(any(Wrapper.class));
+ }
+
+ @Test
+ void rollbackJobRecordsActiveTasksWhenCountIsPositive() {
+ AiAutoTuneJob latestRealJob = job(10L, "manual", "success");
+ AiAutoTuneChange configChange = successChange(10L, "sys_config", "", "conveyorStationTaskLimit", "10", "15");
+ when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L);
+ when(aiAutoTuneChangeService.list(any(Wrapper.class)))
+ .thenReturn(List.of(configChange))
+ .thenReturn(List.of(configChange));
+ when(aiAutoTuneJobService.getById(10L)).thenReturn(latestRealJob);
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "15"));
+
+ service.rollbackLastSuccessfulJob("manual rollback");
+
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertEquals(1, updatedJob.getHasActiveTasks());
+ }
+
+ @Test
+ void rollbackCacheRefreshFailureDoesNotAppendFailedAuditOrJob() {
+ AiAutoTuneJob latestRealJob = job(10L, "manual", "success");
+ AiAutoTuneChange configChange = successChange(10L, "sys_config", "", "conveyorStationTaskLimit", "10", "15");
+ when(aiAutoTuneChangeService.list(any(Wrapper.class)))
+ .thenReturn(List.of(configChange))
+ .thenReturn(List.of(configChange));
+ when(aiAutoTuneJobService.getById(10L)).thenReturn(latestRealJob);
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "15"));
+ doThrow(new IllegalStateException("cache refresh failed"))
+ .when(configService).refreshSystemConfigCache();
+
+ AutoTuneApplyResult result = service.rollbackLastSuccessfulJob("manual rollback");
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertTrue(result.getSuccess());
+ assertEquals("success", updatedJob.getStatus());
+ assertEquals(1, changes.size());
+ assertEquals("success", changes.get(0).getResultStatus());
+ verify(aiAutoTuneChangeService, times(1)).saveBatch(any(Collection.class));
+ verify(aiAutoTuneJobService, times(1)).updateById(any(AiAutoTuneJob.class));
+ verify(configService).refreshSystemConfigCache();
+ }
+
+ @Test
+ void rollbackAuditSaveBatchFailureRollsBackTargetWriteAndReturnsFailedAudit() {
+ AiAutoTuneJob latestRealJob = job(10L, "manual", "success");
+ AiAutoTuneChange configChange = successChange(10L, "sys_config", "", "conveyorStationTaskLimit", "10", "15");
+ when(aiAutoTuneChangeService.list(any(Wrapper.class)))
+ .thenReturn(List.of(configChange))
+ .thenReturn(List.of(configChange));
+ when(aiAutoTuneJobService.getById(10L)).thenReturn(latestRealJob);
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "15"));
+ when(aiAutoTuneChangeService.saveBatch(any(Collection.class)))
+ .thenThrow(new IllegalStateException("rollback audit failed"))
+ .thenReturn(true);
+
+ AutoTuneApplyResult result = service.rollbackLastSuccessfulJob("manual rollback");
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertFalse(result.getSuccess());
+ assertEquals("failed", updatedJob.getStatus());
+ assertEquals("failed", changes.get(0).getResultStatus());
+ assertTrue(changes.get(0).getRejectReason().contains("rollback audit failed"));
+ assertEquals(1, transactionManager.getRollbackCount());
+ assertTrue(transactionManager.getJobSaveInsideTransactionCount() > 0);
+ assertTrue(transactionManager.getJobUpdateInsideTransactionCount() > 0);
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ verify(configService).saveConfigValue("conveyorStationTaskLimit", "10");
+ verify(configService, never()).refreshSystemConfigCache();
+ verify(redisUtil).compareAndDelete(eq(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key), anyString());
+ }
+
+ @Test
+ void rollbackJobUpdateFailureRollsBackAndDoesNotSaveRunningJobOutsideTransaction() {
+ AiAutoTuneJob latestRealJob = job(10L, "manual", "success");
+ AiAutoTuneChange configChange = successChange(10L, "sys_config", "", "conveyorStationTaskLimit", "10", "15");
+ when(aiAutoTuneChangeService.list(any(Wrapper.class)))
+ .thenReturn(List.of(configChange))
+ .thenReturn(List.of(configChange));
+ when(aiAutoTuneJobService.getById(10L)).thenReturn(latestRealJob);
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "15"));
+ when(aiAutoTuneJobService.updateById(any(AiAutoTuneJob.class))).thenAnswer(invocation -> {
+ transactionManager.recordJobUpdateCall();
+ return false;
+ });
+
+ IllegalStateException exception = assertThrows(IllegalStateException.class,
+ () -> service.rollbackLastSuccessfulJob("manual rollback"));
+
+ assertTrue(exception.getMessage().contains("鏇存柊璋冨弬浠诲姟鐘舵�佸け璐�"));
+ assertEquals(2, transactionManager.getRollbackCount());
+ assertEquals(0, transactionManager.getCommitCount());
+ assertTrue(transactionManager.getJobSaveInsideTransactionCount() > 0);
+ assertTrue(transactionManager.getJobUpdateInsideTransactionCount() > 0);
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ verify(configService).saveConfigValue("conveyorStationTaskLimit", "10");
+ verify(configService, never()).refreshSystemConfigCache();
+ verify(redisUtil).compareAndDelete(eq(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key), anyString());
+ }
+
+ @Test
+ void rollbackJobUpdateFailureRecordsFailureJobWhenRecoveryUpdateSucceeds() {
+ AtomicInteger updateAttempts = new AtomicInteger();
+ AiAutoTuneJob latestRealJob = job(10L, "manual", "success");
+ AiAutoTuneChange configChange = successChange(10L, "sys_config", "", "conveyorStationTaskLimit", "10", "15");
+ when(aiAutoTuneChangeService.list(any(Wrapper.class)))
+ .thenReturn(List.of(configChange))
+ .thenReturn(List.of(configChange));
+ when(aiAutoTuneJobService.getById(10L)).thenReturn(latestRealJob);
+ when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "15"));
+ when(aiAutoTuneJobService.updateById(any(AiAutoTuneJob.class))).thenAnswer(invocation -> {
+ transactionManager.recordJobUpdateCall();
+ return updateAttempts.incrementAndGet() > 1;
+ });
+
+ AutoTuneApplyResult result = service.rollbackLastSuccessfulJob("manual rollback");
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertFalse(result.getSuccess());
+ assertEquals("failed", updatedJob.getStatus());
+ assertEquals(1, changes.size());
+ assertEquals("failed", changes.get(0).getResultStatus());
+ assertTrue(changes.get(0).getRejectReason().contains("鏇存柊璋冨弬浠诲姟鐘舵�佸け璐�"));
+ assertEquals(1, transactionManager.getRollbackCount());
+ assertTrue(transactionManager.getCommitCount() >= 1);
+ assertTrue(transactionManager.getJobSaveInsideTransactionCount() > 0);
+ assertTrue(transactionManager.getJobUpdateInsideTransactionCount() > 0);
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ verify(configService).saveConfigValue("conveyorStationTaskLimit", "10");
+ verify(configService, never()).refreshSystemConfigCache();
+ verify(aiAutoTuneChangeService, times(2)).saveBatch(any(Collection.class));
+ verify(aiAutoTuneJobService, times(2)).updateById(any(AiAutoTuneJob.class));
+ verify(redisUtil).compareAndDelete(eq(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key), anyString());
+ }
+
+ @Test
+ void rollbackLockNotAcquiredReturnsFailedAuditWithoutTargetWrite() {
+ when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(false);
+
+ AutoTuneApplyResult result = service.rollbackLastSuccessfulJob("manual rollback");
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ AiAutoTuneJob updatedJob = updatedJob();
+ assertFalse(result.getSuccess());
+ assertEquals("failed", updatedJob.getStatus());
+ assertEquals("failed", changes.get(0).getResultStatus());
+ assertTrue(changes.get(0).getRejectReason().contains("閿佷笉鍙敤"));
+ assertTrue(changes.get(0).getRejectReason().contains("Redis"));
+ assertTrue(transactionManager.getJobSaveInsideTransactionCount() > 0);
+ assertTrue(transactionManager.getJobUpdateInsideTransactionCount() > 0);
+ assertEquals(0, transactionManager.getJobSaveOutsideTransactionCount());
+ assertEquals(0, transactionManager.getJobUpdateOutsideTransactionCount());
+ verify(aiAutoTuneChangeService, never()).list(any(Wrapper.class));
+ verify(aiAutoTuneJobService, never()).getById(any());
+ verify(configService, never()).saveConfigValue(any(), any());
+ verify(configService, never()).refreshSystemConfigCache();
+ verify(redisUtil).hasKey(RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key);
+ verify(redisUtil, never()).compareAndDelete(anyString(), anyString());
}
@Test
@@ -388,9 +827,14 @@
}
private BasStation station(Integer stationId, Integer outTaskLimit) {
+ return station(stationId, outTaskLimit, null);
+ }
+
+ private BasStation station(Integer stationId, Integer outTaskLimit, Integer outBufferCapacity) {
BasStation station = new BasStation();
station.setStationId(stationId);
station.setOutTaskLimit(outTaskLimit);
+ station.setOutBufferCapacity(outBufferCapacity);
return station;
}
@@ -408,14 +852,6 @@
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,
@@ -445,35 +881,61 @@
private List<AiAutoTuneChange> savedChanges() {
ArgumentCaptor<Collection<AiAutoTuneChange>> captor = ArgumentCaptor.forClass(Collection.class);
- verify(aiAutoTuneChangeService).saveBatch(captor.capture());
- return new ArrayList<>(captor.getValue());
+ verify(aiAutoTuneChangeService, atLeastOnce()).saveBatch(captor.capture());
+ List<Collection<AiAutoTuneChange>> allValues = captor.getAllValues();
+ return new ArrayList<>(allValues.get(allValues.size() - 1));
}
private AiAutoTuneJob updatedJob() {
ArgumentCaptor<AiAutoTuneJob> captor = ArgumentCaptor.forClass(AiAutoTuneJob.class);
- verify(aiAutoTuneJobService).updateById(captor.capture());
- return captor.getValue();
+ verify(aiAutoTuneJobService, atLeastOnce()).updateById(captor.capture());
+ List<AiAutoTuneJob> allValues = captor.getAllValues();
+ return allValues.get(allValues.size() - 1);
}
private static class RecordingTransactionManager implements PlatformTransactionManager {
private int beginCount;
private int commitCount;
private int rollbackCount;
+ private boolean transactionActive;
+ private int jobSaveInsideTransactionCount;
+ private int jobSaveOutsideTransactionCount;
+ private int jobUpdateInsideTransactionCount;
+ private int jobUpdateOutsideTransactionCount;
@Override
public TransactionStatus getTransaction(TransactionDefinition definition) {
beginCount++;
+ transactionActive = true;
return new SimpleTransactionStatus();
}
@Override
public void commit(TransactionStatus status) {
commitCount++;
+ transactionActive = false;
}
@Override
public void rollback(TransactionStatus status) {
rollbackCount++;
+ transactionActive = false;
+ }
+
+ public void recordJobSaveCall() {
+ if (transactionActive) {
+ jobSaveInsideTransactionCount++;
+ } else {
+ jobSaveOutsideTransactionCount++;
+ }
+ }
+
+ public void recordJobUpdateCall() {
+ if (transactionActive) {
+ jobUpdateInsideTransactionCount++;
+ } else {
+ jobUpdateOutsideTransactionCount++;
+ }
}
public int getCommitCount() {
@@ -483,5 +945,21 @@
public int getRollbackCount() {
return rollbackCount;
}
+
+ public int getJobSaveInsideTransactionCount() {
+ return jobSaveInsideTransactionCount;
+ }
+
+ public int getJobSaveOutsideTransactionCount() {
+ return jobSaveOutsideTransactionCount;
+ }
+
+ public int getJobUpdateInsideTransactionCount() {
+ return jobUpdateInsideTransactionCount;
+ }
+
+ public int getJobUpdateOutsideTransactionCount() {
+ return jobUpdateOutsideTransactionCount;
+ }
}
}
--
Gitblit v1.9.1