diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/ActionPlanner.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/ActionPlanner.java index 8a2889a2..d4c951a0 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/ActionPlanner.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/ActionPlanner.java @@ -399,6 +399,9 @@ public class ActionPlanner extends AbstractAgentModule.Running> primaryActionChain) { + if (primaryActionChain == null || primaryActionChain.isEmpty()) { + return false; + } // 先将 primaryActionChain 的节点序号修正为从1开始依次增大 fixOrder(primaryActionChain); List fixedOrders = new ArrayList<>(primaryActionChain.keySet().stream().toList()); @@ -409,6 +412,9 @@ public class ActionPlanner extends AbstractAgentModule.Running actionKeys = primaryActionChain.get(fixedOrder); + if (actionKeys == null || actionKeys.isEmpty()) { + continue; + } for (String actionKey : actionKeys) { // 根据 actionKey 加载行动信息,并检查是否存在必需前置依赖 @@ -451,9 +457,12 @@ public class ActionPlanner extends AbstractAgentModule.Running> primaryActionChain) { Map> tempChain = new HashMap<>(primaryActionChain); primaryActionChain.clear(); - int chainSize = tempChain.size(); - for (int i = 0; i < chainSize; i++) { - primaryActionChain.put(i, tempChain.get(i)); + List orders = new ArrayList<>(tempChain.keySet()); + orders.sort(Integer::compareTo); + int fixedOrder = 1; + for (Integer order : orders) { + List actionKeys = tempChain.get(order); + primaryActionChain.put(fixedOrder++, actionKeys == null ? new ArrayList<>() : new ArrayList<>(actionKeys)); } } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/evaluator/entity/EvaluatorResult.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/evaluator/entity/EvaluatorResult.java index c9c0e495..d5d5c101 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/evaluator/entity/EvaluatorResult.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/evaluator/entity/EvaluatorResult.java @@ -3,6 +3,7 @@ package work.slhaf.partner.module.action.planner.evaluator.entity; import lombok.Data; import work.slhaf.partner.core.action.entity.Schedulable; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -31,9 +32,15 @@ public class EvaluatorResult { } public Map> getPrimaryActionChainAsMap() { + if (primaryActionChain == null || primaryActionChain.isEmpty()) { + return new LinkedHashMap<>(); + } return primaryActionChain.stream().collect(Collectors.toMap( ChainElement::getOrder, - ChainElement::getActionKeys, + chainElement -> { + List actionKeys = chainElement.getActionKeys(); + return actionKeys == null ? new ArrayList<>() : new ArrayList<>(actionKeys); + }, (oldValue, newValue) -> newValue, LinkedHashMap::new )); diff --git a/Partner-Core/src/test/java/work/slhaf/partner/module/action/planner/ActionPlannerAssemblyHelperTest.java b/Partner-Core/src/test/java/work/slhaf/partner/module/action/planner/ActionPlannerAssemblyHelperTest.java new file mode 100644 index 00000000..d1467e99 --- /dev/null +++ b/Partner-Core/src/test/java/work/slhaf/partner/module/action/planner/ActionPlannerAssemblyHelperTest.java @@ -0,0 +1,65 @@ +package work.slhaf.partner.module.action.planner; + +import com.alibaba.fastjson2.JSONObject; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import work.slhaf.partner.core.action.ActionCapability; +import work.slhaf.partner.core.action.entity.MetaActionInfo; +import work.slhaf.partner.framework.agent.support.Result; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class ActionPlannerAssemblyHelperTest { + + private static void injectField(Object target, String fieldName, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } + + private static Object getField(Object target, String fieldName) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } + + @Test + void shouldFixNonZeroBasedOrdersWithoutInjectingNullActionKeyList() throws Exception { + ActionCapability actionCapability = Mockito.mock(ActionCapability.class); + Mockito.when(actionCapability.loadMetaActionInfo(Mockito.anyString())) + .thenReturn(Result.success(new MetaActionInfo( + false, + null, + Map.of(), + "desc", + Set.of(), + Set.of(), + Set.of(), + false, + new JSONObject() + ))); + + ActionPlanner planner = new ActionPlanner(); + injectField(planner, "actionCapability", actionCapability); + + Object helper = getField(planner, "assemblyHelper"); + Method fixDependencies = helper.getClass().getDeclaredMethod("fixDependencies", Map.class); + fixDependencies.setAccessible(true); + + Map> chain = new LinkedHashMap<>(); + chain.put(1, new ArrayList<>(List.of("action_a"))); + chain.put(2, new ArrayList<>(List.of("action_b"))); + + boolean fixed = (boolean) fixDependencies.invoke(helper, chain); + + assertTrue(fixed); + assertEquals(List.of(1, 2), new ArrayList<>(chain.keySet())); + assertEquals(List.of("action_a"), chain.get(1)); + assertEquals(List.of("action_b"), chain.get(2)); + assertFalse(chain.containsValue(null)); + } +} diff --git a/Partner-Core/src/test/java/work/slhaf/partner/module/action/planner/evaluator/entity/EvaluatorResultTest.java b/Partner-Core/src/test/java/work/slhaf/partner/module/action/planner/evaluator/entity/EvaluatorResultTest.java new file mode 100644 index 00000000..9b44a4ef --- /dev/null +++ b/Partner-Core/src/test/java/work/slhaf/partner/module/action/planner/evaluator/entity/EvaluatorResultTest.java @@ -0,0 +1,50 @@ +package work.slhaf.partner.module.action.planner.evaluator.entity; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class EvaluatorResultTest { + + @Test + void shouldReturnEmptyMapWhenPrimaryActionChainIsNull() { + EvaluatorResult result = new EvaluatorResult(); + Map> chain = result.getPrimaryActionChainAsMap(); + assertNotNull(chain); + assertTrue(chain.isEmpty()); + } + + @Test + void shouldNormalizeNullActionKeysToEmptyList() { + EvaluatorResult result = new EvaluatorResult(); + EvaluatorResult.ChainElement element = new EvaluatorResult.ChainElement(); + element.setOrder(1); + element.setActionKeys(null); + result.setPrimaryActionChain(List.of(element)); + + Map> chain = result.getPrimaryActionChainAsMap(); + assertEquals(1, chain.size()); + assertNotNull(chain.get(1)); + assertTrue(chain.get(1).isEmpty()); + } + + @Test + void shouldCopyActionKeyListDefensively() { + EvaluatorResult result = new EvaluatorResult(); + EvaluatorResult.ChainElement element = new EvaluatorResult.ChainElement(); + element.setOrder(1); + List keys = new ArrayList<>(List.of("a")); + element.setActionKeys(keys); + result.setPrimaryActionChain(List.of(element)); + + Map> chain = result.getPrimaryActionChainAsMap(); + keys.add("b"); + + assertEquals(new LinkedHashMap<>(Map.of(1, List.of("a"))), new LinkedHashMap<>(chain)); + } +}