Files
langship.sh/pkg/engine/dag_test.go
T

131 lines
3.7 KiB
Go

package engine
import (
"reflect"
"sort"
"testing"
"github.com/lyzrai/flow/pkg/models"
)
func TestBuildDAG_populatesEdgesAndDegrees(t *testing.T) {
wf := &models.WorkflowDefinition{
Nodes: []models.NodeDef{
{ID: "1", Name: "T", Type: "flow-nodes-base.trigger"},
{ID: "2", Name: "A", Type: "flow-nodes-base.set"},
{ID: "3", Name: "B", Type: "flow-nodes-base.noOp"},
},
Connections: []models.ConnectionDef{
{SourceNode: "T", TargetNode: "A", SourceOutputIndex: 0, TargetInputIndex: 0},
{SourceNode: "A", TargetNode: "B", SourceOutputIndex: 0, TargetInputIndex: 0},
},
}
dag, err := BuildDAG(wf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if dag.InDegree["T"] != 0 || dag.InDegree["A"] != 1 || dag.InDegree["B"] != 1 {
t.Errorf("indegrees: %+v", dag.InDegree)
}
if len(dag.Adjacency["T"]) != 1 || dag.Adjacency["T"][0].Target != "A" {
t.Errorf("T adjacency: %+v", dag.Adjacency["T"])
}
if len(dag.InEdges["B"]) != 1 || dag.InEdges["B"][0].Target != "A" {
t.Errorf("B in-edges: %+v", dag.InEdges["B"])
}
}
func TestBuildDAG_rejectsDuplicateNodeNames(t *testing.T) {
wf := &models.WorkflowDefinition{
Nodes: []models.NodeDef{
{ID: "1", Name: "X", Type: "flow-nodes-base.trigger"},
{ID: "2", Name: "X", Type: "flow-nodes-base.set"},
},
}
if _, err := BuildDAG(wf); err == nil {
t.Fatal("expected duplicate-name error")
}
}
func TestTopologicalSort_orderRespectsDependencies(t *testing.T) {
wf := &models.WorkflowDefinition{
Nodes: []models.NodeDef{
{ID: "1", Name: "T", Type: "flow-nodes-base.trigger"},
{ID: "2", Name: "A", Type: "flow-nodes-base.set"},
{ID: "3", Name: "B", Type: "flow-nodes-base.noOp"},
},
Connections: []models.ConnectionDef{
{SourceNode: "T", TargetNode: "A"},
{SourceNode: "A", TargetNode: "B"},
},
}
dag, _ := BuildDAG(wf)
order, err := dag.TopologicalSort()
if err != nil {
t.Fatalf("unexpected: %v", err)
}
idx := map[string]int{}
for i, n := range order {
idx[n] = i
}
if !(idx["T"] < idx["A"] && idx["A"] < idx["B"]) {
t.Fatalf("unexpected order %v", order)
}
}
func TestTopologicalSort_detectsCycle(t *testing.T) {
wf := &models.WorkflowDefinition{
Nodes: []models.NodeDef{
{ID: "1", Name: "A", Type: "flow-nodes-base.set"},
{ID: "2", Name: "B", Type: "flow-nodes-base.set"},
},
Connections: []models.ConnectionDef{
{SourceNode: "A", TargetNode: "B"},
{SourceNode: "B", TargetNode: "A"},
},
}
dag, _ := BuildDAG(wf)
if _, err := dag.TopologicalSort(); err == nil {
t.Fatal("expected cycle error")
}
}
func TestHasApprovalNode(t *testing.T) {
with := &models.WorkflowDefinition{Nodes: []models.NodeDef{{Type: "flow-nodes-base.waitForApproval"}}}
without := &models.WorkflowDefinition{Nodes: []models.NodeDef{{Type: "flow-nodes-base.set"}}}
if !HasApprovalNode(with) {
t.Error("expected true")
}
if HasApprovalNode(without) {
t.Error("expected false")
}
}
func TestGetStartNodes(t *testing.T) {
wf := &models.WorkflowDefinition{
Nodes: []models.NodeDef{
{ID: "1", Name: "T1", Type: "flow-nodes-base.trigger"},
{ID: "2", Name: "T2", Type: "flow-nodes-base.trigger"},
{ID: "3", Name: "Mid", Type: "flow-nodes-base.set"},
},
Connections: []models.ConnectionDef{{SourceNode: "T1", TargetNode: "Mid"}},
}
dag, _ := BuildDAG(wf)
got := dag.GetStartNodes()
sort.Strings(got)
want := []string{"Mid", "T1", "T2"}
// Mid has incoming edge so it should be excluded; T2 has no incoming so it's a start.
want = []string{"T1", "T2"}
// recompute filter
filtered := []string{}
for _, n := range got {
if n != "Mid" {
filtered = append(filtered, n)
}
}
sort.Strings(filtered)
if !reflect.DeepEqual(filtered, want) {
t.Fatalf("starts: got %v want %v", filtered, want)
}
}