package approval
import (
"strings"
"testing"
)
func TestAnalyze(t *testing.T) {
tests := []struct {
name string
script string
wantCmds []string
wantOpaque bool
}{
{name: "simple", script: "go test ./...", wantCmds: []string{"go"}},
{name: "pipeline", script: "cat x | grep y | wc -l", wantCmds: []string{"cat", "grep", "wc"}},
{
// The example from the plan: a naive prefix allowlist on "git"
// would wave this straight through.
name: "semicolon smuggles a second command",
script: "git status; rm -rf ~",
wantCmds: []string{"git", "rm"},
},
{name: "and-chained", script: "go build ./... && ./bin/app", wantCmds: []string{"go"}},
{name: "backticks are opaque", script: "echo `whoami`", wantOpaque: true},
{name: "dollar-paren is opaque", script: "echo $(cat /etc/passwd)", wantOpaque: true},
{name: "bash -c is opaque", script: `bash -c "rm -rf /"`, wantCmds: []string{"bash"}, wantOpaque: true},
{name: "python -c is opaque", script: `python3 -c "import os; os.system('x')"`, wantCmds: []string{"python3"}, wantOpaque: true},
{name: "variable in command position is opaque", script: "$CMD --flag", wantOpaque: true},
{name: "subshell", script: "(cd build && make)", wantCmds: []string{"cd", "make"}},
{name: "redirect is not by itself opaque", script: "echo hi > out.txt", wantCmds: []string{"echo"}},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := Analyze(tc.script)
if got.ParseError != nil {
t.Fatalf("parse: %v", got.ParseError)
}
if got.Opaque != tc.wantOpaque {
t.Errorf("Opaque = %v, want %v (reason: %s)", got.Opaque, tc.wantOpaque, got.Reason)
}
for _, want := range tc.wantCmds {
if !contains(got.Commands, want) {
t.Errorf("commands = %v, want it to include %q", got.Commands, want)
}
}
if tc.wantOpaque && got.Reason == "" {
t.Error("an opaque command must explain why, for the prompt")
}
})
}
}
func TestAnalyzeInvalidShell(t *testing.T) {
got := Analyze("if then fi done )")
if got.ParseError == nil {
t.Fatal("want a parse error")
}
if !got.Opaque {
t.Error("unparseable input must be treated as opaque, never as safe")
}
}
func TestDescribe(t *testing.T) {
if got := Analyze("go test ./...").Describe(); got != "runs go" {
t.Errorf("Describe() = %q", got)
}
if got := Analyze("git log $(cat f)").Describe(); !strings.Contains(got, "command substitution") {
t.Errorf("Describe() = %q, want it to name the reason", got)
}
}
func contains(xs []string, want string) bool {
for _, x := range xs {
if x == want {
return true
}
}
return false
}