package sqlite import ( "context" "testing" "github.com/steveyegge/beads/internal/types" ) func TestDeleteIssues(t *testing.T) { ctx := context.Background() t.Run("delete non-existent issue", func(t *testing.T) { store := newTestStore(t, "file::memory:?mode=memory&cache=private") result, err := store.DeleteIssues(ctx, []string{"bd-999"}, false, false, false) if err != nil { t.Fatalf("DeleteIssues failed: %v", err) } if result.DeletedCount != 0 { t.Errorf("Expected 0 deletions, got %d", result.DeletedCount) } }) t.Run("delete with dependents - should fail without force or cascade", func(t *testing.T) { store := newTestStore(t, "file::memory:?mode=memory&cache=private") // Create issues with dependency issue1 := &types.Issue{ID: "bd-1", Title: "Parent", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} issue2 := &types.Issue{ID: "bd-2", Title: "Child", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} if err := store.CreateIssue(ctx, issue1, "test"); err != nil { t.Fatalf("Failed to create issue1: %v", err) } if err := store.CreateIssue(ctx, issue2, "test"); err != nil { t.Fatalf("Failed to create issue2: %v", err) } dep := &types.Dependency{IssueID: "bd-2", DependsOnID: "bd-1", Type: types.DepBlocks} if err := store.AddDependency(ctx, dep, "test"); err != nil { t.Fatalf("Failed to add dependency: %v", err) } _, err := store.DeleteIssues(ctx, []string{"bd-1"}, false, false, false) if err == nil { t.Fatal("Expected error when deleting issue with dependents") } }) t.Run("delete with cascade - should delete all dependents", func(t *testing.T) { store := newTestStore(t, "file::memory:?mode=memory&cache=private") // Create chain: bd-1 -> bd-2 -> bd-3 issue1 := &types.Issue{ID: "bd-1", Title: "Cascade Parent", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} issue2 := &types.Issue{ID: "bd-2", Title: "Cascade Child", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} issue3 := &types.Issue{ID: "bd-3", Title: "Cascade Grandchild", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} if err := store.CreateIssue(ctx, issue1, "test"); err != nil { t.Fatalf("Failed to create issue1: %v", err) } if err := store.CreateIssue(ctx, issue2, "test"); err != nil { t.Fatalf("Failed to create issue2: %v", err) } if err := store.CreateIssue(ctx, issue3, "test"); err != nil { t.Fatalf("Failed to create issue3: %v", err) } dep1 := &types.Dependency{IssueID: "bd-2", DependsOnID: "bd-1", Type: types.DepBlocks} if err := store.AddDependency(ctx, dep1, "test"); err != nil { t.Fatalf("Failed to add dependency: %v", err) } dep2 := &types.Dependency{IssueID: "bd-3", DependsOnID: "bd-2", Type: types.DepBlocks} if err := store.AddDependency(ctx, dep2, "test"); err != nil { t.Fatalf("Failed to add dependency: %v", err) } result, err := store.DeleteIssues(ctx, []string{"bd-1"}, true, false, false) if err != nil { t.Fatalf("DeleteIssues with cascade failed: %v", err) } if result.DeletedCount != 3 { t.Errorf("Expected 3 deletions (cascade), got %d", result.DeletedCount) } // Verify all deleted if issue, _ := store.GetIssue(ctx, "bd-1"); issue != nil { t.Error("bd-1 should be deleted") } if issue, _ := store.GetIssue(ctx, "bd-2"); issue != nil { t.Error("bd-2 should be deleted") } if issue, _ := store.GetIssue(ctx, "bd-3"); issue != nil { t.Error("bd-3 should be deleted") } }) t.Run("delete with force - should orphan dependents", func(t *testing.T) { store := newTestStore(t, "file::memory:?mode=memory&cache=private") // Create chain: bd-1 -> bd-2 -> bd-3 issue1 := &types.Issue{ID: "bd-1", Title: "Force Parent", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} issue2 := &types.Issue{ID: "bd-2", Title: "Force Child", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} issue3 := &types.Issue{ID: "bd-3", Title: "Force Grandchild", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} if err := store.CreateIssue(ctx, issue1, "test"); err != nil { t.Fatalf("Failed to create issue1: %v", err) } if err := store.CreateIssue(ctx, issue2, "test"); err != nil { t.Fatalf("Failed to create issue2: %v", err) } if err := store.CreateIssue(ctx, issue3, "test"); err != nil { t.Fatalf("Failed to create issue3: %v", err) } dep1 := &types.Dependency{IssueID: "bd-2", DependsOnID: "bd-1", Type: types.DepBlocks} if err := store.AddDependency(ctx, dep1, "test"); err != nil { t.Fatalf("Failed to add dependency: %v", err) } dep2 := &types.Dependency{IssueID: "bd-3", DependsOnID: "bd-2", Type: types.DepBlocks} if err := store.AddDependency(ctx, dep2, "test"); err != nil { t.Fatalf("Failed to add dependency: %v", err) } result, err := store.DeleteIssues(ctx, []string{"bd-1"}, false, true, false) if err != nil { t.Fatalf("DeleteIssues with force failed: %v", err) } if result.DeletedCount != 1 { t.Errorf("Expected 1 deletion (force), got %d", result.DeletedCount) } if len(result.OrphanedIssues) != 1 || result.OrphanedIssues[0] != "bd-2" { t.Errorf("Expected bd-2 to be orphaned, got %v", result.OrphanedIssues) } // Verify bd-1 deleted, bd-2 and bd-3 still exist if issue, _ := store.GetIssue(ctx, "bd-1"); issue != nil { t.Error("bd-1 should be deleted") } if issue, _ := store.GetIssue(ctx, "bd-2"); issue == nil { t.Error("bd-2 should still exist") } if issue, _ := store.GetIssue(ctx, "bd-3"); issue == nil { t.Error("bd-3 should still exist") } }) t.Run("dry run - should not delete", func(t *testing.T) { store := newTestStore(t, "file::memory:?mode=memory&cache=private") issue1 := &types.Issue{ID: "bd-1", Title: "DryRun Issue 1", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} issue2 := &types.Issue{ID: "bd-2", Title: "DryRun Issue 2", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} if err := store.CreateIssue(ctx, issue1, "test"); err != nil { t.Fatalf("Failed to create issue1: %v", err) } if err := store.CreateIssue(ctx, issue2, "test"); err != nil { t.Fatalf("Failed to create issue2: %v", err) } result, err := store.DeleteIssues(ctx, []string{"bd-1", "bd-2"}, false, true, true) if err != nil { t.Fatalf("DeleteIssues dry run failed: %v", err) } // Should report what would be deleted if result.DeletedCount != 2 { t.Errorf("Dry run should report 2 deletions, got %d", result.DeletedCount) } // But issues should still exist if issue, _ := store.GetIssue(ctx, "bd-1"); issue == nil { t.Error("bd-1 should still exist after dry run") } if issue, _ := store.GetIssue(ctx, "bd-2"); issue == nil { t.Error("bd-2 should still exist after dry run") } }) t.Run("delete multiple issues at once", func(t *testing.T) { store := newTestStore(t, "file::memory:?mode=memory&cache=private") independent1 := &types.Issue{ID: "bd-10", Title: "Independent 1", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} independent2 := &types.Issue{ID: "bd-11", Title: "Independent 2", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask} if err := store.CreateIssue(ctx, independent1, "test"); err != nil { t.Fatalf("Failed to create independent1: %v", err) } if err := store.CreateIssue(ctx, independent2, "test"); err != nil { t.Fatalf("Failed to create independent2: %v", err) } result, err := store.DeleteIssues(ctx, []string{"bd-10", "bd-11"}, false, false, false) if err != nil { t.Fatalf("DeleteIssues failed: %v", err) } if result.DeletedCount != 2 { t.Errorf("Expected 2 deletions, got %d", result.DeletedCount) } // Verify both deleted if issue, _ := store.GetIssue(ctx, "bd-10"); issue != nil { t.Error("bd-10 should be deleted") } if issue, _ := store.GetIssue(ctx, "bd-11"); issue != nil { t.Error("bd-11 should be deleted") } }) } func TestDeleteIssue(t *testing.T) { store := newTestStore(t, "file::memory:?mode=memory&cache=private") ctx := context.Background() issue := &types.Issue{ ID: "bd-1", Title: "Single Delete Test Issue", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, } if err := store.CreateIssue(ctx, issue, "test"); err != nil { t.Fatalf("Failed to create issue: %v", err) } // Delete it if err := store.DeleteIssue(ctx, "bd-1"); err != nil { t.Fatalf("DeleteIssue failed: %v", err) } // Verify deleted if issue, _ := store.GetIssue(ctx, "bd-1"); issue != nil { t.Error("Issue should be deleted") } // Delete non-existent - should error if err := store.DeleteIssue(ctx, "bd-999"); err == nil { t.Error("DeleteIssue of non-existent should error") } } func TestBuildIDSet(t *testing.T) { ids := []string{"bd-1", "bd-2", "bd-3"} idSet := buildIDSet(ids) if len(idSet) != 3 { t.Errorf("Expected set size 3, got %d", len(idSet)) } for _, id := range ids { if !idSet[id] { t.Errorf("ID %s should be in set", id) } } if idSet["bd-999"] { t.Error("bd-999 should not be in set") } } func TestBuildSQLInClause(t *testing.T) { ids := []string{"bd-1", "bd-2", "bd-3"} inClause, args := buildSQLInClause(ids) expectedClause := "?,?,?" if inClause != expectedClause { t.Errorf("Expected clause %s, got %s", expectedClause, inClause) } if len(args) != 3 { t.Errorf("Expected 3 args, got %d", len(args)) } for i, id := range ids { if args[i] != id { t.Errorf("Args[%d]: expected %s, got %v", i, id, args[i]) } } }