package tools import ( "context" "encoding/json" "os" "os/exec" "path/filepath" "strings" "testing" ) func requireGit(t *testing.T) { t.Helper() if _, err := exec.LookPath("git"); err != nil { t.Skip("git not installed") } } func runGitForTest(t *testing.T, dir string, args ...string) { t.Helper() gitArgs := append([]string{"-C", dir}, args...) cmd := exec.Command("git", gitArgs...) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %v failed: %v\n%s", args, err, out) } } func initTestRepo(t *testing.T) string { t.Helper() dir := t.TempDir() runGitForTest(t, dir, "init") runGitForTest(t, dir, "-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "--allow-empty", "-m", "initial") return dir } func TestGitStatusToolShowsChangedFiles(t *testing.T) { requireGit(t) dir := initTestRepo(t) path := filepath.Join(dir, "tracked.txt") if err := os.WriteFile(path, []byte("hello"), 0o644); err != nil { t.Fatal(err) } runGitForTest(t, dir, "add", "tracked.txt") runGitForTest(t, dir, "-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-m", "add tracked") if err := os.WriteFile(path, []byte("world"), 0o644); err != nil { t.Fatal(err) } tool := NewGitStatusTool(dir) out, err := tool.Execute(context.Background(), nil) if err != nil { t.Fatal(err) } if !strings.Contains(out, "Changed files:") { t.Fatalf("output missing 'Changed files:': %q", out) } if !strings.Contains(out, "M tracked.txt") { t.Fatalf("output missing 'M tracked.txt': %q", out) } } func TestGitDiffToolShowsUnstagedDiff(t *testing.T) { requireGit(t) dir := initTestRepo(t) path := filepath.Join(dir, "tracked.txt") if err := os.WriteFile(path, []byte("hello"), 0o644); err != nil { t.Fatal(err) } runGitForTest(t, dir, "add", "tracked.txt") runGitForTest(t, dir, "-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-m", "add tracked") if err := os.WriteFile(path, []byte("world"), 0o644); err != nil { t.Fatal(err) } tool := NewGitDiffTool(dir) out, err := tool.Execute(context.Background(), nil) if err != nil { t.Fatal(err) } if !strings.Contains(out, "diff --git") { t.Fatalf("output missing 'diff --git': %q", out) } } func TestGitDiffToolShowsStat(t *testing.T) { requireGit(t) dir := initTestRepo(t) path := filepath.Join(dir, "tracked.txt") if err := os.WriteFile(path, []byte("hello"), 0o644); err != nil { t.Fatal(err) } runGitForTest(t, dir, "add", "tracked.txt") runGitForTest(t, dir, "-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-m", "add tracked") if err := os.WriteFile(path, []byte("world"), 0o644); err != nil { t.Fatal(err) } tool := NewGitDiffTool(dir) out, err := tool.Execute(context.Background(), json.RawMessage(`{"stat":true}`)) if err != nil { t.Fatal(err) } if !strings.Contains(out, "tracked.txt") { t.Fatalf("output missing 'tracked.txt': %q", out) } } func TestGitLogToolShowsCommits(t *testing.T) { requireGit(t) dir := initTestRepo(t) tool := NewGitLogTool(dir) out, err := tool.Execute(context.Background(), json.RawMessage(`{"max_count":1}`)) if err != nil { t.Fatal(err) } if !strings.Contains(out, "initial") { t.Fatalf("output missing 'initial': %q", out) } } func TestGitToolsRejectEscapingPath(t *testing.T) { requireGit(t) dir := initTestRepo(t) tool := NewGitStatusTool(dir) _, err := tool.Execute(context.Background(), json.RawMessage(`{"path":"../outside"}`)) if err == nil { t.Fatal("expected error for escaping path") } if !strings.Contains(err.Error(), "escapes") { t.Fatalf("error = %v", err) } }