fix(tombstone): clear closed_at when converting closed issue to tombstone

When CreateTombstone was called on a closed issue, the CHECK constraint
(status = closed) = (closed_at IS NOT NULL) was violated because
closed_at was not cleared. Now setting closed_at = NULL in the UPDATE.

Added regression test for creating tombstone from closed issue.

Fixes: bd-fi05

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-13 07:30:57 -08:00
parent ac5491a25f
commit 2c6748fd59
2 changed files with 55 additions and 0 deletions

View File

@@ -873,9 +873,12 @@ func (s *SQLiteStorage) CreateTombstone(ctx context.Context, id string, actor st
originalType := string(issue.IssueType)
// Convert issue to tombstone
// Note: closed_at must be set to NULL because of CHECK constraint:
// (status = 'closed') = (closed_at IS NOT NULL)
_, err = tx.ExecContext(ctx, `
UPDATE issues
SET status = ?,
closed_at = NULL,
deleted_at = ?,
deleted_by = ?,
delete_reason = ?,

View File

@@ -54,6 +54,58 @@ func TestCreateTombstone(t *testing.T) {
}
})
t.Run("create tombstone for closed issue", func(t *testing.T) {
// Regression test: closed issues have closed_at set, which must be
// cleared when creating tombstone due to CHECK constraint:
// (status = 'closed') = (closed_at IS NOT NULL)
issue := &types.Issue{
ID: "bd-closed-1",
Title: "Closed Issue",
Status: types.StatusOpen, // Create as open first
Priority: 1,
IssueType: types.TypeTask,
}
if err := store.CreateIssue(ctx, issue, "test"); err != nil {
t.Fatalf("Failed to create issue: %v", err)
}
// Close the issue to set closed_at
if err := store.CloseIssue(ctx, "bd-closed-1", "closing for test", "tester"); err != nil {
t.Fatalf("Failed to close issue: %v", err)
}
// Verify closed_at is set
closedIssue, err := store.GetIssue(ctx, "bd-closed-1")
if err != nil {
t.Fatalf("Failed to get closed issue: %v", err)
}
if closedIssue.ClosedAt == nil {
t.Fatal("closed_at should be set for closed issue")
}
// Create tombstone - this should work without constraint violation
if err := store.CreateTombstone(ctx, "bd-closed-1", "tester", "testing tombstone from closed"); err != nil {
t.Fatalf("CreateTombstone from closed issue failed: %v", err)
}
// Verify tombstone was created correctly
tombstone, err := store.GetIssue(ctx, "bd-closed-1")
if err != nil {
t.Fatalf("Failed to get tombstone: %v", err)
}
if tombstone.Status != types.StatusTombstone {
t.Errorf("Expected status=tombstone, got %s", tombstone.Status)
}
// closed_at should be nil for tombstone
if tombstone.ClosedAt != nil {
t.Error("closed_at should be nil for tombstone")
}
if tombstone.DeletedAt == nil {
t.Error("deleted_at should be set for tombstone")
}
})
t.Run("create tombstone for non-existent issue", func(t *testing.T) {
err := store.CreateTombstone(ctx, "bd-999", "tester", "testing")
if err == nil {