package com.zy.ai.utils;
|
|
import com.zy.ai.entity.AiAutoTuneChange;
|
import com.zy.ai.entity.AiAutoTuneJob;
|
import com.zy.ai.entity.AiAutoTuneMcpCall;
|
import org.junit.jupiter.api.Test;
|
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
class AutoTuneWriteBehaviorUtilsTest {
|
|
@Test
|
void resolveMcpCallDistinguishesDryRunAndAnalysisOnlyApply() {
|
AiAutoTuneMcpCall dryRunCall = new AiAutoTuneMcpCall();
|
dryRunCall.setToolName("wcs_local_dispatch_apply_auto_tune_changes");
|
dryRunCall.setDryRun(1);
|
|
AiAutoTuneMcpCall analysisOnlyCall = new AiAutoTuneMcpCall();
|
analysisOnlyCall.setToolName("wcs_local_dispatch_apply_auto_tune_changes");
|
analysisOnlyCall.setDryRun(0);
|
analysisOnlyCall.setStatus("success");
|
analysisOnlyCall.setResponseJson("{\"analysisOnly\":true,\"noApply\":true,\"summary\":\"仅分析模式禁止实际应用\"}");
|
|
assertEquals("dry_run", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(dryRunCall));
|
assertEquals("analysis_only", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(analysisOnlyCall));
|
}
|
|
@Test
|
void resolveChangeDistinguishesNoChangeAndRejectedAnalysisOnly() {
|
AiAutoTuneChange noChange = new AiAutoTuneChange();
|
noChange.setResultStatus("no_change");
|
|
AiAutoTuneChange analysisOnly = new AiAutoTuneChange();
|
analysisOnly.setResultStatus("rejected");
|
analysisOnly.setRejectReason("仅分析模式禁止实际应用/回滚,未修改运行参数");
|
|
assertEquals("no_change", AutoTuneWriteBehaviorUtils.resolveChangeWriteBehavior(noChange));
|
assertEquals("analysis_only", AutoTuneWriteBehaviorUtils.resolveChangeWriteBehavior(analysisOnly));
|
}
|
|
@Test
|
void resolveMcpCallClassifiesRollbackOnlyWhenSuccessfulChangeExists() {
|
AiAutoTuneMcpCall countSuccessCall = rollbackCall("success");
|
countSuccessCall.setSuccessCount(1);
|
|
AiAutoTuneMcpCall responseSuccessCall = rollbackCall("failed");
|
responseSuccessCall.setResponseJson("{\"changes\":[{\"resultStatus\":\"success\"}]}");
|
|
assertEquals("rollback", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(countSuccessCall));
|
assertEquals("rollback", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(responseSuccessCall));
|
}
|
|
@Test
|
void resolveMcpCallClassifiesRejectedAndFailedRollbackWithoutSuccessfulChange() {
|
AiAutoTuneMcpCall rejectedCall = rollbackCall("rejected");
|
rejectedCall.setResponseJson("{\"success\":false,\"rejectCount\":1}");
|
|
AiAutoTuneMcpCall failedCall = rollbackCall("failed");
|
failedCall.setErrorMessage("rollback write failed");
|
|
assertEquals("rejected", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(rejectedCall));
|
assertEquals("failed", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(failedCall));
|
}
|
|
@Test
|
void resolveMcpCallPrefersAnalysisOnlyAndNoChangeForRollback() {
|
AiAutoTuneMcpCall analysisOnlyCall = rollbackCall("rejected");
|
analysisOnlyCall.setResponseJson("{\"analysisOnly\":true,\"noApply\":true}");
|
|
AiAutoTuneMcpCall noChangeCall = rollbackCall("success");
|
noChangeCall.setResponseJson("{\"success\":true,\"successCount\":0,\"rejectCount\":0}");
|
|
assertEquals("analysis_only", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(analysisOnlyCall));
|
assertEquals("no_change", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(noChangeCall));
|
}
|
|
@Test
|
void resolveMcpCallDoesNotTreatAnalysisOnlyFalseTextAsAnalysisOnly() {
|
AiAutoTuneMcpCall applyCall = new AiAutoTuneMcpCall();
|
applyCall.setToolName("wcs_local_dispatch_apply_auto_tune_changes");
|
applyCall.setDryRun(0);
|
applyCall.setStatus("success");
|
applyCall.setSuccessCount(1);
|
applyCall.setResponseJson("{\"success\":true,\"successCount\":1,\"summary\":\"analysisOnly=false, allowApply=true\"}");
|
|
assertEquals("apply", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(applyCall));
|
}
|
|
@Test
|
void resolveMcpCallUsesResponseChangeDetailsBeforeSuccessCountForApply() {
|
AiAutoTuneMcpCall noChangeCall = applyCall(0, "success");
|
noChangeCall.setSuccessCount(1);
|
noChangeCall.setResponseJson("{\"success\":true,\"successCount\":1,"
|
+ "\"changes\":[{\"resultStatus\":\"no_change\"},{\"resultStatus\":\"no_change\"}]}");
|
|
AiAutoTuneMcpCall mixedSuccessCall = applyCall(0, "success");
|
mixedSuccessCall.setSuccessCount(0);
|
mixedSuccessCall.setResponseJson("{\"success\":true,\"successCount\":0,"
|
+ "\"changes\":[{\"resultStatus\":\"no_change\"},{\"resultStatus\":\"success\"}]}");
|
|
assertEquals("no_change", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(noChangeCall));
|
assertEquals("apply", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(mixedSuccessCall));
|
}
|
|
@Test
|
void resolveMcpCallPrefersFailedAndRejectedOverDryRunAndNoChange() {
|
AiAutoTuneMcpCall failedDryRunCall = applyCall(1, "failed");
|
|
AiAutoTuneMcpCall rejectedNoChangeCall = applyCall(0, "rejected");
|
rejectedNoChangeCall.setResponseJson("{\"success\":true,\"successCount\":0,\"rejectCount\":0}");
|
|
assertEquals("failed", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(failedDryRunCall));
|
assertEquals("rejected", AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(rejectedNoChangeCall));
|
}
|
|
@Test
|
void resolveChangeUsesOwnerTriggerTypeForRollbackSuccess() {
|
AiAutoTuneChange successChange = new AiAutoTuneChange();
|
successChange.setResultStatus("success");
|
|
assertEquals("apply", AutoTuneWriteBehaviorUtils.resolveChangeWriteBehavior(successChange));
|
assertEquals("rollback", AutoTuneWriteBehaviorUtils.resolveChangeWriteBehavior(successChange, "rollback"));
|
assertEquals("apply", AutoTuneWriteBehaviorUtils.resolveChangeWriteBehavior(successChange, "manual"));
|
}
|
|
@Test
|
void resolveChangeClassifiesRejectedAndFailed() {
|
AiAutoTuneChange rejectedChange = new AiAutoTuneChange();
|
rejectedChange.setResultStatus("rejected");
|
rejectedChange.setRejectReason("超出允许范围");
|
|
AiAutoTuneChange failedChange = new AiAutoTuneChange();
|
failedChange.setResultStatus("failed");
|
|
assertEquals("rejected", AutoTuneWriteBehaviorUtils.resolveChangeWriteBehavior(rejectedChange));
|
assertEquals("failed", AutoTuneWriteBehaviorUtils.resolveChangeWriteBehavior(failedChange));
|
}
|
|
@Test
|
void writeBehaviorLabelIncludesRejectedAndFailed() {
|
assertEquals("已拒绝", AutoTuneWriteBehaviorUtils.writeBehaviorLabel("rejected"));
|
assertEquals("失败", AutoTuneWriteBehaviorUtils.writeBehaviorLabel("failed"));
|
}
|
|
@Test
|
void resolveJobPrefersReadOnlyWhenOnlySnapshotToolsWereCalled() {
|
AiAutoTuneJob job = new AiAutoTuneJob();
|
job.setStatus("success");
|
|
Map<String, Object> snapshotCall = new LinkedHashMap<>();
|
snapshotCall.put("writeBehavior", "read_only");
|
|
String writeBehavior = AutoTuneWriteBehaviorUtils.resolveJobWriteBehavior(
|
job,
|
Collections.singletonList(snapshotCall),
|
List.of()
|
);
|
|
assertEquals("read_only", writeBehavior);
|
}
|
|
@Test
|
void resolveJobPrefersFailedAndRejectedOverDryRunNoChangeAndReadOnly() {
|
AiAutoTuneJob failedDryRunJob = new AiAutoTuneJob();
|
failedDryRunJob.setStatus("failed");
|
|
Map<String, Object> dryRunCall = new LinkedHashMap<>();
|
dryRunCall.put("writeBehavior", "dry_run");
|
|
AiAutoTuneJob rejectedNoChangeJob = new AiAutoTuneJob();
|
rejectedNoChangeJob.setStatus("rejected");
|
|
Map<String, Object> noChange = new LinkedHashMap<>();
|
noChange.put("writeBehavior", "no_change");
|
|
AiAutoTuneJob failedReadOnlyJob = new AiAutoTuneJob();
|
failedReadOnlyJob.setStatus("failed");
|
|
Map<String, Object> readOnlyCall = new LinkedHashMap<>();
|
readOnlyCall.put("writeBehavior", "read_only");
|
|
assertEquals("failed", AutoTuneWriteBehaviorUtils.resolveJobWriteBehavior(
|
failedDryRunJob,
|
List.of(dryRunCall),
|
List.of()
|
));
|
assertEquals("rejected", AutoTuneWriteBehaviorUtils.resolveJobWriteBehavior(
|
rejectedNoChangeJob,
|
List.of(),
|
List.of(noChange)
|
));
|
assertEquals("failed", AutoTuneWriteBehaviorUtils.resolveJobWriteBehavior(
|
failedReadOnlyJob,
|
List.of(readOnlyCall),
|
List.of()
|
));
|
}
|
|
@Test
|
void resolveJobClassifiesDirectSuccessfulRollbackWithoutMcpCalls() {
|
AiAutoTuneJob job = new AiAutoTuneJob();
|
job.setTriggerType("rollback");
|
job.setStatus("success");
|
job.setSuccessCount(1);
|
|
String writeBehavior = AutoTuneWriteBehaviorUtils.resolveJobWriteBehavior(
|
job,
|
List.of(),
|
List.of()
|
);
|
|
assertEquals("rollback", writeBehavior);
|
}
|
|
@Test
|
void resolveJobPreservesRollbackWhenSomeRollbackChangesFailed() {
|
AiAutoTuneJob job = new AiAutoTuneJob();
|
job.setTriggerType("rollback");
|
job.setStatus("partial_success");
|
|
Map<String, Object> successChange = new LinkedHashMap<>();
|
successChange.put("writeBehavior", "rollback");
|
successChange.put("resultStatus", "success");
|
|
Map<String, Object> failedChange = new LinkedHashMap<>();
|
failedChange.put("writeBehavior", "failed");
|
failedChange.put("resultStatus", "failed");
|
|
String writeBehavior = AutoTuneWriteBehaviorUtils.resolveJobWriteBehavior(
|
job,
|
List.of(),
|
List.of(successChange, failedChange)
|
);
|
|
assertEquals("rollback", writeBehavior);
|
}
|
|
private AiAutoTuneMcpCall applyCall(Integer dryRun, String status) {
|
AiAutoTuneMcpCall mcpCall = new AiAutoTuneMcpCall();
|
mcpCall.setToolName("wcs_local_dispatch_apply_auto_tune_changes");
|
mcpCall.setDryRun(dryRun);
|
mcpCall.setStatus(status);
|
return mcpCall;
|
}
|
|
private AiAutoTuneMcpCall rollbackCall(String status) {
|
AiAutoTuneMcpCall mcpCall = new AiAutoTuneMcpCall();
|
mcpCall.setToolName("wcs_local_dispatch_revert_last_auto_tune_job");
|
mcpCall.setStatus(status);
|
mcpCall.setSuccessCount(0);
|
return mcpCall;
|
}
|
}
|