/ internal / tools / computer_test.go
computer_test.go
  1  package tools
  2  
  3  import (
  4  	"context"
  5  	"testing"
  6  )
  7  
  8  func TestComputer_Info(t *testing.T) {
  9  	tool := &ComputerTool{client: &AXClient{}}
 10  	info := tool.Info()
 11  	if info.Name != "computer" {
 12  		t.Errorf("expected name 'computer', got %q", info.Name)
 13  	}
 14  	if len(info.Required) != 1 || info.Required[0] != "action" {
 15  		t.Errorf("expected required [action], got %v", info.Required)
 16  	}
 17  	props, ok := info.Parameters["properties"].(map[string]any)
 18  	if !ok {
 19  		t.Fatal("expected properties map in parameters")
 20  	}
 21  	for _, key := range []string{"action", "x", "y", "text", "keys", "button", "clicks"} {
 22  		if _, exists := props[key]; !exists {
 23  			t.Errorf("expected property %q in schema", key)
 24  		}
 25  	}
 26  }
 27  
 28  func TestComputer_RequiresApproval(t *testing.T) {
 29  	tool := &ComputerTool{client: &AXClient{}}
 30  	if !tool.RequiresApproval() {
 31  		t.Error("expected RequiresApproval to return true")
 32  	}
 33  }
 34  
 35  func TestComputer_InvalidArgs(t *testing.T) {
 36  	tool := &ComputerTool{client: &AXClient{}}
 37  	result, err := tool.Run(context.Background(), `not valid json`)
 38  	if err != nil {
 39  		t.Fatalf("unexpected error: %v", err)
 40  	}
 41  	if !result.IsError {
 42  		t.Error("expected error result for invalid JSON")
 43  	}
 44  }
 45  
 46  func TestComputer_MissingAction(t *testing.T) {
 47  	tool := &ComputerTool{client: &AXClient{}}
 48  	result, err := tool.Run(context.Background(), `{}`)
 49  	if err != nil {
 50  		t.Fatalf("unexpected error: %v", err)
 51  	}
 52  	if !result.IsError {
 53  		t.Error("expected error result for missing action")
 54  	}
 55  	if !contains(result.Content, "missing required parameter: action") {
 56  		t.Errorf("expected 'missing required parameter: action' in error, got: %s", result.Content)
 57  	}
 58  }
 59  
 60  func TestComputer_UnknownAction(t *testing.T) {
 61  	tool := &ComputerTool{client: &AXClient{}}
 62  	result, err := tool.Run(context.Background(), `{"action": "fly"}`)
 63  	if err != nil {
 64  		t.Fatalf("unexpected error: %v", err)
 65  	}
 66  	if !result.IsError {
 67  		t.Error("expected error result for unknown action")
 68  	}
 69  	if !contains(result.Content, "unknown action") {
 70  		t.Errorf("expected 'unknown action' in error, got: %s", result.Content)
 71  	}
 72  }
 73  
 74  func TestComputer_TypeMissingText(t *testing.T) {
 75  	tool := &ComputerTool{client: &AXClient{}}
 76  	result, err := tool.Run(context.Background(), `{"action": "type"}`)
 77  	if err != nil {
 78  		t.Fatalf("unexpected error: %v", err)
 79  	}
 80  	if !result.IsError {
 81  		t.Error("expected error result for type without text")
 82  	}
 83  	if !contains(result.Content, "type action requires 'text' parameter") {
 84  		t.Errorf("expected text parameter error, got: %s", result.Content)
 85  	}
 86  }
 87  
 88  func TestComputer_HotkeyMissingKeys(t *testing.T) {
 89  	tool := &ComputerTool{client: &AXClient{}}
 90  	result, err := tool.Run(context.Background(), `{"action": "hotkey"}`)
 91  	if err != nil {
 92  		t.Fatalf("unexpected error: %v", err)
 93  	}
 94  	if !result.IsError {
 95  		t.Error("expected error result for hotkey without keys")
 96  	}
 97  	if !contains(result.Content, "hotkey action requires 'keys' parameter") {
 98  		t.Errorf("expected keys parameter error, got: %s", result.Content)
 99  	}
100  }
101  
102  func TestComputer_EscapeAppleScript(t *testing.T) {
103  	tests := []struct {
104  		input    string
105  		expected string
106  	}{
107  		{`hello`, `hello`},
108  		{`say "hi"`, `say \"hi\"`},
109  		{"line1\nline2", `line1\nline2`},
110  		{`back\slash`, `back\\slash`},
111  	}
112  	for _, tc := range tests {
113  		got := escapeAppleScript(tc.input)
114  		if got != tc.expected {
115  			t.Errorf("escapeAppleScript(%q) = %q, want %q", tc.input, got, tc.expected)
116  		}
117  	}
118  }
119  
120  func TestComputer_NormalizeArgs_LeftClick(t *testing.T) {
121  	args := &computerArgs{Action: "left_click", Coordinate: []int{640, 400}}
122  	normalizeArgs(args)
123  	if args.Action != "click" {
124  		t.Errorf("expected action 'click', got %q", args.Action)
125  	}
126  	if args.X != 640 || args.Y != 400 {
127  		t.Errorf("expected (640, 400), got (%d, %d)", args.X, args.Y)
128  	}
129  	if args.Button != "left" {
130  		t.Errorf("expected button 'left', got %q", args.Button)
131  	}
132  }
133  
134  func TestComputer_NormalizeArgs_RightClick(t *testing.T) {
135  	args := &computerArgs{Action: "right_click", Coordinate: []int{100, 200}}
136  	normalizeArgs(args)
137  	if args.Action != "click" || args.Button != "right" {
138  		t.Errorf("expected click/right, got %s/%s", args.Action, args.Button)
139  	}
140  }
141  
142  func TestComputer_NormalizeArgs_DoubleClick(t *testing.T) {
143  	args := &computerArgs{Action: "double_click", Coordinate: []int{50, 50}}
144  	normalizeArgs(args)
145  	if args.Action != "click" || args.Clicks != 2 {
146  		t.Errorf("expected click with 2 clicks, got %s/%d", args.Action, args.Clicks)
147  	}
148  }
149  
150  func TestComputer_NormalizeArgs_MouseMove(t *testing.T) {
151  	args := &computerArgs{Action: "mouse_move", Coordinate: []int{300, 400}}
152  	normalizeArgs(args)
153  	if args.Action != "move" {
154  		t.Errorf("expected 'move', got %q", args.Action)
155  	}
156  	if args.X != 300 || args.Y != 400 {
157  		t.Errorf("expected (300, 400), got (%d, %d)", args.X, args.Y)
158  	}
159  }
160  
161  func TestComputer_NormalizeArgs_Key(t *testing.T) {
162  	args := &computerArgs{Action: "key", Text: "Return"}
163  	normalizeArgs(args)
164  	if args.Action != "hotkey" {
165  		t.Errorf("expected 'hotkey', got %q", args.Action)
166  	}
167  	if args.Keys != "Return" {
168  		t.Errorf("expected keys 'Return', got %q", args.Keys)
169  	}
170  }
171  
172  func TestComputer_NormalizeArgs_Screenshot(t *testing.T) {
173  	args := &computerArgs{Action: "screenshot"}
174  	normalizeArgs(args)
175  	if args.Action != "screenshot" {
176  		t.Errorf("expected 'screenshot', got %q", args.Action)
177  	}
178  }
179  
180  func TestComputer_NormalizeArgs_NoOp(t *testing.T) {
181  	// Our custom actions pass through unchanged
182  	args := &computerArgs{Action: "click", X: 100, Y: 200}
183  	normalizeArgs(args)
184  	if args.Action != "click" || args.X != 100 || args.Y != 200 {
185  		t.Errorf("expected unchanged, got %s (%d, %d)", args.Action, args.X, args.Y)
186  	}
187  }
188  
189  func TestComputer_ScaleXY(t *testing.T) {
190  	tool := &ComputerTool{client: &AXClient{}, screenW: 1440, screenH: 900}
191  	x, y := tool.scaleXY(640, 400)
192  	if x != 720 || y != 450 {
193  		t.Errorf("expected (720, 450), got (%d, %d)", x, y)
194  	}
195  }
196  
197  func TestComputer_ScaleXY_DefaultFallback(t *testing.T) {
198  	// When screen dims match API dims, no scaling
199  	tool := &ComputerTool{client: &AXClient{}, screenW: 1280, screenH: 800}
200  	x, y := tool.scaleXY(100, 200)
201  	if x != 100 || y != 200 {
202  		t.Errorf("expected (100, 200), got (%d, %d)", x, y)
203  	}
204  }