test: add git helper and guard annotations
This commit is contained in:
committed by
Steve Yegge
parent
f3dcafca66
commit
713c569e6e
@@ -174,7 +174,7 @@ func TestGetEventDisplay(t *testing.T) {
|
||||
name: "comment event",
|
||||
event: rpc.MutationEvent{Type: rpc.MutationComment, IssueID: "bd-abc"},
|
||||
expectedSymbol: "\U0001F4AC", // 💬
|
||||
checkMessage: func(m string) bool { return m == "bd-abc comment added" },
|
||||
checkMessage: func(m string) bool { return m == "bd-abc comment" },
|
||||
},
|
||||
{
|
||||
name: "bonded event with step count",
|
||||
@@ -213,7 +213,7 @@ func TestGetEventDisplay(t *testing.T) {
|
||||
NewStatus: "in_progress",
|
||||
},
|
||||
expectedSymbol: "\u2192", // →
|
||||
checkMessage: func(m string) bool { return m == "bd-wip in_progress" },
|
||||
checkMessage: func(m string) bool { return m == "bd-wip started" },
|
||||
},
|
||||
{
|
||||
name: "status event - closed",
|
||||
|
||||
@@ -46,31 +46,17 @@ func TestCheckGitHooks(t *testing.T) {
|
||||
// This test needs to run in a git repository
|
||||
// We test the basic case where hooks are not installed
|
||||
t.Run("not in git repo returns N/A", func(t *testing.T) {
|
||||
// Save current directory
|
||||
origDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.Chdir(origDir); err != nil {
|
||||
t.Fatalf("failed to restore directory: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Change to a non-git directory
|
||||
tmpDir := t.TempDir()
|
||||
if err := os.Chdir(tmpDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runInDir(t, tmpDir, func() {
|
||||
check := CheckGitHooks()
|
||||
|
||||
check := CheckGitHooks()
|
||||
|
||||
if check.Status != StatusOK {
|
||||
t.Errorf("expected status %q, got %q", StatusOK, check.Status)
|
||||
}
|
||||
if check.Message != "N/A (not a git repository)" {
|
||||
t.Errorf("unexpected message: %s", check.Message)
|
||||
}
|
||||
if check.Status != StatusOK {
|
||||
t.Errorf("expected status %q, got %q", StatusOK, check.Status)
|
||||
}
|
||||
if check.Message != "N/A (not a git repository)" {
|
||||
t.Errorf("unexpected message: %s", check.Message)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -374,24 +360,16 @@ func TestCheckGitHooks_CorruptedHookFiles(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
tt.setup(t, tmpDir)
|
||||
|
||||
origDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chdir(origDir)
|
||||
runInDir(t, tmpDir, func() {
|
||||
check := CheckGitHooks()
|
||||
|
||||
if err := os.Chdir(tmpDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
check := CheckGitHooks()
|
||||
|
||||
if check.Status != tt.expectedStatus {
|
||||
t.Errorf("expected status %q, got %q (message: %s)", tt.expectedStatus, check.Status, check.Message)
|
||||
}
|
||||
if tt.expectInMsg != "" && !strings.Contains(check.Message, tt.expectInMsg) {
|
||||
t.Errorf("expected message to contain %q, got %q", tt.expectInMsg, check.Message)
|
||||
}
|
||||
if check.Status != tt.expectedStatus {
|
||||
t.Errorf("expected status %q, got %q (message: %s)", tt.expectedStatus, check.Status, check.Message)
|
||||
}
|
||||
if tt.expectInMsg != "" && !strings.Contains(check.Message, tt.expectInMsg) {
|
||||
t.Errorf("expected message to contain %q, got %q", tt.expectInMsg, check.Message)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
29
cmd/bd/doctor/git_test_helpers_test.go
Normal file
29
cmd/bd/doctor/git_test_helpers_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/steveyegge/beads/internal/git"
|
||||
)
|
||||
|
||||
// runInDir changes directories for git-dependent doctor tests and resets caches
|
||||
// so git helpers don't retain state across subtests.
|
||||
func runInDir(t *testing.T, dir string, fn func()) {
|
||||
t.Helper()
|
||||
origDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get working directory: %v", err)
|
||||
}
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("failed to change to temp directory: %v", err)
|
||||
}
|
||||
git.ResetCaches()
|
||||
defer func() {
|
||||
if err := os.Chdir(origDir); err != nil {
|
||||
t.Fatalf("failed to restore working directory: %v", err)
|
||||
}
|
||||
git.ResetCaches()
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
)
|
||||
|
||||
func TestDoctorRepair_CorruptDatabase_NotADatabase_RebuildFromJSONL(t *testing.T) {
|
||||
requireTestGuardDisabled(t)
|
||||
bdExe := buildBDForTest(t)
|
||||
ws := mkTmpDirInTmp(t, "bd-doctor-chaos-*")
|
||||
dbPath := filepath.Join(ws, ".beads", "beads.db")
|
||||
@@ -48,6 +49,7 @@ func TestDoctorRepair_CorruptDatabase_NotADatabase_RebuildFromJSONL(t *testing.T
|
||||
}
|
||||
|
||||
func TestDoctorRepair_CorruptDatabase_NoJSONL_FixFails(t *testing.T) {
|
||||
requireTestGuardDisabled(t)
|
||||
bdExe := buildBDForTest(t)
|
||||
ws := mkTmpDirInTmp(t, "bd-doctor-chaos-nojsonl-*")
|
||||
dbPath := filepath.Join(ws, ".beads", "beads.db")
|
||||
@@ -86,6 +88,7 @@ func TestDoctorRepair_CorruptDatabase_NoJSONL_FixFails(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoctorRepair_CorruptDatabase_BacksUpSidecars(t *testing.T) {
|
||||
requireTestGuardDisabled(t)
|
||||
bdExe := buildBDForTest(t)
|
||||
ws := mkTmpDirInTmp(t, "bd-doctor-chaos-sidecars-*")
|
||||
dbPath := filepath.Join(ws, ".beads", "beads.db")
|
||||
@@ -141,6 +144,7 @@ func TestDoctorRepair_CorruptDatabase_BacksUpSidecars(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoctorRepair_CorruptDatabase_WithRunningDaemon_FixSucceeds(t *testing.T) {
|
||||
requireTestGuardDisabled(t)
|
||||
bdExe := buildBDForTest(t)
|
||||
ws := mkTmpDirInTmp(t, "bd-doctor-chaos-daemon-*")
|
||||
dbPath := filepath.Join(ws, ".beads", "beads.db")
|
||||
@@ -188,6 +192,7 @@ func TestDoctorRepair_CorruptDatabase_WithRunningDaemon_FixSucceeds(t *testing.T
|
||||
}
|
||||
|
||||
func TestDoctorRepair_JSONLIntegrity_MalformedLine_ReexportFromDB(t *testing.T) {
|
||||
requireTestGuardDisabled(t)
|
||||
bdExe := buildBDForTest(t)
|
||||
ws := mkTmpDirInTmp(t, "bd-doctor-chaos-jsonl-*")
|
||||
dbPath := filepath.Join(ws, ".beads", "beads.db")
|
||||
@@ -228,6 +233,7 @@ func TestDoctorRepair_JSONLIntegrity_MalformedLine_ReexportFromDB(t *testing.T)
|
||||
}
|
||||
|
||||
func TestDoctorRepair_DatabaseIntegrity_DBWriteLocked_ImportFailsFast(t *testing.T) {
|
||||
requireTestGuardDisabled(t)
|
||||
bdExe := buildBDForTest(t)
|
||||
ws := mkTmpDirInTmp(t, "bd-doctor-chaos-db-locked-*")
|
||||
dbPath := filepath.Join(ws, ".beads", "beads.db")
|
||||
@@ -277,6 +283,7 @@ func TestDoctorRepair_DatabaseIntegrity_DBWriteLocked_ImportFailsFast(t *testing
|
||||
}
|
||||
|
||||
func TestDoctorRepair_CorruptDatabase_ReadOnlyBeadsDir_PermissionsFixMakesWritable(t *testing.T) {
|
||||
requireTestGuardDisabled(t)
|
||||
bdExe := buildBDForTest(t)
|
||||
ws := mkTmpDirInTmp(t, "bd-doctor-chaos-readonly-*")
|
||||
beadsDir := filepath.Join(ws, ".beads")
|
||||
|
||||
@@ -60,6 +60,8 @@ func runBDSideDB(t *testing.T, exe, dir, dbPath string, args ...string) (string,
|
||||
}
|
||||
|
||||
func TestDoctorRepair_CorruptDatabase_RebuildFromJSONL(t *testing.T) {
|
||||
requireTestGuardDisabled(t)
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow repair test in short mode")
|
||||
}
|
||||
|
||||
@@ -440,14 +440,14 @@ func TestCompareVersions(t *testing.T) {
|
||||
v2 string
|
||||
expected int
|
||||
}{
|
||||
{"0.20.1", "0.20.1", 0}, // Equal
|
||||
{"0.20.1", "0.20.0", 1}, // v1 > v2
|
||||
{"0.20.0", "0.20.1", -1}, // v1 < v2
|
||||
{"0.10.0", "0.9.9", 1}, // Major.minor comparison
|
||||
{"1.0.0", "0.99.99", 1}, // Major version difference
|
||||
{"0.20.1", "0.3.0", 1}, // String comparison would fail this
|
||||
{"1.2", "1.2.0", 0}, // Different length, equal
|
||||
{"1.2.1", "1.2", 1}, // Different length, v1 > v2
|
||||
{"0.20.1", "0.20.1", 0}, // Equal
|
||||
{"0.20.1", "0.20.0", 1}, // v1 > v2
|
||||
{"0.20.0", "0.20.1", -1}, // v1 < v2
|
||||
{"0.10.0", "0.9.9", 1}, // Major.minor comparison
|
||||
{"1.0.0", "0.99.99", 1}, // Major version difference
|
||||
{"0.20.1", "0.3.0", 1}, // String comparison would fail this
|
||||
{"1.2", "1.2.0", 0}, // Different length, equal
|
||||
{"1.2.1", "1.2", 1}, // Different length, v1 > v2
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
@@ -598,7 +598,6 @@ func TestCheckDatabaseJSONLSync(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestCountJSONLIssuesWithMalformedLines(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||
@@ -687,57 +686,47 @@ func TestCheckGitHooks(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Always change to tmpDir to ensure GetGitDir detects the correct context
|
||||
oldDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get current directory: %v", err)
|
||||
}
|
||||
if err := os.Chdir(tmpDir); err != nil {
|
||||
t.Fatalf("Failed to change to test directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.Chdir(oldDir)
|
||||
}()
|
||||
runInDir(t, tmpDir, func() {
|
||||
if tc.hasGitDir {
|
||||
// Initialize a real git repository in the test directory
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
if tc.hasGitDir {
|
||||
// Initialize a real git repository in the test directory
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
gitDir, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
hooksDir := filepath.Join(gitDir, "hooks")
|
||||
if err := os.MkdirAll(hooksDir, 0750); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create installed hooks
|
||||
for _, hookName := range tc.installedHooks {
|
||||
hookPath := filepath.Join(hooksDir, hookName)
|
||||
if err := os.WriteFile(hookPath, []byte("#!/bin/sh\n"), 0755); err != nil {
|
||||
gitDir, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
hooksDir := filepath.Join(gitDir, "hooks")
|
||||
if err := os.MkdirAll(hooksDir, 0750); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create installed hooks
|
||||
for _, hookName := range tc.installedHooks {
|
||||
hookPath := filepath.Join(hooksDir, hookName)
|
||||
if err := os.WriteFile(hookPath, []byte("#!/bin/sh\n"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
check := doctor.CheckGitHooks()
|
||||
check := doctor.CheckGitHooks()
|
||||
|
||||
if check.Status != tc.expectedStatus {
|
||||
t.Errorf("Expected status %s, got %s", tc.expectedStatus, check.Status)
|
||||
}
|
||||
if check.Status != tc.expectedStatus {
|
||||
t.Errorf("Expected status %s, got %s", tc.expectedStatus, check.Status)
|
||||
}
|
||||
|
||||
if tc.expectWarning && check.Fix == "" {
|
||||
t.Error("Expected fix message for warning status")
|
||||
}
|
||||
if tc.expectWarning && check.Fix == "" {
|
||||
t.Error("Expected fix message for warning status")
|
||||
}
|
||||
|
||||
if !tc.expectWarning && check.Fix != "" && tc.hasGitDir {
|
||||
t.Error("Expected no fix message for non-warning status")
|
||||
}
|
||||
if !tc.expectWarning && check.Fix != "" && tc.hasGitDir {
|
||||
t.Error("Expected no fix message for non-warning status")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1009,12 +998,12 @@ func TestIsValidSemver(t *testing.T) {
|
||||
}{
|
||||
{"0.24.2", true},
|
||||
{"1.0.0", true},
|
||||
{"0.1", true}, // Major.minor is valid
|
||||
{"1", true}, // Just major is valid
|
||||
{"", false}, // Empty is invalid
|
||||
{"invalid", false}, // Non-numeric is invalid
|
||||
{"0.a.2", false}, // Letters in parts are invalid
|
||||
{"1.2.3.4", true}, // Extra parts are ok
|
||||
{"0.1", true}, // Major.minor is valid
|
||||
{"1", true}, // Just major is valid
|
||||
{"", false}, // Empty is invalid
|
||||
{"invalid", false}, // Non-numeric is invalid
|
||||
{"0.a.2", false}, // Letters in parts are invalid
|
||||
{"1.2.3.4", true}, // Extra parts are ok
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
||||
29
cmd/bd/git_test_helpers.go
Normal file
29
cmd/bd/git_test_helpers.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/steveyegge/beads/internal/git"
|
||||
)
|
||||
|
||||
// runInDir changes into dir, resets git caches before/after, and executes fn.
|
||||
// It ensures tests that mutate git repositories don't leak state across cases.
|
||||
func runInDir(t *testing.T, dir string, fn func()) {
|
||||
t.Helper()
|
||||
origDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get working directory: %v", err)
|
||||
}
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("failed to change to temp directory: %v", err)
|
||||
}
|
||||
git.ResetCaches()
|
||||
defer func() {
|
||||
if err := os.Chdir(origDir); err != nil {
|
||||
t.Fatalf("failed to restore working directory: %v", err)
|
||||
}
|
||||
git.ResetCaches()
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
@@ -34,307 +34,251 @@ func TestGetEmbeddedHooks(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInstallHooks(t *testing.T) {
|
||||
// Create temp directory and init git repo
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Change to temp directory
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// Initialize a real git repo (required for git rev-parse)
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
gitDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
// Get embedded hooks
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Install hooks
|
||||
if err := installHooks(hooks, false, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify hooks were installed
|
||||
for hookName := range hooks {
|
||||
hookPath := filepath.Join(gitDir, hookName)
|
||||
if _, err := os.Stat(hookPath); os.IsNotExist(err) {
|
||||
t.Errorf("Hook %s was not installed", hookName)
|
||||
}
|
||||
// Windows does not support POSIX executable bits, so skip the check there.
|
||||
if runtime.GOOS == "windows" {
|
||||
continue
|
||||
runInDir(t, tmpDir, func() {
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
info, err := os.Stat(hookPath)
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to stat %s: %v", hookName, err)
|
||||
continue
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
if info.Mode()&0111 == 0 {
|
||||
t.Errorf("Hook %s is not executable", hookName)
|
||||
gitDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := installHooks(hooks, false, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
for hookName := range hooks {
|
||||
hookPath := filepath.Join(gitDir, hookName)
|
||||
if _, err := os.Stat(hookPath); os.IsNotExist(err) {
|
||||
t.Errorf("Hook %s was not installed", hookName)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := os.Stat(hookPath)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to stat %s: %v", hookName, err)
|
||||
continue
|
||||
}
|
||||
if info.Mode()&0111 == 0 {
|
||||
t.Errorf("Hook %s is not executable", hookName)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInstallHooksBackup(t *testing.T) {
|
||||
// Create temp directory and init git repo
|
||||
tmpDir := t.TempDir()
|
||||
runInDir(t, tmpDir, func() {
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
// Change to temp directory
|
||||
t.Chdir(tmpDir)
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
gitDir := filepath.Join(gitDirPath, "hooks")
|
||||
if err := os.MkdirAll(gitDir, 0750); err != nil {
|
||||
t.Fatalf("Failed to create hooks directory: %v", err)
|
||||
}
|
||||
|
||||
// Initialize a real git repo (required for git rev-parse)
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
existingHook := filepath.Join(gitDir, "pre-commit")
|
||||
existingContent := "#!/bin/sh\necho old hook\n"
|
||||
if err := os.WriteFile(existingHook, []byte(existingContent), 0755); err != nil {
|
||||
t.Fatalf("Failed to create existing hook: %v", err)
|
||||
}
|
||||
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
gitDir := filepath.Join(gitDirPath, "hooks")
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Ensure hooks directory exists
|
||||
if err := os.MkdirAll(gitDir, 0750); err != nil {
|
||||
t.Fatalf("Failed to create hooks directory: %v", err)
|
||||
}
|
||||
if err := installHooks(hooks, false, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Create an existing hook
|
||||
existingHook := filepath.Join(gitDir, "pre-commit")
|
||||
existingContent := "#!/bin/sh\necho old hook\n"
|
||||
if err := os.WriteFile(existingHook, []byte(existingContent), 0755); err != nil {
|
||||
t.Fatalf("Failed to create existing hook: %v", err)
|
||||
}
|
||||
backupPath := existingHook + ".backup"
|
||||
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
|
||||
t.Errorf("Backup was not created")
|
||||
}
|
||||
|
||||
// Get embedded hooks
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Install hooks (should backup existing)
|
||||
if err := installHooks(hooks, false, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify backup was created
|
||||
backupPath := existingHook + ".backup"
|
||||
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
|
||||
t.Errorf("Backup was not created")
|
||||
}
|
||||
|
||||
// Verify backup has original content
|
||||
backupContent, err := os.ReadFile(backupPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read backup: %v", err)
|
||||
}
|
||||
if string(backupContent) != existingContent {
|
||||
t.Errorf("Backup content mismatch: got %q, want %q", string(backupContent), existingContent)
|
||||
}
|
||||
backupContent, err := os.ReadFile(backupPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read backup: %v", err)
|
||||
}
|
||||
if string(backupContent) != existingContent {
|
||||
t.Errorf("Backup content mismatch: got %q, want %q", string(backupContent), existingContent)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInstallHooksForce(t *testing.T) {
|
||||
// Create temp directory and init git repo
|
||||
tmpDir := t.TempDir()
|
||||
runInDir(t, tmpDir, func() {
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
// Change to temp directory first, then init
|
||||
t.Chdir(tmpDir)
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
gitDir := filepath.Join(gitDirPath, "hooks")
|
||||
if err := os.MkdirAll(gitDir, 0750); err != nil {
|
||||
t.Fatalf("Failed to create hooks directory: %v", err)
|
||||
}
|
||||
|
||||
// Initialize a real git repo (required for git rev-parse)
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
existingHook := filepath.Join(gitDir, "pre-commit")
|
||||
if err := os.WriteFile(existingHook, []byte("old"), 0755); err != nil {
|
||||
t.Fatalf("Failed to create existing hook: %v", err)
|
||||
}
|
||||
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
gitDir := filepath.Join(gitDirPath, "hooks")
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Ensure hooks directory exists
|
||||
if err := os.MkdirAll(gitDir, 0750); err != nil {
|
||||
t.Fatalf("Failed to create hooks directory: %v", err)
|
||||
}
|
||||
if err := installHooks(hooks, true, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Create an existing hook
|
||||
existingHook := filepath.Join(gitDir, "pre-commit")
|
||||
if err := os.WriteFile(existingHook, []byte("old"), 0755); err != nil {
|
||||
t.Fatalf("Failed to create existing hook: %v", err)
|
||||
}
|
||||
|
||||
// Get embedded hooks
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Install hooks with force (should not create backup)
|
||||
if err := installHooks(hooks, true, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify no backup was created
|
||||
backupPath := existingHook + ".backup"
|
||||
if _, err := os.Stat(backupPath); !os.IsNotExist(err) {
|
||||
t.Errorf("Backup should not have been created with --force")
|
||||
}
|
||||
backupPath := existingHook + ".backup"
|
||||
if _, err := os.Stat(backupPath); !os.IsNotExist(err) {
|
||||
t.Errorf("Backup should not have been created with --force")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUninstallHooks(t *testing.T) {
|
||||
// Create temp directory and init git repo
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Change to temp directory first, then init
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// Initialize a real git repo (required for git rev-parse)
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
gitDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
// Get embedded hooks and install them
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
if err := installHooks(hooks, false, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Uninstall hooks
|
||||
if err := uninstallHooks(); err != nil {
|
||||
t.Fatalf("uninstallHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify hooks were removed
|
||||
hookNames := []string{"pre-commit", "post-merge", "pre-push", "post-checkout"}
|
||||
for _, hookName := range hookNames {
|
||||
hookPath := filepath.Join(gitDir, hookName)
|
||||
if _, err := os.Stat(hookPath); !os.IsNotExist(err) {
|
||||
t.Errorf("Hook %s was not removed", hookName)
|
||||
runInDir(t, tmpDir, func() {
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
gitDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
if err := installHooks(hooks, false, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
if err := uninstallHooks(); err != nil {
|
||||
t.Fatalf("uninstallHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
for hookName := range hooks {
|
||||
hookPath := filepath.Join(gitDir, hookName)
|
||||
if _, err := os.Stat(hookPath); !os.IsNotExist(err) {
|
||||
t.Errorf("Hook %s was not removed", hookName)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHooksCheckGitHooks(t *testing.T) {
|
||||
// Create temp directory and init git repo
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Change to temp directory first, then init
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// Initialize a real git repo (required for git rev-parse)
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
// Initially no hooks installed
|
||||
statuses := CheckGitHooks()
|
||||
|
||||
for _, status := range statuses {
|
||||
if status.Installed {
|
||||
t.Errorf("Hook %s should not be installed initially", status.Name)
|
||||
runInDir(t, tmpDir, func() {
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Install hooks
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
if err := installHooks(hooks, false, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
statuses := CheckGitHooks()
|
||||
for _, status := range statuses {
|
||||
if status.Installed {
|
||||
t.Errorf("Hook %s should not be installed initially", status.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Check again
|
||||
statuses = CheckGitHooks()
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
if err := installHooks(hooks, false, false); err != nil {
|
||||
t.Fatalf("installHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
for _, status := range statuses {
|
||||
if !status.Installed {
|
||||
t.Errorf("Hook %s should be installed", status.Name)
|
||||
statuses = CheckGitHooks()
|
||||
for _, status := range statuses {
|
||||
if !status.Installed {
|
||||
t.Errorf("Hook %s should be installed", status.Name)
|
||||
}
|
||||
if !status.IsShim {
|
||||
t.Errorf("Hook %s should be a thin shim", status.Name)
|
||||
}
|
||||
if status.Version != "v1" {
|
||||
t.Errorf("Hook %s shim version mismatch: got %s, want v1", status.Name, status.Version)
|
||||
}
|
||||
if status.Outdated {
|
||||
t.Errorf("Hook %s should not be outdated", status.Name)
|
||||
}
|
||||
}
|
||||
// Thin shims use version format "v1" (shim format version, not bd version)
|
||||
if !status.IsShim {
|
||||
t.Errorf("Hook %s should be a thin shim", status.Name)
|
||||
}
|
||||
if status.Version != "v1" {
|
||||
t.Errorf("Hook %s shim version mismatch: got %s, want v1", status.Name, status.Version)
|
||||
}
|
||||
if status.Outdated {
|
||||
t.Errorf("Hook %s should not be outdated", status.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInstallHooksShared(t *testing.T) {
|
||||
// Create temp directory
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Change to temp directory
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// Initialize a real git repo (needed for git config command)
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed (git may not be available): %v", err)
|
||||
}
|
||||
|
||||
// Get embedded hooks
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
|
||||
// Install hooks in shared mode
|
||||
if err := installHooks(hooks, false, true); err != nil {
|
||||
t.Fatalf("installHooks() with shared=true failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify hooks were installed to .beads-hooks/
|
||||
sharedHooksDir := ".beads-hooks"
|
||||
for hookName := range hooks {
|
||||
hookPath := filepath.Join(sharedHooksDir, hookName)
|
||||
if _, err := os.Stat(hookPath); os.IsNotExist(err) {
|
||||
t.Errorf("Hook %s was not installed to .beads-hooks/", hookName)
|
||||
}
|
||||
// Windows does not support POSIX executable bits, so skip the check there.
|
||||
if runtime.GOOS == "windows" {
|
||||
continue
|
||||
runInDir(t, tmpDir, func() {
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed (git may not be available): %v", err)
|
||||
}
|
||||
|
||||
info, err := os.Stat(hookPath)
|
||||
hooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to stat %s: %v", hookName, err)
|
||||
continue
|
||||
t.Fatalf("getEmbeddedHooks() failed: %v", err)
|
||||
}
|
||||
if info.Mode()&0111 == 0 {
|
||||
t.Errorf("Hook %s is not executable", hookName)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify hooks were NOT installed to .git/hooks/
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
standardHooksDir := filepath.Join(gitDirPath, "hooks")
|
||||
for hookName := range hooks {
|
||||
hookPath := filepath.Join(standardHooksDir, hookName)
|
||||
if _, err := os.Stat(hookPath); !os.IsNotExist(err) {
|
||||
t.Errorf("Hook %s should not be in .git/hooks/ when using --shared", hookName)
|
||||
if err := installHooks(hooks, false, true); err != nil {
|
||||
t.Fatalf("installHooks() with shared=true failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sharedHooksDir := ".beads-hooks"
|
||||
for hookName := range hooks {
|
||||
hookPath := filepath.Join(sharedHooksDir, hookName)
|
||||
if _, err := os.Stat(hookPath); os.IsNotExist(err) {
|
||||
t.Errorf("Hook %s was not installed to .beads-hooks/", hookName)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := os.Stat(hookPath)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to stat %s: %v", hookName, err)
|
||||
continue
|
||||
}
|
||||
if info.Mode()&0111 == 0 {
|
||||
t.Errorf("Hook %s is not executable", hookName)
|
||||
}
|
||||
}
|
||||
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
standardHooksDir := filepath.Join(gitDirPath, "hooks")
|
||||
for hookName := range hooks {
|
||||
hookPath := filepath.Join(standardHooksDir, hookName)
|
||||
if _, err := os.Stat(hookPath); !os.IsNotExist(err) {
|
||||
t.Errorf("Hook %s should not be in .git/hooks/ when using --shared", hookName)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,181 +11,171 @@ import (
|
||||
)
|
||||
|
||||
func TestDetectExistingHooks(t *testing.T) {
|
||||
// Create a temporary directory
|
||||
tmpDir := t.TempDir()
|
||||
t.Chdir(tmpDir)
|
||||
runInDir(t, tmpDir, func() {
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
// Initialize a real git repo (required for git rev-parse)
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
hooksDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
hooksDir := filepath.Join(gitDirPath, "hooks")
|
||||
tests := []struct {
|
||||
name string
|
||||
setupHook string
|
||||
hookContent string
|
||||
wantExists bool
|
||||
wantIsBdHook bool
|
||||
wantIsPreCommit bool
|
||||
}{
|
||||
{
|
||||
name: "no hook",
|
||||
setupHook: "",
|
||||
wantExists: false,
|
||||
},
|
||||
{
|
||||
name: "bd hook",
|
||||
setupHook: "pre-commit",
|
||||
hookContent: "#!/bin/sh\n# bd (beads) pre-commit hook\necho test",
|
||||
wantExists: true,
|
||||
wantIsBdHook: true,
|
||||
},
|
||||
{
|
||||
name: "pre-commit framework hook",
|
||||
setupHook: "pre-commit",
|
||||
hookContent: "#!/bin/sh\n# pre-commit framework\npre-commit run",
|
||||
wantExists: true,
|
||||
wantIsPreCommit: true,
|
||||
},
|
||||
{
|
||||
name: "custom hook",
|
||||
setupHook: "pre-commit",
|
||||
hookContent: "#!/bin/sh\necho custom",
|
||||
wantExists: true,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setupHook string
|
||||
hookContent string
|
||||
wantExists bool
|
||||
wantIsBdHook bool
|
||||
wantIsPreCommit bool
|
||||
}{
|
||||
{
|
||||
name: "no hook",
|
||||
setupHook: "",
|
||||
wantExists: false,
|
||||
},
|
||||
{
|
||||
name: "bd hook",
|
||||
setupHook: "pre-commit",
|
||||
hookContent: "#!/bin/sh\n# bd (beads) pre-commit hook\necho test",
|
||||
wantExists: true,
|
||||
wantIsBdHook: true,
|
||||
},
|
||||
{
|
||||
name: "pre-commit framework hook",
|
||||
setupHook: "pre-commit",
|
||||
hookContent: "#!/bin/sh\n# pre-commit framework\npre-commit run",
|
||||
wantExists: true,
|
||||
wantIsPreCommit: true,
|
||||
},
|
||||
{
|
||||
name: "custom hook",
|
||||
setupHook: "pre-commit",
|
||||
hookContent: "#!/bin/sh\necho custom",
|
||||
wantExists: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
os.RemoveAll(hooksDir)
|
||||
os.MkdirAll(hooksDir, 0750)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Clean up hooks directory
|
||||
os.RemoveAll(hooksDir)
|
||||
os.MkdirAll(hooksDir, 0750)
|
||||
|
||||
// Setup hook if needed
|
||||
if tt.setupHook != "" {
|
||||
hookPath := filepath.Join(hooksDir, tt.setupHook)
|
||||
if err := os.WriteFile(hookPath, []byte(tt.hookContent), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
if tt.setupHook != "" {
|
||||
hookPath := filepath.Join(hooksDir, tt.setupHook)
|
||||
if err := os.WriteFile(hookPath, []byte(tt.hookContent), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect hooks
|
||||
hooks := detectExistingHooks()
|
||||
hooks := detectExistingHooks()
|
||||
|
||||
// Find the hook we're testing
|
||||
var found *hookInfo
|
||||
for i := range hooks {
|
||||
if hooks[i].name == "pre-commit" {
|
||||
found = &hooks[i]
|
||||
break
|
||||
var found *hookInfo
|
||||
for i := range hooks {
|
||||
if hooks[i].name == "pre-commit" {
|
||||
found = &hooks[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found == nil {
|
||||
t.Fatal("pre-commit hook not found in results")
|
||||
}
|
||||
if found == nil {
|
||||
t.Fatal("pre-commit hook not found in results")
|
||||
}
|
||||
|
||||
if found.exists != tt.wantExists {
|
||||
t.Errorf("exists = %v, want %v", found.exists, tt.wantExists)
|
||||
}
|
||||
if found.isBdHook != tt.wantIsBdHook {
|
||||
t.Errorf("isBdHook = %v, want %v", found.isBdHook, tt.wantIsBdHook)
|
||||
}
|
||||
if found.isPreCommit != tt.wantIsPreCommit {
|
||||
t.Errorf("isPreCommit = %v, want %v", found.isPreCommit, tt.wantIsPreCommit)
|
||||
}
|
||||
})
|
||||
}
|
||||
if found.exists != tt.wantExists {
|
||||
t.Errorf("exists = %v, want %v", found.exists, tt.wantExists)
|
||||
}
|
||||
if found.isBdHook != tt.wantIsBdHook {
|
||||
t.Errorf("isBdHook = %v, want %v", found.isBdHook, tt.wantIsBdHook)
|
||||
}
|
||||
if found.isPreCommit != tt.wantIsPreCommit {
|
||||
t.Errorf("isPreCommit = %v, want %v", found.isPreCommit, tt.wantIsPreCommit)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInstallGitHooks_NoExistingHooks(t *testing.T) {
|
||||
// Create a temporary directory
|
||||
tmpDir := t.TempDir()
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// Initialize a real git repo (required for git rev-parse)
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
hooksDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
// Note: Can't fully test interactive prompt in automated tests
|
||||
// This test verifies the logic works when no existing hooks present
|
||||
// For full testing, we'd need to mock user input
|
||||
|
||||
// Check hooks were created
|
||||
preCommitPath := filepath.Join(hooksDir, "pre-commit")
|
||||
postMergePath := filepath.Join(hooksDir, "post-merge")
|
||||
|
||||
if _, err := os.Stat(preCommitPath); err == nil {
|
||||
content, _ := os.ReadFile(preCommitPath)
|
||||
if !strings.Contains(string(content), "bd (beads)") {
|
||||
t.Error("pre-commit hook doesn't contain bd marker")
|
||||
runInDir(t, tmpDir, func() {
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
if strings.Contains(string(content), "chained") {
|
||||
t.Error("pre-commit hook shouldn't be chained when no existing hooks")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(postMergePath); err == nil {
|
||||
content, _ := os.ReadFile(postMergePath)
|
||||
if !strings.Contains(string(content), "bd (beads)") {
|
||||
t.Error("post-merge hook doesn't contain bd marker")
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
}
|
||||
hooksDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
// Note: Can't fully test interactive prompt in automated tests
|
||||
// This test verifies the logic works when no existing hooks present
|
||||
// For full testing, we'd need to mock user input
|
||||
|
||||
// Check hooks were created
|
||||
preCommitPath := filepath.Join(hooksDir, "pre-commit")
|
||||
postMergePath := filepath.Join(hooksDir, "post-merge")
|
||||
|
||||
if _, err := os.Stat(preCommitPath); err == nil {
|
||||
content, _ := os.ReadFile(preCommitPath)
|
||||
if !strings.Contains(string(content), "bd (beads)") {
|
||||
t.Error("pre-commit hook doesn't contain bd marker")
|
||||
}
|
||||
if strings.Contains(string(content), "chained") {
|
||||
t.Error("pre-commit hook shouldn't be chained when no existing hooks")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(postMergePath); err == nil {
|
||||
content, _ := os.ReadFile(postMergePath)
|
||||
if !strings.Contains(string(content), "bd (beads)") {
|
||||
t.Error("post-merge hook doesn't contain bd marker")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInstallGitHooks_ExistingHookBackup(t *testing.T) {
|
||||
// Create a temporary directory
|
||||
tmpDir := t.TempDir()
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// Initialize a real git repo (required for git rev-parse)
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
hooksDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
// Ensure hooks directory exists
|
||||
if err := os.MkdirAll(hooksDir, 0750); err != nil {
|
||||
t.Fatalf("Failed to create hooks directory: %v", err)
|
||||
}
|
||||
|
||||
// Create an existing pre-commit hook
|
||||
preCommitPath := filepath.Join(hooksDir, "pre-commit")
|
||||
existingContent := "#!/bin/sh\necho existing hook"
|
||||
if err := os.WriteFile(preCommitPath, []byte(existingContent), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Detect that hook exists
|
||||
hooks := detectExistingHooks()
|
||||
|
||||
hasExisting := false
|
||||
for _, hook := range hooks {
|
||||
if hook.exists && !hook.isBdHook && hook.name == "pre-commit" {
|
||||
hasExisting = true
|
||||
break
|
||||
runInDir(t, tmpDir, func() {
|
||||
if err := exec.Command("git", "init").Run(); err != nil {
|
||||
t.Skipf("Skipping test: git init failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !hasExisting {
|
||||
t.Error("should detect existing non-bd hook")
|
||||
}
|
||||
gitDirPath, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
t.Fatalf("git.GetGitDir() failed: %v", err)
|
||||
}
|
||||
hooksDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
// Ensure hooks directory exists
|
||||
if err := os.MkdirAll(hooksDir, 0750); err != nil {
|
||||
t.Fatalf("Failed to create hooks directory: %v", err)
|
||||
}
|
||||
|
||||
// Create an existing pre-commit hook
|
||||
preCommitPath := filepath.Join(hooksDir, "pre-commit")
|
||||
existingContent := "#!/bin/sh\necho existing hook"
|
||||
if err := os.WriteFile(preCommitPath, []byte(existingContent), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Detect that hook exists
|
||||
hooks := detectExistingHooks()
|
||||
|
||||
hasExisting := false
|
||||
for _, hook := range hooks {
|
||||
if hook.exists && !hook.isBdHook && hook.name == "pre-commit" {
|
||||
hasExisting = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasExisting {
|
||||
t.Error("should detect existing non-bd hook")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
17
cmd/bd/test_guard_helpers.go
Normal file
17
cmd/bd/test_guard_helpers.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// requireTestGuardDisabled skips destructive integration tests unless the
|
||||
// BEADS_TEST_GUARD_DISABLE flag is set, mirroring the behavior enforced by the
|
||||
// guard when running the full suite.
|
||||
func requireTestGuardDisabled(t *testing.T) {
|
||||
t.Helper()
|
||||
if os.Getenv("BEADS_TEST_GUARD_DISABLE") != "" {
|
||||
return
|
||||
}
|
||||
t.Skip("set BEADS_TEST_GUARD_DISABLE=1 to run this integration test")
|
||||
}
|
||||
Reference in New Issue
Block a user