Add tests for createTombstone and deleteIssue wrappers (bd-d28c)

Test scenarios cover:
- Successful tombstone creation with actor/reason tracking
- Original type preservation in tombstone
- Audit trail recording
- Issue deletion with dependency cleanup
- Error handling for non-existent issues
- Unsupported storage backend fallback

Both functions achieve 100% test coverage.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-23 21:32:08 -08:00
parent f11663cea9
commit 7d3447b971

View File

@@ -272,3 +272,330 @@ func countJSONLIssuesTest(t *testing.T, jsonlPath string) int {
}
return count
}
// TestCreateTombstoneWrapper tests the createTombstone wrapper function
func TestCreateTombstoneWrapper(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
tmpDir := t.TempDir()
beadsDir := filepath.Join(tmpDir, ".beads")
testDB := filepath.Join(beadsDir, "beads.db")
s := newTestStore(t, testDB)
ctx := context.Background()
// Save and restore global store
oldStore := store
defer func() { store = oldStore }()
store = s
t.Run("successful tombstone creation", func(t *testing.T) {
issue := &types.Issue{
Title: "Test Issue",
Description: "Issue to be tombstoned",
Status: types.StatusOpen,
Priority: 2,
IssueType: "task",
}
if err := s.CreateIssue(ctx, issue, "test"); err != nil {
t.Fatalf("Failed to create issue: %v", err)
}
err := createTombstone(ctx, issue.ID, "test-actor", "Test deletion reason")
if err != nil {
t.Fatalf("createTombstone failed: %v", err)
}
// Verify tombstone status
updated, err := s.GetIssue(ctx, issue.ID)
if err != nil {
t.Fatalf("GetIssue failed: %v", err)
}
if updated == nil {
t.Fatal("Issue should still exist as tombstone")
}
if updated.Status != types.StatusTombstone {
t.Errorf("Expected status %s, got %s", types.StatusTombstone, updated.Status)
}
})
t.Run("tombstone with actor and reason tracking", func(t *testing.T) {
issue := &types.Issue{
Title: "Issue with tracking",
Description: "Check actor/reason",
Status: types.StatusOpen,
Priority: 1,
IssueType: "bug",
}
if err := s.CreateIssue(ctx, issue, "test"); err != nil {
t.Fatalf("Failed to create issue: %v", err)
}
actor := "admin-user"
reason := "Duplicate issue"
err := createTombstone(ctx, issue.ID, actor, reason)
if err != nil {
t.Fatalf("createTombstone failed: %v", err)
}
// Verify actor and reason were recorded
updated, err := s.GetIssue(ctx, issue.ID)
if err != nil {
t.Fatalf("GetIssue failed: %v", err)
}
if updated.DeletedBy != actor {
t.Errorf("Expected DeletedBy %q, got %q", actor, updated.DeletedBy)
}
if updated.DeleteReason != reason {
t.Errorf("Expected DeleteReason %q, got %q", reason, updated.DeleteReason)
}
})
t.Run("error when issue does not exist", func(t *testing.T) {
err := createTombstone(ctx, "nonexistent-issue-id", "actor", "reason")
if err == nil {
t.Error("Expected error for non-existent issue")
}
})
t.Run("verify tombstone preserves original type", func(t *testing.T) {
issue := &types.Issue{
Title: "Feature issue",
Description: "Should preserve type",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeFeature,
}
if err := s.CreateIssue(ctx, issue, "test"); err != nil {
t.Fatalf("Failed to create issue: %v", err)
}
err := createTombstone(ctx, issue.ID, "actor", "reason")
if err != nil {
t.Fatalf("createTombstone failed: %v", err)
}
updated, err := s.GetIssue(ctx, issue.ID)
if err != nil {
t.Fatalf("GetIssue failed: %v", err)
}
if updated.OriginalType != string(types.TypeFeature) {
t.Errorf("Expected OriginalType %q, got %q", types.TypeFeature, updated.OriginalType)
}
})
t.Run("verify audit trail recorded", func(t *testing.T) {
issue := &types.Issue{
Title: "Issue for audit",
Description: "Check event recording",
Status: types.StatusOpen,
Priority: 2,
IssueType: "task",
}
if err := s.CreateIssue(ctx, issue, "test"); err != nil {
t.Fatalf("Failed to create issue: %v", err)
}
err := createTombstone(ctx, issue.ID, "audit-actor", "audit-reason")
if err != nil {
t.Fatalf("createTombstone failed: %v", err)
}
// Verify an event was recorded
events, err := s.GetEvents(ctx, issue.ID, 100)
if err != nil {
t.Fatalf("GetEvents failed: %v", err)
}
found := false
for _, e := range events {
if e.EventType == "deleted" && e.Actor == "audit-actor" {
found = true
break
}
}
if !found {
t.Error("Expected 'deleted' event in audit trail")
}
})
}
// TestDeleteIssueWrapper tests the deleteIssue wrapper function
func TestDeleteIssueWrapper(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
tmpDir := t.TempDir()
beadsDir := filepath.Join(tmpDir, ".beads")
testDB := filepath.Join(beadsDir, "beads.db")
s := newTestStore(t, testDB)
ctx := context.Background()
// Save and restore global store
oldStore := store
defer func() { store = oldStore }()
store = s
t.Run("successful issue deletion", func(t *testing.T) {
issue := &types.Issue{
Title: "Issue to delete",
Description: "Will be permanently deleted",
Status: types.StatusOpen,
Priority: 2,
IssueType: "task",
}
if err := s.CreateIssue(ctx, issue, "test"); err != nil {
t.Fatalf("Failed to create issue: %v", err)
}
err := deleteIssue(ctx, issue.ID)
if err != nil {
t.Fatalf("deleteIssue failed: %v", err)
}
// Verify issue is gone
deleted, err := s.GetIssue(ctx, issue.ID)
if err != nil {
t.Fatalf("GetIssue failed: %v", err)
}
if deleted != nil {
t.Error("Issue should be completely deleted")
}
})
t.Run("error on non-existent issue", func(t *testing.T) {
err := deleteIssue(ctx, "nonexistent-issue-id")
if err == nil {
t.Error("Expected error for non-existent issue")
}
})
t.Run("verify dependencies are removed", func(t *testing.T) {
// Create two issues with a dependency
issue1 := &types.Issue{
Title: "Blocker issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: "task",
}
issue2 := &types.Issue{
Title: "Dependent issue",
Status: types.StatusOpen,
Priority: 2,
IssueType: "task",
}
if err := s.CreateIssue(ctx, issue1, "test"); err != nil {
t.Fatalf("Failed to create issue1: %v", err)
}
if err := s.CreateIssue(ctx, issue2, "test"); err != nil {
t.Fatalf("Failed to create issue2: %v", err)
}
// Add dependency: issue2 depends on issue1
dep := &types.Dependency{
IssueID: issue2.ID,
DependsOnID: issue1.ID,
Type: types.DepBlocks,
}
if err := s.AddDependency(ctx, dep, "test"); err != nil {
t.Fatalf("Failed to add dependency: %v", err)
}
// Delete issue1 (the blocker)
err := deleteIssue(ctx, issue1.ID)
if err != nil {
t.Fatalf("deleteIssue failed: %v", err)
}
// Verify issue2 no longer has dependencies
deps, err := s.GetDependencies(ctx, issue2.ID)
if err != nil {
t.Fatalf("GetDependencies failed: %v", err)
}
if len(deps) > 0 {
t.Errorf("Expected no dependencies after deleting blocker, got %d", len(deps))
}
})
t.Run("verify issue removed from database", func(t *testing.T) {
issue := &types.Issue{
Title: "Verify removal",
Status: types.StatusOpen,
Priority: 2,
IssueType: "task",
}
if err := s.CreateIssue(ctx, issue, "test"); err != nil {
t.Fatalf("Failed to create issue: %v", err)
}
// Get statistics before delete
statsBefore, err := s.GetStatistics(ctx)
if err != nil {
t.Fatalf("GetStatistics failed: %v", err)
}
err = deleteIssue(ctx, issue.ID)
if err != nil {
t.Fatalf("deleteIssue failed: %v", err)
}
// Get statistics after delete
statsAfter, err := s.GetStatistics(ctx)
if err != nil {
t.Fatalf("GetStatistics failed: %v", err)
}
if statsAfter.TotalIssues != statsBefore.TotalIssues-1 {
t.Errorf("Expected total issues to decrease by 1, was %d now %d",
statsBefore.TotalIssues, statsAfter.TotalIssues)
}
})
}
func TestCreateTombstoneUnsupportedStorage(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
oldStore := store
defer func() { store = oldStore }()
// Set store to nil - the type assertion will fail
store = nil
ctx := context.Background()
err := createTombstone(ctx, "any-id", "actor", "reason")
if err == nil {
t.Error("Expected error when storage is nil")
}
expectedMsg := "tombstone operation not supported by this storage backend"
if err.Error() != expectedMsg {
t.Errorf("Expected error %q, got %q", expectedMsg, err.Error())
}
}
func TestDeleteIssueUnsupportedStorage(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
oldStore := store
defer func() { store = oldStore }()
// Set store to nil - the type assertion will fail
store = nil
ctx := context.Background()
err := deleteIssue(ctx, "any-id")
if err == nil {
t.Error("Expected error when storage is nil")
}
expectedMsg := "delete operation not supported by this storage backend"
if err.Error() != expectedMsg {
t.Errorf("Expected error %q, got %q", expectedMsg, err.Error())
}
}