@@ -4,6 +4,8 @@ import (
44 "context"
55 "strings"
66 "testing"
7+
8+ "github.com/stretchr/testify/require"
79)
810
911func TestCommandBlocking(t *testing.T) {
@@ -56,10 +58,7 @@ func TestCommandBlocking(t *testing.T) {
5658 {
5759 name: "block npm global install with -g",
5860 blockFuncs: []BlockFunc{
59− ArgumentsBlocker([][]string{
60− {"npm", "install", "-g"},
61− {"npm", "install", "--global"},
62− }),
61+ ArgumentsBlocker("npm", []string{"install"}, []string{"-g"}),
6362 },
6463 command: "npm install -g typescript",
6564 shouldBlock: true,
@@ -67,10 +66,7 @@ func TestCommandBlocking(t *testing.T) {
6766 {
6867 name: "block npm global install with --global",
6968 blockFuncs: []BlockFunc{
70− ArgumentsBlocker([][]string{
71− {"npm", "install", "-g"},
72− {"npm", "install", "--global"},
73− }),
69+ ArgumentsBlocker("npm", []string{"install"}, []string{"--global"}),
7470 },
7571 command: "npm install --global typescript",
7672 shouldBlock: true,
@@ -78,10 +74,8 @@ func TestCommandBlocking(t *testing.T) {
7874 {
7975 name: "allow npm local install",
8076 blockFuncs: []BlockFunc{
81− ArgumentsBlocker([][]string{
82− {"npm", "install", "-g"},
83− {"npm", "install", "--global"},
84− }),
77+ ArgumentsBlocker("npm", []string{"install"}, []string{"-g"}),
78+ ArgumentsBlocker("npm", []string{"install"}, []string{"--global"}),
8579 },
8680 command: "npm install typescript",
8781 shouldBlock: false,
@@ -116,3 +110,232 @@ func TestCommandBlocking(t *testing.T) {
116110 })
117111 }
118112}
113+
114+func TestArgumentsBlocker(t *testing.T) {
115+ tests := []struct {
116+ name string
117+ cmd string
118+ args []string
119+ flags []string
120+ input []string
121+ shouldBlock bool
122+ }{
123+ // Basic command blocking
124+ {
125+ name: "block exact command match",
126+ cmd: "npm",
127+ args: []string{"install"},
128+ flags: nil,
129+ input: []string{"npm", "install", "package"},
130+ shouldBlock: true,
131+ },
132+ {
133+ name: "allow different command",
134+ cmd: "npm",
135+ args: []string{"install"},
136+ flags: nil,
137+ input: []string{"yarn", "install", "package"},
138+ shouldBlock: false,
139+ },
140+ {
141+ name: "allow different subcommand",
142+ cmd: "npm",
143+ args: []string{"install"},
144+ flags: nil,
145+ input: []string{"npm", "list"},
146+ shouldBlock: false,
147+ },
148+
149+ // Flag-based blocking
150+ {
151+ name: "block with single flag",
152+ cmd: "npm",
153+ args: []string{"install"},
154+ flags: []string{"-g"},
155+ input: []string{"npm", "install", "-g", "typescript"},
156+ shouldBlock: true,
157+ },
158+ {
159+ name: "block with flag in different position",
160+ cmd: "npm",
161+ args: []string{"install"},
162+ flags: []string{"-g"},
163+ input: []string{"npm", "install", "typescript", "-g"},
164+ shouldBlock: true,
165+ },
166+ {
167+ name: "allow without required flag",
168+ cmd: "npm",
169+ args: []string{"install"},
170+ flags: []string{"-g"},
171+ input: []string{"npm", "install", "typescript"},
172+ shouldBlock: false,
173+ },
174+ {
175+ name: "block with multiple flags",
176+ cmd: "pip",
177+ args: []string{"install"},
178+ flags: []string{"--user"},
179+ input: []string{"pip", "install", "--user", "--upgrade", "package"},
180+ shouldBlock: true,
181+ },
182+
183+ // Complex argument patterns
184+ {
185+ name: "block multi-arg subcommand",
186+ cmd: "yarn",
187+ args: []string{"global", "add"},
188+ flags: nil,
189+ input: []string{"yarn", "global", "add", "typescript"},
190+ shouldBlock: true,
191+ },
192+ {
193+ name: "allow partial multi-arg match",
194+ cmd: "yarn",
195+ args: []string{"global", "add"},
196+ flags: nil,
197+ input: []string{"yarn", "global", "list"},
198+ shouldBlock: false,
199+ },
200+
201+ // Edge cases
202+ {
203+ name: "handle empty input",
204+ cmd: "npm",
205+ args: []string{"install"},
206+ flags: nil,
207+ input: []string{},
208+ shouldBlock: false,
209+ },
210+ {
211+ name: "handle command only",
212+ cmd: "npm",
213+ args: []string{"install"},
214+ flags: nil,
215+ input: []string{"npm"},
216+ shouldBlock: false,
217+ },
218+ {
219+ name: "block pacman with -S flag",
220+ cmd: "pacman",
221+ args: nil,
222+ flags: []string{"-S"},
223+ input: []string{"pacman", "-S", "package"},
224+ shouldBlock: true,
225+ },
226+ {
227+ name: "allow pacman without -S flag",
228+ cmd: "pacman",
229+ args: nil,
230+ flags: []string{"-S"},
231+ input: []string{"pacman", "-Q", "package"},
232+ shouldBlock: false,
233+ },
234+ }
235+
236+ for _, tt := range tests {
237+ t.Run(tt.name, func(t *testing.T) {
238+ blocker := ArgumentsBlocker(tt.cmd, tt.args, tt.flags)
239+ result := blocker(tt.input)
240+ require.Equal(t, tt.shouldBlock, result,
241+ "Expected block=%v for input %v", tt.shouldBlock, tt.input)
242+ })
243+ }
244+}
245+
246+func TestCommandsBlocker(t *testing.T) {
247+ tests := []struct {
248+ name string
249+ banned []string
250+ input []string
251+ shouldBlock bool
252+ }{
253+ {
254+ name: "block single banned command",
255+ banned: []string{"curl"},
256+ input: []string{"curl", "https://example.com"},
257+ shouldBlock: true,
258+ },
259+ {
260+ name: "allow non-banned command",
261+ banned: []string{"curl", "wget"},
262+ input: []string{"echo", "hello"},
263+ shouldBlock: false,
264+ },
265+ {
266+ name: "block from multiple banned",
267+ banned: []string{"curl", "wget", "nc"},
268+ input: []string{"wget", "https://example.com"},
269+ shouldBlock: true,
270+ },
271+ {
272+ name: "handle empty input",
273+ banned: []string{"curl"},
274+ input: []string{},
275+ shouldBlock: false,
276+ },
277+ {
278+ name: "case sensitive matching",
279+ banned: []string{"curl"},
280+ input: []string{"CURL", "https://example.com"},
281+ shouldBlock: false,
282+ },
283+ }
284+
285+ for _, tt := range tests {
286+ t.Run(tt.name, func(t *testing.T) {
287+ blocker := CommandsBlocker(tt.banned)
288+ result := blocker(tt.input)
289+ require.Equal(t, tt.shouldBlock, result,
290+ "Expected block=%v for input %v", tt.shouldBlock, tt.input)
291+ })
292+ }
293+}
294+
295+func TestSplitArgsFlags(t *testing.T) {
296+ tests := []struct {
297+ name string
298+ input []string
299+ wantArgs []string
300+ wantFlags []string
301+ }{
302+ {
303+ name: "only args",
304+ input: []string{"install", "package", "another"},
305+ wantArgs: []string{"install", "package", "another"},
306+ wantFlags: []string{},
307+ },
308+ {
309+ name: "only flags",
310+ input: []string{"-g", "--verbose", "-f"},
311+ wantArgs: []string{},
312+ wantFlags: []string{"-g", "--verbose", "-f"},
313+ },
314+ {
315+ name: "mixed args and flags",
316+ input: []string{"install", "-g", "package", "--verbose"},
317+ wantArgs: []string{"install", "package"},
318+ wantFlags: []string{"-g", "--verbose"},
319+ },
320+ {
321+ name: "empty input",
322+ input: []string{},
323+ wantArgs: []string{},
324+ wantFlags: []string{},
325+ },
326+ {
327+ name: "single dash flag",
328+ input: []string{"-S", "package"},
329+ wantArgs: []string{"package"},
330+ wantFlags: []string{"-S"},
331+ },
332+ }
333+
334+ for _, tt := range tests {
335+ t.Run(tt.name, func(t *testing.T) {
336+ args, flags := splitArgsFlags(tt.input)
337+ require.Equal(t, tt.wantArgs, args, "args mismatch")
338+ require.Equal(t, tt.wantFlags, flags, "flags mismatch")
339+ })
340+ }
341+}