From ac24a631879d6bfc1513b95e7b6aaf337f83fcbe Mon Sep 17 00:00:00 2001 From: beads/crew/dave Date: Sat, 10 Jan 2026 20:39:55 -0800 Subject: [PATCH] fix: make tests resilient to project .beads/redirect Tests were failing because beads.FindDatabasePath() follows the project's .beads/redirect file, causing tests to find unexpected databases. Fixed by: - Setting BEADS_DIR in tests that need isolation from git repo detection - Clearing BEADS_DIR in TestMain to prevent global contamination - Updating migration test schema to include owner column This ensures tests work correctly in crew directories that have redirect files pointing to shared .beads directories. Co-Authored-By: Claude Opus 4.5 Executed-By: beads/crew/dave Rig: beads Role: crew --- cmd/bd/init_test.go | 15 +++++++-- cmd/bd/test_repo_beads_guard_test.go | 11 +++++++ cmd/bd/worktree_daemon_test.go | 24 +++++++++++++++ internal/beads/beads_test.go | 36 ++++++++++++++++------ internal/storage/sqlite/migrations_test.go | 9 +++--- internal/syncbranch/syncbranch_test.go | 13 ++++++++ 6 files changed, 92 insertions(+), 16 deletions(-) diff --git a/cmd/bd/init_test.go b/cmd/bd/init_test.go index 3d1a0ee9..ae468c76 100644 --- a/cmd/bd/init_test.go +++ b/cmd/bd/init_test.go @@ -466,16 +466,27 @@ func TestInitNoDbMode(t *testing.T) { // Reset global state origDBPath := dbPath origNoDb := noDb - defer func() { + defer func() { dbPath = origDBPath noDb = origNoDb }() dbPath = "" noDb = false - + tmpDir := t.TempDir() t.Chdir(tmpDir) + // Set BEADS_DIR to prevent git repo detection from finding project's .beads + origBeadsDir := os.Getenv("BEADS_DIR") + os.Setenv("BEADS_DIR", filepath.Join(tmpDir, ".beads")) + defer func() { + if origBeadsDir != "" { + os.Setenv("BEADS_DIR", origBeadsDir) + } else { + os.Unsetenv("BEADS_DIR") + } + }() + // Initialize with --no-db flag rootCmd.SetArgs([]string{"init", "--no-db", "--no-daemon", "--prefix", "test", "--quiet"}) diff --git a/cmd/bd/test_repo_beads_guard_test.go b/cmd/bd/test_repo_beads_guard_test.go index e878114b..594c1dc0 100644 --- a/cmd/bd/test_repo_beads_guard_test.go +++ b/cmd/bd/test_repo_beads_guard_test.go @@ -29,6 +29,17 @@ func TestMain(m *testing.M) { } }() + // Clear BEADS_DIR to prevent tests from accidentally picking up the project's + // .beads directory via git repo detection when there's a redirect file. + // Each test that needs a .beads directory should set BEADS_DIR explicitly. + origBeadsDir := os.Getenv("BEADS_DIR") + os.Unsetenv("BEADS_DIR") + defer func() { + if origBeadsDir != "" { + os.Setenv("BEADS_DIR", origBeadsDir) + } + }() + if os.Getenv("BEADS_TEST_GUARD_DISABLE") != "" { os.Exit(m.Run()) } diff --git a/cmd/bd/worktree_daemon_test.go b/cmd/bd/worktree_daemon_test.go index 5887e0d3..b8c34885 100644 --- a/cmd/bd/worktree_daemon_test.go +++ b/cmd/bd/worktree_daemon_test.go @@ -86,6 +86,18 @@ func TestShouldDisableDaemonForWorktree(t *testing.T) { // Reset git caches after changing directory (required for IsWorktree to re-detect) git.ResetCaches() + // Set BEADS_DIR to the test's .beads directory to prevent + // git repo detection from finding the project's .beads + origBeadsDir := os.Getenv("BEADS_DIR") + os.Setenv("BEADS_DIR", mainDir+"/.beads") + defer func() { + if origBeadsDir != "" { + os.Setenv("BEADS_DIR", origBeadsDir) + } else { + os.Unsetenv("BEADS_DIR") + } + }() + // No sync-branch configured os.Unsetenv("BEADS_SYNC_BRANCH") @@ -217,6 +229,18 @@ func TestShouldAutoStartDaemonWorktreeIntegration(t *testing.T) { // Reset git caches after changing directory git.ResetCaches() + // Set BEADS_DIR to the test's .beads directory to prevent + // git repo detection from finding the project's .beads + origBeadsDir := os.Getenv("BEADS_DIR") + os.Setenv("BEADS_DIR", mainDir+"/.beads") + defer func() { + if origBeadsDir != "" { + os.Setenv("BEADS_DIR", origBeadsDir) + } else { + os.Unsetenv("BEADS_DIR") + } + }() + // Clear all daemon-related env vars os.Unsetenv("BEADS_NO_DAEMON") os.Unsetenv("BEADS_AUTO_START_DAEMON") diff --git a/internal/beads/beads_test.go b/internal/beads/beads_test.go index 91a5470e..7e1d7004 100644 --- a/internal/beads/beads_test.go +++ b/internal/beads/beads_test.go @@ -10,16 +10,25 @@ import ( ) func TestFindDatabasePathEnvVar(t *testing.T) { - // Save original env var - originalEnv := os.Getenv("BEADS_DB") + // Save original env vars + originalDB := os.Getenv("BEADS_DB") + originalDir := os.Getenv("BEADS_DIR") defer func() { - if originalEnv != "" { - _ = os.Setenv("BEADS_DB", originalEnv) + if originalDB != "" { + _ = os.Setenv("BEADS_DB", originalDB) } else { _ = os.Unsetenv("BEADS_DB") } + if originalDir != "" { + _ = os.Setenv("BEADS_DIR", originalDir) + } else { + _ = os.Unsetenv("BEADS_DIR") + } }() + // Clear BEADS_DIR to prevent it from interfering + _ = os.Unsetenv("BEADS_DIR") + // Set env var to a test path (platform-agnostic) testPath := filepath.Join("test", "path", "test.db") _ = os.Setenv("BEADS_DB", testPath) @@ -33,17 +42,23 @@ func TestFindDatabasePathEnvVar(t *testing.T) { } func TestFindDatabasePathInTree(t *testing.T) { - // Save original env var - originalEnv := os.Getenv("BEADS_DB") + // Save original env vars + originalDB := os.Getenv("BEADS_DB") + originalDir := os.Getenv("BEADS_DIR") defer func() { - if originalEnv != "" { - os.Setenv("BEADS_DB", originalEnv) + if originalDB != "" { + os.Setenv("BEADS_DB", originalDB) } else { os.Unsetenv("BEADS_DB") } + if originalDir != "" { + os.Setenv("BEADS_DIR", originalDir) + } else { + os.Unsetenv("BEADS_DIR") + } }() - // Clear env var + // Clear env vars os.Unsetenv("BEADS_DB") // Create temporary directory structure @@ -67,6 +82,9 @@ func TestFindDatabasePathInTree(t *testing.T) { } f.Close() + // Set BEADS_DIR to our test .beads directory to override git repo detection + os.Setenv("BEADS_DIR", beadsDir) + // Create a subdirectory and change to it subDir := filepath.Join(tmpDir, "sub", "nested") err = os.MkdirAll(subDir, 0o750) diff --git a/internal/storage/sqlite/migrations_test.go b/internal/storage/sqlite/migrations_test.go index 23a50814..5a340500 100644 --- a/internal/storage/sqlite/migrations_test.go +++ b/internal/storage/sqlite/migrations_test.go @@ -454,6 +454,7 @@ func TestMigrateContentHashColumn(t *testing.T) { } // Drop the column to simulate fresh migration + // Note: Schema must include owner column for GetIssue to work _, err = s.db.Exec(` CREATE TABLE issues_backup AS SELECT * FROM issues; DROP TABLE issues; @@ -471,8 +472,10 @@ func TestMigrateContentHashColumn(t *testing.T) { estimated_minutes INTEGER, created_at DATETIME NOT NULL, created_by TEXT DEFAULT '', + owner TEXT DEFAULT '', updated_at DATETIME NOT NULL, closed_at DATETIME, + closed_by_session TEXT DEFAULT '', external_ref TEXT, compaction_level INTEGER DEFAULT 0, compacted_at DATETIME, @@ -488,10 +491,6 @@ func TestMigrateContentHashColumn(t *testing.T) { ephemeral INTEGER DEFAULT 0, pinned INTEGER DEFAULT 0, is_template INTEGER DEFAULT 0, - replies_to TEXT DEFAULT '', - relates_to TEXT DEFAULT '', - duplicate_of TEXT DEFAULT '', - superseded_by TEXT DEFAULT '', await_type TEXT DEFAULT '', await_id TEXT DEFAULT '', timeout_ns INTEGER DEFAULT 0, @@ -511,7 +510,7 @@ func TestMigrateContentHashColumn(t *testing.T) { defer_until DATETIME, CHECK ((status = 'closed') = (closed_at IS NOT NULL)) ); - INSERT INTO issues SELECT id, title, description, design, acceptance_criteria, notes, status, priority, issue_type, assignee, estimated_minutes, created_at, '', updated_at, closed_at, external_ref, compaction_level, compacted_at, original_size, compacted_at_commit, source_repo, '', NULL, '', '', '', '', 0, 0, 0, '', '', '', '', '', '', 0, '', '', '', '', NULL, '', '', '', '', '', '', '', NULL, NULL FROM issues_backup; + INSERT INTO issues SELECT id, title, description, design, acceptance_criteria, notes, status, priority, issue_type, assignee, estimated_minutes, created_at, '', '', updated_at, closed_at, '', external_ref, compaction_level, compacted_at, original_size, compacted_at_commit, source_repo, '', NULL, '', '', '', '', 0, 0, 0, '', '', 0, '', '', '', '', NULL, '', '', '', '', '', '', '', NULL, NULL FROM issues_backup; DROP TABLE issues_backup; `) if err != nil { diff --git a/internal/syncbranch/syncbranch_test.go b/internal/syncbranch/syncbranch_test.go index 20e47126..07e49d29 100644 --- a/internal/syncbranch/syncbranch_test.go +++ b/internal/syncbranch/syncbranch_test.go @@ -3,6 +3,7 @@ package syncbranch import ( "context" "os" + "path/filepath" "strings" "testing" @@ -439,6 +440,18 @@ func TestIsConfiguredWithDB(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "test-no-beads-*") defer os.RemoveAll(tmpDir) + // Set BEADS_DIR to a nonexistent path to prevent git repo detection + // from finding the project's .beads directory + origBeadsDir := os.Getenv("BEADS_DIR") + os.Setenv("BEADS_DIR", filepath.Join(tmpDir, ".beads")) + defer func() { + if origBeadsDir != "" { + os.Setenv("BEADS_DIR", origBeadsDir) + } else { + os.Unsetenv("BEADS_DIR") + } + }() + origWd, _ := os.Getwd() os.Chdir(tmpDir) defer os.Chdir(origWd)