fix: complete OpenClaw plugin compatibility with test coverage

- Add test OpenClaw plugins: oc-simple (openclaw.plugin.json) and oc-pkg (package.json#extensions)
- Fix plugin.go Start() to detect package.json#openclaw.extensions as OC manifest
- Add hasOCExtensions() helper for package.json OC field detection
- Tests: simulator with manifest entry discovery, package.json extension discovery,
  full Plugin.Start() -> loadOCPlugin() -> register -> call pipeline
This commit is contained in:
root
2026-07-04 13:46:15 +08:00
parent e6038bc618
commit 10ebd962f4
7 changed files with 290 additions and 1 deletions

View File

@ -5,6 +5,8 @@ import (
"os"
"path/filepath"
"testing"
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
)
func TestLaunchSidecarNoMainJS(t *testing.T) {
@ -188,3 +190,190 @@ func TestConcurrentCalls(t *testing.T) {
<-done
}
}
// ---- Simulator + OpenClaw plugin tests ----
func launchSimulator(t *testing.T, pluginDir, name string) *sidecarProcess {
t.Helper()
simPath, err := filepath.Abs(filepath.Join("simulator", "main.js"))
if err != nil {
t.Fatalf("abs simulator path: %v", err)
}
sp, err := launchProcess("node", simPath, pluginDir, name)
if err != nil {
t.Fatalf("launch simulator for %s: %v", name, err)
}
return sp
}
func TestSimulatorWithOCSimple(t *testing.T) {
pluginDir, err := filepath.Abs(filepath.Join("testdata", "oc-simple"))
if err != nil {
t.Fatalf("abs testdata: %v", err)
}
sp := launchSimulator(t, pluginDir, "oc-simple")
defer sp.Close()
tools, err := sp.ListTools()
if err != nil {
t.Fatalf("list tools: %v", err)
}
if len(tools) != 2 {
t.Fatalf("expected 2 tools, got %d: %+v", len(tools), tools)
}
found := map[string]bool{"greet": false, "ping": false}
for _, tool := range tools {
found[tool.Name] = true
}
if !found["greet"] || !found["ping"] {
t.Fatalf("expected greet and ping tools, got %+v", tools)
}
result, err := sp.CallTool("greet", map[string]interface{}{"name": "Test"})
if err != nil {
t.Fatalf("call greet: %v", err)
}
if result != "Hello, Test!" {
t.Fatalf("expected 'Hello, Test!', got %q", result)
}
}
func TestSimulatorWithOCPackage(t *testing.T) {
pluginDir, err := filepath.Abs(filepath.Join("testdata", "oc-pkg"))
if err != nil {
t.Fatalf("abs testdata: %v", err)
}
sp := launchSimulator(t, pluginDir, "oc-pkg")
defer sp.Close()
tools, err := sp.ListTools()
if err != nil {
t.Fatalf("list tools: %v", err)
}
if len(tools) != 2 {
t.Fatalf("expected 2 tools, got %d: %+v", len(tools), tools)
}
found := map[string]bool{"add": false, "info": false}
for _, tool := range tools {
found[tool.Name] = true
if tool.Name == "add" {
if tool.InputSchema == nil {
t.Error("add tool should have inputSchema")
}
}
}
if !found["add"] || !found["info"] {
t.Fatalf("expected add and info tools, got %+v", tools)
}
result, err := sp.CallTool("add", map[string]interface{}{"a": 10.0, "b": 20.0})
if err != nil {
t.Fatalf("call add: %v", err)
}
if result != "30" {
t.Fatalf("expected '30', got %q", result)
}
infoResult, err := sp.CallTool("info", nil)
if err != nil {
t.Fatalf("call info: %v", err)
}
if infoResult == "" {
t.Fatal("expected non-empty info result")
}
t.Logf("info result: %s", infoResult)
}
func TestLoadOCPluginViaPluginStart(t *testing.T) {
skillsDir := t.TempDir()
ocSimpleDir := filepath.Join(skillsDir, "oc-simple")
if err := os.MkdirAll(ocSimpleDir, 0755); err != nil {
t.Fatalf("mkdir: %v", err)
}
for _, name := range []string{"openclaw.plugin.json", "index.js"} {
src := filepath.Join("testdata", "oc-simple", name)
data, err := os.ReadFile(src)
if err != nil {
t.Fatalf("read %s: %v", name, err)
}
if err := os.WriteFile(filepath.Join(ocSimpleDir, name), data, 0644); err != nil {
t.Fatalf("write %s: %v", name, err)
}
}
ocPkgDir := filepath.Join(skillsDir, "oc-pkg")
if err := os.MkdirAll(filepath.Join(ocPkgDir, "lib"), 0755); err != nil {
t.Fatalf("mkdir: %v", err)
}
for _, name := range []string{"package.json", "lib/entry.js"} {
src := filepath.Join("testdata", "oc-pkg", name)
data, err := os.ReadFile(src)
if err != nil {
t.Fatalf("read %s: %v", name, err)
}
dst := filepath.Join(ocPkgDir, name)
if err := os.WriteFile(dst, data, 0644); err != nil {
t.Fatalf("write %s: %v", name, err)
}
}
SimulatorDir = filepath.Join(t.TempDir(), ".simulator")
p := New("openclaw", skillsDir)
var registeredTools []string
registeredHandlers := make(map[string]sdk.ToolHandler)
sdk := sdk.New("openclaw", nil, nil, nil, nil, nil, nil, nil, nil,
func(name string, def sdk.ToolDef, handler sdk.ToolHandler) error {
registeredTools = append(registeredTools, name)
registeredHandlers[name] = handler
return nil
},
nil, nil)
if err := p.Start(sdk); err != nil {
t.Fatalf("start plugin: %v", err)
}
defer p.Stop()
expected := []string{"oc-simple_greet", "oc-simple_ping", "oc-pkg_add", "oc-pkg_info"}
for _, exp := range expected {
found := false
for _, name := range registeredTools {
if name == exp {
found = true
break
}
}
if !found {
t.Fatalf("expected tool %q not registered. got: %v", exp, registeredTools)
}
}
handler, ok := registeredHandlers["oc-simple_greet"]
if !ok {
t.Fatal("greet handler not registered")
}
result, err := handler(map[string]interface{}{"name": "OpenClaw"})
if err != nil {
t.Fatalf("exec greet: %v", err)
}
if result != "Hello, OpenClaw!" {
t.Fatalf("expected 'Hello, OpenClaw!', got %v", result)
}
handler, ok = registeredHandlers["oc-pkg_add"]
if !ok {
t.Fatal("add handler not registered")
}
result, err = handler(map[string]interface{}{"a": 7.0, "b": 8.0})
if err != nil {
t.Fatalf("exec add: %v", err)
}
if result != "15" {
t.Fatalf("expected '15', got %v", result)
}
}