blob: 9a2b83105bca0434553d0450f706f0349e71906e [file] [log] [blame]
Josh Bleecher Snyder495c1fa2025-05-29 00:37:22 +00001package bashkit
2
3import (
4 "reflect"
5 "testing"
6)
7
8func TestExtractCommands(t *testing.T) {
9 tests := []struct {
10 name string
11 input string
12 expected []string
13 }{
14 {
15 name: "simple command",
16 input: "ls -la",
17 expected: []string{"ls"},
18 },
19 {
20 name: "command with pipe",
21 input: "ls -la | grep test",
22 expected: []string{"ls", "grep"},
23 },
24 {
25 name: "command with logical and (builtin filtered)",
26 input: "mkdir test && cd test",
27 expected: []string{"mkdir"}, // cd is builtin, filtered out
28 },
29 {
30 name: "if statement with commands (builtin filtered)",
31 input: "if [ -f file.txt ]; then cat file.txt; fi",
32 expected: []string{"cat"}, // [ is builtin, filtered out
33 },
34 {
35 name: "variable assignment with command (builtin filtered)",
36 input: "FOO=bar echo $FOO",
37 expected: []string{}, // echo is builtin, filtered out
38 },
39 {
40 name: "script path filtered out (builtin also filtered)",
41 input: "./script.sh && echo done",
42 expected: []string{}, // echo is builtin, filtered out
43 },
44 {
45 name: "multiline script (builtin filtered)",
46 input: "python3 -c 'print(\"hello\")'\necho 'done'",
47 expected: []string{"python3"}, // echo is builtin, filtered out
48 },
49 {
50 name: "complex command chain (builtin filtered)",
51 input: "curl -s https://api.github.com | jq '.name' && echo 'done'",
52 expected: []string{"curl", "jq"}, // echo is builtin, filtered out
53 },
54 {
55 name: "builtins filtered out",
56 input: "echo 'test' && true && ls",
57 expected: []string{"ls"},
58 },
59 {
60 name: "empty command",
61 input: "",
62 expected: []string{},
63 },
64 }
65
66 for _, tt := range tests {
67 t.Run(tt.name, func(t *testing.T) {
68 result, err := ExtractCommands(tt.input)
69 if err != nil {
70 t.Fatalf("ExtractCommands() error = %v", err)
71 }
72 // Handle empty slice comparison
73 if len(result) == 0 && len(tt.expected) == 0 {
74 return // Both are empty, test passes
75 }
76 if !reflect.DeepEqual(result, tt.expected) {
77 t.Errorf("ExtractCommands() = %v, want %v", result, tt.expected)
78 }
79 })
80 }
81}
82
83func TestIsBuiltin(t *testing.T) {
84 tests := []struct {
85 name string
86 command string
87 expected bool
88 }{
89 {"echo is builtin", "echo", true},
90 {"cd is builtin", "cd", true},
91 {"test is builtin", "test", true},
92 {"[ is builtin", "[", true},
93 {"ls is not builtin", "ls", false},
94 {"curl is not builtin", "curl", false},
95 {"python3 is not builtin", "python3", false},
96 {"nonexistent is not builtin", "nonexistent", false},
97 }
98
99 for _, tt := range tests {
100 t.Run(tt.name, func(t *testing.T) {
101 result := isBuiltin(tt.command)
102 if result != tt.expected {
103 t.Errorf("IsBuiltin(%q) = %v, want %v", tt.command, result, tt.expected)
104 }
105 })
106 }
107}
108
109func TestExtractCommandsErrorHandling(t *testing.T) {
110 // Test with syntactically invalid bash
111 invalidBash := "if [ incomplete"
112 _, err := ExtractCommands(invalidBash)
113 if err == nil {
114 t.Error("ExtractCommands() should return error for invalid bash syntax")
115 }
116}
117
118func TestExtractCommandsPathFiltering(t *testing.T) {
119 // Test that commands with paths are properly filtered out during extraction
120 tests := []struct {
121 name string
122 input string
123 expected []string
124 }{
125 {
126 name: "relative script path filtered (builtin also filtered)",
127 input: "./my-script.sh && echo 'done'",
128 expected: []string{}, // echo is builtin, filtered out
129 },
130 {
131 name: "absolute path filtered",
132 input: "/usr/bin/custom-tool --help",
133 expected: []string{},
134 },
135 {
136 name: "parent directory script filtered",
137 input: "../scripts/build.sh",
138 expected: []string{},
139 },
140 {
141 name: "home directory path filtered",
142 input: "~/.local/bin/tool",
143 expected: []string{},
144 },
145 {
146 name: "simple commands without paths included",
147 input: "curl https://example.com | jq '.name'",
148 expected: []string{"curl", "jq"},
149 },
150 {
151 name: "mixed paths and simple commands",
152 input: "./setup.sh && python3 -c 'print(\"hello\")' && /bin/ls",
153 expected: []string{"python3"},
154 },
155 }
156
157 for _, tt := range tests {
158 t.Run(tt.name, func(t *testing.T) {
159 result, err := ExtractCommands(tt.input)
160 if err != nil {
161 t.Fatalf("ExtractCommands() error = %v", err)
162 }
163 // Handle empty slice comparison
164 if len(result) == 0 && len(tt.expected) == 0 {
165 return // Both are empty, test passes
166 }
167 if !reflect.DeepEqual(result, tt.expected) {
168 t.Errorf("ExtractCommands() = %v, want %v", result, tt.expected)
169 }
170 })
171 }
172}