File size: 3,665 Bytes
6a7089a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | package scheduler
import (
"testing"
"time"
)
func TestTaskStateIsTerminal(t *testing.T) {
terminal := []TaskState{StateDone, StateFailed, StateCancelled, StateRejected}
for _, s := range terminal {
if !s.IsTerminal() {
t.Errorf("expected %q to be terminal", s)
}
}
nonTerminal := []TaskState{StateQueued, StateAssigned, StateRunning}
for _, s := range nonTerminal {
if s.IsTerminal() {
t.Errorf("expected %q to be non-terminal", s)
}
}
}
func TestTaskSetState_ValidTransitions(t *testing.T) {
cases := []struct {
from TaskState
to TaskState
}{
{StateQueued, StateAssigned},
{StateQueued, StateCancelled},
{StateQueued, StateFailed},
{StateQueued, StateRejected},
{StateAssigned, StateRunning},
{StateAssigned, StateCancelled},
{StateRunning, StateDone},
{StateRunning, StateFailed},
{StateRunning, StateCancelled},
}
for _, tc := range cases {
task := &Task{State: tc.from, CreatedAt: time.Now()}
if err := task.SetState(tc.to); err != nil {
t.Errorf("transition %s → %s should be valid, got: %v", tc.from, tc.to, err)
}
if task.GetState() != tc.to {
t.Errorf("expected state %s, got %s", tc.to, task.GetState())
}
}
}
func TestTaskSetState_InvalidTransitions(t *testing.T) {
cases := []struct {
from TaskState
to TaskState
}{
{StateQueued, StateRunning},
{StateQueued, StateDone},
{StateAssigned, StateDone},
{StateAssigned, StateFailed},
{StateRunning, StateQueued},
{StateRunning, StateAssigned},
}
for _, tc := range cases {
task := &Task{State: tc.from}
if err := task.SetState(tc.to); err == nil {
t.Errorf("transition %s → %s should be invalid", tc.from, tc.to)
}
}
}
func TestTaskSetState_TerminalBlocked(t *testing.T) {
for _, terminal := range []TaskState{StateDone, StateFailed, StateCancelled, StateRejected} {
task := &Task{State: terminal}
if err := task.SetState(StateQueued); err == nil {
t.Errorf("should not allow transition from terminal state %s", terminal)
}
}
}
func TestTaskSetState_SetsTimestamps(t *testing.T) {
task := &Task{State: StateQueued, CreatedAt: time.Now()}
if err := task.SetState(StateAssigned); err != nil {
t.Fatal(err)
}
if task.StartedAt.IsZero() {
t.Error("StartedAt should be set on assigned")
}
if err := task.SetState(StateRunning); err != nil {
t.Fatal(err)
}
if err := task.SetState(StateDone); err != nil {
t.Fatal(err)
}
if task.CompletedAt.IsZero() {
t.Error("CompletedAt should be set on done")
}
if task.LatencyMs <= 0 {
// Allow 0 only if instant (<1ms)
t.Log("latency was 0, which can happen on fast machines")
}
}
func TestTaskSnapshot(t *testing.T) {
task := &Task{
ID: "tsk_test1",
AgentID: "agent-1",
Action: "click",
State: StateQueued,
}
snap := task.Snapshot()
if snap.ID != task.ID || snap.AgentID != task.AgentID {
t.Error("snapshot should copy fields")
}
}
func TestSubmitRequestValidate(t *testing.T) {
valid := SubmitRequest{AgentID: "a", Action: "click"}
if err := valid.Validate(); err != nil {
t.Errorf("valid request should pass: %v", err)
}
noAgent := SubmitRequest{Action: "click"}
if err := noAgent.Validate(); err == nil {
t.Error("missing agentId should fail")
}
noAction := SubmitRequest{AgentID: "a"}
if err := noAction.Validate(); err == nil {
t.Error("missing action should fail")
}
}
func TestGenerateTaskID(t *testing.T) {
id := generateTaskID()
if len(id) < 12 {
t.Errorf("task ID too short: %s", id)
}
if id[:4] != "tsk_" {
t.Errorf("task ID should start with tsk_: %s", id)
}
id2 := generateTaskID()
if id == id2 {
t.Error("consecutive task IDs should be unique")
}
}
|