Add extensive test coverage for the Dolt storage implementation: - dependencies_extended_test.go: Extended dependency operation tests - dolt_benchmark_test.go: Performance benchmarks for Dolt operations - history_test.go: Version history query tests - labels_test.go: Label operation tests These tests validate Dolt backend correctness and provide performance baselines for comparison with SQLite. Co-authored-by: upstream_syncer <matthew.baker@pihealth.ai> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
581 lines
18 KiB
Go
581 lines
18 KiB
Go
//go:build cgo
|
|
|
|
package dolt
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/steveyegge/beads/internal/types"
|
|
)
|
|
|
|
// =============================================================================
|
|
// GetDependenciesWithMetadata Tests
|
|
// =============================================================================
|
|
|
|
func TestGetDependenciesWithMetadata(t *testing.T) {
|
|
// Note: This test is skipped in embedded Dolt mode because GetDependenciesWithMetadata
|
|
// makes nested GetIssue calls inside a rows cursor, which can cause connection issues.
|
|
// This is a known limitation of the current implementation (see bd-tdgo.3).
|
|
t.Skip("Skipping: GetDependenciesWithMetadata has nested query issue in embedded Dolt mode")
|
|
}
|
|
|
|
func TestGetDependenciesWithMetadata_NoResults(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create an issue with no dependencies
|
|
issue := &types.Issue{
|
|
ID: "no-deps-issue",
|
|
Title: "No Dependencies",
|
|
Status: types.StatusOpen,
|
|
Priority: 2,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
|
|
deps, err := store.GetDependenciesWithMetadata(ctx, issue.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetDependenciesWithMetadata failed: %v", err)
|
|
}
|
|
|
|
if len(deps) != 0 {
|
|
t.Errorf("expected 0 dependencies, got %d", len(deps))
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// GetDependentsWithMetadata Tests
|
|
// =============================================================================
|
|
|
|
func TestGetDependentsWithMetadata(t *testing.T) {
|
|
// Note: This test is skipped in embedded Dolt mode because GetDependentsWithMetadata
|
|
// makes nested GetIssue calls inside a rows cursor, which can cause connection issues.
|
|
// This is a known limitation of the current implementation (see bd-tdgo.3).
|
|
t.Skip("Skipping: GetDependentsWithMetadata has nested query issue in embedded Dolt mode")
|
|
}
|
|
|
|
// =============================================================================
|
|
// GetDependencyRecords Tests
|
|
// =============================================================================
|
|
|
|
func TestGetDependencyRecords(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create issues
|
|
issue := &types.Issue{
|
|
ID: "records-main",
|
|
Title: "Main Issue",
|
|
Status: types.StatusOpen,
|
|
Priority: 1,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
|
|
dep1 := &types.Issue{
|
|
ID: "records-dep1",
|
|
Title: "Dependency 1",
|
|
Status: types.StatusOpen,
|
|
Priority: 2,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
if err := store.CreateIssue(ctx, dep1, "tester"); err != nil {
|
|
t.Fatalf("failed to create dep1: %v", err)
|
|
}
|
|
|
|
dep2 := &types.Issue{
|
|
ID: "records-dep2",
|
|
Title: "Dependency 2",
|
|
Status: types.StatusOpen,
|
|
Priority: 2,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
if err := store.CreateIssue(ctx, dep2, "tester"); err != nil {
|
|
t.Fatalf("failed to create dep2: %v", err)
|
|
}
|
|
|
|
// Add dependencies
|
|
for _, depIssue := range []string{dep1.ID, dep2.ID} {
|
|
d := &types.Dependency{
|
|
IssueID: issue.ID,
|
|
DependsOnID: depIssue,
|
|
Type: types.DepBlocks,
|
|
}
|
|
if err := store.AddDependency(ctx, d, "tester"); err != nil {
|
|
t.Fatalf("failed to add dependency: %v", err)
|
|
}
|
|
}
|
|
|
|
// Get dependency records
|
|
records, err := store.GetDependencyRecords(ctx, issue.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetDependencyRecords failed: %v", err)
|
|
}
|
|
|
|
if len(records) != 2 {
|
|
t.Fatalf("expected 2 records, got %d", len(records))
|
|
}
|
|
|
|
// Verify structure
|
|
for _, r := range records {
|
|
if r.IssueID != issue.ID {
|
|
t.Errorf("expected IssueID %q, got %q", issue.ID, r.IssueID)
|
|
}
|
|
if r.Type != types.DepBlocks {
|
|
t.Errorf("expected type %q, got %q", types.DepBlocks, r.Type)
|
|
}
|
|
if r.CreatedBy != "tester" {
|
|
t.Errorf("expected CreatedBy 'tester', got %q", r.CreatedBy)
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// GetAllDependencyRecords Tests
|
|
// =============================================================================
|
|
|
|
func TestGetAllDependencyRecords(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create several issues with dependencies
|
|
issueA := &types.Issue{ID: "all-deps-a", Title: "Issue A", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
issueB := &types.Issue{ID: "all-deps-b", Title: "Issue B", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
issueC := &types.Issue{ID: "all-deps-c", Title: "Issue C", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
|
|
for _, issue := range []*types.Issue{issueA, issueB, issueC} {
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
}
|
|
|
|
// A depends on B, B depends on C
|
|
deps := []*types.Dependency{
|
|
{IssueID: issueA.ID, DependsOnID: issueB.ID, Type: types.DepBlocks},
|
|
{IssueID: issueB.ID, DependsOnID: issueC.ID, Type: types.DepBlocks},
|
|
}
|
|
for _, d := range deps {
|
|
if err := store.AddDependency(ctx, d, "tester"); err != nil {
|
|
t.Fatalf("failed to add dependency: %v", err)
|
|
}
|
|
}
|
|
|
|
// Get all dependency records
|
|
allRecords, err := store.GetAllDependencyRecords(ctx)
|
|
if err != nil {
|
|
t.Fatalf("GetAllDependencyRecords failed: %v", err)
|
|
}
|
|
|
|
// Should have records keyed by issueA and issueB
|
|
if len(allRecords) < 2 {
|
|
t.Errorf("expected at least 2 issues with dependencies, got %d", len(allRecords))
|
|
}
|
|
|
|
if _, ok := allRecords[issueA.ID]; !ok {
|
|
t.Errorf("expected records for %q", issueA.ID)
|
|
}
|
|
if _, ok := allRecords[issueB.ID]; !ok {
|
|
t.Errorf("expected records for %q", issueB.ID)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// GetDependencyCounts Tests
|
|
// =============================================================================
|
|
|
|
func TestGetDependencyCounts(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create issues: root blocks mid1 and mid2, mid1 blocks leaf
|
|
root := &types.Issue{ID: "counts-root", Title: "Root", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
mid1 := &types.Issue{ID: "counts-mid1", Title: "Mid 1", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeTask}
|
|
mid2 := &types.Issue{ID: "counts-mid2", Title: "Mid 2", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeTask}
|
|
leaf := &types.Issue{ID: "counts-leaf", Title: "Leaf", Status: types.StatusOpen, Priority: 3, IssueType: types.TypeTask}
|
|
|
|
for _, issue := range []*types.Issue{root, mid1, mid2, leaf} {
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
}
|
|
|
|
// Add blocking dependencies
|
|
deps := []*types.Dependency{
|
|
{IssueID: mid1.ID, DependsOnID: root.ID, Type: types.DepBlocks}, // mid1 blocked by root
|
|
{IssueID: mid2.ID, DependsOnID: root.ID, Type: types.DepBlocks}, // mid2 blocked by root
|
|
{IssueID: leaf.ID, DependsOnID: mid1.ID, Type: types.DepBlocks}, // leaf blocked by mid1
|
|
}
|
|
for _, d := range deps {
|
|
if err := store.AddDependency(ctx, d, "tester"); err != nil {
|
|
t.Fatalf("failed to add dependency: %v", err)
|
|
}
|
|
}
|
|
|
|
// Get counts for all issues
|
|
issueIDs := []string{root.ID, mid1.ID, mid2.ID, leaf.ID}
|
|
counts, err := store.GetDependencyCounts(ctx, issueIDs)
|
|
if err != nil {
|
|
t.Fatalf("GetDependencyCounts failed: %v", err)
|
|
}
|
|
|
|
// root: 0 deps, 2 dependents
|
|
if counts[root.ID].DependencyCount != 0 {
|
|
t.Errorf("root should have 0 deps, got %d", counts[root.ID].DependencyCount)
|
|
}
|
|
if counts[root.ID].DependentCount != 2 {
|
|
t.Errorf("root should have 2 dependents, got %d", counts[root.ID].DependentCount)
|
|
}
|
|
|
|
// mid1: 1 dep, 1 dependent
|
|
if counts[mid1.ID].DependencyCount != 1 {
|
|
t.Errorf("mid1 should have 1 dep, got %d", counts[mid1.ID].DependencyCount)
|
|
}
|
|
if counts[mid1.ID].DependentCount != 1 {
|
|
t.Errorf("mid1 should have 1 dependent, got %d", counts[mid1.ID].DependentCount)
|
|
}
|
|
|
|
// leaf: 1 dep, 0 dependents
|
|
if counts[leaf.ID].DependencyCount != 1 {
|
|
t.Errorf("leaf should have 1 dep, got %d", counts[leaf.ID].DependencyCount)
|
|
}
|
|
if counts[leaf.ID].DependentCount != 0 {
|
|
t.Errorf("leaf should have 0 dependents, got %d", counts[leaf.ID].DependentCount)
|
|
}
|
|
}
|
|
|
|
func TestGetDependencyCounts_EmptyList(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
counts, err := store.GetDependencyCounts(ctx, []string{})
|
|
if err != nil {
|
|
t.Fatalf("GetDependencyCounts failed: %v", err)
|
|
}
|
|
|
|
if len(counts) != 0 {
|
|
t.Errorf("expected empty map, got %d entries", len(counts))
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// GetDependencyTree Tests
|
|
// =============================================================================
|
|
|
|
func TestGetDependencyTree(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create a simple tree: root -> child1, root -> child2
|
|
root := &types.Issue{ID: "tree-root", Title: "Root", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeEpic}
|
|
child1 := &types.Issue{ID: "tree-child1", Title: "Child 1", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeTask}
|
|
child2 := &types.Issue{ID: "tree-child2", Title: "Child 2", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeTask}
|
|
|
|
for _, issue := range []*types.Issue{root, child1, child2} {
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
}
|
|
|
|
// Add dependencies
|
|
for _, childID := range []string{child1.ID, child2.ID} {
|
|
d := &types.Dependency{
|
|
IssueID: root.ID,
|
|
DependsOnID: childID,
|
|
Type: types.DepBlocks,
|
|
}
|
|
if err := store.AddDependency(ctx, d, "tester"); err != nil {
|
|
t.Fatalf("failed to add dependency: %v", err)
|
|
}
|
|
}
|
|
|
|
// Get dependency tree (forward direction)
|
|
tree, err := store.GetDependencyTree(ctx, root.ID, 3, false, false)
|
|
if err != nil {
|
|
t.Fatalf("GetDependencyTree failed: %v", err)
|
|
}
|
|
|
|
// Should have root + 2 children = 3 nodes
|
|
if len(tree) != 3 {
|
|
t.Errorf("expected 3 nodes in tree, got %d", len(tree))
|
|
}
|
|
|
|
// Verify root is at depth 0
|
|
if tree[0].Issue.ID != root.ID || tree[0].Depth != 0 {
|
|
t.Errorf("expected root at depth 0, got %q at depth %d", tree[0].Issue.ID, tree[0].Depth)
|
|
}
|
|
}
|
|
|
|
func TestGetDependencyTree_Reverse(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create chain: leaf -> mid -> root
|
|
root := &types.Issue{ID: "rtree-root", Title: "Root", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
mid := &types.Issue{ID: "rtree-mid", Title: "Mid", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeTask}
|
|
leaf := &types.Issue{ID: "rtree-leaf", Title: "Leaf", Status: types.StatusOpen, Priority: 3, IssueType: types.TypeTask}
|
|
|
|
for _, issue := range []*types.Issue{root, mid, leaf} {
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
}
|
|
|
|
// mid depends on root, leaf depends on mid
|
|
deps := []*types.Dependency{
|
|
{IssueID: mid.ID, DependsOnID: root.ID, Type: types.DepBlocks},
|
|
{IssueID: leaf.ID, DependsOnID: mid.ID, Type: types.DepBlocks},
|
|
}
|
|
for _, d := range deps {
|
|
if err := store.AddDependency(ctx, d, "tester"); err != nil {
|
|
t.Fatalf("failed to add dependency: %v", err)
|
|
}
|
|
}
|
|
|
|
// Get reverse tree from root (shows dependents)
|
|
tree, err := store.GetDependencyTree(ctx, root.ID, 3, false, true)
|
|
if err != nil {
|
|
t.Fatalf("GetDependencyTree reverse failed: %v", err)
|
|
}
|
|
|
|
// Should have root + mid = 2 nodes (leaf is dependent of mid, not root)
|
|
if len(tree) < 2 {
|
|
t.Errorf("expected at least 2 nodes in reverse tree, got %d", len(tree))
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// DetectCycles Tests
|
|
// =============================================================================
|
|
|
|
func TestDetectCycles_NoCycle(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create linear chain: A -> B -> C
|
|
issueA := &types.Issue{ID: "nocycle-a", Title: "A", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
issueB := &types.Issue{ID: "nocycle-b", Title: "B", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
issueC := &types.Issue{ID: "nocycle-c", Title: "C", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
|
|
for _, issue := range []*types.Issue{issueA, issueB, issueC} {
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
}
|
|
|
|
// A depends on B, B depends on C
|
|
deps := []*types.Dependency{
|
|
{IssueID: issueA.ID, DependsOnID: issueB.ID, Type: types.DepBlocks},
|
|
{IssueID: issueB.ID, DependsOnID: issueC.ID, Type: types.DepBlocks},
|
|
}
|
|
for _, d := range deps {
|
|
if err := store.AddDependency(ctx, d, "tester"); err != nil {
|
|
t.Fatalf("failed to add dependency: %v", err)
|
|
}
|
|
}
|
|
|
|
cycles, err := store.DetectCycles(ctx)
|
|
if err != nil {
|
|
t.Fatalf("DetectCycles failed: %v", err)
|
|
}
|
|
|
|
if len(cycles) != 0 {
|
|
t.Errorf("expected no cycles, found %d", len(cycles))
|
|
}
|
|
}
|
|
|
|
func TestDetectCycles_WithCycle(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create cycle: A -> B -> C -> A
|
|
issueA := &types.Issue{ID: "cycle-a", Title: "A", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
issueB := &types.Issue{ID: "cycle-b", Title: "B", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
issueC := &types.Issue{ID: "cycle-c", Title: "C", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask}
|
|
|
|
for _, issue := range []*types.Issue{issueA, issueB, issueC} {
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
}
|
|
|
|
// Create cycle
|
|
deps := []*types.Dependency{
|
|
{IssueID: issueA.ID, DependsOnID: issueB.ID, Type: types.DepBlocks},
|
|
{IssueID: issueB.ID, DependsOnID: issueC.ID, Type: types.DepBlocks},
|
|
{IssueID: issueC.ID, DependsOnID: issueA.ID, Type: types.DepBlocks}, // Creates cycle
|
|
}
|
|
for _, d := range deps {
|
|
if err := store.AddDependency(ctx, d, "tester"); err != nil {
|
|
t.Fatalf("failed to add dependency: %v", err)
|
|
}
|
|
}
|
|
|
|
cycles, err := store.DetectCycles(ctx)
|
|
if err != nil {
|
|
t.Fatalf("DetectCycles failed: %v", err)
|
|
}
|
|
|
|
if len(cycles) == 0 {
|
|
t.Error("expected to find a cycle")
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// GetNewlyUnblockedByClose Tests
|
|
// =============================================================================
|
|
|
|
func TestGetNewlyUnblockedByClose(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create blocker and two blocked issues
|
|
blocker := &types.Issue{
|
|
ID: "unblock-blocker",
|
|
Title: "Blocker",
|
|
Status: types.StatusOpen,
|
|
Priority: 1,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
blockedOnly := &types.Issue{
|
|
ID: "unblock-only",
|
|
Title: "Blocked Only by One",
|
|
Status: types.StatusOpen,
|
|
Priority: 2,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
blockedMultiple := &types.Issue{
|
|
ID: "unblock-multi",
|
|
Title: "Blocked by Multiple",
|
|
Status: types.StatusOpen,
|
|
Priority: 2,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
otherBlocker := &types.Issue{
|
|
ID: "unblock-other",
|
|
Title: "Other Blocker",
|
|
Status: types.StatusOpen,
|
|
Priority: 1,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
|
|
for _, issue := range []*types.Issue{blocker, blockedOnly, blockedMultiple, otherBlocker} {
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
}
|
|
|
|
// blockedOnly depends only on blocker
|
|
// blockedMultiple depends on both blocker and otherBlocker
|
|
deps := []*types.Dependency{
|
|
{IssueID: blockedOnly.ID, DependsOnID: blocker.ID, Type: types.DepBlocks},
|
|
{IssueID: blockedMultiple.ID, DependsOnID: blocker.ID, Type: types.DepBlocks},
|
|
{IssueID: blockedMultiple.ID, DependsOnID: otherBlocker.ID, Type: types.DepBlocks},
|
|
}
|
|
for _, d := range deps {
|
|
if err := store.AddDependency(ctx, d, "tester"); err != nil {
|
|
t.Fatalf("failed to add dependency: %v", err)
|
|
}
|
|
}
|
|
|
|
// Get issues that would be unblocked if we close 'blocker'
|
|
unblocked, err := store.GetNewlyUnblockedByClose(ctx, blocker.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetNewlyUnblockedByClose failed: %v", err)
|
|
}
|
|
|
|
// Only blockedOnly should be newly unblocked (blockedMultiple still has otherBlocker)
|
|
if len(unblocked) != 1 {
|
|
t.Fatalf("expected 1 newly unblocked issue, got %d", len(unblocked))
|
|
}
|
|
|
|
if unblocked[0].ID != blockedOnly.ID {
|
|
t.Errorf("expected %q to be unblocked, got %q", blockedOnly.ID, unblocked[0].ID)
|
|
}
|
|
}
|
|
|
|
func TestGetNewlyUnblockedByClose_ClosedDependent(t *testing.T) {
|
|
store, cleanup := setupTestStore(t)
|
|
defer cleanup()
|
|
|
|
ctx, cancel := testContext(t)
|
|
defer cancel()
|
|
|
|
// Create issues where the dependent is already closed
|
|
blocker := &types.Issue{
|
|
ID: "unblock-closed-blocker",
|
|
Title: "Blocker",
|
|
Status: types.StatusOpen,
|
|
Priority: 1,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
closedDependent := &types.Issue{
|
|
ID: "unblock-closed-dep",
|
|
Title: "Already Closed",
|
|
Status: types.StatusClosed,
|
|
Priority: 2,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
|
|
for _, issue := range []*types.Issue{blocker, closedDependent} {
|
|
if err := store.CreateIssue(ctx, issue, "tester"); err != nil {
|
|
t.Fatalf("failed to create issue: %v", err)
|
|
}
|
|
}
|
|
|
|
// Add dependency
|
|
dep := &types.Dependency{
|
|
IssueID: closedDependent.ID,
|
|
DependsOnID: blocker.ID,
|
|
Type: types.DepBlocks,
|
|
}
|
|
if err := store.AddDependency(ctx, dep, "tester"); err != nil {
|
|
t.Fatalf("failed to add dependency: %v", err)
|
|
}
|
|
|
|
// Closed issues should not be returned as newly unblocked
|
|
unblocked, err := store.GetNewlyUnblockedByClose(ctx, blocker.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetNewlyUnblockedByClose failed: %v", err)
|
|
}
|
|
|
|
if len(unblocked) != 0 {
|
|
t.Errorf("expected 0 unblocked (closed issue shouldn't count), got %d", len(unblocked))
|
|
}
|
|
}
|
|
|
|
// Note: testContext is already defined in dolt_test.go for this package
|