Go 73 lines
package cockpit
import (
"os"
"path/filepath"
"testing"
"github.com/ajhahnde/eeco/internal/config"
)
func TestScanAllowlistForWriteGitVerbs(t *testing.T) {
base := composeAllowedTools(loadHandover(t))
cases := []struct {
name string
allowlist []string
want []string
}{
{"real handover allowlist holds", base, nil},
{"git commit is forbidden", append(append([]string{}, base...), "Bash(git commit:*)"), []string{"commit"}},
{"git push is forbidden", append(append([]string{}, base...), "Bash(git push:*)"), []string{"push"}},
{"git stash list passes", []string{"Bash(git stash list:*)"}, nil},
{"bare git stash fails", []string{"Bash(git stash:*)"}, []string{"stash"}},
{"git branch --show-current passes", []string{"Bash(git branch --show-current:*)"}, nil},
{"bare git branch fails", []string{"Bash(git branch:*)"}, []string{"branch"}},
{"git branch -D fails", []string{"Bash(git branch -D:*)"}, []string{"branch"}},
{"non-bash tools ignored", []string{"Read", "Write", "Agent"}, nil},
{"non-git bash ignored", []string{"Bash(rm -rf:*)"}, nil}, // rm is not a git subverb here
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := ScanAllowlistForWriteGitVerbs(tc.allowlist, defaultForbiddenGitVerbs)
if len(got) != len(tc.want) {
t.Fatalf("got %v, want %v", got, tc.want)
}
for i := range tc.want {
if got[i] != tc.want[i] {
t.Errorf("hit %d = %q, want %q", i, got[i], tc.want[i])
}
}
})
}
}
func TestGenerate_RefusesForbiddenVerb(t *testing.T) {
cfg := testConfig(t)
pb := loadHandover(t)
// Poison the playbook: add a write-git capability.
pb.Capabilities = append(pb.Capabilities, Capability{Kind: "bash", Verb: "git commit", Scope: "*"})
if _, err := Generate(cfg, pb, "claude"); err == nil {
t.Fatal("expected Generate to refuse a poisoned playbook")
}
// Nothing written, no ledger.
if _, err := os.Stat(filepath.Join(cfg.UserDir, ".claude", "skills", "handover", "SKILL.md")); err == nil {
t.Error("a SKILL.md was written despite the safety refusal")
}
if _, err := os.Stat(ledgerPath(cfg)); err == nil {
t.Error("a ledger was written despite the safety refusal")
}
}
// testConfig builds a minimal Config whose UserDir/Workspace point inside a
// throwaway temp dir, the only fields the cockpit emit path touches.
func testConfig(t *testing.T) *config.Config {
t.Helper()
root := t.TempDir()
return &config.Config{
UserDir: filepath.Join(root, "tester"),
Workspace: filepath.Join(root, "tester", ".eeco"),
}
}