Files
beads/cmd/bd/rename_prefix_repair_test.go
kustrun 16af63dc73 fix(rename-prefix): sync JSONL before and after prefix rename (#893)
- Pull from sync-branch before rename if configured
- Import all issues from JSONL before rename to prevent data loss
- Export directly to JSONL after rename (don't rely on flushManager)
- Apply same pattern to --repair mode
- Add newSilentLogger() for production use (not test-only)
- Add comprehensive tests for JSONL update scenarios

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 10:53:31 -08:00

135 lines
3.7 KiB
Go

package main
import (
"context"
"os"
"path/filepath"
"testing"
"time"
"github.com/steveyegge/beads/internal/storage/sqlite"
"github.com/steveyegge/beads/internal/types"
)
func TestRepairMultiplePrefixes(t *testing.T) {
// Create a temporary database with .beads directory structure
tempDir := t.TempDir()
testDBPath := filepath.Join(tempDir, ".beads", "beads.db")
// Create .beads directory
if err := os.MkdirAll(filepath.Dir(testDBPath), 0750); err != nil {
t.Fatalf("failed to create .beads dir: %v", err)
}
testStore, err := sqlite.New(context.Background(), testDBPath)
if err != nil {
t.Fatalf("failed to create store: %v", err)
}
defer testStore.Close()
// Set global dbPath so findJSONLPath() finds the right location
oldDBPath := dbPath
dbPath = testDBPath
defer func() { dbPath = oldDBPath }()
ctx := context.Background()
// Set initial prefix
if err := testStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("failed to set prefix: %v", err)
}
// Create issues with multiple prefixes (simulating corruption)
// We need to directly insert into the database to bypass prefix validation
db := testStore.UnderlyingDB()
now := time.Now()
issues := []struct {
ID string
Title string
}{
{"test-1", "Test issue 1"},
{"test-2", "Test issue 2"},
{"old-1", "Old issue 1"},
{"old-2", "Old issue 2"},
{"another-1", "Another issue 1"},
}
for _, issue := range issues {
_, err := db.ExecContext(ctx, `
INSERT INTO issues (id, title, status, priority, issue_type, created_at, updated_at)
VALUES (?, ?, 'open', 2, 'task', ?, ?)
`, issue.ID, issue.Title, now, now)
if err != nil {
t.Fatalf("failed to create issue %s: %v", issue.ID, err)
}
}
// Verify we have multiple prefixes
allIssues, err := testStore.SearchIssues(ctx, "", types.IssueFilter{})
if err != nil {
t.Fatalf("failed to search issues: %v", err)
}
prefixes := detectPrefixes(allIssues)
if len(prefixes) != 3 {
t.Fatalf("expected 3 prefixes, got %d: %v", len(prefixes), prefixes)
}
// Test repair
if err := repairPrefixes(ctx, testStore, "test", "test", allIssues, prefixes, false); err != nil {
t.Fatalf("repair failed: %v", err)
}
// Verify all issues now have correct prefix
allIssues, err = testStore.SearchIssues(ctx, "", types.IssueFilter{})
if err != nil {
t.Fatalf("failed to search issues after repair: %v", err)
}
prefixes = detectPrefixes(allIssues)
if len(prefixes) != 1 {
t.Fatalf("expected 1 prefix after repair, got %d: %v", len(prefixes), prefixes)
}
if _, ok := prefixes["test"]; !ok {
t.Fatalf("expected prefix 'test', got %v", prefixes)
}
// Verify the original test-1 and test-2 are unchanged
for _, id := range []string{"test-1", "test-2"} {
issue, err := testStore.GetIssue(ctx, id)
if err != nil {
t.Fatalf("expected issue %s to exist unchanged: %v", id, err)
}
if issue == nil {
t.Fatalf("expected issue %s to exist", id)
}
}
// Verify the others were renamed with hash IDs (not sequential)
// We have 5 total issues, 2 original (test-1, test-2), 3 renamed
if len(allIssues) != 5 {
t.Fatalf("expected 5 issues total, got %d", len(allIssues))
}
// Count issues with correct prefix and verify old IDs no longer exist
testPrefixCount := 0
for _, issue := range allIssues {
if len(issue.ID) > 5 && issue.ID[:5] == "test-" {
testPrefixCount++
}
}
if testPrefixCount != 5 {
t.Fatalf("expected all 5 issues to have 'test-' prefix, got %d", testPrefixCount)
}
// Verify old IDs no longer exist
for _, oldID := range []string{"old-1", "old-2", "another-1"} {
issue, err := testStore.GetIssue(ctx, oldID)
if err == nil && issue != nil {
t.Fatalf("expected old ID %s to no longer exist", oldID)
}
}
}