From 202a617bb5f8d6396dc20266bce1bc6a35f804f4 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Tue, 30 Dec 2025 18:19:53 -0800 Subject: [PATCH] test: add coverage for GH#807 sync.branch main/master rejection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add TestSet cases for main/master rejection in syncbranch_test.go - Add TestInitWithSyncBranch to verify --branch flag works - Add TestInitWithoutBranchFlag to verify no auto-detection (root cause) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- cmd/bd/init_test.go | 90 ++++++++++++++++++++++++++ internal/syncbranch/syncbranch_test.go | 30 ++++++++- 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/cmd/bd/init_test.go b/cmd/bd/init_test.go index e3de68a1..db0632b2 100644 --- a/cmd/bd/init_test.go +++ b/cmd/bd/init_test.go @@ -183,6 +183,96 @@ func TestInitCommand(t *testing.T) { // Note: Error case testing is omitted because the init command calls os.Exit() // on errors, which makes it difficult to test in a unit test context. +// GH#807: Rejection of main/master as sync branch is tested at unit level in +// internal/syncbranch/syncbranch_test.go (TestValidateSyncBranchName, TestSet). + +// TestInitWithSyncBranch verifies that --branch flag correctly sets sync.branch +// GH#807: Also verifies that valid sync branches work (rejection is tested at unit level) +func TestInitWithSyncBranch(t *testing.T) { + // Reset global state + origDBPath := dbPath + defer func() { dbPath = origDBPath }() + dbPath = "" + + // Reset Cobra flags + initCmd.Flags().Set("branch", "") + + tmpDir := t.TempDir() + t.Chdir(tmpDir) + + // Initialize git repo first (needed for sync branch to make sense) + if err := runCommandInDir(tmpDir, "git", "init", "--initial-branch=dev"); err != nil { + t.Fatalf("Failed to init git: %v", err) + } + + // Run bd init with --branch flag + rootCmd.SetArgs([]string{"init", "--prefix", "test", "--branch", "beads-sync", "--quiet"}) + if err := rootCmd.Execute(); err != nil { + t.Fatalf("Init with --branch failed: %v", err) + } + + // Verify database was created + dbFilePath := filepath.Join(tmpDir, ".beads", "beads.db") + store, err := openExistingTestDB(t, dbFilePath) + if err != nil { + t.Fatalf("Failed to open database: %v", err) + } + defer store.Close() + + // Verify sync.branch was set correctly + ctx := context.Background() + syncBranch, err := store.GetConfig(ctx, "sync.branch") + if err != nil { + t.Fatalf("Failed to get sync.branch from database: %v", err) + } + if syncBranch != "beads-sync" { + t.Errorf("Expected sync.branch 'beads-sync', got %q", syncBranch) + } +} + +// TestInitWithoutBranchFlag verifies that sync.branch is NOT auto-set when --branch is omitted +// GH#807: This was the root cause - init was auto-detecting current branch (e.g., main) +func TestInitWithoutBranchFlag(t *testing.T) { + // Reset global state + origDBPath := dbPath + defer func() { dbPath = origDBPath }() + dbPath = "" + + // Reset Cobra flags + initCmd.Flags().Set("branch", "") + + tmpDir := t.TempDir() + t.Chdir(tmpDir) + + // Initialize git repo on 'main' branch + if err := runCommandInDir(tmpDir, "git", "init", "--initial-branch=main"); err != nil { + t.Fatalf("Failed to init git: %v", err) + } + + // Run bd init WITHOUT --branch flag + rootCmd.SetArgs([]string{"init", "--prefix", "test", "--quiet"}) + if err := rootCmd.Execute(); err != nil { + t.Fatalf("Init failed: %v", err) + } + + // Verify database was created + dbFilePath := filepath.Join(tmpDir, ".beads", "beads.db") + store, err := openExistingTestDB(t, dbFilePath) + if err != nil { + t.Fatalf("Failed to open database: %v", err) + } + defer store.Close() + + // Verify sync.branch was NOT set (empty = use current branch directly) + ctx := context.Background() + syncBranch, err := store.GetConfig(ctx, "sync.branch") + if err != nil { + t.Fatalf("Failed to get sync.branch from database: %v", err) + } + if syncBranch != "" { + t.Errorf("Expected sync.branch to be empty (not auto-detected), got %q", syncBranch) + } +} func TestInitAlreadyInitialized(t *testing.T) { // Reset global state diff --git a/internal/syncbranch/syncbranch_test.go b/internal/syncbranch/syncbranch_test.go index 67fff330..5a53d550 100644 --- a/internal/syncbranch/syncbranch_test.go +++ b/internal/syncbranch/syncbranch_test.go @@ -3,6 +3,7 @@ package syncbranch import ( "context" "os" + "strings" "testing" "github.com/steveyegge/beads/internal/storage/sqlite" @@ -216,12 +217,39 @@ func TestSet(t *testing.T) { t.Run("rejects invalid branch name", func(t *testing.T) { store := newTestStore(t) defer store.Close() - + err := Set(ctx, store, "invalid..branch") if err == nil { t.Error("Set() expected error for invalid branch name, got nil") } }) + + // GH#807: Verify Set() rejects main/master (not just ValidateSyncBranchName) + t.Run("rejects main as sync branch", func(t *testing.T) { + store := newTestStore(t) + defer store.Close() + + err := Set(ctx, store, "main") + if err == nil { + t.Error("Set() expected error for 'main', got nil") + } + if err != nil && !strings.Contains(err.Error(), "cannot use 'main'") { + t.Errorf("Set() error should mention 'cannot use main', got: %v", err) + } + }) + + t.Run("rejects master as sync branch", func(t *testing.T) { + store := newTestStore(t) + defer store.Close() + + err := Set(ctx, store, "master") + if err == nil { + t.Error("Set() expected error for 'master', got nil") + } + if err != nil && !strings.Contains(err.Error(), "cannot use 'master'") { + t.Errorf("Set() error should mention 'cannot use master', got: %v", err) + } + }) } func TestUnset(t *testing.T) {