From 8e8069362418d8db5179f7716d71d9fb3e4a0034 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期一, 27 四月 2026 13:05:42 +0800
Subject: [PATCH] fix: make auto tune apply writes auditable atomically
---
src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java | 75 +++++++++++++++++++++++++++++++++++--
1 files changed, 71 insertions(+), 4 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..9519218 100644
--- a/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
+++ b/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
@@ -15,6 +15,8 @@
import com.zy.asrs.service.BasDualCrnpService;
import com.zy.asrs.service.BasStationService;
import com.zy.asrs.service.StationFlowCapacityService;
+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;
@@ -40,9 +42,13 @@
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.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -67,6 +73,8 @@
private BasDualCrnpService basDualCrnpService;
@Mock
private StationFlowCapacityService stationFlowCapacityService;
+ @Mock
+ private RedisUtil redisUtil;
private RecordingTransactionManager transactionManager;
@BeforeEach
@@ -81,6 +89,7 @@
ReflectionTestUtils.setField(service, "basDualCrnpService", basDualCrnpService);
ReflectionTestUtils.setField(service, "stationFlowCapacityService", stationFlowCapacityService);
ReflectionTestUtils.setField(service, "transactionManager", transactionManager);
+ ReflectionTestUtils.setField(service, "redisUtil", redisUtil);
AtomicLong jobId = new AtomicLong(100);
when(aiAutoTuneJobService.save(any(AiAutoTuneJob.class))).thenAnswer(invocation -> {
@@ -96,6 +105,7 @@
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
@@ -311,6 +321,61 @@
assertEquals("failed", changes.get(0).getResultStatus());
assertTrue(changes.get(0).getRejectReason().contains("db write failed"));
assertEquals(1, transactionManager.getRollbackCount());
+ 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());
+ 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))).thenReturn(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());
+ verify(configService).saveConfigValue("conveyorStationTaskLimit", "15");
+ verify(configService, never()).refreshSystemConfigCache();
+ 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("姝e湪鎵ц"));
+ verify(configService, never()).getOne(any(Wrapper.class));
+ verify(configService, never()).saveConfigValue(any(), any());
+ verify(redisUtil, never()).compareAndDelete(anyString(), anyString());
}
@Test
@@ -445,14 +510,16 @@
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 {
--
Gitblit v1.9.1