Files
tasks/components/callbacks/statemachine_test.go
zeekay 5a601d081b rebrand: go.temporal.io/server -> github.com/hanzoai/tasks
Full module path rebrand from Temporal to Hanzo Tasks:
- go.mod module: github.com/hanzoai/tasks
- All internal Go imports updated (1906 files)
- cmd/server renamed to cmd/tasksd
- Binary name: tasksd (was temporal-server)
- Docker images: hanzoai/tasks (was temporalio/server)
- README rewritten with Hanzo Tasks branding
- LLM.md created with integration notes
- Makefile, goreleaser, CI workflows updated
- go.temporal.io/sdk and go.temporal.io/api imports preserved (separate repos)

Fork of Temporal.io (MIT license).
2026-03-19 10:36:40 -07:00

195 lines
6.0 KiB
Go

package callbacks_test
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
enumsspb "github.com/hanzoai/tasks/api/enums/v1"
persistencespb "github.com/hanzoai/tasks/api/persistence/v1"
"github.com/hanzoai/tasks/common"
"github.com/hanzoai/tasks/common/backoff"
"github.com/hanzoai/tasks/components/callbacks"
"github.com/hanzoai/tasks/service/history/hsm"
)
func TestValidTransitions(t *testing.T) {
// Setup
currentTime := time.Now().UTC()
callback := callbacks.Callback{
&persistencespb.CallbackInfo{
Callback: &persistencespb.Callback{
Variant: &persistencespb.Callback_Nexus_{
Nexus: &persistencespb.Callback_Nexus{
Url: "http://address:666/path/to/callback?query=string",
},
},
},
State: enumsspb.CALLBACK_STATE_SCHEDULED,
},
}
// AttemptFailed
out, err := callbacks.TransitionAttemptFailed.Apply(callback, callbacks.EventAttemptFailed{
Time: currentTime,
Err: fmt.Errorf("test"),
RetryPolicy: backoff.NewExponentialRetryPolicy(time.Second),
})
require.NoError(t, err)
// Assert info object is updated
require.Equal(t, enumsspb.CALLBACK_STATE_BACKING_OFF, callback.State())
require.Equal(t, int32(1), callback.Attempt)
require.Equal(t, "test", callback.LastAttemptFailure.Message)
require.False(t, callback.LastAttemptFailure.GetApplicationFailureInfo().NonRetryable)
require.Equal(t, currentTime, callback.LastAttemptCompleteTime.AsTime())
dt := currentTime.Add(time.Second).Sub(callback.NextAttemptScheduleTime.AsTime())
require.True(t, dt < time.Millisecond*200)
// Assert backoff task is generated
require.Equal(t, 1, len(out.Tasks))
boTask := out.Tasks[0].(callbacks.BackoffTask)
require.Equal(t, callback.NextAttemptScheduleTime.AsTime(), boTask.Deadline())
// Rescheduled
out, err = callbacks.TransitionRescheduled.Apply(callback, callbacks.EventRescheduled{})
require.NoError(t, err)
// Assert info object is updated only where needed
require.Equal(t, enumsspb.CALLBACK_STATE_SCHEDULED, callback.State())
require.Equal(t, int32(1), callback.Attempt)
require.Equal(t, "test", callback.LastAttemptFailure.Message)
// Remains unmodified
require.Equal(t, currentTime, callback.LastAttemptCompleteTime.AsTime())
require.Nil(t, callback.NextAttemptScheduleTime)
// Assert callback task is generated
require.Equal(t, 1, len(out.Tasks))
cbTask := out.Tasks[0].(callbacks.InvocationTask)
require.Equal(t, "http://address:666", cbTask.Destination())
// Store the pre-succeeded state to test Failed later
dup := callbacks.Callback{common.CloneProto(callback.CallbackInfo)}
// Succeeded
currentTime = currentTime.Add(time.Second)
out, err = callbacks.TransitionSucceeded.Apply(callback, callbacks.EventSucceeded{Time: currentTime})
require.NoError(t, err)
// Assert info object is updated only where needed
require.Equal(t, enumsspb.CALLBACK_STATE_SUCCEEDED, callback.State())
require.Equal(t, int32(2), callback.Attempt)
require.Nil(t, callback.LastAttemptFailure)
require.Equal(t, currentTime, callback.LastAttemptCompleteTime.AsTime())
require.Nil(t, callback.NextAttemptScheduleTime)
// Assert no additional tasks are generated
require.Equal(t, 0, len(out.Tasks))
// Reset back to scheduled
callback = dup
// Increment the time to ensure it's updated in the transition
currentTime = currentTime.Add(time.Second)
// failed
out, err = callbacks.TransitionFailed.Apply(callback, callbacks.EventFailed{Time: currentTime, Err: fmt.Errorf("failed")})
require.NoError(t, err)
// Assert info object is updated only where needed
require.Equal(t, enumsspb.CALLBACK_STATE_FAILED, callback.State())
require.Equal(t, int32(2), callback.Attempt)
require.Equal(t, "failed", callback.LastAttemptFailure.Message)
require.True(t, callback.LastAttemptFailure.GetApplicationFailureInfo().NonRetryable)
require.Equal(t, currentTime, callback.LastAttemptCompleteTime.AsTime())
require.Nil(t, callback.NextAttemptScheduleTime)
// Assert no additional tasks are generated
require.Equal(t, 0, len(out.Tasks))
}
func TestCompareState(t *testing.T) {
reg := hsm.NewRegistry()
require.NoError(t, callbacks.RegisterStateMachine(reg))
def, ok := reg.Machine(callbacks.StateMachineType)
require.True(t, ok)
cases := []struct {
name string
s1, s2 enumsspb.CallbackState
attempts1, attempts2 int32
sign int
expectError bool
}{
{
name: "standby < scheduled",
s1: enumsspb.CALLBACK_STATE_STANDBY,
s2: enumsspb.CALLBACK_STATE_SCHEDULED,
sign: -1,
},
{
name: "scheduled = scheduled",
s1: enumsspb.CALLBACK_STATE_SCHEDULED,
s2: enumsspb.CALLBACK_STATE_SCHEDULED,
attempts1: 2,
attempts2: 2,
sign: 0,
},
{
name: "succeeded not comparable to failed",
s1: enumsspb.CALLBACK_STATE_SUCCEEDED,
s2: enumsspb.CALLBACK_STATE_FAILED,
expectError: true,
},
{
name: "backing off < failed",
s1: enumsspb.CALLBACK_STATE_BACKING_OFF,
s2: enumsspb.CALLBACK_STATE_FAILED,
sign: -1,
},
{
name: "scheduled > backing off",
s1: enumsspb.CALLBACK_STATE_SCHEDULED,
s2: enumsspb.CALLBACK_STATE_BACKING_OFF,
sign: 1,
},
{
name: "backing off < scheduled with greater attempt",
s1: enumsspb.CALLBACK_STATE_BACKING_OFF,
s2: enumsspb.CALLBACK_STATE_SCHEDULED,
attempts2: 1,
sign: -1,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
s1 := callbacks.Callback{
CallbackInfo: &persistencespb.CallbackInfo{
State: tc.s1,
Attempt: tc.attempts1,
},
}
s2 := callbacks.Callback{
CallbackInfo: &persistencespb.CallbackInfo{
State: tc.s2,
Attempt: tc.attempts2,
},
}
res, err := def.CompareState(s1, s2)
if tc.expectError {
require.Error(t, err)
return
}
require.NoError(t, err)
if tc.sign == 0 {
require.Equal(t, 0, res)
} else if tc.sign > 0 {
require.Greater(t, res, 0)
} else {
require.Greater(t, 0, res)
}
})
}
}