From 345766badcf3e20932777027c125fe1030244ff3 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Thu, 20 Nov 2025 18:55:10 -0500 Subject: [PATCH] Fix FK constraint failures in AddComment and ApplyCompaction (bd-5arw) Amp-Thread-ID: https://ampcode.com/threads/T-4358e6e4-28ea-4ed7-ba3f-3da39072e169 Co-authored-by: Amp --- internal/storage/sqlite/compact.go | 10 ++++++++- internal/storage/sqlite/compact_test.go | 20 ++++++++++++++++++ internal/storage/sqlite/events.go | 28 ++++++++++++++++--------- internal/storage/sqlite/events_test.go | 19 +++++++++++++++++ 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/internal/storage/sqlite/compact.go b/internal/storage/sqlite/compact.go index 2870ef24..fe460480 100644 --- a/internal/storage/sqlite/compact.go +++ b/internal/storage/sqlite/compact.go @@ -273,7 +273,7 @@ func (s *SQLiteStorage) ApplyCompaction(ctx context.Context, issueID string, lev commitHashPtr = &commitHash } - _, err := tx.ExecContext(ctx, ` + res, err := tx.ExecContext(ctx, ` UPDATE issues SET compaction_level = ?, compacted_at = ?, @@ -287,6 +287,14 @@ func (s *SQLiteStorage) ApplyCompaction(ctx context.Context, issueID string, lev return fmt.Errorf("failed to apply compaction metadata: %w", err) } + rows, err := res.RowsAffected() + if err != nil { + return fmt.Errorf("failed to get rows affected: %w", err) + } + if rows == 0 { + return fmt.Errorf("issue %s not found", issueID) + } + reductionPct := 0.0 if originalSize > 0 { reductionPct = (1.0 - float64(compressedSize)/float64(originalSize)) * 100 diff --git a/internal/storage/sqlite/compact_test.go b/internal/storage/sqlite/compact_test.go index 019259d5..dfffab6f 100644 --- a/internal/storage/sqlite/compact_test.go +++ b/internal/storage/sqlite/compact_test.go @@ -3,6 +3,7 @@ package sqlite import ( "context" "database/sql" + "strings" "testing" "time" @@ -369,3 +370,22 @@ func TestApplyCompaction(t *testing.T) { func timePtr(t time.Time) *time.Time { return &t } + +func TestApplyCompactionNotFound(t *testing.T) { + store, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + nonExistentID := "bd-999" + + err := store.ApplyCompaction(ctx, nonExistentID, 1, 100, 50, "abc123") + if err == nil { + t.Fatal("Expected error, got nil") + } + + expectedError := "issue bd-999 not found" + if !strings.Contains(err.Error(), expectedError) { + t.Errorf("Expected error to contain %q, got %q", expectedError, err.Error()) + } +} + diff --git a/internal/storage/sqlite/events.go b/internal/storage/sqlite/events.go index 256e3d97..9409102c 100644 --- a/internal/storage/sqlite/events.go +++ b/internal/storage/sqlite/events.go @@ -14,7 +14,24 @@ const limitClause = " LIMIT ?" // AddComment adds a comment to an issue func (s *SQLiteStorage) AddComment(ctx context.Context, issueID, actor, comment string) error { return s.withTx(ctx, func(tx *sql.Tx) error { - _, err := tx.ExecContext(ctx, ` + // Update issue updated_at timestamp first to verify issue exists + now := time.Now() + res, err := tx.ExecContext(ctx, ` + UPDATE issues SET updated_at = ? WHERE id = ? + `, now, issueID) + if err != nil { + return fmt.Errorf("failed to update timestamp: %w", err) + } + + rows, err := res.RowsAffected() + if err != nil { + return fmt.Errorf("failed to get rows affected: %w", err) + } + if rows == 0 { + return fmt.Errorf("issue %s not found", issueID) + } + + _, err = tx.ExecContext(ctx, ` INSERT INTO events (issue_id, event_type, actor, comment) VALUES (?, ?, ?, ?) `, issueID, types.EventCommented, actor, comment) @@ -22,15 +39,6 @@ func (s *SQLiteStorage) AddComment(ctx context.Context, issueID, actor, comment return fmt.Errorf("failed to add comment: %w", err) } - // Update issue updated_at timestamp - now := time.Now() - _, err = tx.ExecContext(ctx, ` - UPDATE issues SET updated_at = ? WHERE id = ? - `, now, issueID) - if err != nil { - return fmt.Errorf("failed to update timestamp: %w", err) - } - // Mark issue as dirty for incremental export _, err = tx.ExecContext(ctx, ` INSERT INTO dirty_issues (issue_id, marked_at) diff --git a/internal/storage/sqlite/events_test.go b/internal/storage/sqlite/events_test.go index 08f7bb99..af7b66d5 100644 --- a/internal/storage/sqlite/events_test.go +++ b/internal/storage/sqlite/events_test.go @@ -2,6 +2,7 @@ package sqlite import ( "context" + "strings" "testing" "github.com/steveyegge/beads/internal/types" @@ -339,3 +340,21 @@ func TestEventTypesInHistory(t *testing.T) { t.Error("Expected EventClosed in history") } } + +func TestAddCommentNotFound(t *testing.T) { + store, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + nonExistentID := "bd-999" + + err := store.AddComment(ctx, nonExistentID, "alice", "This should fail cleanly") + if err == nil { + t.Fatal("Expected error, got nil") + } + + expectedError := "issue bd-999 not found" + if !strings.Contains(err.Error(), expectedError) { + t.Errorf("Expected error to contain %q, got %q", expectedError, err.Error()) + } +}