Files
agents_and_robots/pkg/tools/devicemesh/registry_test.go
T
egutierrez bcd246bf85 feat(0144a): tool registry framework para device-mesh
Anade pkg/tools/devicemesh con Client HTTP al device_agent + ToolRegistry
con 16 tools standard (exec, fs.*, git.*, docker.*, proc.*, pkg.*, shell.eval).
RegisterBuiltins filtra por mode user/sudo via RequiresApproval flag.
Hook al pkg/decision con ActionKindDeviceMesh + DeviceMeshAction.
Runner soporta dispatch via NewRunnerWithDeviceMesh (back-compat NewRunner).

Tests: 25 nuevos en devicemesh + 4 en runner. Build clean.
2026-05-24 14:07:13 +02:00

148 lines
3.9 KiB
Go

package devicemesh
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestToolRegistry_RegisterListGet(t *testing.T) {
reg := NewToolRegistry(nil)
reg.Register(ToolSpec{Name: "a", Capability: "x.a"})
reg.Register(ToolSpec{Name: "b", Capability: "x.b"})
got, ok := reg.Get("a")
if !ok {
t.Fatalf("Get(a) not found")
}
if got.Capability != "x.a" {
t.Errorf("capability: %q", got.Capability)
}
names := reg.Names()
if len(names) != 2 || names[0] != "a" || names[1] != "b" {
t.Errorf("Names sort: %v", names)
}
}
func TestToolRegistry_Call_UnknownTool(t *testing.T) {
reg := NewToolRegistry(NewClient("http://nowhere.invalid"))
_, err := reg.Call(context.Background(), "no.such.tool", nil)
if err == nil {
t.Fatalf("expected error for unknown tool")
}
if !strings.Contains(err.Error(), "unknown tool") {
t.Errorf("error message: %v", err)
}
}
func TestToolRegistry_Call_NilClient(t *testing.T) {
reg := NewToolRegistry(nil)
reg.Register(ToolSpec{Name: "x", Capability: "x.y"})
_, err := reg.Call(context.Background(), "x", nil)
if err == nil {
t.Fatalf("expected error when client is nil")
}
}
func TestToolRegistry_Call_InvalidInput(t *testing.T) {
reg := NewToolRegistry(NewClient("http://nowhere.invalid"))
reg.Register(ToolSpec{
Name: "needs_string",
Capability: "x.y",
InputSchema: map[string]any{
"type": "object",
"required": []string{"foo"},
"properties": map[string]any{
"foo": map[string]any{"type": "string"},
},
"additionalProperties": false,
},
})
// Missing required
_, err := reg.Call(context.Background(), "needs_string", map[string]any{})
if err == nil {
t.Errorf("expected error for missing required field")
}
// Wrong type
_, err = reg.Call(context.Background(), "needs_string", map[string]any{"foo": 42})
if err == nil {
t.Errorf("expected error for wrong type")
}
// Extra field
_, err = reg.Call(context.Background(), "needs_string", map[string]any{"foo": "bar", "extra": 1})
if err == nil {
t.Errorf("expected error for additional property")
}
}
func TestToolRegistry_Call_HappyPath(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req CapabilityRequest
body, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(body, &req)
// Echo back the args under "received".
_ = json.NewEncoder(w).Encode(CapabilityResponse{
RequestID: req.RequestID,
OK: true,
Result: map[string]any{"received": req.Args},
})
}))
defer srv.Close()
reg := NewToolRegistry(NewClient(srv.URL))
reg.Register(ToolSpec{
Name: "echo",
Capability: "x.echo",
InputSchema: map[string]any{
"type": "object",
"required": []string{"msg"},
"properties": map[string]any{
"msg": map[string]any{"type": "string"},
},
},
ArgMapping: func(in map[string]any) (map[string]any, error) {
return map[string]any{"upper_msg": strings.ToUpper(in["msg"].(string))}, nil
},
ResultMapping: func(r map[string]any) (any, error) {
received := r["received"].(map[string]any)
return received["upper_msg"], nil
},
})
out, err := reg.Call(context.Background(), "echo", map[string]any{"msg": "hola"})
if err != nil {
t.Fatalf("call: %v", err)
}
if out != "HOLA" {
t.Errorf("expected HOLA, got %v", out)
}
}
func TestToolRegistry_Call_DeviceErrorPropagates(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(CapabilityResponse{
OK: false,
Error: "binary not whitelisted",
})
}))
defer srv.Close()
reg := NewToolRegistry(NewClient(srv.URL))
reg.Register(ToolSpec{Name: "exec", Capability: "shell.exec"})
_, err := reg.Call(context.Background(), "exec", nil)
if err == nil {
t.Fatalf("expected device-side error to propagate")
}
if !strings.Contains(err.Error(), "binary not whitelisted") {
t.Errorf("error message lost: %v", err)
}
}