* test(doctor): add comprehensive tests for fix and check functions Add edge case tests, e2e tests, and improve test coverage for: - database_test.go: database integrity and sync checks - git_test.go: git hooks, merge driver, sync branch tests - gitignore_test.go: gitignore validation - prefix_test.go: ID prefix handling - fix/fix_test.go: fix operations - fix/e2e_test.go: end-to-end fix scenarios - fix/fix_edge_cases_test.go: edge case handling * docs: add testing philosophy and anti-patterns guide - Create TESTING_PHILOSOPHY.md covering test pyramid, priority matrix, what NOT to test, and 5 anti-patterns with code examples - Add cross-reference from README_TESTING.md - Document beads-specific guidance (well-covered areas vs gaps) - Include target metrics (test-to-code ratio, execution time targets) * chore: revert .beads/ to upstream/main state * refactor(doctor): add category grouping and Ayu theme colors - Add Category field to DoctorCheck for organizing checks by type - Define category constants: Core, Git, Runtime, Data, Integration, Metadata - Update thanks command to use shared Ayu color palette from internal/ui - Simplify test fixtures by removing redundant test cases * fix(doctor): prevent test fork bomb and fix test failures - Add ErrTestBinary guard in getBdBinary() to prevent tests from recursively executing the test binary when calling bd subcommands - Update claude_test.go to use new check names (CLI Availability, Prime Documentation) - Fix syncbranch test path comparison by resolving symlinks (/var vs /private/var on macOS) - Fix permissions check to use exact comparison instead of bitmask - Fix UntrackedJSONL to use git commit --only to preserve staged changes - Fix MergeDriver edge case test by making both .git dir and config read-only - Add skipIfTestBinary helper for E2E tests that need real bd binary * test(doctor): skip read-only config test in CI environments GitHub Actions containers may have CAP_DAC_OVERRIDE or similar capabilities that allow writing to read-only files, causing the test to fail. Skip the test when CI=true or GITHUB_ACTIONS=true.
182 lines
5.9 KiB
Go
182 lines
5.9 KiB
Go
package syncbranch
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestGetBeadsWorktreePath tests the worktree path calculation for various repo structures.
|
|
// This is the regression test for GH#639.
|
|
func TestGetBeadsWorktreePath(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping integration test in short mode")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
t.Run("regular repo returns .git/beads-worktrees path", func(t *testing.T) {
|
|
// Create a regular git repository
|
|
tmpDir := t.TempDir()
|
|
runGitCmd(t, tmpDir, "init")
|
|
runGitCmd(t, tmpDir, "config", "user.email", "test@test.com")
|
|
runGitCmd(t, tmpDir, "config", "user.name", "Test User")
|
|
|
|
// Create initial commit
|
|
testFile := filepath.Join(tmpDir, "test.txt")
|
|
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
|
|
t.Fatalf("Failed to write test file: %v", err)
|
|
}
|
|
runGitCmd(t, tmpDir, "add", ".")
|
|
runGitCmd(t, tmpDir, "commit", "-m", "initial")
|
|
|
|
// Test getBeadsWorktreePath
|
|
path := getBeadsWorktreePath(ctx, tmpDir, "beads-sync")
|
|
|
|
// Should be under .git/beads-worktrees
|
|
expectedSuffix := filepath.Join(".git", "beads-worktrees", "beads-sync")
|
|
if !strings.HasSuffix(path, expectedSuffix) {
|
|
t.Errorf("Expected path to end with %q, got %q", expectedSuffix, path)
|
|
}
|
|
|
|
// Path should be absolute
|
|
if !filepath.IsAbs(path) {
|
|
t.Errorf("Expected absolute path, got %q", path)
|
|
}
|
|
})
|
|
|
|
t.Run("bare repo returns correct worktree path", func(t *testing.T) {
|
|
// Create a bare repository
|
|
tmpDir := t.TempDir()
|
|
bareRepoPath := filepath.Join(tmpDir, "bare.git")
|
|
runGitCmd(t, tmpDir, "init", "--bare", bareRepoPath)
|
|
|
|
// Test getBeadsWorktreePath from bare repo
|
|
path := getBeadsWorktreePath(ctx, bareRepoPath, "beads-sync")
|
|
|
|
// For bare repos, git-common-dir returns the bare repo itself
|
|
// So the path should be <bare-repo>/beads-worktrees/beads-sync
|
|
expectedPath := filepath.Join(bareRepoPath, "beads-worktrees", "beads-sync")
|
|
if path != expectedPath {
|
|
t.Errorf("Expected path %q, got %q", expectedPath, path)
|
|
}
|
|
|
|
// Path should be absolute
|
|
if !filepath.IsAbs(path) {
|
|
t.Errorf("Expected absolute path, got %q", path)
|
|
}
|
|
|
|
// Verify it's NOT trying to create .git/beads-worktrees inside the bare repo
|
|
// (which would fail since bare repos don't have a .git subdirectory)
|
|
badPath := filepath.Join(bareRepoPath, ".git", "beads-worktrees", "beads-sync")
|
|
if path == badPath {
|
|
t.Errorf("Bare repo should not use .git subdirectory path: %q", path)
|
|
}
|
|
})
|
|
|
|
t.Run("worktree of regular repo uses common git dir", func(t *testing.T) {
|
|
// Create a regular repository
|
|
tmpDir := t.TempDir()
|
|
mainRepoPath := filepath.Join(tmpDir, "main-repo")
|
|
if err := os.MkdirAll(mainRepoPath, 0750); err != nil {
|
|
t.Fatalf("Failed to create main repo dir: %v", err)
|
|
}
|
|
|
|
runGitCmd(t, mainRepoPath, "init")
|
|
runGitCmd(t, mainRepoPath, "config", "user.email", "test@test.com")
|
|
runGitCmd(t, mainRepoPath, "config", "user.name", "Test User")
|
|
|
|
// Create initial commit
|
|
testFile := filepath.Join(mainRepoPath, "test.txt")
|
|
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
|
|
t.Fatalf("Failed to write test file: %v", err)
|
|
}
|
|
runGitCmd(t, mainRepoPath, "add", ".")
|
|
runGitCmd(t, mainRepoPath, "commit", "-m", "initial")
|
|
|
|
// Create a worktree
|
|
worktreePath := filepath.Join(tmpDir, "feature-worktree")
|
|
runGitCmd(t, mainRepoPath, "worktree", "add", worktreePath, "-b", "feature")
|
|
|
|
// Test getBeadsWorktreePath from the worktree
|
|
path := getBeadsWorktreePath(ctx, worktreePath, "beads-sync")
|
|
|
|
// Should point to the main repo's .git/beads-worktrees, not the worktree's
|
|
mainGitDir := filepath.Join(mainRepoPath, ".git")
|
|
expectedPath := filepath.Join(mainGitDir, "beads-worktrees", "beads-sync")
|
|
|
|
// Resolve symlinks for comparison (on macOS, /var -> /private/var)
|
|
resolvedExpected, err := filepath.EvalSymlinks(mainRepoPath)
|
|
if err == nil {
|
|
expectedPath = filepath.Join(resolvedExpected, ".git", "beads-worktrees", "beads-sync")
|
|
}
|
|
|
|
if path != expectedPath {
|
|
t.Errorf("Expected path %q, got %q", expectedPath, path)
|
|
}
|
|
})
|
|
|
|
t.Run("fallback works when git command fails", func(t *testing.T) {
|
|
// Test with a non-git directory (should fallback to legacy behavior)
|
|
tmpDir := t.TempDir()
|
|
|
|
path := getBeadsWorktreePath(ctx, tmpDir, "beads-sync")
|
|
|
|
// Should fallback to legacy .git/beads-worktrees path
|
|
expectedPath := filepath.Join(tmpDir, ".git", "beads-worktrees", "beads-sync")
|
|
if path != expectedPath {
|
|
t.Errorf("Expected fallback path %q, got %q", expectedPath, path)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestGetBeadsWorktreePathRelativePath tests that relative paths from git are handled correctly
|
|
func TestGetBeadsWorktreePathRelativePath(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping integration test in short mode")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create a regular git repository
|
|
tmpDir := t.TempDir()
|
|
runGitCmd(t, tmpDir, "init")
|
|
runGitCmd(t, tmpDir, "config", "user.email", "test@test.com")
|
|
runGitCmd(t, tmpDir, "config", "user.name", "Test User")
|
|
|
|
// Create initial commit
|
|
testFile := filepath.Join(tmpDir, "test.txt")
|
|
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
|
|
t.Fatalf("Failed to write test file: %v", err)
|
|
}
|
|
runGitCmd(t, tmpDir, "add", ".")
|
|
runGitCmd(t, tmpDir, "commit", "-m", "initial")
|
|
|
|
// Test from a subdirectory
|
|
subDir := filepath.Join(tmpDir, "subdir")
|
|
if err := os.MkdirAll(subDir, 0750); err != nil {
|
|
t.Fatalf("Failed to create subdir: %v", err)
|
|
}
|
|
|
|
// getBeadsWorktreePath should still return an absolute path
|
|
path := getBeadsWorktreePath(ctx, subDir, "beads-sync")
|
|
|
|
if !filepath.IsAbs(path) {
|
|
t.Errorf("Expected absolute path, got %q", path)
|
|
}
|
|
}
|
|
|
|
// runGitCmd is a helper to run git commands
|
|
func runGitCmd(t *testing.T, dir string, args ...string) {
|
|
t.Helper()
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = dir
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("git %v failed: %v\n%s", args, err, output)
|
|
}
|
|
}
|