* 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.
631 lines
17 KiB
Go
631 lines
17 KiB
Go
package fix
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// setupTestWorkspace creates a temporary directory with a .beads directory
|
|
func setupTestWorkspace(t *testing.T) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
beadsDir := filepath.Join(dir, ".beads")
|
|
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
|
t.Fatalf("failed to create .beads directory: %v", err)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
// setupTestGitRepo creates a temporary git repository with a .beads directory
|
|
func setupTestGitRepo(t *testing.T) string {
|
|
t.Helper()
|
|
dir := setupTestWorkspace(t)
|
|
|
|
// Initialize git repo
|
|
cmd := exec.Command("git", "init")
|
|
cmd.Dir = dir
|
|
if err := cmd.Run(); err != nil {
|
|
t.Fatalf("failed to init git repo: %v", err)
|
|
}
|
|
|
|
// Configure git user for commits
|
|
cmd = exec.Command("git", "config", "user.email", "test@test.com")
|
|
cmd.Dir = dir
|
|
_ = cmd.Run()
|
|
|
|
cmd = exec.Command("git", "config", "user.name", "Test User")
|
|
cmd.Dir = dir
|
|
_ = cmd.Run()
|
|
|
|
return dir
|
|
}
|
|
|
|
// runGit runs a git command and returns output
|
|
func runGit(t *testing.T, dir string, args ...string) string {
|
|
t.Helper()
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = dir
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Logf("git %v: %s", args, output)
|
|
}
|
|
return string(output)
|
|
}
|
|
|
|
// TestValidateBeadsWorkspace tests the workspace validation function
|
|
func TestValidateBeadsWorkspace(t *testing.T) {
|
|
t.Run("invalid path", func(t *testing.T) {
|
|
err := validateBeadsWorkspace("/nonexistent/path/that/does/not/exist")
|
|
if err == nil {
|
|
t.Error("expected error for nonexistent path")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestGitHooks_Validation tests GitHooks validation
|
|
func TestGitHooks_Validation(t *testing.T) {
|
|
t.Run("not a git repository", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
err := GitHooks(dir)
|
|
if err == nil {
|
|
t.Error("expected error for non-git repository")
|
|
}
|
|
if err.Error() != "not a git repository" {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestMergeDriver_Validation tests MergeDriver validation
|
|
func TestMergeDriver_Validation(t *testing.T) {
|
|
t.Run("sets correct merge driver config", func(t *testing.T) {
|
|
dir := setupTestGitRepo(t)
|
|
|
|
err := MergeDriver(dir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Verify the config was set
|
|
cmd := exec.Command("git", "config", "merge.beads.driver")
|
|
cmd.Dir = dir
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
t.Fatalf("failed to get git config: %v", err)
|
|
}
|
|
|
|
expected := "bd merge %A %O %A %B\n"
|
|
if string(output) != expected {
|
|
t.Errorf("expected %q, got %q", expected, string(output))
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestDaemon_Validation tests Daemon validation
|
|
func TestDaemon_Validation(t *testing.T) {
|
|
t.Run("no socket - nothing to do", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
err := Daemon(dir)
|
|
if err != nil {
|
|
t.Errorf("expected no error when no socket exists, got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestDBJSONLSync_Validation tests DBJSONLSync validation
|
|
func TestDBJSONLSync_Validation(t *testing.T) {
|
|
t.Run("no database - nothing to do", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
err := DBJSONLSync(dir)
|
|
if err != nil {
|
|
t.Errorf("expected no error when no database exists, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("no JSONL - nothing to do", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
// Create a database file
|
|
dbPath := filepath.Join(dir, ".beads", "beads.db")
|
|
if err := os.WriteFile(dbPath, []byte("test"), 0600); err != nil {
|
|
t.Fatalf("failed to create test db: %v", err)
|
|
}
|
|
err := DBJSONLSync(dir)
|
|
if err != nil {
|
|
t.Errorf("expected no error when no JSONL exists, got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestSyncBranchConfig_Validation tests SyncBranchConfig validation
|
|
func TestSyncBranchConfig_Validation(t *testing.T) {
|
|
t.Run("not a git repository", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
err := SyncBranchConfig(dir)
|
|
if err == nil {
|
|
t.Error("expected error for non-git repository")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestSyncBranchHealth_Validation tests SyncBranchHealth validation
|
|
func TestSyncBranchHealth_Validation(t *testing.T) {
|
|
t.Run("no main or master branch", func(t *testing.T) {
|
|
dir := setupTestGitRepo(t)
|
|
// Create a commit on a different branch
|
|
cmd := exec.Command("git", "checkout", "-b", "other")
|
|
cmd.Dir = dir
|
|
_ = cmd.Run()
|
|
|
|
// Create a file and commit
|
|
testFile := filepath.Join(dir, "test.txt")
|
|
if err := os.WriteFile(testFile, []byte("test"), 0600); err != nil {
|
|
t.Fatalf("failed to create test file: %v", err)
|
|
}
|
|
cmd = exec.Command("git", "add", "test.txt")
|
|
cmd.Dir = dir
|
|
_ = cmd.Run()
|
|
cmd = exec.Command("git", "commit", "-m", "initial")
|
|
cmd.Dir = dir
|
|
_ = cmd.Run()
|
|
|
|
err := SyncBranchHealth(dir, "beads-sync")
|
|
if err == nil {
|
|
t.Error("expected error when neither main nor master exists")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestUntrackedJSONL_Validation tests UntrackedJSONL validation
|
|
func TestUntrackedJSONL_Validation(t *testing.T) {
|
|
t.Run("not a git repository", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
err := UntrackedJSONL(dir)
|
|
if err == nil {
|
|
t.Error("expected error for non-git repository")
|
|
}
|
|
})
|
|
|
|
t.Run("no untracked files", func(t *testing.T) {
|
|
dir := setupTestGitRepo(t)
|
|
err := UntrackedJSONL(dir)
|
|
// Should succeed with no untracked files
|
|
if err != nil {
|
|
t.Errorf("expected no error, got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestMigrateTombstones tests the MigrateTombstones function
|
|
func TestMigrateTombstones(t *testing.T) {
|
|
t.Run("no deletions.jsonl - nothing to migrate", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
err := MigrateTombstones(dir)
|
|
if err != nil {
|
|
t.Errorf("expected no error when no deletions.jsonl exists, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("empty deletions.jsonl", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
deletionsPath := filepath.Join(dir, ".beads", "deletions.jsonl")
|
|
if err := os.WriteFile(deletionsPath, []byte(""), 0600); err != nil {
|
|
t.Fatalf("failed to create deletions.jsonl: %v", err)
|
|
}
|
|
err := MigrateTombstones(dir)
|
|
if err != nil {
|
|
t.Errorf("expected no error for empty deletions.jsonl, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("migrates deletions to tombstones", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
|
|
// Create deletions.jsonl with a deletion record
|
|
deletionsPath := filepath.Join(dir, ".beads", "deletions.jsonl")
|
|
deletion := legacyDeletionRecord{
|
|
ID: "test-123",
|
|
Timestamp: time.Now(),
|
|
Actor: "testuser",
|
|
Reason: "test deletion",
|
|
}
|
|
data, _ := json.Marshal(deletion)
|
|
if err := os.WriteFile(deletionsPath, append(data, '\n'), 0600); err != nil {
|
|
t.Fatalf("failed to create deletions.jsonl: %v", err)
|
|
}
|
|
|
|
// Create empty issues.jsonl
|
|
jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
|
|
if err := os.WriteFile(jsonlPath, []byte(""), 0600); err != nil {
|
|
t.Fatalf("failed to create issues.jsonl: %v", err)
|
|
}
|
|
|
|
err := MigrateTombstones(dir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Verify deletions.jsonl was renamed
|
|
if _, err := os.Stat(deletionsPath); !os.IsNotExist(err) {
|
|
t.Error("deletions.jsonl should have been renamed")
|
|
}
|
|
migratedPath := deletionsPath + ".migrated"
|
|
if _, err := os.Stat(migratedPath); os.IsNotExist(err) {
|
|
t.Error("deletions.jsonl.migrated should exist")
|
|
}
|
|
|
|
// Verify tombstone was written to issues.jsonl
|
|
content, err := os.ReadFile(jsonlPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read issues.jsonl: %v", err)
|
|
}
|
|
if len(content) == 0 {
|
|
t.Error("expected tombstone to be written to issues.jsonl")
|
|
}
|
|
|
|
// Verify the tombstone content
|
|
var issue struct {
|
|
ID string `json:"id"`
|
|
Status string `json:"status"`
|
|
}
|
|
if err := json.Unmarshal(content[:len(content)-1], &issue); err != nil {
|
|
t.Fatalf("failed to parse tombstone: %v", err)
|
|
}
|
|
if issue.ID != "test-123" {
|
|
t.Errorf("expected ID test-123, got %s", issue.ID)
|
|
}
|
|
if issue.Status != "tombstone" {
|
|
t.Errorf("expected status tombstone, got %s", issue.Status)
|
|
}
|
|
})
|
|
|
|
t.Run("skips already existing tombstones", func(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
|
|
// Create deletions.jsonl with a deletion record
|
|
deletionsPath := filepath.Join(dir, ".beads", "deletions.jsonl")
|
|
deletion := legacyDeletionRecord{
|
|
ID: "test-123",
|
|
Timestamp: time.Now(),
|
|
Actor: "testuser",
|
|
}
|
|
data, _ := json.Marshal(deletion)
|
|
if err := os.WriteFile(deletionsPath, append(data, '\n'), 0600); err != nil {
|
|
t.Fatalf("failed to create deletions.jsonl: %v", err)
|
|
}
|
|
|
|
// Create issues.jsonl with an existing tombstone for the same ID
|
|
jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
|
|
existingTombstone := map[string]interface{}{
|
|
"id": "test-123",
|
|
"status": "tombstone",
|
|
}
|
|
existingData, _ := json.Marshal(existingTombstone)
|
|
if err := os.WriteFile(jsonlPath, append(existingData, '\n'), 0600); err != nil {
|
|
t.Fatalf("failed to create issues.jsonl: %v", err)
|
|
}
|
|
|
|
originalContent, _ := os.ReadFile(jsonlPath)
|
|
|
|
err := MigrateTombstones(dir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Verify issues.jsonl was not modified (tombstone already exists)
|
|
newContent, _ := os.ReadFile(jsonlPath)
|
|
if string(newContent) != string(originalContent) {
|
|
t.Error("issues.jsonl should not have been modified when tombstone already exists")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestLoadLegacyDeletions tests the loadLegacyDeletions helper
|
|
func TestLoadLegacyDeletions(t *testing.T) {
|
|
t.Run("nonexistent file returns empty map", func(t *testing.T) {
|
|
records, err := loadLegacyDeletions("/nonexistent/path")
|
|
if err != nil {
|
|
t.Errorf("expected no error, got: %v", err)
|
|
}
|
|
if len(records) != 0 {
|
|
t.Errorf("expected empty map, got %d records", len(records))
|
|
}
|
|
})
|
|
|
|
t.Run("parses valid deletions", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "deletions.jsonl")
|
|
|
|
deletion := legacyDeletionRecord{
|
|
ID: "test-abc",
|
|
Timestamp: time.Now(),
|
|
Actor: "user",
|
|
Reason: "testing",
|
|
}
|
|
data, _ := json.Marshal(deletion)
|
|
if err := os.WriteFile(path, append(data, '\n'), 0600); err != nil {
|
|
t.Fatalf("failed to write file: %v", err)
|
|
}
|
|
|
|
records, err := loadLegacyDeletions(path)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(records) != 1 {
|
|
t.Fatalf("expected 1 record, got %d", len(records))
|
|
}
|
|
if records["test-abc"].Actor != "user" {
|
|
t.Errorf("expected actor 'user', got %s", records["test-abc"].Actor)
|
|
}
|
|
})
|
|
|
|
t.Run("skips invalid lines", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "deletions.jsonl")
|
|
|
|
content := `{"id":"valid-1","ts":"2024-01-01T00:00:00Z","by":"user"}
|
|
invalid json line
|
|
{"id":"valid-2","ts":"2024-01-01T00:00:00Z","by":"user"}
|
|
`
|
|
if err := os.WriteFile(path, []byte(content), 0600); err != nil {
|
|
t.Fatalf("failed to write file: %v", err)
|
|
}
|
|
|
|
records, err := loadLegacyDeletions(path)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(records) != 2 {
|
|
t.Fatalf("expected 2 valid records, got %d", len(records))
|
|
}
|
|
})
|
|
|
|
t.Run("skips records without ID", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "deletions.jsonl")
|
|
|
|
content := `{"id":"valid-1","ts":"2024-01-01T00:00:00Z","by":"user"}
|
|
{"ts":"2024-01-01T00:00:00Z","by":"user"}
|
|
`
|
|
if err := os.WriteFile(path, []byte(content), 0600); err != nil {
|
|
t.Fatalf("failed to write file: %v", err)
|
|
}
|
|
|
|
records, err := loadLegacyDeletions(path)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(records) != 1 {
|
|
t.Fatalf("expected 1 valid record, got %d", len(records))
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestConvertLegacyDeletionToTombstone tests tombstone conversion
|
|
func TestConvertLegacyDeletionToTombstone(t *testing.T) {
|
|
t.Run("converts with all fields", func(t *testing.T) {
|
|
ts := time.Now()
|
|
record := legacyDeletionRecord{
|
|
ID: "test-xyz",
|
|
Timestamp: ts,
|
|
Actor: "admin",
|
|
Reason: "cleanup",
|
|
}
|
|
|
|
tombstone := convertLegacyDeletionToTombstone(record)
|
|
|
|
if tombstone.ID != "test-xyz" {
|
|
t.Errorf("expected ID test-xyz, got %s", tombstone.ID)
|
|
}
|
|
if tombstone.Status != "tombstone" {
|
|
t.Errorf("expected status tombstone, got %s", tombstone.Status)
|
|
}
|
|
if tombstone.DeletedBy != "admin" {
|
|
t.Errorf("expected DeletedBy admin, got %s", tombstone.DeletedBy)
|
|
}
|
|
if tombstone.DeleteReason != "cleanup" {
|
|
t.Errorf("expected DeleteReason cleanup, got %s", tombstone.DeleteReason)
|
|
}
|
|
if tombstone.DeletedAt == nil {
|
|
t.Error("expected DeletedAt to be set")
|
|
}
|
|
})
|
|
|
|
t.Run("handles zero timestamp", func(t *testing.T) {
|
|
record := legacyDeletionRecord{
|
|
ID: "test-zero",
|
|
Actor: "user",
|
|
}
|
|
|
|
tombstone := convertLegacyDeletionToTombstone(record)
|
|
|
|
if tombstone.DeletedAt == nil {
|
|
t.Error("expected DeletedAt to be set even with zero timestamp")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestFindJSONLPath tests the findJSONLPath helper
|
|
func TestFindJSONLPath(t *testing.T) {
|
|
t.Run("returns empty for no JSONL", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := findJSONLPath(dir)
|
|
if path != "" {
|
|
t.Errorf("expected empty path, got %s", path)
|
|
}
|
|
})
|
|
|
|
t.Run("finds issues.jsonl", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
jsonlPath := filepath.Join(dir, "issues.jsonl")
|
|
if err := os.WriteFile(jsonlPath, []byte("{}"), 0600); err != nil {
|
|
t.Fatalf("failed to create file: %v", err)
|
|
}
|
|
|
|
path := findJSONLPath(dir)
|
|
if path != jsonlPath {
|
|
t.Errorf("expected %s, got %s", jsonlPath, path)
|
|
}
|
|
})
|
|
|
|
t.Run("finds beads.jsonl as fallback", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
jsonlPath := filepath.Join(dir, "beads.jsonl")
|
|
if err := os.WriteFile(jsonlPath, []byte("{}"), 0600); err != nil {
|
|
t.Fatalf("failed to create file: %v", err)
|
|
}
|
|
|
|
path := findJSONLPath(dir)
|
|
if path != jsonlPath {
|
|
t.Errorf("expected %s, got %s", jsonlPath, path)
|
|
}
|
|
})
|
|
|
|
t.Run("prefers issues.jsonl over beads.jsonl", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
issuesPath := filepath.Join(dir, "issues.jsonl")
|
|
beadsPath := filepath.Join(dir, "beads.jsonl")
|
|
if err := os.WriteFile(issuesPath, []byte("{}"), 0600); err != nil {
|
|
t.Fatalf("failed to create issues.jsonl: %v", err)
|
|
}
|
|
if err := os.WriteFile(beadsPath, []byte("{}"), 0600); err != nil {
|
|
t.Fatalf("failed to create beads.jsonl: %v", err)
|
|
}
|
|
|
|
path := findJSONLPath(dir)
|
|
if path != issuesPath {
|
|
t.Errorf("expected %s, got %s", issuesPath, path)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestIsWithinWorkspace tests the isWithinWorkspace helper
|
|
func TestIsWithinWorkspace(t *testing.T) {
|
|
root := t.TempDir()
|
|
|
|
tests := []struct {
|
|
name string
|
|
candidate string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "same directory",
|
|
candidate: root,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "subdirectory",
|
|
candidate: filepath.Join(root, "subdir"),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "nested subdirectory",
|
|
candidate: filepath.Join(root, "sub", "dir", "nested"),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "parent directory",
|
|
candidate: filepath.Dir(root),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "sibling directory",
|
|
candidate: filepath.Join(filepath.Dir(root), "sibling"),
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := isWithinWorkspace(root, tt.candidate)
|
|
if got != tt.want {
|
|
t.Errorf("isWithinWorkspace(%q, %q) = %v, want %v", root, tt.candidate, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
// TestDBJSONLSync_MissingDatabase tests DBJSONLSync when database doesn't exist
|
|
func TestDBJSONLSync_MissingDatabase(t *testing.T) {
|
|
dir := setupTestWorkspace(t)
|
|
beadsDir := filepath.Join(dir, ".beads")
|
|
|
|
// Create only JSONL file
|
|
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
|
|
issue := map[string]interface{}{
|
|
"id": "test-no-db",
|
|
"title": "No DB Test",
|
|
"status": "open",
|
|
}
|
|
data, _ := json.Marshal(issue)
|
|
if err := os.WriteFile(jsonlPath, append(data, '\n'), 0600); err != nil {
|
|
t.Fatalf("failed to create jsonl: %v", err)
|
|
}
|
|
|
|
// Should return without error since there's nothing to sync
|
|
err := DBJSONLSync(dir)
|
|
if err != nil {
|
|
t.Errorf("expected no error when database doesn't exist, got: %v", err)
|
|
}
|
|
}
|
|
|
|
|
|
// TestSyncBranchConfig_BranchDoesNotExist tests fixing config when branch doesn't exist
|
|
func TestSyncBranchConfig_BranchDoesNotExist(t *testing.T) {
|
|
// Skip if running as test binary (can't execute bd subcommands)
|
|
skipIfTestBinary(t)
|
|
|
|
dir := setupTestGitRepo(t)
|
|
|
|
// Try to run fix without any commits (no branch exists yet)
|
|
err := SyncBranchConfig(dir)
|
|
if err == nil {
|
|
t.Error("expected error when no branch exists")
|
|
}
|
|
if err != nil && !strings.Contains(err.Error(), "failed to get current branch") {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestSyncBranchConfig_InvalidRemoteURL tests fix behavior with invalid remote
|
|
func TestSyncBranchConfig_InvalidRemoteURL(t *testing.T) {
|
|
// Skip if running as test binary (can't execute bd subcommands)
|
|
skipIfTestBinary(t)
|
|
|
|
dir := setupTestGitRepo(t)
|
|
|
|
// Create initial commit
|
|
testFile := filepath.Join(dir, "test.txt")
|
|
if err := os.WriteFile(testFile, []byte("test"), 0600); err != nil {
|
|
t.Fatalf("failed to create test file: %v", err)
|
|
}
|
|
runGit(t, dir, "add", "test.txt")
|
|
runGit(t, dir, "commit", "-m", "initial commit")
|
|
|
|
// Add invalid remote
|
|
runGit(t, dir, "remote", "add", "origin", "invalid://bad-url")
|
|
|
|
// Fix should still succeed - it only sets config, doesn't interact with remote
|
|
err := SyncBranchConfig(dir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error with invalid remote: %v", err)
|
|
}
|
|
|
|
// Verify config was set
|
|
cmd := exec.Command("git", "config", "sync.branch")
|
|
cmd.Dir = dir
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
t.Fatalf("failed to get sync.branch config: %v", err)
|
|
}
|
|
if strings.TrimSpace(string(output)) == "" {
|
|
t.Error("sync.branch config was not set")
|
|
}
|
|
}
|
|
|