Add a consolidated "Pending Migrations" check to bd doctor that: - Detects sequential ID usage (suggests bd migrate hash-ids) - Detects legacy deletions.jsonl (suggests bd migrate tombstones) - Detects missing sync-branch config (suggests bd migrate sync) - Detects database version mismatches (suggests bd migrate) Also updates existing checks to use correct modern commands: - CheckIDFormat: bd migrate hash-ids (was bd migrate --to-hash-ids) - CheckDeletionsManifest: bd migrate tombstones (was bd migrate-tombstones) - CheckSyncBranchConfig: bd migrate sync beads-sync (was config.yaml edit) Removes TODO(bd-7l27) comments from migrate_*.go files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
189 lines
5.4 KiB
Go
189 lines
5.4 KiB
Go
package doctor
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestCheckPendingMigrations(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(t *testing.T, dir string)
|
|
wantStatus string
|
|
wantMessage string
|
|
wantMigrations int
|
|
}{
|
|
{
|
|
name: "no beads directory",
|
|
setup: func(t *testing.T, dir string) {},
|
|
wantStatus: StatusOK,
|
|
wantMessage: "None required",
|
|
},
|
|
{
|
|
name: "empty beads directory",
|
|
setup: func(t *testing.T, dir string) {
|
|
if err := os.MkdirAll(filepath.Join(dir, ".beads"), 0755); err != nil {
|
|
t.Fatalf("failed to create .beads: %v", err)
|
|
}
|
|
},
|
|
wantStatus: StatusOK,
|
|
wantMessage: "None required",
|
|
},
|
|
{
|
|
name: "deletions.jsonl exists with entries",
|
|
setup: func(t *testing.T, dir string) {
|
|
beadsDir := filepath.Join(dir, ".beads")
|
|
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
|
t.Fatalf("failed to create .beads: %v", err)
|
|
}
|
|
// Create deletions.jsonl with an entry
|
|
content := `{"id":"bd-test","ts":"2024-01-01T00:00:00Z","by":"test"}`
|
|
if err := os.WriteFile(filepath.Join(beadsDir, "deletions.jsonl"), []byte(content), 0644); err != nil {
|
|
t.Fatalf("failed to create deletions.jsonl: %v", err)
|
|
}
|
|
},
|
|
wantStatus: StatusWarning,
|
|
wantMigrations: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "bd-doctor-migration-*")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
tt.setup(t, tmpDir)
|
|
|
|
check := CheckPendingMigrations(tmpDir)
|
|
|
|
if check.Status != tt.wantStatus {
|
|
t.Errorf("status = %q, want %q", check.Status, tt.wantStatus)
|
|
}
|
|
|
|
if tt.wantMessage != "" && check.Message != tt.wantMessage {
|
|
t.Errorf("message = %q, want %q", check.Message, tt.wantMessage)
|
|
}
|
|
|
|
if check.Category != CategoryMaintenance {
|
|
t.Errorf("category = %q, want %q", check.Category, CategoryMaintenance)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDetectPendingMigrations(t *testing.T) {
|
|
t.Run("no beads directory returns empty", func(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "bd-doctor-migration-*")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
migrations := DetectPendingMigrations(tmpDir)
|
|
if len(migrations) != 0 {
|
|
t.Errorf("expected 0 migrations, got %d", len(migrations))
|
|
}
|
|
})
|
|
|
|
t.Run("empty beads directory returns empty", func(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "bd-doctor-migration-*")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
if err := os.MkdirAll(filepath.Join(tmpDir, ".beads"), 0755); err != nil {
|
|
t.Fatalf("failed to create .beads: %v", err)
|
|
}
|
|
|
|
migrations := DetectPendingMigrations(tmpDir)
|
|
if len(migrations) != 0 {
|
|
t.Errorf("expected 0 migrations, got %d", len(migrations))
|
|
}
|
|
})
|
|
|
|
t.Run("deletions.jsonl triggers tombstones migration", func(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "bd-doctor-migration-*")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
beadsDir := filepath.Join(tmpDir, ".beads")
|
|
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
|
t.Fatalf("failed to create .beads: %v", err)
|
|
}
|
|
|
|
// Create deletions.jsonl with an entry
|
|
content := `{"id":"bd-test","ts":"2024-01-01T00:00:00Z","by":"test"}`
|
|
if err := os.WriteFile(filepath.Join(beadsDir, "deletions.jsonl"), []byte(content), 0644); err != nil {
|
|
t.Fatalf("failed to create deletions.jsonl: %v", err)
|
|
}
|
|
|
|
migrations := DetectPendingMigrations(tmpDir)
|
|
if len(migrations) != 1 {
|
|
t.Errorf("expected 1 migration, got %d", len(migrations))
|
|
return
|
|
}
|
|
|
|
if migrations[0].Name != "tombstones" {
|
|
t.Errorf("migration name = %q, want %q", migrations[0].Name, "tombstones")
|
|
}
|
|
|
|
if migrations[0].Command != "bd migrate tombstones" {
|
|
t.Errorf("migration command = %q, want %q", migrations[0].Command, "bd migrate tombstones")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNeedsTombstonesMigration(t *testing.T) {
|
|
t.Run("no deletions.jsonl returns false", func(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "bd-doctor-migration-*")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
if needsTombstonesMigration(tmpDir) {
|
|
t.Error("expected false for non-existent deletions.jsonl")
|
|
}
|
|
})
|
|
|
|
t.Run("empty deletions.jsonl returns false", func(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "bd-doctor-migration-*")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "deletions.jsonl"), []byte(""), 0644); err != nil {
|
|
t.Fatalf("failed to create deletions.jsonl: %v", err)
|
|
}
|
|
|
|
if needsTombstonesMigration(tmpDir) {
|
|
t.Error("expected false for empty deletions.jsonl")
|
|
}
|
|
})
|
|
|
|
t.Run("deletions.jsonl with entries returns true", func(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "bd-doctor-migration-*")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
content := `{"id":"bd-test","ts":"2024-01-01T00:00:00Z","by":"test"}`
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "deletions.jsonl"), []byte(content), 0644); err != nil {
|
|
t.Fatalf("failed to create deletions.jsonl: %v", err)
|
|
}
|
|
|
|
if !needsTombstonesMigration(tmpDir) {
|
|
t.Error("expected true for deletions.jsonl with entries")
|
|
}
|
|
})
|
|
}
|