ajhahn.de
← eeco
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"),
	}
}