From 3c1543a1049670c227755229a0305613442bcda8 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 29 四月 2026 20:43:13 +0800
Subject: [PATCH] Merge branch 'codex/ai-provider-protocol-gateway'
---
src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 270 insertions(+), 11 deletions(-)
diff --git a/src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java b/src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java
index 2121fdb..0b245a1 100644
--- a/src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java
+++ b/src/test/java/com/zy/ai/service/AutoTuneCoordinatorServiceImplTest.java
@@ -1,6 +1,7 @@
package com.zy.ai.service;
import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.zy.ai.domain.autotune.AutoTuneApplyRequest;
import com.zy.ai.domain.autotune.AutoTuneApplyResult;
@@ -17,6 +18,7 @@
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.AutoTuneControlModeServiceImpl;
import com.zy.ai.service.impl.AutoTuneCoordinatorServiceImpl;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.utils.RedisUtil;
@@ -44,6 +46,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -54,6 +57,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -82,6 +86,7 @@
private AiPromptTemplateService aiPromptTemplateService;
@Mock
private ConfigService configService;
+ private AutoTuneControlModeService autoTuneControlModeService;
@Mock
private WrkMastService wrkMastService;
@Mock
@@ -93,12 +98,15 @@
@BeforeEach
void setUp() {
+ autoTuneControlModeService = new AutoTuneControlModeServiceImpl(configService);
tools = new AutoTuneMcpTools(
autoTuneSnapshotService,
autoTuneApplyService,
aiAutoTuneJobService,
aiAutoTuneChangeService,
aiAutoTuneMcpCallService);
+ lenient().when(configService.getConfigValue("aiAutoTuneEnabled", "N")).thenReturn("Y");
+ lenient().when(configService.getConfigValue("aiAutoTuneAnalysisOnly", "Y")).thenReturn("N");
}
@Test
@@ -121,12 +129,19 @@
job.setSummary("applied");
job.setSuccessCount(1);
job.setRejectCount(0);
- AiAutoTuneChange change = new AiAutoTuneChange();
- change.setJobId(70L);
- change.setTargetType("sys_config");
- change.setTargetKey("conveyorStationTaskLimit");
- change.setRequestedValue("12");
- change.setResultStatus("success");
+ AiAutoTuneChange agentChange = new AiAutoTuneChange();
+ agentChange.setJobId(7L);
+ agentChange.setTargetType("station");
+ agentChange.setTargetId("101");
+ agentChange.setTargetKey("outTaskLimit");
+ agentChange.setRequestedValue("3");
+ agentChange.setResultStatus("success");
+ AiAutoTuneChange applyChange = new AiAutoTuneChange();
+ applyChange.setJobId(70L);
+ applyChange.setTargetType("sys_config");
+ applyChange.setTargetKey("conveyorStationTaskLimit");
+ applyChange.setRequestedValue("12");
+ applyChange.setResultStatus("success");
com.zy.ai.entity.AiAutoTuneMcpCall mcpCall = new com.zy.ai.entity.AiAutoTuneMcpCall();
mcpCall.setAgentJobId(7L);
mcpCall.setCallSeq(1);
@@ -136,7 +151,10 @@
when(aiAutoTuneJobService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(job));
when(aiAutoTuneMcpCallService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(mcpCall));
- when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(change));
+ List<AiAutoTuneChange> auditChanges = new ArrayList<>();
+ auditChanges.add(agentChange);
+ auditChanges.add(applyChange);
+ when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(auditChanges);
List<Map<String, Object>> result = tools.getRecentAutoTuneJobs(99);
@@ -145,11 +163,54 @@
assertFalse(result.get(0).containsKey("reasoningDigest"));
assertEquals(1, result.get(0).get("mcpCallCount"));
List<?> changes = (List<?>) result.get(0).get("changes");
- assertEquals(1, changes.size());
+ assertEquals(2, changes.size());
+ assertEquals(7L, ((Map<?, ?>) changes.get(0)).get("jobId"));
+ assertEquals(70L, ((Map<?, ?>) changes.get(1)).get("jobId"));
ArgumentCaptor<Wrapper<AiAutoTuneJob>> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
verify(aiAutoTuneJobService).list(wrapperCaptor.capture());
assertTrue(wrapperCaptor.getValue().getSqlSegment().contains("limit 20"));
+
+ ArgumentCaptor<Wrapper<AiAutoTuneChange>> changeWrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
+ verify(aiAutoTuneChangeService).list(changeWrapperCaptor.capture());
+ Wrapper<AiAutoTuneChange> changeWrapper = changeWrapperCaptor.getValue();
+ assertTrue(changeWrapper.getSqlSegment().contains("job_id IN"));
+ List<Object> changeQueryParams = wrapperParamValues(changeWrapper);
+ assertTrue(changeQueryParams.contains(7L));
+ assertTrue(changeQueryParams.contains(70L));
+ }
+
+ @Test
+ void recentJobsMarksRollbackMcpChangesAsRollback() {
+ AiAutoTuneJob job = new AiAutoTuneJob();
+ job.setId(8L);
+ job.setTriggerType("manual");
+ job.setStatus("success");
+ job.setSuccessCount(1);
+ job.setRejectCount(0);
+ AiAutoTuneChange rollbackChange = new AiAutoTuneChange();
+ rollbackChange.setJobId(80L);
+ rollbackChange.setTargetType("sys_config");
+ rollbackChange.setTargetKey("conveyorStationTaskLimit");
+ rollbackChange.setResultStatus("success");
+ com.zy.ai.entity.AiAutoTuneMcpCall mcpCall = new com.zy.ai.entity.AiAutoTuneMcpCall();
+ mcpCall.setAgentJobId(8L);
+ mcpCall.setCallSeq(1);
+ mcpCall.setToolName("wcs_local_dispatch_revert_last_auto_tune_job");
+ mcpCall.setStatus("success");
+ mcpCall.setApplyJobId(80L);
+ mcpCall.setSuccessCount(1);
+
+ when(aiAutoTuneJobService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(job));
+ when(aiAutoTuneMcpCallService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(mcpCall));
+ when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(rollbackChange));
+
+ List<Map<String, Object>> result = tools.getRecentAutoTuneJobs(1);
+
+ assertEquals("rollback", result.get(0).get("writeBehavior"));
+ List<?> changes = (List<?>) result.get(0).get("changes");
+ assertEquals(1, changes.size());
+ assertEquals("rollback", ((Map<?, ?>) changes.get(0)).get("writeBehavior"));
}
@Test
@@ -157,6 +218,7 @@
AutoTuneApplyResult expected = new AutoTuneApplyResult();
expected.setDryRun(true);
expected.setSuccess(true);
+ expected.setChanges(Collections.singletonList(applyResultChange("dry_run")));
when(autoTuneApplyService.apply(any(AutoTuneApplyRequest.class))).thenReturn(expected);
AutoTuneChangeCommand command = new AutoTuneChangeCommand();
@@ -203,10 +265,38 @@
}
@Test
+ void applyToolRejectsDuplicateDryRunTargetsBeforeServiceCall() {
+ List<AutoTuneChangeCommand> changes = new ArrayList<>();
+ changes.add(change(" sys_config ", "ignored-first", " conveyorStationTaskLimit ", "12"));
+ changes.add(change("SYS_CONFIG", "ignored-second", "conveyorStationTaskLimit", "13"));
+
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> tools.applyAutoTuneChanges("duplicate target", 10, "agent", true, null, changes));
+
+ assertTrue(exception.getMessage().contains("Duplicate auto-tune change target"));
+ assertTrue(exception.getMessage().contains("targetType=sys_config"));
+ assertTrue(exception.getMessage().contains("targetKey=conveyorStationTaskLimit"));
+ verify(autoTuneApplyService, never()).apply(any(AutoTuneApplyRequest.class));
+ }
+
+ @Test
+ void applyToolRejectsRealApplyWithoutDryRunTokenInAnalysisOnlyMode() {
+ AutoTuneChangeCommand command = change("sys_config", null, "conveyorStationTaskLimit", "12");
+
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> tools.applyAutoTuneChanges("direct apply", 10, "agent", false, null,
+ Collections.singletonList(command)));
+
+ assertTrue(exception.getMessage().contains("dryRunToken is required"));
+ verify(autoTuneApplyService, never()).apply(any(AutoTuneApplyRequest.class));
+ }
+
+ @Test
void applyToolAllowsRealApplyOnlyWithMatchingDryRunToken() {
AutoTuneApplyResult dryRunResult = new AutoTuneApplyResult();
dryRunResult.setDryRun(true);
dryRunResult.setSuccess(true);
+ dryRunResult.setChanges(Collections.singletonList(applyResultChange("dry_run")));
AutoTuneApplyResult applyResult = new AutoTuneApplyResult();
applyResult.setDryRun(false);
applyResult.setSuccess(true);
@@ -230,6 +320,7 @@
AutoTuneApplyResult dryRunResult = new AutoTuneApplyResult();
dryRunResult.setDryRun(true);
dryRunResult.setSuccess(true);
+ dryRunResult.setChanges(Collections.singletonList(applyResultChange("dry_run")));
when(autoTuneApplyService.apply(any(AutoTuneApplyRequest.class))).thenReturn(dryRunResult);
AutoTuneApplyResult preview = tools.applyAutoTuneChanges("preview", 10, "agent", true, null,
@@ -250,6 +341,7 @@
AutoTuneApplyResult dryRunResult = new AutoTuneApplyResult();
dryRunResult.setDryRun(true);
dryRunResult.setSuccess(true);
+ dryRunResult.setChanges(Collections.singletonList(applyResultChange("dry_run")));
when(autoTuneApplyService.apply(any(AutoTuneApplyRequest.class))).thenReturn(dryRunResult);
List<AutoTuneChangeCommand> changes = Collections.singletonList(
change("sys_config", null, "conveyorStationTaskLimit", "12"));
@@ -264,8 +356,40 @@
}
@Test
+ void applyToolDoesNotIssueDryRunTokenWhenAllChangesAreNoChange() {
+ AutoTuneApplyResult dryRunResult = new AutoTuneApplyResult();
+ dryRunResult.setDryRun(true);
+ dryRunResult.setSuccess(true);
+ dryRunResult.setSuccessCount(0);
+ dryRunResult.setRejectCount(0);
+ dryRunResult.setChanges(Collections.singletonList(applyResultChange("no_change")));
+ when(autoTuneApplyService.apply(any(AutoTuneApplyRequest.class))).thenReturn(dryRunResult);
+
+ AutoTuneApplyResult preview = tools.applyAutoTuneChanges("preview", 10, "agent", true, null,
+ Collections.singletonList(change("sys_config", null, "conveyorStationTaskLimit", "12")));
+
+ assertNull(preview.getDryRunToken());
+ verify(autoTuneApplyService, times(1)).apply(any(AutoTuneApplyRequest.class));
+ }
+
+ @Test
void rollbackToolDelegatesToApplyServiceRollback() {
AutoTuneApplyResult expected = new AutoTuneApplyResult();
+ when(autoTuneApplyService.rollbackLastSuccessfulJob("bad result")).thenReturn(expected);
+
+ AutoTuneApplyResult result = tools.revertLastAutoTuneJob("bad result");
+
+ assertSame(expected, result);
+ verify(autoTuneApplyService).rollbackLastSuccessfulJob("bad result");
+ }
+
+ @Test
+ void rollbackToolBlocksRollbackInAnalysisOnlyMode() {
+ AutoTuneApplyResult expected = new AutoTuneApplyResult();
+ expected.setSuccess(false);
+ expected.setAnalysisOnly(true);
+ expected.setNoApply(true);
+ expected.setRejectCount(1);
when(autoTuneApplyService.rollbackLastSuccessfulJob("bad result")).thenReturn(expected);
AutoTuneApplyResult result = tools.revertLastAutoTuneJob("bad result");
@@ -563,7 +687,8 @@
AutoTuneAgentServiceImpl service = new AutoTuneAgentServiceImpl(
llmChatService,
mcpToolManager,
- aiPromptTemplateService);
+ aiPromptTemplateService,
+ autoTuneControlModeService);
AiPromptTemplate promptTemplate = new AiPromptTemplate();
promptTemplate.setContent("system prompt");
when(aiPromptTemplateService.resolvePublished("wcs_auto_tune_dispatch")).thenReturn(promptTemplate);
@@ -658,6 +783,72 @@
}
@Test
+ void agentCallsMcpForRealApplyInAnalysisOnlyMode() {
+ when(configService.getConfigValue("aiAutoTuneAnalysisOnly", "Y")).thenReturn("Y");
+ AutoTuneAgentServiceImpl service = agentService();
+ when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools());
+ when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenAnswer(invocation -> {
+ String toolName = invocation.getArgument(0);
+ if ("wcs_local_dispatch_apply_auto_tune_changes".equals(toolName)) {
+ return rejectedAnalysisOnlyResult(false);
+ }
+ return Collections.singletonMap("ok", true);
+ });
+ when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any()))
+ .thenReturn(
+ response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot",
+ "{}"), 10, 5),
+ response("apply", toolCall("call_2", "wcs_local_dispatch_apply_auto_tune_changes",
+ "{\"dryRun\":false,\"changes\":[{\"targetType\":\"sys_config\",\"targetKey\":\"conveyorStationTaskLimit\",\"newValue\":\"12\"}]}"), 10, 5),
+ response("stop", null, 10, 5)
+ );
+
+ AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("manual");
+
+ assertFalse(result.getSuccess());
+ assertTrue(result.getAnalysisOnly());
+ assertFalse(result.getAllowApply());
+ assertEquals("analysis_only", result.getExecutionMode());
+ assertFalse(result.getActualApplyCalled());
+ assertEquals(1, result.getRejectCount());
+ assertTrue(result.getSummary().contains("浠呭垎鏋愭ā寮忕姝㈠疄闄呭簲鐢�/鍥炴粴"));
+ verify(mcpToolManager).callTool(eq("wcs_local_dispatch_get_auto_tune_snapshot"), any(JSONObject.class));
+ verify(mcpToolManager).callTool(eq("wcs_local_dispatch_apply_auto_tune_changes"), any(JSONObject.class));
+ }
+
+ @Test
+ void agentCallsMcpForRollbackInAnalysisOnlyMode() {
+ when(configService.getConfigValue("aiAutoTuneAnalysisOnly", "Y")).thenReturn("Y");
+ AutoTuneAgentServiceImpl service = agentService();
+ when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools());
+ when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenAnswer(invocation -> {
+ String toolName = invocation.getArgument(0);
+ if ("wcs_local_dispatch_revert_last_auto_tune_job".equals(toolName)) {
+ return rejectedAnalysisOnlyResult(false);
+ }
+ return Collections.singletonMap("ok", true);
+ });
+ when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any()))
+ .thenReturn(
+ response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot",
+ "{}"), 10, 5),
+ response("rollback", toolCall("call_2", "wcs_local_dispatch_revert_last_auto_tune_job",
+ "{\"reason\":\"bad result\"}"), 10, 5),
+ response("stop", null, 10, 5)
+ );
+
+ AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("manual");
+
+ assertFalse(result.getSuccess());
+ assertTrue(result.getAnalysisOnly());
+ assertTrue(result.getRollbackCalled());
+ assertEquals(1, result.getRejectCount());
+ assertTrue(result.getSummary().contains("浠呭垎鏋愭ā寮忕姝㈠疄闄呭簲鐢�/鍥炴粴"));
+ verify(mcpToolManager).callTool(eq("wcs_local_dispatch_get_auto_tune_snapshot"), any(JSONObject.class));
+ verify(mcpToolManager).callTool(eq("wcs_local_dispatch_revert_last_auto_tune_job"), any(JSONObject.class));
+ }
+
+ @Test
void agentMarksSnapshotOnlyRunAsNoActualMutationEvenIfAssistantClaimsApplied() {
AutoTuneAgentServiceImpl service = agentService();
when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools());
@@ -674,7 +865,44 @@
assertFalse(result.getActualApplyCalled());
assertFalse(result.getRollbackCalled());
assertEquals(0, result.getSuccessCount());
- assertTrue(result.getSummary().startsWith("鑷姩璋冨弬 Agent 鏈皟鐢ㄥ疄闄呭簲鐢ㄦ垨鍥炴粴宸ュ叿锛屾湭淇敼杩愯鍙傛暟銆�"));
+ assertTrue(result.getSummary().contains("鑷姩璋冨弬 Agent 鏈皟鐢ㄥ疄闄呭簲鐢ㄦ垨鍥炴粴宸ュ叿锛屾湭淇敼杩愯鍙傛暟銆�"));
+ }
+
+ @Test
+ void agentDoesNotMarkNoChangeRealApplyAsActualApplyWhenSuccessCountIsPositive() {
+ AutoTuneAgentServiceImpl service = agentService();
+ when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools());
+ when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenAnswer(invocation -> {
+ String toolName = invocation.getArgument(0);
+ if ("wcs_local_dispatch_apply_auto_tune_changes".equals(toolName)) {
+ LinkedHashMap<String, Object> noChange = new LinkedHashMap<>();
+ noChange.put("resultStatus", "no_change");
+
+ LinkedHashMap<String, Object> applyOutput = new LinkedHashMap<>();
+ applyOutput.put("success", true);
+ applyOutput.put("dryRun", false);
+ applyOutput.put("successCount", 1);
+ applyOutput.put("rejectCount", 0);
+ applyOutput.put("changes", Collections.singletonList(noChange));
+ return applyOutput;
+ }
+ return Collections.singletonMap("ok", true);
+ });
+ when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any()))
+ .thenReturn(
+ response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot",
+ "{}"), 10, 5),
+ response("apply no change", toolCall("call_2", "wcs_local_dispatch_apply_auto_tune_changes",
+ "{\"dryRun\":false,\"dryRunToken\":\"token-123\",\"changes\":[{\"targetType\":\"sys_config\",\"targetKey\":\"conveyorStationTaskLimit\",\"newValue\":\"12\"}]}"), 10, 5),
+ response("鏃犲彉鏇�", null, 10, 5)
+ );
+
+ AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("manual");
+
+ assertTrue(result.getSuccess());
+ assertFalse(result.getActualApplyCalled());
+ assertEquals(1, result.getSuccessCount());
+ assertEquals(0, result.getRejectCount());
}
@Test
@@ -820,12 +1048,17 @@
AiPromptTemplate promptTemplate = new AiPromptTemplate();
promptTemplate.setContent("system prompt");
when(aiPromptTemplateService.resolvePublished("wcs_auto_tune_dispatch")).thenReturn(promptTemplate);
- return new AutoTuneAgentServiceImpl(llmChatService, mcpToolManager, aiPromptTemplateService);
+ return new AutoTuneAgentServiceImpl(
+ llmChatService,
+ mcpToolManager,
+ aiPromptTemplateService,
+ autoTuneControlModeService);
}
private AutoTuneCoordinatorServiceImpl coordinatorService() {
return new AutoTuneCoordinatorServiceImpl(
configService,
+ autoTuneControlModeService,
wrkMastService,
aiAutoTuneJobService,
aiAutoTuneMcpCallService,
@@ -883,6 +1116,19 @@
return result;
}
+ private AutoTuneApplyResult rejectedAnalysisOnlyResult(Boolean dryRun) {
+ AutoTuneApplyResult result = new AutoTuneApplyResult();
+ result.setDryRun(dryRun);
+ result.setSuccess(false);
+ result.setAnalysisOnly(true);
+ result.setNoApply(true);
+ result.setSuccessCount(0);
+ result.setRejectCount(1);
+ result.setSummary("浠呭垎鏋愭ā寮忕姝㈠疄闄呭簲鐢�/鍥炴粴锛屾湭淇敼杩愯鍙傛暟");
+ result.setChanges(new ArrayList<>());
+ return result;
+ }
+
private AutoTuneChangeCommand change(String targetType, String targetId, String targetKey, String newValue) {
AutoTuneChangeCommand command = new AutoTuneChangeCommand();
command.setTargetType(targetType);
@@ -892,6 +1138,19 @@
return command;
}
+ private AiAutoTuneChange applyResultChange(String resultStatus) {
+ AiAutoTuneChange change = new AiAutoTuneChange();
+ change.setResultStatus(resultStatus);
+ return change;
+ }
+
+ private List<Object> wrapperParamValues(Wrapper<?> wrapper) {
+ if (!(wrapper instanceof AbstractWrapper<?, ?, ?> abstractWrapper)) {
+ return Collections.emptyList();
+ }
+ return new ArrayList<>(abstractWrapper.getParamNameValuePairs().values());
+ }
+
private List<Object> allowedOpenAiTools() {
List<Object> tools = new ArrayList<>();
tools.add(openAiTool("wcs_local_dispatch_get_auto_tune_snapshot"));
--
Gitblit v1.9.1