mirror of
https://github.com/open-gitagent/langship.sh.git
synced 2026-08-03 07:21:04 +02:00
174 lines
5.0 KiB
Go
174 lines
5.0 KiB
Go
package executors
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/lyzrai/flow/pkg/durability"
|
|
"github.com/lyzrai/flow/pkg/engine"
|
|
"github.com/lyzrai/flow/pkg/models"
|
|
)
|
|
|
|
func TestTrigger_emitsAtLeastOneItemWhenInputEmpty(t *testing.T) {
|
|
out, err := (&TriggerExecutor{}).Execute(context.Background(),
|
|
models.NodeDef{Name: "t", Type: "flow-nodes-base.trigger"}, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(out[0]) != 1 {
|
|
t.Fatalf("expected 1 item on output 0, got %d", len(out[0]))
|
|
}
|
|
}
|
|
|
|
func TestTrigger_passesThroughInputItems(t *testing.T) {
|
|
in := [][]models.Item{{{"a": 1}, {"a": 2}}}
|
|
out, err := (&TriggerExecutor{}).Execute(context.Background(),
|
|
models.NodeDef{Name: "t", Type: "flow-nodes-base.trigger"}, in, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(out[0]) != 2 {
|
|
t.Fatalf("expected 2 items, got %d", len(out[0]))
|
|
}
|
|
}
|
|
|
|
func TestNoOp_passesAllItemsThrough(t *testing.T) {
|
|
in := [][]models.Item{{{"x": "a"}}, {{"x": "b"}}}
|
|
out, err := (&NoOpExecutor{}).Execute(context.Background(), models.NodeDef{}, in, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(out[0]) != 2 {
|
|
t.Fatalf("expected 2 items, got %d", len(out[0]))
|
|
}
|
|
}
|
|
|
|
func TestSet_v3Assignments(t *testing.T) {
|
|
node := models.NodeDef{Parameters: map[string]any{
|
|
"assignments": map[string]any{
|
|
"assignments": []any{
|
|
map[string]any{"name": "greeting", "value": "hi", "type": "string"},
|
|
map[string]any{"name": "version", "value": float64(2), "type": "number"},
|
|
},
|
|
},
|
|
}}
|
|
in := [][]models.Item{{{"existing": true}}}
|
|
out, err := (&SetExecutor{}).Execute(context.Background(), node, in, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(out[0]) != 1 {
|
|
t.Fatalf("expected 1 item, got %d", len(out[0]))
|
|
}
|
|
got := out[0][0]
|
|
if got["greeting"] != "hi" || got["version"] != float64(2) || got["existing"] != true {
|
|
t.Fatalf("unexpected merged item: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestSet_v3FieldsValuesShape(t *testing.T) {
|
|
node := models.NodeDef{Parameters: map[string]any{
|
|
"fields": map[string]any{
|
|
"values": []any{
|
|
map[string]any{"name": "country", "stringValue": "IN"},
|
|
map[string]any{"name": "rank", "numberValue": float64(7)},
|
|
},
|
|
},
|
|
}}
|
|
in := [][]models.Item{{{}}}
|
|
out, err := (&SetExecutor{}).Execute(context.Background(), node, in, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
got := out[0][0]
|
|
if got["country"] != "IN" || got["rank"] != float64(7) {
|
|
t.Fatalf("unexpected: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestSet_legacyValuesShape(t *testing.T) {
|
|
node := models.NodeDef{Parameters: map[string]any{
|
|
"values": map[string]any{
|
|
"string": []any{
|
|
map[string]any{"name": "env", "value": "prod"},
|
|
},
|
|
"number": []any{
|
|
map[string]any{"name": "port", "value": float64(8080)},
|
|
},
|
|
},
|
|
}}
|
|
in := [][]models.Item{{{}}}
|
|
out, err := (&SetExecutor{}).Execute(context.Background(), node, in, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
got := out[0][0]
|
|
if got["env"] != "prod" || got["port"] != float64(8080) {
|
|
t.Fatalf("unexpected: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestSet_doesNotMutateInputItem(t *testing.T) {
|
|
original := models.Item{"k": "v"}
|
|
node := models.NodeDef{Parameters: map[string]any{
|
|
"assignments": map[string]any{
|
|
"assignments": []any{map[string]any{"name": "k", "value": "v2"}},
|
|
},
|
|
}}
|
|
in := [][]models.Item{{original}}
|
|
out, _ := (&SetExecutor{}).Execute(context.Background(), node, in, nil)
|
|
if out[0][0]["k"] != "v2" {
|
|
t.Fatalf("expected output to be overridden, got %v", out[0][0]["k"])
|
|
}
|
|
if original["k"] != "v" {
|
|
t.Fatal("Set must not mutate the input item in place")
|
|
}
|
|
}
|
|
|
|
// Approval node refuses to run without a Restate context, returning a
|
|
// PermanentError so the engine doesn't retry.
|
|
func TestApproval_requiresRestateContext(t *testing.T) {
|
|
node := models.NodeDef{Name: "Approve", Type: "flow-nodes-base.waitForApproval"}
|
|
in := [][]models.Item{{{}}}
|
|
|
|
_, err := (&ApprovalExecutor{}).Execute(context.Background(), node, in, &engine.ExecutionContext{})
|
|
if err == nil {
|
|
t.Fatal("expected approval node to refuse running outside Restate")
|
|
}
|
|
var perm *durability.PermanentError
|
|
if !errors.As(err, &perm) {
|
|
t.Fatalf("expected PermanentError so engine doesn't retry, got %T", err)
|
|
}
|
|
}
|
|
|
|
func TestRegistry_RegisterAll_registersExpectedTypes(t *testing.T) {
|
|
RegisterAll()
|
|
for _, want := range []string{
|
|
"flow-nodes-base.trigger",
|
|
"flow-nodes-base.noOp",
|
|
"flow-nodes-base.set",
|
|
"flow-nodes-base.waitForApproval",
|
|
} {
|
|
if _, err := Get(want); err != nil {
|
|
t.Errorf("missing executor for %q: %v", want, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildLookup_returnsFunctioningExecutor(t *testing.T) {
|
|
RegisterAll()
|
|
lookup := BuildLookup()
|
|
fn, err := lookup("flow-nodes-base.trigger")
|
|
if err != nil {
|
|
t.Fatalf("lookup miss: %v", err)
|
|
}
|
|
out, err := fn(context.Background(), models.NodeDef{Type: "flow-nodes-base.trigger"}, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("trigger execution failed: %v", err)
|
|
}
|
|
if len(out[0]) != 1 {
|
|
t.Fatalf("trigger should emit 1 item, got %d", len(out[0]))
|
|
}
|
|
}
|