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

148 lines
3.9 KiB
Go

package engine
import (
"context"
"errors"
"testing"
"github.com/lyzrai/flow/pkg/models"
)
func TestExecuteWithRetry_skipsRetryForNonRetryableNode(t *testing.T) {
calls := 0
exec := func(_ context.Context, _ models.NodeDef, _ [][]models.Item, _ *ExecutionContext) (map[int][]models.Item, error) {
calls++
return nil, errors.New("boom")
}
node := models.NodeDef{
Type: "flow-nodes-base.set", // not in retryableNodeTypes
Settings: map[string]any{
"retryOnFail": true,
"maxTries": float64(3),
"waitBetweenTries": float64(1),
},
}
_, err := executeWithRetry(context.Background(), exec, node, nil, nil)
if err == nil {
t.Fatal("expected error")
}
if calls != 1 {
t.Fatalf("expected 1 call (retry disabled), got %d", calls)
}
}
func TestExecuteWithRetry_retriesRetryableNode(t *testing.T) {
calls := 0
exec := func(_ context.Context, _ models.NodeDef, _ [][]models.Item, _ *ExecutionContext) (map[int][]models.Item, error) {
calls++
if calls < 3 {
return nil, errors.New("flaky")
}
return map[int][]models.Item{0: {{"ok": true}}}, nil
}
node := models.NodeDef{
Type: "flow-nodes-base.httpRequest",
Settings: map[string]any{
"retryOnFail": true,
"maxTries": float64(5),
"waitBetweenTries": float64(1),
},
}
got, err := executeWithRetry(context.Background(), exec, node, nil, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if calls != 3 {
t.Fatalf("expected 3 calls, got %d", calls)
}
if got[0][0]["ok"] != true {
t.Fatalf("unexpected output: %+v", got)
}
}
func TestExecuteWithRetry_noRetryWhenFlagOff(t *testing.T) {
calls := 0
exec := func(_ context.Context, _ models.NodeDef, _ [][]models.Item, _ *ExecutionContext) (map[int][]models.Item, error) {
calls++
return nil, errors.New("boom")
}
node := models.NodeDef{
Type: "flow-nodes-base.httpRequest",
// No Settings → retryOnFail defaults false.
}
_, _ = executeWithRetry(context.Background(), exec, node, nil, nil)
if calls != 1 {
t.Fatalf("expected 1 call, got %d", calls)
}
}
func TestForwardInputs_storesFlattenedItemsOnOutput0(t *testing.T) {
wf := &models.WorkflowDefinition{Nodes: []models.NodeDef{{Name: "X"}}}
dag, _ := BuildDAG(wf)
c := NewExecutionContext(wf, dag)
forwardInputs(c, "X", [][]models.Item{{{"a": 1}}, {{"b": 2}}})
got := c.GetNodeOutput("X", 0)
if len(got) != 2 {
t.Fatalf("expected 2 forwarded items, got %d", len(got))
}
}
func TestGetTerminalOutputs_excludesNodesWithDownstream(t *testing.T) {
wf := &models.WorkflowDefinition{
Nodes: []models.NodeDef{
{ID: "1", Name: "A"},
{ID: "2", Name: "B"},
{ID: "3", Name: "C"},
},
Connections: []models.ConnectionDef{
{SourceNode: "A", TargetNode: "B"},
{SourceNode: "B", TargetNode: "C"},
},
}
dag, _ := BuildDAG(wf)
c := NewExecutionContext(wf, dag)
c.SetOutput("A", 0, []models.Item{{"a": 1}})
c.SetOutput("B", 0, []models.Item{{"b": 1}})
c.SetOutput("C", 0, []models.Item{{"c": 1}})
got := getTerminalOutputs(c, dag)
if _, ok := got["C"]; !ok {
t.Fatal("C should be terminal")
}
if _, ok := got["A"]; ok {
t.Fatal("A should not be terminal")
}
if _, ok := got["B"]; ok {
t.Fatal("B should not be terminal")
}
}
func TestExecutionContext_GatherInputs(t *testing.T) {
wf := &models.WorkflowDefinition{
Nodes: []models.NodeDef{{ID: "1", Name: "A"}, {ID: "2", Name: "B"}},
Connections: []models.ConnectionDef{
{SourceNode: "A", SourceOutputIndex: 0, TargetNode: "B", TargetInputIndex: 0},
},
}
dag, _ := BuildDAG(wf)
c := NewExecutionContext(wf, dag)
c.SetOutput("A", 0, []models.Item{{"k": "v"}})
inputs := c.GatherInputs("B")
if len(inputs) != 1 || len(inputs[0]) != 1 || inputs[0][0]["k"] != "v" {
t.Fatalf("unexpected gather: %+v", inputs)
}
}
func TestExecutionContext_DelegationFlag(t *testing.T) {
c := &ExecutionContext{}
if c.IsDelegated("X") {
t.Fatal("default false")
}
c.MarkDelegated("X")
if !c.IsDelegated("X") {
t.Fatal("expected true after Mark")
}
}