Files
tasks/pkg/sdk/workflow/execute_child_workflow_test.go
zeekay 072ecefbfe feat(sdk): close caller-symbol gap for base/commerce migration
Adds every symbol hanzoai/base/plugins/tasks and hanzoai/commerce
call today that was missing from pkg/sdk/*: SignalWithStartWorkflow,
GetWorkflow, QueryWorkflow, CheckHealth, WorkflowRunGetOptions +
WorkflowRun.GetWithOptions, ExecuteChildWorkflow + ChildWorkflowFuture,
LocalActivityOptions + WithLocalActivityOptions + ExecuteLocalActivity
(Phase-1 remote-path), worker.InterruptCh(), worker.Options rate-limit
fields wired via golang.org/x/time/rate.

New opcodes 0x0066 signalWithStartWorkflow / 0x0067 queryWorkflow
land here; frontend-side handlers were wired in PR-A via the
translate.go dispatch table.

Closes red-review §2 gap table. Dry-run base migration compiles.
2026-04-23 12:53:15 -07:00

105 lines
3.0 KiB
Go

// Copyright © 2026 Hanzo AI. MIT License.
package workflow
import (
"errors"
"testing"
)
// DemoChildWorkflow is a test-only workflow function value used as the
// child-workflow handle argument.
func DemoChildWorkflow(_ Context, _ string) (int, error) { return 0, nil }
// TestExecuteChildWorkflowStub exercises the stub env's queued-reply
// behaviour and confirms the ChildWorkflowFuture shape is consistent
// with a plain Future on result + a side-band execution future.
func TestExecuteChildWorkflowStub(t *testing.T) {
t.Parallel()
env := NewStubEnv()
env.OnChildWorkflow(DemoChildWorkflow).Return(42, nil)
ctx := NewContextFromEnv(env)
cf := ExecuteChildWorkflow(ctx, DemoChildWorkflow, "hello")
// Execution future settles first (stub settles both synchronously;
// test the ordering contract by reading execution eagerly).
var got WorkflowExecution
if err := cf.GetChildWorkflowExecution().Get(ctx, &got); err != nil {
t.Fatalf("GetChildWorkflowExecution.Get: %v", err)
}
if got.WorkflowID == "" {
t.Fatal("expected non-empty child WorkflowID from stub")
}
// Result future decodes the queued return value.
var result int
if err := cf.Get(ctx, &result); err != nil {
t.Fatalf("Get result: %v", err)
}
if result != 42 {
t.Fatalf("result = %d, want 42", result)
}
}
// TestExecuteChildWorkflowSurfacesError propagates child errors to the
// parent via the result future.
func TestExecuteChildWorkflowSurfacesError(t *testing.T) {
t.Parallel()
env := NewStubEnv()
env.OnChildWorkflow(DemoChildWorkflow).Return(0, errors.New("boom"))
ctx := NewContextFromEnv(env)
cf := ExecuteChildWorkflow(ctx, DemoChildWorkflow)
var out any
if err := cf.Get(ctx, &out); err == nil {
t.Fatal("expected error from child workflow")
}
}
// TestExecuteChildWorkflowNilArgsAccepted confirms nil/empty args are
// not a validation error at the caller — matches upstream.
func TestExecuteChildWorkflowNilArgsAccepted(t *testing.T) {
t.Parallel()
env := NewStubEnv()
env.OnChildWorkflow(DemoChildWorkflow).Return(nil, nil)
ctx := NewContextFromEnv(env)
cf := ExecuteChildWorkflow(ctx, DemoChildWorkflow)
var out any
if err := cf.Get(ctx, &out); err != nil {
t.Fatalf("Get: %v", err)
}
}
// TestExecuteChildWorkflowNilChild rejects a nil child handle with a
// typed error.
func TestExecuteChildWorkflowNilChild(t *testing.T) {
t.Parallel()
env := NewStubEnv()
ctx := NewContextFromEnv(env)
cf := ExecuteChildWorkflow(ctx, nil)
var out any
if err := cf.Get(ctx, &out); err == nil {
t.Fatal("expected error from nil child handle")
}
}
// TestChildWorkflowFutureShape exercises the ReadyCh/IsReady plumbing
// that the Selector depends on.
func TestChildWorkflowFutureShape(t *testing.T) {
t.Parallel()
env := NewStubEnv()
env.OnChildWorkflow(DemoChildWorkflow).Return(1, nil)
ctx := NewContextFromEnv(env)
cf := ExecuteChildWorkflow(ctx, DemoChildWorkflow)
select {
case <-cf.ReadyCh():
// settled
default:
t.Fatal("stub-env ChildWorkflowFuture must settle synchronously")
}
if !cf.IsReady() {
t.Fatal("IsReady must be true after settle")
}
}