From 892e45141a55bfea0e28c6a34a384f2ce8ee7d16 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期一, 27 四月 2026 13:20:17 +0800
Subject: [PATCH] fix: make auto tune rollback auditable atomically

---
 src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java |   92 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 92 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 138d1f1..13c5bcd 100644
--- a/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
+++ b/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
@@ -427,6 +427,98 @@
         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());
+    }
+
+    @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 rollbackLockNotAcquiredReturnsFailedAuditWithoutTargetWrite() {
+        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(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(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

--
Gitblit v1.9.1