mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
pkg/sdk/examples/{hello,signal,cron}.go drive the engine end-to-end
through pkg/sdk/client over the ZAP wire — zero HTTP, zero
go.temporal.io. Tests boot tasks.Embed in-process, dial the local
ZAP node, and round-trip every workflow + schedule opcode.
Engine fixes (pkg/tasks/embed.go):
- ZAP handlers now read the SDK's snake_case JSON shape
(workflow_id / workflow_type / task_queue / signal_name / schedule_id)
in addition to the camelCase shape used by the HTTP shim
- DescribeWorkflow returns {info: WorkflowExecutionInfo} (SDK shape)
- WorkflowExecutionInfo emits status as int8 (1=RUNNING / 4=CANCELED / 5=TERMINATED)
- StartWorkflow returns {run_id, workflow_id}
- PauseSchedule respects the requested paused flag (was always true)
- Schedule list response goes through scheduleSDK marshaller
- Empty time.Time fields are omitted (SDK can't parse "")
Store fix:
- store.list() snapshots under RLock then iterates without lock,
preventing the RWMutex upgrade deadlock that hung GET /v1/tasks/...
whenever the cron sweeper's put() was waiting on a Lock that
competed with reader RLocks
go test ./pkg/sdk/examples/... → 4/4 PASS:
TestHealth, TestHello, TestSignal, TestCron
78 lines
2.1 KiB
Go
78 lines
2.1 KiB
Go
// Copyright © 2026 Hanzo AI. MIT License.
|
|
|
|
// Package examples — workflow patterns that exercise the Hanzo Tasks
|
|
// engine end-to-end via the native ZAP SDK at pkg/sdk/client. Zero
|
|
// go.temporal.io/* and zero google.golang.org/grpc imports.
|
|
//
|
|
// The samples run against an embedded tasksd boot (see _test.go) so
|
|
// they double as the integration test corpus for every shipped
|
|
// release.
|
|
package examples
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/hanzoai/tasks/pkg/sdk/client"
|
|
)
|
|
|
|
// Hello starts a no-input workflow and confirms its execution record
|
|
// is round-trippable across the full client surface: ExecuteWorkflow →
|
|
// DescribeWorkflow → ListWorkflows → CancelWorkflow → DescribeWorkflow.
|
|
//
|
|
// The returned Result captures the lifecycle so tests can assert on
|
|
// each transition without re-issuing the calls.
|
|
type HelloResult struct {
|
|
StartedID string
|
|
StartedRunID string
|
|
BeforeStatus int
|
|
AfterStatus int
|
|
ListedCount int
|
|
}
|
|
|
|
// Hello drives the basic workflow lifecycle. It never blocks on
|
|
// workflow completion (the engine doesn't execute user code yet);
|
|
// it only verifies the control-plane round-trips end-to-end.
|
|
func Hello(ctx context.Context, c client.Client, namespace string) (*HelloResult, error) {
|
|
if c == nil {
|
|
return nil, errors.New("examples: client is nil")
|
|
}
|
|
run, err := c.ExecuteWorkflow(ctx, client.StartWorkflowOptions{
|
|
ID: "hello-" + time.Now().UTC().Format("150405.000000"),
|
|
TaskQueue: "default",
|
|
}, "Hello")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
id, runID := run.GetID(), run.GetRunID()
|
|
res := &HelloResult{StartedID: id, StartedRunID: runID}
|
|
|
|
info, err := c.DescribeWorkflow(ctx, id, runID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res.BeforeStatus = int(info.Status)
|
|
|
|
list, err := c.ListWorkflows(ctx, "", 100, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, e := range list.Executions {
|
|
if e.WorkflowID == id {
|
|
res.ListedCount++
|
|
}
|
|
}
|
|
|
|
if err := c.CancelWorkflow(ctx, id, runID); err != nil {
|
|
return nil, err
|
|
}
|
|
info2, err := c.DescribeWorkflow(ctx, id, runID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res.AfterStatus = int(info2.Status)
|
|
|
|
return res, nil
|
|
}
|