Add comprehensive tests for merge driver auto-config in bd init

- Test auto-install in quiet mode (bd init --quiet)
- Test --skip-merge-driver flag
- Test detection of already-installed merge driver (no duplication)
- Test append to existing .gitattributes (preserve content)
- Test non-git repo gracefully skips merge driver
- Test git config has correct merge.beads.driver settings

Fixes: bd-csvy
Amp-Thread-ID: https://ampcode.com/threads/T-b237be26-6dd8-4d4e-9b9d-b460ace8ce72
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-11-06 15:57:51 -08:00
parent 725e43d49c
commit 6424ebdada
2 changed files with 189 additions and 1 deletions

View File

@@ -181,7 +181,7 @@
{"id":"bd-cdf7","content_hash":"50ba3c5bc3a0f06ebd20dd9b373086e0ecd218eea56a9edbef321f4e042115a4","title":"Add tests for DetectCycles to improve coverage from 29.6%","description":"DetectCycles currently has 29.6% coverage. Need comprehensive tests for:\n- Simple cycles (A-\u003eB-\u003eA)\n- Complex multi-node cycles\n- Acyclic graphs (should not detect cycles)\n- Self-loops\n- Multiple independent cycles\n- Edge cases (empty graph, single node)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-01T22:40:58.977156-07:00","updated_at":"2025-11-01T22:52:02.243223-07:00","closed_at":"2025-11-01T22:52:02.243223-07:00","source_repo":"."}
{"id":"bd-ce37850f","content_hash":"3ef2872c3fcb1e5acc90d33fd5a76291742cbcecfbf697b611aa5b4d8ce80078","title":"Add embedding generation for duplicate detection","description":"Use embeddings for scalable duplicate detection.\n\nModel: text-embedding-3-small (OpenAI) or all-MiniLM-L6-v2 (local)\nStorage: SQLite vector extension or in-memory\nCost: ~/bin/bash.0002 per 100 issues\n\nMuch cheaper than LLM comparisons for large databases.\n\nFiles: internal/embeddings/ (new package)","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-28T14:48:29.072913-07:00","updated_at":"2025-10-30T17:12:58.219921-07:00","source_repo":"."}
{"id":"bd-chsc","content_hash":"ea167029efad3c506e42dfc20748a6ada0914aa93cb04caa14a48ca223386365","title":"Test lowercase p0","description":"","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-05T12:58:41.457875-08:00","updated_at":"2025-11-05T12:58:44.721486-08:00","closed_at":"2025-11-05T12:58:44.721486-08:00","source_repo":"."}
{"id":"bd-csvy","content_hash":"3451bbf916d00dae7ccca2d93e5b43b05f80654537cee27e989c68208e43a38c","title":"Add tests for merge driver auto-config in bd init","description":"Add comprehensive tests for the merge driver auto-configuration functionality in `bd init`.\n\n**Test cases needed:**\n- Auto-install in quiet mode\n- Skip with --skip-merge-driver flag\n- Detect already-installed merge driver\n- Append to existing .gitattributes\n- Interactive prompt behavior (if feasible)\n\n**File:** `cmd/bd/init_test.go`","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-05T19:27:04.133078-08:00","updated_at":"2025-11-05T19:27:04.133078-08:00","source_repo":".","dependencies":[{"issue_id":"bd-csvy","depends_on_id":"bd-32nm","type":"discovered-from","created_at":"2025-11-05T19:27:04.134299-08:00","created_by":"daemon"}]}
{"id":"bd-csvy","content_hash":"3451bbf916d00dae7ccca2d93e5b43b05f80654537cee27e989c68208e43a38c","title":"Add tests for merge driver auto-config in bd init","description":"Add comprehensive tests for the merge driver auto-configuration functionality in `bd init`.\n\n**Test cases needed:**\n- Auto-install in quiet mode\n- Skip with --skip-merge-driver flag\n- Detect already-installed merge driver\n- Append to existing .gitattributes\n- Interactive prompt behavior (if feasible)\n\n**File:** `cmd/bd/init_test.go`","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-05T19:27:04.133078-08:00","updated_at":"2025-11-06T15:56:36.014814-08:00","closed_at":"2025-11-06T15:56:36.014814-08:00","source_repo":".","dependencies":[{"issue_id":"bd-csvy","depends_on_id":"bd-32nm","type":"discovered-from","created_at":"2025-11-05T19:27:04.134299-08:00","created_by":"daemon"}]}
{"id":"bd-d19a","content_hash":"5ff9ba5e70c3e3eeaff40887421797e30dfb75e56e97fcaaf3f3d32332f22aa2","title":"Fix import failure on missing parent issues","description":"Import process fails atomically when JSONL references deleted parent issues. Implement hybrid solution: topological sorting + parent resurrection to handle deleted parents gracefully while maintaining referential integrity. See docs/import-bug-analysis-bd-3xq.md for full analysis.","status":"closed","priority":0,"issue_type":"epic","created_at":"2025-11-04T12:31:30.994759-08:00","updated_at":"2025-11-04T17:11:40.563983-08:00","closed_at":"2025-11-04T17:11:40.563983-08:00","source_repo":"."}
{"id":"bd-d33c","content_hash":"d0820d5dd6ea4ab198e013861d3d7d01da701daa8ab8ec59ad5ef855e6f83b2b","title":"Separate process/lock/PID concerns into process.go","description":"Create internal/daemonrunner/process.go with: acquireDaemonLock, PID file read/write, stopDaemon, isDaemonRunning, getPIDFilePath, socket path helpers, version check.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-01T11:41:14.871122-07:00","updated_at":"2025-11-01T23:43:55.66159-07:00","closed_at":"2025-11-01T23:43:55.66159-07:00","source_repo":"."}
{"id":"bd-d355a07d","content_hash":"b4f98403e209eadf33dd4913660c1538fd922c89339a9ed034ef504aac358662","title":"Import validation falsely reports data loss on collision resolution","description":"## Problem\n\nPost-import validation reports 'data loss detected!' when import count reduces due to legitimate collision resolution.\n\n## Example\n\n```\nImport complete: 1 created, 8 updated, 142 unchanged, 19 skipped, 1 issues remapped\nPost-import validation failed: import reduced issue count: 165 → 164 (data loss detected!)\n```\n\nThis was actually successful collision resolution (bd-70419816 duplicated → remapped to-70419816), not data loss.\n\n## Impact\n\n- False alarms waste investigation time\n- Undermines confidence in import validation\n- Confuses users/agents about sync health\n\n## Solution\n\nImprove validation to distinguish:\n- Collision-resolution merges (expected count reduction)\n- Actual data loss (unexpected disappearance)\n\nTrack remapped issue count and adjust expected post-import count accordingly.","status":"open","priority":2,"issue_type":"bug","created_at":"2025-10-29T23:15:00.815227-07:00","updated_at":"2025-10-31T19:38:09.19996-07:00","source_repo":"."}

View File

@@ -599,4 +599,192 @@ func TestInitMergeDriverAutoConfiguration(t *testing.T) {
t.Error(".beads directory should be created even without git")
}
})
t.Run("detect already-installed merge driver", func(t *testing.T) {
// Reset global state
origDBPath := dbPath
defer func() { dbPath = origDBPath }()
dbPath = ""
tmpDir := t.TempDir()
originalWd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
defer os.Chdir(originalWd)
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to change to temp directory: %v", err)
}
// Initialize git repo
if err := runCommandInDir(tmpDir, "git", "init"); err != nil {
t.Fatalf("Failed to init git: %v", err)
}
// Pre-configure merge driver manually
if err := runCommandInDir(tmpDir, "git", "config", "merge.beads.driver", "bd merge %A %O %L %R"); err != nil {
t.Fatalf("Failed to set git config: %v", err)
}
// Create .gitattributes with merge driver
gitattrsPath := filepath.Join(tmpDir, ".gitattributes")
initialContent := "# Existing config\n.beads/beads.jsonl merge=beads\n"
if err := os.WriteFile(gitattrsPath, []byte(initialContent), 0644); err != nil {
t.Fatalf("Failed to create .gitattributes: %v", err)
}
// Run bd init - should detect existing config
rootCmd.SetArgs([]string{"init", "--prefix", "test", "--quiet"})
if err := rootCmd.Execute(); err != nil {
t.Fatalf("Init failed: %v", err)
}
// Verify git config still exists (not duplicated)
output, err := runCommandInDirWithOutput(tmpDir, "git", "config", "merge.beads.driver")
if err != nil {
t.Fatalf("Git config should still be set: %v", err)
}
if !strings.Contains(output, "bd merge") {
t.Errorf("Expected merge driver to contain 'bd merge', got: %s", output)
}
// Verify .gitattributes wasn't duplicated
content, err := os.ReadFile(gitattrsPath)
if err != nil {
t.Fatalf("Failed to read .gitattributes: %v", err)
}
contentStr := string(content)
// Count occurrences - should only appear once
count := strings.Count(contentStr, ".beads/beads.jsonl merge=beads")
if count != 1 {
t.Errorf("Expected .gitattributes to contain merge config exactly once, found %d times", count)
}
// Should still have the comment
if !strings.Contains(contentStr, "# Existing config") {
t.Error(".gitattributes should preserve existing content")
}
})
t.Run("append to existing .gitattributes", func(t *testing.T) {
// Reset global state
origDBPath := dbPath
defer func() { dbPath = origDBPath }()
dbPath = ""
// Reset Cobra flags
initCmd.Flags().Set("skip-merge-driver", "false")
tmpDir := t.TempDir()
originalWd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
defer os.Chdir(originalWd)
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to change to temp directory: %v", err)
}
// Initialize git repo
if err := runCommandInDir(tmpDir, "git", "init"); err != nil {
t.Fatalf("Failed to init git: %v", err)
}
// Create .gitattributes with existing content (no newline at end)
gitattrsPath := filepath.Join(tmpDir, ".gitattributes")
existingContent := "*.txt text\n*.jpg binary"
if err := os.WriteFile(gitattrsPath, []byte(existingContent), 0644); err != nil {
t.Fatalf("Failed to create .gitattributes: %v", err)
}
// Run bd init
rootCmd.SetArgs([]string{"init", "--prefix", "test", "--quiet"})
if err := rootCmd.Execute(); err != nil {
t.Fatalf("Init failed: %v", err)
}
// Verify .gitattributes was appended to, not overwritten
content, err := os.ReadFile(gitattrsPath)
if err != nil {
t.Fatalf("Failed to read .gitattributes: %v", err)
}
contentStr := string(content)
// Should contain original content
if !strings.Contains(contentStr, "*.txt text") {
t.Error(".gitattributes should preserve original content")
}
if !strings.Contains(contentStr, "*.jpg binary") {
t.Error(".gitattributes should preserve original content")
}
// Should contain beads config
if !strings.Contains(contentStr, ".beads/beads.jsonl merge=beads") {
t.Error(".gitattributes should contain beads merge config")
}
// Beads config should come after existing content
txtIdx := strings.Index(contentStr, "*.txt")
beadsIdx := strings.Index(contentStr, ".beads/beads.jsonl")
if txtIdx >= beadsIdx {
t.Error("Beads config should be appended after existing content")
}
})
t.Run("verify git config has correct settings", func(t *testing.T) {
// Reset global state
origDBPath := dbPath
defer func() { dbPath = origDBPath }()
dbPath = ""
// Reset Cobra flags
initCmd.Flags().Set("skip-merge-driver", "false")
tmpDir := t.TempDir()
originalWd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
defer os.Chdir(originalWd)
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to change to temp directory: %v", err)
}
// Initialize git repo
if err := runCommandInDir(tmpDir, "git", "init"); err != nil {
t.Fatalf("Failed to init git: %v", err)
}
// Run bd init
rootCmd.SetArgs([]string{"init", "--prefix", "test", "--quiet"})
if err := rootCmd.Execute(); err != nil {
t.Fatalf("Init failed: %v", err)
}
// Verify merge.beads.driver is set correctly
driver, err := runCommandInDirWithOutput(tmpDir, "git", "config", "merge.beads.driver")
if err != nil {
t.Fatalf("Failed to get merge.beads.driver: %v", err)
}
driver = strings.TrimSpace(driver)
expected := "bd merge %A %O %L %R"
if driver != expected {
t.Errorf("Expected merge.beads.driver to be %q, got %q", expected, driver)
}
// Verify merge.beads.name is set
name, err := runCommandInDirWithOutput(tmpDir, "git", "config", "merge.beads.name")
if err != nil {
t.Fatalf("Failed to get merge.beads.name: %v", err)
}
name = strings.TrimSpace(name)
if !strings.Contains(name, "bd") {
t.Errorf("Expected merge.beads.name to contain 'bd', got %q", name)
}
})
}