fix: replace string(rune()) with strconv.Itoa in tests (bd-fmc)

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 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-02 22:41:07 -08:00
parent a067796055
commit cd7830a5a6
7 changed files with 22 additions and 15 deletions

View File

@@ -6,6 +6,7 @@ import (
"encoding/hex" "encoding/hex"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"testing" "testing"
"github.com/steveyegge/beads/internal/storage/sqlite" "github.com/steveyegge/beads/internal/storage/sqlite"
@@ -42,9 +43,9 @@ func TestExportIntegrityAfterJSONLTruncation(t *testing.T) {
var allIssues []*types.Issue var allIssues []*types.Issue
for i := 1; i <= numIssues; i++ { for i := 1; i <= numIssues; i++ {
issue := &types.Issue{ issue := &types.Issue{
ID: "bd-" + string(rune('0'+i)), ID: "bd-" + strconv.Itoa(i),
Title: "Test issue " + string(rune('0'+i)), Title: "Test issue " + strconv.Itoa(i),
Description: "Description " + string(rune('0'+i)), Description: "Description " + strconv.Itoa(i),
Status: types.StatusOpen, Status: types.StatusOpen,
Priority: 1, Priority: 1,
IssueType: types.TypeTask, IssueType: types.TypeTask,
@@ -276,8 +277,8 @@ func TestMultipleExportsStayConsistent(t *testing.T) {
var issues []*types.Issue var issues []*types.Issue
for i := 1; i <= 5; i++ { for i := 1; i <= 5; i++ {
issue := &types.Issue{ issue := &types.Issue{
ID: "bd-" + string(rune('0'+i)), ID: "bd-" + strconv.Itoa(i),
Title: "Issue " + string(rune('0'+i)), Title: "Issue " + strconv.Itoa(i),
Status: types.StatusOpen, Status: types.StatusOpen,
Priority: 1, Priority: 1,
IssueType: types.TypeTask, IssueType: types.TypeTask,

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"path/filepath" "path/filepath"
"strconv"
"testing" "testing"
"time" "time"
@@ -215,7 +216,7 @@ func TestStaleIssuesWithLimit(t *testing.T) {
for i := 1; i <= 5; i++ { for i := 1; i <= 5; i++ {
updatedAt := oldTime.Add(time.Duration(i) * time.Hour) // Slightly different times for sorting updatedAt := oldTime.Add(time.Duration(i) * time.Hour) // Slightly different times for sorting
issue := &types.Issue{ issue := &types.Issue{
ID: "test-stale-limit-" + string(rune('0'+i)), ID: "test-stale-limit-" + strconv.Itoa(i),
Title: "Stale issue", Title: "Stale issue",
Status: types.StatusOpen, Status: types.StatusOpen,
Priority: 1, Priority: 1,
@@ -231,7 +232,7 @@ func TestStaleIssuesWithLimit(t *testing.T) {
// Update timestamps directly in DB using datetime() function // Update timestamps directly in DB using datetime() function
db := s.UnderlyingDB() db := s.UnderlyingDB()
for i := 1; i <= 5; i++ { 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) // 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) _, err := db.ExecContext(ctx, "UPDATE issues SET updated_at = datetime('now', '-40 days', '+' || ? || ' hours') WHERE id = ?", i, id)
if err != nil { if err != nil {

View File

@@ -187,7 +187,7 @@ Configuration keys use dot-notation namespaces to organize settings:
- `export.write_manifest` - Write .manifest.json with export metadata (default: false) - `export.write_manifest` - Write .manifest.json with export metadata (default: false)
- `auto_export.error_policy` - Override error policy for auto-exports (default: `best-effort`) - `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.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 ### Integration Namespaces
@@ -336,7 +336,8 @@ Controls for the sync branch workflow (see docs/PROTECTED_BRANCHES.md):
bd config set sync.branch beads-metadata bd config set sync.branch beads-metadata
# Enable mass deletion protection (optional, default: false) # 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 # 1. Show forensic info about vanished issues
# 2. Prompt for confirmation before pushing # 2. Prompt for confirmation before pushing
bd config set sync.require_confirmation_on_mass_delete "true" bd config set sync.require_confirmation_on_mass_delete "true"

View File

@@ -2,6 +2,7 @@ package sqlite
import ( import (
"context" "context"
"strconv"
"testing" "testing"
"github.com/steveyegge/beads/internal/types" "github.com/steveyegge/beads/internal/types"
@@ -289,7 +290,7 @@ func TestDeepHierarchyCacheCorrectness(t *testing.T) {
var issues []*types.Issue var issues []*types.Issue
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
issue := &types.Issue{ issue := &types.Issue{
Title: "Level " + string(rune('0'+i)), Title: "Level " + strconv.Itoa(i),
Status: types.StatusOpen, Status: types.StatusOpen,
Priority: 1, Priority: 1,
IssueType: types.TypeEpic, IssueType: types.TypeEpic,

View File

@@ -2,6 +2,7 @@ package sqlite
import ( import (
"context" "context"
"strconv"
"testing" "testing"
"github.com/steveyegge/beads/internal/types" "github.com/steveyegge/beads/internal/types"
@@ -143,7 +144,7 @@ func TestGetIssueCommentsOrdering(t *testing.T) {
// Add comments with identifiable ordering // Add comments with identifiable ordering
for i := 1; i <= 5; i++ { 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) _, err := store.AddIssueComment(ctx, issue.ID, "alice", text)
if err != nil { if err != nil {
t.Fatalf("AddIssueComment failed: %v", err) t.Fatalf("AddIssueComment failed: %v", err)
@@ -162,7 +163,7 @@ func TestGetIssueCommentsOrdering(t *testing.T) {
} }
for i := 0; i < len(comments); i++ { 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 { if comments[i].Text != expectedText {
t.Errorf("Comment %d: expected text %s, got %s", i, expectedText, comments[i].Text) t.Errorf("Comment %d: expected text %s, got %s", i, expectedText, comments[i].Text)
} }

View File

@@ -4,6 +4,7 @@ package sqlite
import ( import (
"context" "context"
"strconv"
"testing" "testing"
"time" "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() 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()) { func setupBenchDB(tb testing.TB) (*SQLiteStorage, func()) {

View File

@@ -2,6 +2,7 @@ package sqlite
import ( import (
"context" "context"
"strconv"
"testing" "testing"
"github.com/steveyegge/beads/internal/types" "github.com/steveyegge/beads/internal/types"
@@ -415,7 +416,7 @@ func TestDetectCyclesLongCycle(t *testing.T) {
issues := make([]*types.Issue, cycleLength) issues := make([]*types.Issue, cycleLength)
for i := 0; i < cycleLength; i++ { for i := 0; i < cycleLength; i++ {
issues[i] = &types.Issue{ issues[i] = &types.Issue{
Title: "Issue " + string(rune('0'+i)), Title: "Issue " + strconv.Itoa(i),
Status: types.StatusOpen, Status: types.StatusOpen,
Priority: 1, Priority: 1,
IssueType: types.TypeTask, IssueType: types.TypeTask,