package effects import ( "context" "errors" "io" "log/slog" "strings" "testing" "github.com/enmanuel/agents/pkg/decision" ) // stubMeshCaller is a minimal DeviceMeshCaller for runner tests. type stubMeshCaller struct { tool string input map[string]any result any err error } func (s *stubMeshCaller) Call(_ context.Context, toolName string, input map[string]any) (any, error) { s.tool = toolName s.input = input return s.result, s.err } func newSilentLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) } func TestRunner_DeviceMesh_Success(t *testing.T) { stub := &stubMeshCaller{result: map[string]any{"stdout": "hello", "exit_code": 0}} r := NewRunnerWithDeviceMesh(nil, nil, stub, newSilentLogger()) results := r.Execute(context.Background(), "!room", []decision.Action{{ Kind: decision.ActionKindDeviceMesh, DeviceMesh: &decision.DeviceMeshAction{ Tool: "exec", Input: map[string]any{"argv": []string{"echo", "hello"}}, }, }}) if len(results) != 1 { t.Fatalf("expected 1 result, got %d", len(results)) } res := results[0] if res.Err != nil { t.Fatalf("expected no error, got %v", res.Err) } if stub.tool != "exec" { t.Errorf("stub.tool=%q", stub.tool) } if !strings.Contains(res.Output, "hello") { t.Errorf("output missing 'hello': %q", res.Output) } if !strings.Contains(res.Output, "exit_code") { t.Errorf("output should be JSON containing exit_code: %q", res.Output) } } func TestRunner_DeviceMesh_PropagatesError(t *testing.T) { stub := &stubMeshCaller{err: errors.New("approval timeout")} r := NewRunnerWithDeviceMesh(nil, nil, stub, newSilentLogger()) results := r.Execute(context.Background(), "!room", []decision.Action{{ Kind: decision.ActionKindDeviceMesh, DeviceMesh: &decision.DeviceMeshAction{Tool: "pkg.install", Input: map[string]any{"name": "jq"}}, }}) if results[0].Err == nil { t.Fatalf("expected error to propagate") } if !strings.Contains(results[0].Err.Error(), "approval") { t.Errorf("error mismatch: %v", results[0].Err) } } func TestRunner_DeviceMesh_NilAction(t *testing.T) { r := NewRunnerWithDeviceMesh(nil, nil, &stubMeshCaller{}, newSilentLogger()) results := r.Execute(context.Background(), "!room", []decision.Action{{ Kind: decision.ActionKindDeviceMesh, // DeviceMesh field is nil }}) if results[0].Err == nil { t.Fatalf("expected error for nil DeviceMesh field") } } func TestRunner_DeviceMesh_NoCaller(t *testing.T) { // Using NewRunner (legacy) — should fail gracefully on DeviceMesh action. r := NewRunner(nil, nil, newSilentLogger()) results := r.Execute(context.Background(), "!room", []decision.Action{{ Kind: decision.ActionKindDeviceMesh, DeviceMesh: &decision.DeviceMeshAction{Tool: "exec", Input: map[string]any{"argv": []string{"x"}}}, }}) if results[0].Err == nil { t.Fatalf("expected error when Runner has no DeviceMeshCaller") } if !strings.Contains(results[0].Err.Error(), "DeviceMeshCaller") { t.Errorf("error should mention DeviceMeshCaller: %v", results[0].Err) } }