/ internal / tools / imaging_test.go
imaging_test.go
  1  package tools
  2  
  3  import (
  4  	"bytes"
  5  	"encoding/base64"
  6  	"image"
  7  	"image/color"
  8  	"image/jpeg"
  9  	"image/png"
 10  	"os"
 11  	"path/filepath"
 12  	"testing"
 13  )
 14  
 15  func createMinimalPNG() []byte {
 16  	img := image.NewRGBA(image.Rect(0, 0, 1, 1))
 17  	img.Set(0, 0, color.RGBA{255, 0, 0, 255})
 18  	var buf bytes.Buffer
 19  	png.Encode(&buf, img)
 20  	return buf.Bytes()
 21  }
 22  
 23  func createMinimalJPEG() []byte {
 24  	img := image.NewRGBA(image.Rect(0, 0, 1, 1))
 25  	img.Set(0, 0, color.RGBA{0, 255, 0, 255})
 26  	var buf bytes.Buffer
 27  	jpeg.Encode(&buf, img, nil)
 28  	return buf.Bytes()
 29  }
 30  
 31  func TestEncodeImage(t *testing.T) {
 32  	pngData := createMinimalPNG()
 33  	path := filepath.Join(t.TempDir(), "test.png")
 34  	if err := os.WriteFile(path, pngData, 0644); err != nil {
 35  		t.Fatalf("failed to write test file: %v", err)
 36  	}
 37  
 38  	block, err := EncodeImage(path)
 39  	if err != nil {
 40  		t.Fatalf("EncodeImage error: %v", err)
 41  	}
 42  	if block.MediaType != "image/png" {
 43  		t.Errorf("expected MediaType 'image/png', got %q", block.MediaType)
 44  	}
 45  
 46  	decoded, err := base64.StdEncoding.DecodeString(block.Data)
 47  	if err != nil {
 48  		t.Fatalf("base64 decode error: %v", err)
 49  	}
 50  	if !bytes.Equal(decoded, pngData) {
 51  		t.Error("decoded base64 data does not match original PNG bytes")
 52  	}
 53  }
 54  
 55  func TestEncodeImage_FileNotFound(t *testing.T) {
 56  	_, err := EncodeImage("/nonexistent/file.png")
 57  	if err == nil {
 58  		t.Error("expected error for nonexistent file")
 59  	}
 60  }
 61  
 62  func TestEncodeImage_JPEG(t *testing.T) {
 63  	jpegData := createMinimalJPEG()
 64  	path := filepath.Join(t.TempDir(), "test.jpg")
 65  	if err := os.WriteFile(path, jpegData, 0644); err != nil {
 66  		t.Fatalf("failed to write test file: %v", err)
 67  	}
 68  
 69  	block, err := EncodeImage(path)
 70  	if err != nil {
 71  		t.Fatalf("EncodeImage error: %v", err)
 72  	}
 73  	if block.MediaType != "image/jpeg" {
 74  		t.Errorf("expected MediaType 'image/jpeg', got %q", block.MediaType)
 75  	}
 76  }
 77  
 78  func TestEncodeImage_JPEG_Uppercase(t *testing.T) {
 79  	jpegData := createMinimalJPEG()
 80  	path := filepath.Join(t.TempDir(), "test.JPEG")
 81  	if err := os.WriteFile(path, jpegData, 0644); err != nil {
 82  		t.Fatalf("failed to write test file: %v", err)
 83  	}
 84  
 85  	block, err := EncodeImage(path)
 86  	if err != nil {
 87  		t.Fatalf("EncodeImage error: %v", err)
 88  	}
 89  	if block.MediaType != "image/jpeg" {
 90  		t.Errorf("expected MediaType 'image/jpeg', got %q", block.MediaType)
 91  	}
 92  }
 93  
 94  func TestGetScreenDimensions(t *testing.T) {
 95  	w, h, err := GetScreenDimensions()
 96  	if err != nil {
 97  		t.Skipf("skipping: no display available (%v)", err)
 98  	}
 99  	if w <= 0 {
100  		t.Errorf("expected width > 0, got %d", w)
101  	}
102  	if h <= 0 {
103  		t.Errorf("expected height > 0, got %d", h)
104  	}
105  }
106  
107  func TestParseScreenDimensions_Resolution(t *testing.T) {
108  	output := `Graphics/Displays:
109  
110      Apple M2 Pro:
111  
112        Chipset Model: Apple M2 Pro
113        Type: GPU
114        Bus: Built-In
115        Total Number of Cores: 19
116        Vendor: Apple (0x106b)
117        Metal Support: Metal 3
118        Displays:
119          Color LCD:
120            Display Type: Built-In Retina LCD
121            Resolution: 1512 x 982
122            Main Display: Yes
123            Mirror: Off
124            Online: Yes
125            Automatically Adjust Brightness: Yes
126            Connection Type: Internal
127  `
128  	w, h, err := parseScreenDimensions(output)
129  	if err != nil {
130  		t.Fatalf("parseScreenDimensions error: %v", err)
131  	}
132  	if w != 1512 {
133  		t.Errorf("expected width 1512, got %d", w)
134  	}
135  	if h != 982 {
136  		t.Errorf("expected height 982, got %d", h)
137  	}
138  }
139  
140  func TestParseScreenDimensions_UILooksLike(t *testing.T) {
141  	output := `Graphics/Displays:
142  
143      Apple M1:
144  
145        Chipset Model: Apple M1
146        Displays:
147          Color LCD:
148            Display Type: Built-In Retina LCD
149            Resolution: 2560 x 1600 (Retina)
150            UI Looks like: 1440 x 900 @ 120.00Hz
151            Main Display: Yes
152  `
153  	w, h, err := parseScreenDimensions(output)
154  	if err != nil {
155  		t.Fatalf("parseScreenDimensions error: %v", err)
156  	}
157  	if w != 1440 {
158  		t.Errorf("expected width 1440, got %d", w)
159  	}
160  	if h != 900 {
161  		t.Errorf("expected height 900, got %d", h)
162  	}
163  }
164  
165  func TestParseScreenDimensions_RetinaStripped(t *testing.T) {
166  	output := `Graphics/Displays:
167  
168      Apple M2:
169  
170        Displays:
171          Color LCD:
172            Resolution: 2880 x 1800 (Retina)
173            Main Display: Yes
174  `
175  	w, h, err := parseScreenDimensions(output)
176  	if err != nil {
177  		t.Fatalf("parseScreenDimensions error: %v", err)
178  	}
179  	if w != 2880 {
180  		t.Errorf("expected width 2880, got %d", w)
181  	}
182  	if h != 1800 {
183  		t.Errorf("expected height 1800, got %d", h)
184  	}
185  }
186  
187  func TestParseScreenDimensions_NoDisplay(t *testing.T) {
188  	output := `Graphics/Displays:
189  
190      Apple M2 Pro:
191  
192        Chipset Model: Apple M2 Pro
193  `
194  	_, _, err := parseScreenDimensions(output)
195  	if err == nil {
196  		t.Error("expected error for output with no display info")
197  	}
198  }
199  
200  func TestScaleCoordinates(t *testing.T) {
201  	// API: 1280x800, Screen: 1440x900
202  	x, y := ScaleCoordinates(640, 400, 1280, 800, 1440, 900)
203  	if x != 720 {
204  		t.Errorf("expected x=720, got %d", x)
205  	}
206  	if y != 450 {
207  		t.Errorf("expected y=450, got %d", y)
208  	}
209  }
210  
211  func TestScaleCoordinates_Identity(t *testing.T) {
212  	x, y := ScaleCoordinates(100, 200, 1280, 800, 1280, 800)
213  	if x != 100 {
214  		t.Errorf("expected x=100, got %d", x)
215  	}
216  	if y != 200 {
217  		t.Errorf("expected y=200, got %d", y)
218  	}
219  }
220  
221  func TestScaleCoordinates_Origin(t *testing.T) {
222  	x, y := ScaleCoordinates(0, 0, 1280, 800, 1920, 1080)
223  	if x != 0 {
224  		t.Errorf("expected x=0, got %d", x)
225  	}
226  	if y != 0 {
227  		t.Errorf("expected y=0, got %d", y)
228  	}
229  }
230  
231  func TestScaleCoordinates_MaxCorner(t *testing.T) {
232  	x, y := ScaleCoordinates(1280, 800, 1280, 800, 1920, 1080)
233  	if x != 1920 {
234  		t.Errorf("expected x=1920, got %d", x)
235  	}
236  	if y != 1080 {
237  		t.Errorf("expected y=1080, got %d", y)
238  	}
239  }
240  
241  func TestClampCoordinates(t *testing.T) {
242  	x, y := ClampCoordinates(-10, 1000, 1280, 800)
243  	if x != 0 {
244  		t.Errorf("expected x=0, got %d", x)
245  	}
246  	if y != 799 {
247  		t.Errorf("expected y=799, got %d", y)
248  	}
249  }
250  
251  func TestClampCoordinates_InBounds(t *testing.T) {
252  	x, y := ClampCoordinates(500, 400, 1280, 800)
253  	if x != 500 {
254  		t.Errorf("expected x=500, got %d", x)
255  	}
256  	if y != 400 {
257  		t.Errorf("expected y=400, got %d", y)
258  	}
259  }
260  
261  func TestClampCoordinates_BothNegative(t *testing.T) {
262  	x, y := ClampCoordinates(-5, -10, 1920, 1080)
263  	if x != 0 {
264  		t.Errorf("expected x=0, got %d", x)
265  	}
266  	if y != 0 {
267  		t.Errorf("expected y=0, got %d", y)
268  	}
269  }
270  
271  func TestClampCoordinates_BothOverflow(t *testing.T) {
272  	x, y := ClampCoordinates(2000, 1500, 1280, 800)
273  	if x != 1279 {
274  		t.Errorf("expected x=1279, got %d", x)
275  	}
276  	if y != 799 {
277  		t.Errorf("expected y=799, got %d", y)
278  	}
279  }
280  
281  func TestClampCoordinates_ExactBoundary(t *testing.T) {
282  	x, y := ClampCoordinates(1280, 800, 1280, 800)
283  	if x != 1279 {
284  		t.Errorf("expected x=1279, got %d", x)
285  	}
286  	if y != 799 {
287  		t.Errorf("expected y=799, got %d", y)
288  	}
289  }