tests: add chaos doctor repair coverage and stabilize git init

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
Jordan Hubbard
2025-12-25 21:50:13 -04:00
parent 1184bd1e59
commit b089aaa0d6
8 changed files with 323 additions and 193 deletions

View File

@@ -26,36 +26,36 @@ func TestGitPullSyncIntegration(t *testing.T) {
// Create temp directory for test repositories
tempDir := t.TempDir()
// Create "remote" repository
remoteDir := filepath.Join(tempDir, "remote")
if err := os.MkdirAll(remoteDir, 0750); err != nil {
t.Fatalf("Failed to create remote dir: %v", err)
}
// Initialize remote git repo
runGitCmd(t, remoteDir, "init", "--bare")
runGitCmd(t, remoteDir, "init", "--bare", "-b", "master")
// Create "clone1" repository
clone1Dir := filepath.Join(tempDir, "clone1")
runGitCmd(t, tempDir, "clone", remoteDir, clone1Dir)
configureGit(t, clone1Dir)
// Initialize beads in clone1
clone1BeadsDir := filepath.Join(clone1Dir, ".beads")
if err := os.MkdirAll(clone1BeadsDir, 0750); err != nil {
t.Fatalf("Failed to create .beads dir: %v", err)
}
clone1DBPath := filepath.Join(clone1BeadsDir, "test.db")
clone1Store := newTestStore(t, clone1DBPath)
defer clone1Store.Close()
ctx := context.Background()
if err := clone1Store.SetMetadata(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set prefix: %v", err)
}
// Create and close an issue in clone1
issue := &types.Issue{
Title: "Test sync issue",
@@ -69,80 +69,80 @@ func TestGitPullSyncIntegration(t *testing.T) {
t.Fatalf("Failed to create issue: %v", err)
}
issueID := issue.ID
// Close the issue
if err := clone1Store.CloseIssue(ctx, issueID, "Test completed", "test-user"); err != nil {
t.Fatalf("Failed to close issue: %v", err)
}
// Export to JSONL
jsonlPath := filepath.Join(clone1BeadsDir, "issues.jsonl")
if err := exportIssuesToJSONL(ctx, clone1Store, jsonlPath); err != nil {
t.Fatalf("Failed to export: %v", err)
}
// Commit and push from clone1
runGitCmd(t, clone1Dir, "add", ".beads")
runGitCmd(t, clone1Dir, "commit", "-m", "Add closed issue")
runGitCmd(t, clone1Dir, "push", "origin", "master")
// Create "clone2" repository
clone2Dir := filepath.Join(tempDir, "clone2")
runGitCmd(t, tempDir, "clone", remoteDir, clone2Dir)
configureGit(t, clone2Dir)
// Initialize empty database in clone2
clone2BeadsDir := filepath.Join(clone2Dir, ".beads")
clone2DBPath := filepath.Join(clone2BeadsDir, "test.db")
clone2Store := newTestStore(t, clone2DBPath)
defer clone2Store.Close()
if err := clone2Store.SetMetadata(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set prefix: %v", err)
}
// Import the existing JSONL (simulating initial sync)
clone2JSONLPath := filepath.Join(clone2BeadsDir, "issues.jsonl")
if err := importJSONLToStore(ctx, clone2Store, clone2DBPath, clone2JSONLPath); err != nil {
t.Fatalf("Failed to import: %v", err)
}
// Verify issue exists and is closed
verifyIssueClosed(t, clone2Store, issueID)
// Note: We don't commit in clone2 - it stays clean as a read-only consumer
// Now test git pull scenario: Clone1 makes a change (update priority)
if err := clone1Store.UpdateIssue(ctx, issueID, map[string]interface{}{
"priority": 0,
}, "test-user"); err != nil {
t.Fatalf("Failed to update issue: %v", err)
}
if err := exportIssuesToJSONL(ctx, clone1Store, jsonlPath); err != nil {
t.Fatalf("Failed to export after update: %v", err)
}
runGitCmd(t, clone1Dir, "add", ".beads/issues.jsonl")
runGitCmd(t, clone1Dir, "commit", "-m", "Update priority")
runGitCmd(t, clone1Dir, "push", "origin", "master")
// Clone2 pulls the change
runGitCmd(t, clone2Dir, "pull")
// Test auto-import in non-daemon mode
t.Run("NonDaemonAutoImport", func(t *testing.T) {
// Use a temporary local store for this test
localStore := newTestStore(t, clone2DBPath)
defer localStore.Close()
// Manually import to simulate auto-import behavior
startTime := time.Now()
if err := importJSONLToStore(ctx, localStore, clone2DBPath, clone2JSONLPath); err != nil {
t.Fatalf("Failed to auto-import: %v", err)
}
elapsed := time.Since(startTime)
// Verify priority was updated
issue, err := localStore.GetIssue(ctx, issueID)
if err != nil {
@@ -151,13 +151,13 @@ func TestGitPullSyncIntegration(t *testing.T) {
if issue.Priority != 0 {
t.Errorf("Expected priority 0 after auto-import, got %d", issue.Priority)
}
// Verify performance: import should be fast
if elapsed > 100*time.Millisecond {
t.Logf("Info: import took %v", elapsed)
}
})
// Test bd sync --import-only command
t.Run("BdSyncCommand", func(t *testing.T) {
// Make another change in clone1 (change priority back to 1)
@@ -166,27 +166,27 @@ func TestGitPullSyncIntegration(t *testing.T) {
}, "test-user"); err != nil {
t.Fatalf("Failed to update issue: %v", err)
}
if err := exportIssuesToJSONL(ctx, clone1Store, jsonlPath); err != nil {
t.Fatalf("Failed to export: %v", err)
}
runGitCmd(t, clone1Dir, "add", ".beads/issues.jsonl")
runGitCmd(t, clone1Dir, "commit", "-m", "Update priority")
runGitCmd(t, clone1Dir, "push", "origin", "master")
// Clone2 pulls
runGitCmd(t, clone2Dir, "pull")
// Use a fresh store for import
syncStore := newTestStore(t, clone2DBPath)
defer syncStore.Close()
// Manually trigger import via in-process equivalent
if err := importJSONLToStore(ctx, syncStore, clone2DBPath, clone2JSONLPath); err != nil {
t.Fatalf("Failed to import via sync: %v", err)
}
// Verify priority was updated back to 1
issue, err := syncStore.GetIssue(ctx, issueID)
if err != nil {
@@ -214,7 +214,7 @@ func configureGit(t *testing.T, dir string) {
runGitCmd(t, dir, "config", "user.email", "test@example.com")
runGitCmd(t, dir, "config", "user.name", "Test User")
runGitCmd(t, dir, "config", "pull.rebase", "false")
// Create .gitignore to prevent test database files from being tracked
gitignorePath := filepath.Join(dir, ".gitignore")
gitignoreContent := `# Test database files
@@ -233,7 +233,7 @@ func exportIssuesToJSONL(ctx context.Context, store *sqlite.SQLiteStorage, jsonl
if err != nil {
return err
}
// Populate dependencies
allDeps, err := store.GetAllDependencyRecords(ctx)
if err != nil {
@@ -244,20 +244,20 @@ func exportIssuesToJSONL(ctx context.Context, store *sqlite.SQLiteStorage, jsonl
labels, _ := store.GetLabels(ctx, issue.ID)
issue.Labels = labels
}
f, err := os.Create(jsonlPath)
if err != nil {
return err
}
defer f.Close()
encoder := json.NewEncoder(f)
for _, issue := range issues {
if err := encoder.Encode(issue); err != nil {
return err
}
}
return nil
}
@@ -266,7 +266,7 @@ func importJSONLToStore(ctx context.Context, store *sqlite.SQLiteStorage, dbPath
if err != nil {
return err
}
// Use the autoimport package's AutoImportIfNewer function
// For testing, we'll directly parse and import
var issues []*types.Issue
@@ -278,7 +278,7 @@ func importJSONLToStore(ctx context.Context, store *sqlite.SQLiteStorage, dbPath
}
issues = append(issues, &issue)
}
// Import each issue
for _, issue := range issues {
existing, _ := store.GetIssue(ctx, issue.ID)
@@ -298,12 +298,12 @@ func importJSONLToStore(ctx context.Context, store *sqlite.SQLiteStorage, dbPath
}
}
}
// Set last_import_time metadata so staleness check works
if err := store.SetMetadata(ctx, "last_import_time", time.Now().Format(time.RFC3339)); err != nil {
return err
}
return nil
}