From cd7830a5a6ca3ac4aa1b0d19111978204b8ec288 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Tue, 2 Dec 2025 22:41:07 -0800 Subject: [PATCH] fix: replace string(rune()) with strconv.Itoa in tests (bd-fmc) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also updated CONFIG.md to clarify mass delete threshold requires >5 issues (bd-in6). The string(rune('0'+i)) pattern produces incorrect characters when i >= 10. Changed to strconv.Itoa(i) for reliable conversion. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- cmd/bd/export_integrity_integration_test.go | 11 ++++++----- cmd/bd/stale_test.go | 5 +++-- docs/CONFIG.md | 5 +++-- internal/storage/sqlite/blocked_cache_test.go | 3 ++- internal/storage/sqlite/comments_test.go | 5 +++-- internal/storage/sqlite/compact_bench_test.go | 5 +++-- internal/storage/sqlite/cycle_detection_test.go | 3 ++- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/cmd/bd/export_integrity_integration_test.go b/cmd/bd/export_integrity_integration_test.go index 4cbd4241..73747085 100644 --- a/cmd/bd/export_integrity_integration_test.go +++ b/cmd/bd/export_integrity_integration_test.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "os" "path/filepath" + "strconv" "testing" "github.com/steveyegge/beads/internal/storage/sqlite" @@ -42,9 +43,9 @@ func TestExportIntegrityAfterJSONLTruncation(t *testing.T) { var allIssues []*types.Issue for i := 1; i <= numIssues; i++ { issue := &types.Issue{ - ID: "bd-" + string(rune('0'+i)), - Title: "Test issue " + string(rune('0'+i)), - Description: "Description " + string(rune('0'+i)), + ID: "bd-" + strconv.Itoa(i), + Title: "Test issue " + strconv.Itoa(i), + Description: "Description " + strconv.Itoa(i), Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, @@ -276,8 +277,8 @@ func TestMultipleExportsStayConsistent(t *testing.T) { var issues []*types.Issue for i := 1; i <= 5; i++ { issue := &types.Issue{ - ID: "bd-" + string(rune('0'+i)), - Title: "Issue " + string(rune('0'+i)), + ID: "bd-" + strconv.Itoa(i), + Title: "Issue " + strconv.Itoa(i), Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, diff --git a/cmd/bd/stale_test.go b/cmd/bd/stale_test.go index 504f00f8..b2577c08 100644 --- a/cmd/bd/stale_test.go +++ b/cmd/bd/stale_test.go @@ -3,6 +3,7 @@ package main import ( "context" "path/filepath" + "strconv" "testing" "time" @@ -215,7 +216,7 @@ func TestStaleIssuesWithLimit(t *testing.T) { for i := 1; i <= 5; i++ { updatedAt := oldTime.Add(time.Duration(i) * time.Hour) // Slightly different times for sorting issue := &types.Issue{ - ID: "test-stale-limit-" + string(rune('0'+i)), + ID: "test-stale-limit-" + strconv.Itoa(i), Title: "Stale issue", Status: types.StatusOpen, Priority: 1, @@ -231,7 +232,7 @@ func TestStaleIssuesWithLimit(t *testing.T) { // Update timestamps directly in DB using datetime() function db := s.UnderlyingDB() for i := 1; i <= 5; i++ { - id := "test-stale-limit-" + string(rune('0'+i)) + id := "test-stale-limit-" + strconv.Itoa(i) // Make each slightly different (40 days ago + i hours) _, err := db.ExecContext(ctx, "UPDATE issues SET updated_at = datetime('now', '-40 days', '+' || ? || ' hours') WHERE id = ?", i, id) if err != nil { diff --git a/docs/CONFIG.md b/docs/CONFIG.md index e4125b1f..4898fc33 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -187,7 +187,7 @@ Configuration keys use dot-notation namespaces to organize settings: - `export.write_manifest` - Write .manifest.json with export metadata (default: false) - `auto_export.error_policy` - Override error policy for auto-exports (default: `best-effort`) - `sync.branch` - Name of the dedicated sync branch for beads data (see docs/PROTECTED_BRANCHES.md) -- `sync.require_confirmation_on_mass_delete` - Require interactive confirmation before pushing when >50% of issues vanish during a merge (default: `false`) +- `sync.require_confirmation_on_mass_delete` - Require interactive confirmation before pushing when >50% of issues vanish during a merge AND more than 5 issues existed before (default: `false`) ### Integration Namespaces @@ -336,7 +336,8 @@ Controls for the sync branch workflow (see docs/PROTECTED_BRANCHES.md): bd config set sync.branch beads-metadata # Enable mass deletion protection (optional, default: false) -# When enabled, if >50% of issues vanish during a merge, bd sync will: +# When enabled, if >50% of issues vanish during a merge AND more than 5 +# issues existed before the merge, bd sync will: # 1. Show forensic info about vanished issues # 2. Prompt for confirmation before pushing bd config set sync.require_confirmation_on_mass_delete "true" diff --git a/internal/storage/sqlite/blocked_cache_test.go b/internal/storage/sqlite/blocked_cache_test.go index 6f3e8340..18296e85 100644 --- a/internal/storage/sqlite/blocked_cache_test.go +++ b/internal/storage/sqlite/blocked_cache_test.go @@ -2,6 +2,7 @@ package sqlite import ( "context" + "strconv" "testing" "github.com/steveyegge/beads/internal/types" @@ -289,7 +290,7 @@ func TestDeepHierarchyCacheCorrectness(t *testing.T) { var issues []*types.Issue for i := 0; i < 4; i++ { issue := &types.Issue{ - Title: "Level " + string(rune('0'+i)), + Title: "Level " + strconv.Itoa(i), Status: types.StatusOpen, Priority: 1, IssueType: types.TypeEpic, diff --git a/internal/storage/sqlite/comments_test.go b/internal/storage/sqlite/comments_test.go index 73f3d305..3f5534e0 100644 --- a/internal/storage/sqlite/comments_test.go +++ b/internal/storage/sqlite/comments_test.go @@ -2,6 +2,7 @@ package sqlite import ( "context" + "strconv" "testing" "github.com/steveyegge/beads/internal/types" @@ -143,7 +144,7 @@ func TestGetIssueCommentsOrdering(t *testing.T) { // Add comments with identifiable ordering for i := 1; i <= 5; i++ { - text := "Comment " + string(rune('0'+i)) + text := "Comment " + strconv.Itoa(i) _, err := store.AddIssueComment(ctx, issue.ID, "alice", text) if err != nil { t.Fatalf("AddIssueComment failed: %v", err) @@ -162,7 +163,7 @@ func TestGetIssueCommentsOrdering(t *testing.T) { } for i := 0; i < len(comments); i++ { - expectedText := "Comment " + string(rune('0'+i+1)) + expectedText := "Comment " + strconv.Itoa(i+1) if comments[i].Text != expectedText { t.Errorf("Comment %d: expected text %s, got %s", i, expectedText, comments[i].Text) } diff --git a/internal/storage/sqlite/compact_bench_test.go b/internal/storage/sqlite/compact_bench_test.go index 4caf6065..ab5b012a 100644 --- a/internal/storage/sqlite/compact_bench_test.go +++ b/internal/storage/sqlite/compact_bench_test.go @@ -4,6 +4,7 @@ package sqlite import ( "context" + "strconv" "testing" "time" @@ -112,9 +113,9 @@ func BenchmarkCheckEligibility(b *testing.B) { } } -func generateID(b testing.TB, prefix string, n int) string{ +func generateID(b testing.TB, prefix string, n int) string { b.Helper() - return prefix + string(rune('0'+n/10)) + string(rune('0'+n%10)) + return prefix + strconv.Itoa(n) } func setupBenchDB(tb testing.TB) (*SQLiteStorage, func()) { diff --git a/internal/storage/sqlite/cycle_detection_test.go b/internal/storage/sqlite/cycle_detection_test.go index 395ae67f..478b04e0 100644 --- a/internal/storage/sqlite/cycle_detection_test.go +++ b/internal/storage/sqlite/cycle_detection_test.go @@ -2,6 +2,7 @@ package sqlite import ( "context" + "strconv" "testing" "github.com/steveyegge/beads/internal/types" @@ -415,7 +416,7 @@ func TestDetectCyclesLongCycle(t *testing.T) { issues := make([]*types.Issue, cycleLength) for i := 0; i < cycleLength; i++ { issues[i] = &types.Issue{ - Title: "Issue " + string(rune('0'+i)), + Title: "Issue " + strconv.Itoa(i), Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask,