1package tui
2
3import (
4 "bytes"
5 "errors"
6 "image"
7 "image/color"
8 "image/jpeg"
9 "image/png"
10 "os"
11 "path/filepath"
12 "runtime"
13 "testing"
14)
15
16func testClipboardPNG(t *testing.T) []byte {
17 t.Helper()
18 img := image.NewRGBA(image.Rect(0, 0, 2, 2))
19 img.Set(0, 0, color.RGBA{R: 255, A: 255})
20 img.Set(1, 0, color.RGBA{G: 255, A: 255})
21 var buf bytes.Buffer
22 if err := png.Encode(&buf, img); err != nil {
23 t.Fatal(err)
24 }
25 return buf.Bytes()
26}
27
28func TestNormalizeClipboardImagePNGValidatesPNG(t *testing.T) {
29 got, err := normalizeClipboardImagePNG(testClipboardPNG(t))
30 if err != nil {
31 t.Fatal(err)
32 }
33 if _, format, err := image.Decode(bytes.NewReader(got)); err != nil || format != "png" {
34 t.Fatalf("decoded format = %q, err = %v", format, err)
35 }
36}
37
38func TestNormalizeClipboardImagePNGConvertsJPEG(t *testing.T) {
39 img := image.NewRGBA(image.Rect(0, 0, 2, 2))
40 var jpegData bytes.Buffer
41 if err := jpeg.Encode(&jpegData, img, nil); err != nil {
42 t.Fatal(err)
43 }
44
45 got, err := normalizeClipboardImagePNG(jpegData.Bytes())
46 if err != nil {
47 t.Fatal(err)
48 }
49 if _, format, err := image.Decode(bytes.NewReader(got)); err != nil || format != "png" {
50 t.Fatalf("decoded format = %q, err = %v", format, err)
51 }
52}
53
54func TestNormalizeClipboardImagePNGRejectsMalformedData(t *testing.T) {
55 if _, err := normalizeClipboardImagePNG([]byte("not an image")); err == nil {
56 t.Fatal("normalizeClipboardImagePNG accepted malformed data")
57 }
58}
59
60func TestNormalizeClipboardImagePNGEnforcesLimits(t *testing.T) {
61 data := testClipboardPNG(t)
62 if _, err := normalizeClipboardImagePNGWithLimits(data, len(data)-1, 100, 1024); !errors.Is(err, errClipboardImageTooLarge) {
63 t.Fatalf("input limit error = %v", err)
64 }
65 if _, err := normalizeClipboardImagePNGWithLimits(data, len(data), 3, 1024); !errors.Is(err, errClipboardImageTooLarge) {
66 t.Fatalf("pixel limit error = %v", err)
67 }
68 if _, err := normalizeClipboardImagePNGWithLimits(data, len(data), 100, 1); !errors.Is(err, errClipboardImageTooLarge) {
69 t.Fatalf("output limit error = %v", err)
70 }
71}
72
73func TestWriteClipboardPNGFileUsesRestrictivePermissions(t *testing.T) {
74 if runtime.GOOS == "windows" {
75 t.Skip("Windows FileMode permission bits do not represent ACL access")
76 }
77 path := filepath.Join(t.TempDir(), "clipboard.png")
78 if err := writeClipboardPNGFile(path, testClipboardPNG(t)); err != nil {
79 t.Fatal(err)
80 }
81 info, err := os.Stat(path)
82 if err != nil {
83 t.Fatal(err)
84 }
85 if got := info.Mode().Perm(); got&0o077 != 0 {
86 t.Fatalf("file permissions = %o, want no group or other access", got)
87 }
88}
89
90func TestRunClipboardImageCommandSeparatesStderr(t *testing.T) {
91 if runtime.GOOS == "windows" {
92 t.Skip("test helper is a shell script")
93 }
94 dir := t.TempDir()
95 path := filepath.Join(dir, "clipboard-image-test")
96 if err := os.WriteFile(path, []byte("#!/bin/sh\nprintf payload\nprintf diagnostic >&2\n"), 0o700); err != nil {
97 t.Fatal(err)
98 }
99 t.Setenv("PATH", dir)
100
101 got, err := runClipboardImageCommand("clipboard-image-test", 100)
102 if err != nil {
103 t.Fatal(err)
104 }
105 if string(got) != "payload" {
106 t.Fatalf("output = %q, want payload", got)
107 }
108}
109
110func TestRunClipboardImageCommandCapsOutput(t *testing.T) {
111 if runtime.GOOS == "windows" {
112 t.Skip("test helper is a shell script")
113 }
114 dir := t.TempDir()
115 path := filepath.Join(dir, "clipboard-image-test")
116 if err := os.WriteFile(path, []byte("#!/bin/sh\nprintf 'too much data'\n"), 0o700); err != nil {
117 t.Fatal(err)
118 }
119 t.Setenv("PATH", dir)
120
121 if _, err := runClipboardImageCommand("clipboard-image-test", 3); err == nil {
122 t.Fatal("runClipboardImageCommand accepted oversized output")
123 }
124}