refactor(action): support built-in actions

This commit is contained in:
2026-03-12 10:41:17 +08:00
parent 3c550af33d
commit 6c8a1b2636
10 changed files with 310 additions and 19 deletions

View File

@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.module.modules.action.builtin.BuiltinActionRegistry;
import java.io.IOException;
import java.nio.file.Files;
@@ -843,6 +844,53 @@ public class LocalRunnerClientTest {
}
}
@Test
void testDoRunWithBuiltin(@TempDir Path tempDir) {
ConcurrentHashMap<String, MetaActionInfo> existedMetaActions = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
LocalRunnerClient client = new LocalRunnerClient(existedMetaActions, executor, tempDir.toString());
BuiltinActionRegistry registry = new BuiltinActionRegistry() {
@Override
protected List<BuiltinActionDefinition> buildDefinitions() {
return List.of(
definition("echo", buildMetaActionInfo("echo"), params -> params.get("value"))
);
}
};
client.setBuiltinActionRegistry(registry);
registry.getDefinitions().put(
"builtin::echo",
BuiltinActionRegistry.definition("echo", buildMetaActionInfo("echo"), params -> params.get("value"))
);
try {
MetaAction metaAction = buildMetaAction(MetaAction.Type.BUILTIN, "builtin", "echo", Map.of("value", "ok"));
RunnerClient.RunnerResponse response = client.doRun(metaAction);
Assertions.assertNotNull(response);
Assertions.assertTrue(response.isOk());
Assertions.assertEquals("ok", response.getData());
} finally {
executor.shutdownNow();
}
}
@Test
void testDoRunWithBuiltinMissingRegistry(@TempDir Path tempDir) {
ConcurrentHashMap<String, MetaActionInfo> existedMetaActions = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
LocalRunnerClient client = new LocalRunnerClient(existedMetaActions, executor, tempDir.toString());
try {
MetaAction metaAction = buildMetaAction(MetaAction.Type.BUILTIN, "builtin", "echo", Map.of());
RunnerClient.RunnerResponse response = client.doRun(metaAction);
Assertions.assertNotNull(response);
Assertions.assertFalse(response.isOk());
Assertions.assertEquals("BuiltinActionRegistry 未初始化", response.getData());
} finally {
executor.shutdownNow();
}
}
@Test
void testDoRunWithMcpLoadedFromCommonConfig(@TempDir Path tempDir) throws IOException, InterruptedException {
ConcurrentHashMap<String, MetaActionInfo> existedMetaActions = new ConcurrentHashMap<>();

View File

@@ -0,0 +1,126 @@
package work.slhaf.partner.module.modules.action.builtin;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.exception.MetaActionNotFoundException;
import work.slhaf.partner.core.action.runner.RunnerClient;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.mockito.Mockito.*;
class BuiltinActionRegistryTest {
private static void injectActionCapability(BuiltinActionRegistry registry, ActionCapability actionCapability) throws Exception {
Field field = BuiltinActionRegistry.class.getDeclaredField("actionCapability");
field.setAccessible(true);
field.set(registry, actionCapability);
}
private static Map<String, BuiltinActionRegistry.BuiltinActionDefinition> indexDefinitions(
List<BuiltinActionRegistry.BuiltinActionDefinition> definitions
) {
Map<String, BuiltinActionRegistry.BuiltinActionDefinition> map = new HashMap<>();
for (BuiltinActionRegistry.BuiltinActionDefinition definition : definitions) {
map.put(definition.actionKey(), definition);
}
return map;
}
private static MetaActionInfo buildMetaActionInfo(String description) {
MetaActionInfo info = new MetaActionInfo();
info.setDescription(description);
info.setParams(new HashMap<>());
return info;
}
@Test
void testInitRegistersMetaActionsAndMountsRunner() throws Exception {
ActionCapability actionCapability = mock(ActionCapability.class);
RunnerClient runnerClient = mock(RunnerClient.class);
when(actionCapability.runnerClient()).thenReturn(runnerClient);
BuiltinActionRegistry registry = new TestRegistry(List.of(
BuiltinActionRegistry.definition("echo", buildMetaActionInfo("echo"), params -> params.get("value"))
));
injectActionCapability(registry, actionCapability);
registry.init();
verify(actionCapability).registerMetaActions(argThat(metaActions ->
metaActions.containsKey("builtin::echo")
&& "echo".equals(metaActions.get("builtin::echo").getDescription())
));
verify(runnerClient).setBuiltinActionRegistry(registry);
}
@Test
void testCallReturnsStringifiedResults() {
BuiltinActionRegistry registry = new TestRegistry(List.of(
BuiltinActionRegistry.definition("echo", buildMetaActionInfo("echo"), params -> params.get("value")),
BuiltinActionRegistry.definition("json", buildMetaActionInfo("json"), params -> Map.of("ok", true)),
BuiltinActionRegistry.definition("nil", buildMetaActionInfo("nil"), params -> null)
));
registry.getDefinitions().putAll(indexDefinitions(registry.buildDefinitions()));
Assertions.assertEquals("hello", registry.call("builtin::echo", Map.of("value", "hello")));
Assertions.assertEquals("{\"ok\":true}", registry.call("builtin::json", Map.of()));
Assertions.assertEquals("null", registry.call("builtin::nil", Map.of()));
}
@Test
void testCallThrowsWhenMissingDefinition() {
BuiltinActionRegistry registry = new TestRegistry(List.of());
Assertions.assertThrows(MetaActionNotFoundException.class, () -> registry.call("builtin::missing", Map.of()));
}
@Test
void testCallPropagatesInvokerFailure() {
BuiltinActionRegistry registry = new TestRegistry(List.of(
BuiltinActionRegistry.definition("boom", buildMetaActionInfo("boom"), params -> {
throw new IllegalStateException("boom");
})
));
registry.getDefinitions().putAll(indexDefinitions(registry.buildDefinitions()));
IllegalStateException exception = Assertions.assertThrows(IllegalStateException.class,
() -> registry.call("builtin::boom", Map.of()));
Assertions.assertEquals("boom", exception.getMessage());
}
@Test
void testActionCoreLoadsBuiltinMetaAction() throws Exception {
ActionCore actionCore = new ActionCore();
try {
actionCore.registerMetaActions(Map.of("builtin::echo", buildMetaActionInfo("echo")));
Assertions.assertTrue(actionCore.listAvailableMetaActions().containsKey("builtin::echo"));
Assertions.assertEquals("echo", actionCore.loadMetaActionInfo("builtin::echo").getDescription());
Assertions.assertEquals("builtin::echo", actionCore.loadMetaAction("builtin::echo").getKey());
Assertions.assertEquals(ActionCore.BUILTIN_LOCATION, actionCore.loadMetaAction("builtin::echo").getLocation());
} finally {
actionCore.getExecutor(ActionCore.ExecutorType.PLATFORM).shutdownNow();
actionCore.getExecutor(ActionCore.ExecutorType.VIRTUAL).shutdownNow();
}
}
private static class TestRegistry extends BuiltinActionRegistry {
private final List<BuiltinActionDefinition> definitions;
private TestRegistry(List<BuiltinActionDefinition> definitions) {
this.definitions = definitions;
}
@Override
protected List<BuiltinActionDefinition> buildDefinitions() {
return definitions;
}
}
}