From dc1078bcd01c3b650163cf45553bd098a85a4c82 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期一, 27 四月 2026 12:25:21 +0800
Subject: [PATCH] feat: add background auto tune scheduler

---
 src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java |  126 ++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 126 insertions(+), 0 deletions(-)

diff --git a/src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java b/src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java
index 17d087f..70bd28d 100644
--- a/src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java
+++ b/src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java
@@ -6,6 +6,7 @@
 import com.zy.ai.domain.autotune.AutoTuneApplyResult;
 import com.zy.ai.domain.autotune.AutoTuneChangeCommand;
 import com.zy.ai.domain.autotune.AutoTuneSnapshot;
+import com.zy.ai.domain.autotune.AutoTuneTriggerType;
 import com.zy.ai.entity.AiAutoTuneChange;
 import com.zy.ai.entity.AiAutoTuneJob;
 import com.zy.ai.entity.AiPromptTemplate;
@@ -14,6 +15,12 @@
 import com.zy.ai.mcp.service.SpringAiMcpToolManager;
 import com.zy.ai.mcp.tool.AutoTuneMcpTools;
 import com.zy.ai.service.impl.AutoTuneAgentServiceImpl;
+import com.zy.ai.service.impl.AutoTuneCoordinatorServiceImpl;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.system.service.ConfigService;
+import com.zy.system.service.OperateLogService;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -24,6 +31,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -39,6 +47,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyDouble;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -63,6 +73,16 @@
     private SpringAiMcpToolManager mcpToolManager;
     @Mock
     private AiPromptTemplateService aiPromptTemplateService;
+    @Mock
+    private ConfigService configService;
+    @Mock
+    private WrkMastService wrkMastService;
+    @Mock
+    private AutoTuneAgentService autoTuneAgentService;
+    @Mock
+    private RedisUtil redisUtil;
+    @Mock
+    private OperateLogService operateLogService;
 
     @BeforeEach
     void setUp() {
@@ -239,6 +259,88 @@
     }
 
     @Test
+    void coordinatorSkipsWhenDisabled() {
+        when(configService.getConfigValue("aiAutoTuneEnabled", "N")).thenReturn("N");
+
+        AutoTuneCoordinatorService.AutoTuneCoordinatorResult result = coordinatorService().runAutoTuneIfEligible();
+
+        assertTrue(result.getSkipped());
+        assertEquals("disabled", result.getReason());
+        verify(wrkMastService, never()).count(any(Wrapper.class));
+        verify(autoTuneAgentService, never()).runAutoTune(anyString());
+    }
+
+    @Test
+    void coordinatorSkipsWhenNoActiveTasks() {
+        when(configService.getConfigValue("aiAutoTuneEnabled", "N")).thenReturn("Y");
+        when(configService.getConfigValue("aiAutoTuneIntervalMinutes", "10")).thenReturn("10");
+        when(wrkMastService.count(any(Wrapper.class))).thenReturn(0L);
+
+        AutoTuneCoordinatorService.AutoTuneCoordinatorResult result = coordinatorService().runAutoTuneIfEligible();
+
+        assertTrue(result.getSkipped());
+        assertEquals("no_active_tasks", result.getReason());
+        verify(autoTuneAgentService, never()).runAutoTune(anyString());
+    }
+
+    @Test
+    void coordinatorSkipsWhenIntervalNotReached() {
+        AiAutoTuneJob recentJob = new AiAutoTuneJob();
+        recentJob.setId(11L);
+        recentJob.setFinishTime(new Date());
+        when(configService.getConfigValue("aiAutoTuneEnabled", "N")).thenReturn("true");
+        when(configService.getConfigValue("aiAutoTuneIntervalMinutes", "10")).thenReturn("10");
+        when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L);
+        when(redisUtil.get(RedisKeyType.AI_AUTO_TUNE_LAST_TRIGGER_GUARD.key)).thenReturn(null);
+        when(aiAutoTuneJobService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(recentJob));
+
+        AutoTuneCoordinatorService.AutoTuneCoordinatorResult result = coordinatorService().runAutoTuneIfEligible();
+
+        assertTrue(result.getSkipped());
+        assertEquals("interval_not_reached", result.getReason());
+        verify(autoTuneAgentService, never()).runAutoTune(anyString());
+    }
+
+    @Test
+    void coordinatorTriggersAgentWhenEligible() {
+        AutoTuneAgentService.AutoTuneAgentResult agentResult = successfulAgentResult();
+        when(configService.getConfigValue("aiAutoTuneEnabled", "N")).thenReturn("1");
+        when(configService.getConfigValue("aiAutoTuneIntervalMinutes", "10")).thenReturn("10");
+        when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L);
+        when(redisUtil.get(RedisKeyType.AI_AUTO_TUNE_LAST_TRIGGER_GUARD.key)).thenReturn(null);
+        when(aiAutoTuneJobService.list(any(Wrapper.class))).thenReturn(Collections.emptyList(), Collections.emptyList());
+        when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(true);
+        when(autoTuneAgentService.runAutoTune(AutoTuneTriggerType.AUTO.getCode())).thenReturn(agentResult);
+
+        AutoTuneCoordinatorService.AutoTuneCoordinatorResult result = coordinatorService().runAutoTuneIfEligible();
+
+        assertFalse(result.getSkipped());
+        assertTrue(result.getTriggered());
+        assertSame(agentResult, result.getAgentResult());
+        verify(autoTuneAgentService).runAutoTune(AutoTuneTriggerType.AUTO.getCode());
+        verify(operateLogService).save(any());
+        verify(redisUtil).set(anyString(), any(), anyLong());
+        verify(redisUtil).compareAndDelete(anyString(), anyString());
+    }
+
+    @Test
+    void coordinatorSkipsWhenRunningLockIsNotAcquired() {
+        when(configService.getConfigValue("aiAutoTuneEnabled", "N")).thenReturn("Y");
+        when(configService.getConfigValue("aiAutoTuneIntervalMinutes", "10")).thenReturn("10");
+        when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L);
+        when(redisUtil.get(RedisKeyType.AI_AUTO_TUNE_LAST_TRIGGER_GUARD.key)).thenReturn(null);
+        when(aiAutoTuneJobService.list(any(Wrapper.class))).thenReturn(Collections.emptyList());
+        when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(false);
+
+        AutoTuneCoordinatorService.AutoTuneCoordinatorResult result = coordinatorService().runAutoTuneIfEligible();
+
+        assertTrue(result.getSkipped());
+        assertEquals("running_lock_not_acquired", result.getReason());
+        verify(autoTuneAgentService, never()).runAutoTune(anyString());
+        verify(redisUtil, never()).compareAndDelete(anyString(), anyString());
+    }
+
+    @Test
     void agentExecutesSnapshotDryRunAndRealApplyToolSequence() {
         AutoTuneAgentServiceImpl service = new AutoTuneAgentServiceImpl(
                 llmChatService,
@@ -370,6 +472,30 @@
         return new AutoTuneAgentServiceImpl(llmChatService, mcpToolManager, aiPromptTemplateService);
     }
 
+    private AutoTuneCoordinatorServiceImpl coordinatorService() {
+        return new AutoTuneCoordinatorServiceImpl(
+                configService,
+                wrkMastService,
+                aiAutoTuneJobService,
+                autoTuneAgentService,
+                redisUtil,
+                operateLogService);
+    }
+
+    private AutoTuneAgentService.AutoTuneAgentResult successfulAgentResult() {
+        AutoTuneAgentService.AutoTuneAgentResult result = new AutoTuneAgentService.AutoTuneAgentResult();
+        result.setSuccess(true);
+        result.setTriggerType(AutoTuneTriggerType.AUTO.getCode());
+        result.setSummary("no changes needed");
+        result.setToolCallCount(1);
+        result.setLlmCallCount(1);
+        result.setPromptTokens(10L);
+        result.setCompletionTokens(5L);
+        result.setTotalTokens(15L);
+        result.setMaxRoundsReached(false);
+        return result;
+    }
+
     private AutoTuneChangeCommand change(String targetType, String targetId, String targetKey, String newValue) {
         AutoTuneChangeCommand command = new AutoTuneChangeCommand();
         command.setTargetType(targetType);

--
Gitblit v1.9.1