From 1080e8e862fcfb9492fd3eed78c34b7ba3abfe32 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期一, 27 四月 2026 11:44:08 +0800
Subject: [PATCH] fix: strengthen auto tune apply transaction safety
---
src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 102 insertions(+), 0 deletions(-)
diff --git a/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java b/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
index cab797d..2d538bb 100644
--- a/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
+++ b/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
@@ -26,6 +26,10 @@
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.SimpleTransactionStatus;
import java.util.ArrayList;
import java.util.Collection;
@@ -63,10 +67,12 @@
private BasDualCrnpService basDualCrnpService;
@Mock
private StationFlowCapacityService stationFlowCapacityService;
+ private RecordingTransactionManager transactionManager;
@BeforeEach
void setUp() {
service = new AutoTuneApplyServiceImpl();
+ transactionManager = new RecordingTransactionManager();
ReflectionTestUtils.setField(service, "aiAutoTuneJobService", aiAutoTuneJobService);
ReflectionTestUtils.setField(service, "aiAutoTuneChangeService", aiAutoTuneChangeService);
ReflectionTestUtils.setField(service, "configService", configService);
@@ -74,6 +80,7 @@
ReflectionTestUtils.setField(service, "basCrnpService", basCrnpService);
ReflectionTestUtils.setField(service, "basDualCrnpService", basDualCrnpService);
ReflectionTestUtils.setField(service, "stationFlowCapacityService", stationFlowCapacityService);
+ ReflectionTestUtils.setField(service, "transactionManager", transactionManager);
AtomicLong jobId = new AtomicLong(100);
when(aiAutoTuneJobService.save(any(AiAutoTuneJob.class))).thenAnswer(invocation -> {
@@ -229,6 +236,41 @@
}
@Test
+ void whitespaceTargetFieldsAreNormalizedBeforeValidationWriteAndAudit() {
+ when(basCrnpService.getById(1)).thenReturn(crn(1, 1, 9));
+
+ AutoTuneApplyResult result = service.apply(request(false, command(" crn ", " 1 ", " maxOutTask ", "2")));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ assertTrue(result.getSuccess());
+ assertEquals("crn", changes.get(0).getTargetType());
+ assertEquals("1", changes.get(0).getTargetId());
+ assertEquals("maxOutTask", changes.get(0).getTargetKey());
+ assertEquals("success", changes.get(0).getResultStatus());
+ assertEquals(1, transactionManager.getCommitCount());
+ verify(basCrnpService).update(any(Wrapper.class));
+ }
+
+ @Test
+ void whitespaceTargetFieldsUseNormalizedCooldownScope() {
+ when(basCrnpService.getById(1)).thenReturn(crn(1, 1, 9));
+ 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));
+
+ AutoTuneApplyResult result = service.apply(request(true, command(" crn ", " 1 ", " maxOutTask ", "2")));
+
+ List<AiAutoTuneChange> changes = savedChanges();
+ assertFalse(result.getSuccess());
+ assertEquals("crn", changes.get(0).getTargetType());
+ assertEquals("1", changes.get(0).getTargetId());
+ assertEquals("maxOutTask", changes.get(0).getTargetKey());
+ 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));
@@ -249,6 +291,7 @@
assertEquals(4, result.getSuccessCount());
assertEquals(0, result.getRejectCount());
assertTrue(changes.stream().allMatch(change -> "success".equals(change.getResultStatus())));
+ assertEquals(1, transactionManager.getCommitCount());
verify(configService).saveConfigValue("conveyorStationTaskLimit", "15");
verify(configService).refreshSystemConfigCache();
verify(basStationService).update(any(Wrapper.class));
@@ -267,6 +310,7 @@
assertFalse(result.getSuccess());
assertEquals("failed", changes.get(0).getResultStatus());
assertTrue(changes.get(0).getRejectReason().contains("db write failed"));
+ assertEquals(1, transactionManager.getRollbackCount());
}
@Test
@@ -293,6 +337,28 @@
verify(configService).saveConfigValue("conveyorStationTaskLimit", "10");
verify(configService).refreshSystemConfigCache();
verify(basStationService).update(any(Wrapper.class));
+ }
+
+ @Test
+ void fullyFailedRollbackReturnsFailedStatus() {
+ 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(configService.saveConfigValue("conveyorStationTaskLimit", "10"))
+ .thenThrow(new IllegalStateException("rollback write failed"));
+
+ 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());
+ assertEquals(1, transactionManager.getRollbackCount());
}
private AutoTuneApplyRequest request(boolean dryRun, AutoTuneChangeCommand... commands) {
@@ -382,4 +448,40 @@
verify(aiAutoTuneChangeService).saveBatch(captor.capture());
return new ArrayList<>(captor.getValue());
}
+
+ private AiAutoTuneJob updatedJob() {
+ ArgumentCaptor<AiAutoTuneJob> captor = ArgumentCaptor.forClass(AiAutoTuneJob.class);
+ verify(aiAutoTuneJobService).updateById(captor.capture());
+ return captor.getValue();
+ }
+
+ private static class RecordingTransactionManager implements PlatformTransactionManager {
+ private int beginCount;
+ private int commitCount;
+ private int rollbackCount;
+
+ @Override
+ public TransactionStatus getTransaction(TransactionDefinition definition) {
+ beginCount++;
+ return new SimpleTransactionStatus();
+ }
+
+ @Override
+ public void commit(TransactionStatus status) {
+ commitCount++;
+ }
+
+ @Override
+ public void rollback(TransactionStatus status) {
+ rollbackCount++;
+ }
+
+ public int getCommitCount() {
+ return commitCount;
+ }
+
+ public int getRollbackCount() {
+ return rollbackCount;
+ }
+ }
}
--
Gitblit v1.9.1