parsing_test.go

  1package 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 TestExtractCommandsErrorHandling(t *testing.T) {
 84	// Test with syntactically invalid bash
 85	invalidBash := "if [ incomplete"
 86	_, err := ExtractCommands(invalidBash)
 87	if err == nil {
 88		t.Error("ExtractCommands() should return error for invalid bash syntax")
 89	}
 90}
 91
 92func TestExtractCommandsPathFiltering(t *testing.T) {
 93	// Test that commands with paths are properly filtered out during extraction
 94	tests := []struct {
 95		name     string
 96		input    string
 97		expected []string
 98	}{
 99		{
100			name:     "relative script path filtered (builtin also filtered)",
101			input:    "./my-script.sh && echo 'done'",
102			expected: []string{}, // echo is builtin, filtered out
103		},
104		{
105			name:     "absolute path filtered",
106			input:    "/usr/bin/custom-tool --help",
107			expected: []string{},
108		},
109		{
110			name:     "parent directory script filtered",
111			input:    "../scripts/build.sh",
112			expected: []string{},
113		},
114		{
115			name:     "home directory path filtered",
116			input:    "~/.local/bin/tool",
117			expected: []string{},
118		},
119		{
120			name:     "simple commands without paths included",
121			input:    "curl https://example.com | jq '.name'",
122			expected: []string{"curl", "jq"},
123		},
124		{
125			name:     "mixed paths and simple commands",
126			input:    "./setup.sh && python3 -c 'print(\"hello\")' && /bin/ls",
127			expected: []string{"python3"},
128		},
129	}
130
131	for _, tt := range tests {
132		t.Run(tt.name, func(t *testing.T) {
133			result, err := ExtractCommands(tt.input)
134			if err != nil {
135				t.Fatalf("ExtractCommands() error = %v", err)
136			}
137			// Handle empty slice comparison
138			if len(result) == 0 && len(tt.expected) == 0 {
139				return // Both are empty, test passes
140			}
141			if !reflect.DeepEqual(result, tt.expected) {
142				t.Errorf("ExtractCommands() = %v, want %v", result, tt.expected)
143			}
144		})
145	}
146}
147
148func TestExtractCommandsEdgeCases(t *testing.T) {
149	tests := []struct {
150		name     string
151		input    string
152		expected []string
153	}{
154		{
155			name:     "empty command name",
156			input:    "",
157			expected: []string{},
158		},
159		{
160			name:     "duplicate commands deduplication",
161			input:    "ls -la && ls -la",
162			expected: []string{"ls"},
163		},
164		{
165			name:     "multiple duplicates with different order",
166			input:    "git status && ls -la && git add . && ls -la",
167			expected: []string{"git", "ls"},
168		},
169		{
170			name:     "variable assignment with non-builtin command",
171			input:    "TEST=value mytool",
172			expected: []string{"mytool"},
173		},
174		{
175			name:     "command with slash in name filtered out",
176			input:    "path/to/command --help",
177			expected: []string{},
178		},
179		{
180			name:     "command with empty name",
181			input:    "\"\" arg", // Command with empty string name
182			expected: []string{},
183		},
184		{
185			name:     "builtin command filtered out",
186			input:    "echo hello",
187			expected: []string{},
188		},
189	}
190
191	for _, tt := range tests {
192		t.Run(tt.name, func(t *testing.T) {
193			result, err := ExtractCommands(tt.input)
194			if err != nil {
195				t.Fatalf("ExtractCommands() error = %v", err)
196			}
197			if len(result) == 0 && len(tt.expected) == 0 {
198				return
199			}
200			if !reflect.DeepEqual(result, tt.expected) {
201				t.Errorf("ExtractCommands() = %v, want %v", result, tt.expected)
202			}
203		})
204	}
205}