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 }