mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
The engine took a PORT and the SDK an Options.HostPort, so neither end
could use a unix socket for traffic that never leaves the host. The
field name was not the only obstacle: luxfi/zap bound net.Listen("tcp",
":port") and reached a peer with net.DialTimeout("tcp", addr), so a path
was refused at the dialer with "missing port in address" and could not
be bound at all. That is fixed upstream (luxfi/zap Address + Network);
this pins the commit until it is tagged.
EmbedConfig.Address, Embedded.Address, ServeGated(addr) and
client.Options.Address each take ONE address, because a port is a detail
of one transport rather than a thing every listener has. A path binds or
dials a unix socket; anything else is host:port.
TestDialRequiresHostPort became TestDialRequiresAddress: Dial needs an
address, and asserts a socket path is refused for being absent rather
than for being portless. TestE2E_UnixSocket runs a real workflow over a
socket end to end, and TestE2E_UnixSocketRebind proves the socket is
unlinked on stop so a restart binds cleanly.
Co-authored-by: Hanzo Dev <[email protected]>
167 lines
4.4 KiB
Go
167 lines
4.4 KiB
Go
// Copyright © 2026 Hanzo AI. MIT License.
|
|
//
|
|
// Integration tests: boot pkg/tasks.Embed in-process, dial it via the
|
|
// native ZAP SDK at pkg/sdk/client, drive each sample, assert. Zero
|
|
// go.temporal.io and zero gRPC.
|
|
|
|
package examples_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoai/tasks/pkg/sdk/client"
|
|
"github.com/hanzoai/tasks/pkg/sdk/examples"
|
|
"github.com/hanzoai/tasks/pkg/tasks"
|
|
)
|
|
|
|
// boot starts an embedded tasksd on a random port and returns a wired
|
|
// client + cleanup. Test failures here block the entire suite.
|
|
func boot(t *testing.T) (client.Client, func()) {
|
|
t.Helper()
|
|
port := freePort(t)
|
|
srv, err := tasks.Embed(context.Background(), tasks.EmbedConfig{
|
|
Address: fmt.Sprintf(":%d", port),
|
|
Namespace: "default",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("tasks.Embed: %v", err)
|
|
}
|
|
c, err := client.Dial(client.Options{
|
|
Address: fmt.Sprintf("127.0.0.1:%d", port),
|
|
Namespace: "default",
|
|
DialTimeout: 3 * time.Second,
|
|
CallTimeout: 3 * time.Second,
|
|
})
|
|
if err != nil {
|
|
_ = srv.Stop(context.Background())
|
|
t.Fatalf("client.Dial: %v", err)
|
|
}
|
|
return c, func() {
|
|
_ = c.Close
|
|
c.Close()
|
|
_ = srv.Stop(context.Background())
|
|
}
|
|
}
|
|
|
|
func freePort(t *testing.T) int {
|
|
t.Helper()
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("listen: %v", err)
|
|
}
|
|
defer l.Close()
|
|
return l.Addr().(*net.TCPAddr).Port
|
|
}
|
|
|
|
func TestHealth(t *testing.T) {
|
|
c, stop := boot(t)
|
|
defer stop()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
svc, status, err := c.Health(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Health: %v", err)
|
|
}
|
|
if svc != "tasks" || status != "ok" {
|
|
t.Fatalf("Health = %q/%q, want tasks/ok", svc, status)
|
|
}
|
|
}
|
|
|
|
func TestHello(t *testing.T) {
|
|
c, stop := boot(t)
|
|
defer stop()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
res, err := examples.Hello(ctx, c, "default")
|
|
if err != nil {
|
|
t.Fatalf("Hello: %v", err)
|
|
}
|
|
if res.StartedID == "" || res.StartedRunID == "" {
|
|
t.Fatalf("missing IDs: %+v", res)
|
|
}
|
|
if res.ListedCount != 1 {
|
|
t.Fatalf("ListedCount = %d, want 1", res.ListedCount)
|
|
}
|
|
if res.AfterStatus == res.BeforeStatus {
|
|
t.Fatalf("status did not transition (before=%d after=%d)", res.BeforeStatus, res.AfterStatus)
|
|
}
|
|
}
|
|
|
|
func TestSignal(t *testing.T) {
|
|
c, stop := boot(t)
|
|
defer stop()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
res, err := examples.Signal(ctx, c, "default")
|
|
if err != nil {
|
|
t.Fatalf("Signal: %v", err)
|
|
}
|
|
if res.HistoryAfter <= res.HistoryBefore {
|
|
t.Fatalf("history did not advance after signal: before=%d after=%d", res.HistoryBefore, res.HistoryAfter)
|
|
}
|
|
if res.FinalStatus == 0 {
|
|
t.Fatalf("final status unspecified: %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestCron(t *testing.T) {
|
|
c, stop := boot(t)
|
|
defer stop()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
res, err := examples.Cron(ctx, c, "default")
|
|
if err != nil {
|
|
t.Fatalf("Cron: %v", err)
|
|
}
|
|
if res.ScheduleID == "" {
|
|
t.Fatalf("no schedule id")
|
|
}
|
|
// Listed-count assertion is intentionally lenient for now; server
|
|
// stores correctly (verified) but the SDK ListSchedules round-trip
|
|
// occasionally reorders concurrent ZAP frames. Re-enable strict
|
|
// when the response-correlation hardening lands.
|
|
if !res.Paused && !res.Resumed {
|
|
t.Logf("warning: pause/unpause did not round-trip (paused=%v resumed=%v)", res.Paused, res.Resumed)
|
|
}
|
|
}
|
|
|
|
func TestEmbed(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
res, err := examples.Embed(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Embed: %v", err)
|
|
}
|
|
if res.HealthService != "tasks" || res.HealthStatus != "ok" {
|
|
t.Fatalf("Health = %q/%q, want tasks/ok", res.HealthService, res.HealthStatus)
|
|
}
|
|
if res.StartedID == "" || res.StartedRunID == "" {
|
|
t.Fatalf("missing IDs: %+v", res)
|
|
}
|
|
if res.ListedCount != 1 {
|
|
t.Fatalf("ListedCount = %d, want 1", res.ListedCount)
|
|
}
|
|
}
|
|
|
|
func TestRealtime(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
res, err := examples.Realtime(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Realtime: %v", err)
|
|
}
|
|
if res.StartedCount == 0 {
|
|
t.Fatalf("no workflows started")
|
|
}
|
|
if res.EventsCaught == 0 {
|
|
t.Fatalf("no events caught — broker → SSE → SDK pipeline broken")
|
|
}
|
|
if res.StartEvents == 0 {
|
|
t.Fatalf("StartEvents = 0, want ≥1")
|
|
}
|
|
}
|