package graph import ( "testing" "github.com/stretchr/testify/assert" ) func TestNewDependencyGraph(t *testing.T) { graph := NewDependencyGraph() assert.NotNil(t, graph) assert.Equal(t, 0, len(graph.GetAllTasks())) } func TestAddTask(t *testing.T) { graph := NewDependencyGraph() task := &Task{ID: "T1", Title: "Task 1"} err := graph.AddTask(task) assert.NoError(t, err) retrieved, exists := graph.GetTask("T1") assert.True(t, exists) assert.Equal(t, "T1", retrieved.ID) } func TestAddTaskNil(t *testing.T) { graph := NewDependencyGraph() err := graph.AddTask(nil) assert.Error(t, err) } func TestAddTaskDuplicate(t *testing.T) { graph := NewDependencyGraph() task := &Task{ID: "T1", Title: "Task 1"} graph.AddTask(task) err := graph.AddTask(task) assert.Error(t, err) } func TestAddDependency(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) graph.AddTask(&Task{ID: "T2", Title: "Task 2"}) err := graph.AddDependency("T2", "T1") assert.NoError(t, err) deps, _ := graph.GetDependencies("T2") assert.Equal(t, 1, len(deps)) assert.Equal(t, "T1", deps[0]) } func TestAddDependencyNotFound(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) err := graph.AddDependency("T2", "T1") assert.Error(t, err) } func TestValidateGraphNoCycles(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) graph.AddTask(&Task{ID: "T2", Title: "Task 2"}) graph.AddTask(&Task{ID: "T3", Title: "Task 3"}) graph.AddDependency("T2", "T1") graph.AddDependency("T3", "T2") err := graph.ValidateGraph() assert.NoError(t, err) } func TestValidateGraphWithCycle(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) graph.AddTask(&Task{ID: "T2", Title: "Task 2"}) graph.AddTask(&Task{ID: "T3", Title: "Task 3"}) graph.AddDependency("T2", "T1") graph.AddDependency("T3", "T2") graph.AddDependency("T1", "T3") // Creates cycle err := graph.ValidateGraph() assert.Error(t, err) } func TestGetTopologicalOrder(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) graph.AddTask(&Task{ID: "T2", Title: "Task 2"}) graph.AddTask(&Task{ID: "T3", Title: "Task 3"}) graph.AddDependency("T2", "T1") graph.AddDependency("T3", "T2") graph.ValidateGraph() order, err := graph.GetTopologicalOrder() assert.NoError(t, err) assert.Equal(t, 3, len(order)) assert.Equal(t, "T1", order[0]) assert.Equal(t, "T2", order[1]) assert.Equal(t, "T3", order[2]) } func TestGetReadyTasks(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) graph.AddTask(&Task{ID: "T2", Title: "Task 2"}) graph.AddTask(&Task{ID: "T3", Title: "Task 3"}) graph.AddDependency("T2", "T1") graph.AddDependency("T3", "T1") ready := graph.GetReadyTasks() assert.Equal(t, 1, len(ready)) assert.Equal(t, "T1", ready[0]) graph.MarkCompleted("T1") ready = graph.GetReadyTasks() assert.Equal(t, 2, len(ready)) } func TestMarkCompleted(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) err := graph.MarkCompleted("T1") assert.NoError(t, err) status, _ := graph.GetTaskStatus("T1") assert.Equal(t, "completed", status) } func TestMarkFailed(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) err := graph.MarkFailed("T1") assert.NoError(t, err) status, _ := graph.GetTaskStatus("T1") assert.Equal(t, "failed", status) } func TestGetDependencies(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) graph.AddTask(&Task{ID: "T2", Title: "Task 2"}) graph.AddTask(&Task{ID: "T3", Title: "Task 3"}) graph.AddDependency("T3", "T1") graph.AddDependency("T3", "T2") deps, _ := graph.GetDependencies("T3") assert.Equal(t, 2, len(deps)) } func TestGetDependents(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) graph.AddTask(&Task{ID: "T2", Title: "Task 2"}) graph.AddTask(&Task{ID: "T3", Title: "Task 3"}) graph.AddDependency("T2", "T1") graph.AddDependency("T3", "T1") dependents, _ := graph.GetDependents("T1") assert.Equal(t, 2, len(dependents)) } func TestCanExecuteTask(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) graph.AddTask(&Task{ID: "T2", Title: "Task 2"}) graph.AddDependency("T2", "T1") assert.False(t, graph.CanExecuteTask("T2")) graph.MarkCompleted("T1") assert.True(t, graph.CanExecuteTask("T2")) } func TestGetGraphStats(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) graph.AddTask(&Task{ID: "T2", Title: "Task 2"}) graph.MarkCompleted("T1") stats := graph.GetGraphStats() assert.Equal(t, 2, stats["total_tasks"]) assert.Equal(t, 1, stats["completed_tasks"]) assert.Equal(t, 1, stats["pending_tasks"]) } func TestClear(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1", Title: "Task 1"}) assert.Equal(t, 1, len(graph.GetAllTasks())) graph.Clear() assert.Equal(t, 0, len(graph.GetAllTasks())) } func TestMultipleDependencies(t *testing.T) { graph := NewDependencyGraph() for i := 1; i <= 5; i++ { id := string(rune(48 + i)) graph.AddTask(&Task{ID: "T" + id, Title: "Task " + id}) } // Chain: T1 -> T2 -> T3 -> T4 -> T5 for i := 2; i <= 5; i++ { graph.AddDependency("T"+string(rune(48+i)), "T"+string(rune(48+i-1))) } ready := graph.GetReadyTasks() assert.Equal(t, 1, len(ready)) assert.Equal(t, "T1", ready[0]) } func TestDiamondDependency(t *testing.T) { graph := NewDependencyGraph() // Diamond: T1 -> (T2, T3) -> T4 graph.AddTask(&Task{ID: "T1"}) graph.AddTask(&Task{ID: "T2"}) graph.AddTask(&Task{ID: "T3"}) graph.AddTask(&Task{ID: "T4"}) graph.AddDependency("T2", "T1") graph.AddDependency("T3", "T1") graph.AddDependency("T4", "T2") graph.AddDependency("T4", "T3") graph.ValidateGraph() order, _ := graph.GetTopologicalOrder() assert.Equal(t, 4, len(order)) assert.Equal(t, "T1", order[0]) assert.Equal(t, "T4", order[3]) } func TestGetCriticalPath(t *testing.T) { graph := NewDependencyGraph() graph.AddTask(&Task{ID: "T1"}) graph.AddTask(&Task{ID: "T2"}) graph.AddTask(&Task{ID: "T3"}) graph.AddTask(&Task{ID: "T4"}) graph.AddDependency("T2", "T1") graph.AddDependency("T3", "T2") graph.AddDependency("T4", "T3") graph.ValidateGraph() graph.GetTopologicalOrder() path := graph.GetCriticalPath() assert.Greater(t, len(path), 0) assert.Equal(t, "T1", path[0]) } func TestComplexGraph(t *testing.T) { graph := NewDependencyGraph() // Create 10 tasks with complex dependencies for i := 1; i <= 10; i++ { id := string(rune(48 + i%10)) if i >= 10 { id = "T" + id } else { id = "T0" + id } graph.AddTask(&Task{ID: id}) } // Add various dependencies graph.AddDependency("T02", "T01") graph.AddDependency("T03", "T01") graph.AddDependency("T04", "T02") graph.AddDependency("T04", "T03") graph.ValidateGraph() order, _ := graph.GetTopologicalOrder() assert.Equal(t, 10, len(order)) } func TestTaskWithMetadata(t *testing.T) { graph := NewDependencyGraph() task := &Task{ ID: "T1", Title: "Task 1", Metadata: map[string]interface{}{ "priority": "high", "owner": "team-a", }, } graph.AddTask(task) retrieved, _ := graph.GetTask("T1") assert.Equal(t, "high", retrieved.Metadata["priority"]) } func TestGetAllTasks(t *testing.T) { graph := NewDependencyGraph() for i := 1; i <= 5; i++ { id := string(rune(48 + i)) graph.AddTask(&Task{ID: "T" + id}) } all := graph.GetAllTasks() assert.Equal(t, 5, len(all)) } func BenchmarkAddTask(b *testing.B) { graph := NewDependencyGraph() for i := 0; i < b.N; i++ { id := string(rune(48 + i%100)) graph.AddTask(&Task{ID: "T" + id}) } } func BenchmarkAddDependency(b *testing.B) { graph := NewDependencyGraph() for i := 0; i < 1000; i++ { graph.AddTask(&Task{ID: "T" + string(rune(48+i%100))}) } b.ResetTimer() for i := 0; i < b.N; i++ { from := "T" + string(rune(48+i%100)) to := "T" + string(rune(48+(i+1)%100)) graph.AddDependency(from, to) } } func BenchmarkGetReadyTasks(b *testing.B) { graph := NewDependencyGraph() for i := 1; i <= 100; i++ { id := "T" + string(rune(48+i%100)) graph.AddTask(&Task{ID: id}) } b.ResetTimer() for i := 0; i < b.N; i++ { graph.GetReadyTasks() } }