package agent import ( "context" "errors" "sync/atomic" "testing" "time" ) var taskFixture = TaskIdentity{DispatcherID: "c046b893-8628-4589-ae50-619d049248a6", TenantID: 42, TaskID: "task-a"} func TestTaskCallsPauseHangupWaitsForCallAndRefusesNewWork(t *testing.T) { var calls TaskCalls canceled := make(chan struct{}) release, err := calls.Register(taskFixture, "call-a", func() { close(canceled) }) if err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() completed := make(chan error, 1) go func() { completed <- calls.Apply(ctx, taskFixture, "pause", "hangup") }() select { case <-canceled: case <-ctx.Done(): t.Fatal("pause did not request hangup") } if _, err := calls.Register(taskFixture, "call-b", func() {}); !errors.Is(err, ErrTaskAdmissionClosed) { t.Fatalf("pause admitted another call: %v", err) } select { case err := <-completed: t.Fatalf("pause acknowledged before active call ended: %v", err) default: } release() if err := <-completed; err != nil { t.Fatalf("pause did not wait for confirmed completion: %v", err) } if err := calls.Apply(ctx, taskFixture, "resume", ""); err != nil { t.Fatalf("Dispatcher-approved resume was rejected: %v", err) } releaseAgain, err := calls.Register(taskFixture, "call-b", func() {}) if err != nil { t.Fatalf("resumed task did not admit work: %v", err) } releaseAgain() } func TestTaskCallsStopDrainKeepsBarrierAfterTimeoutAndCannotResume(t *testing.T) { var calls TaskCalls var hangups atomic.Int32 release, err := calls.Register(taskFixture, "call-a", func() { hangups.Add(1) }) if err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) defer cancel() if err := calls.Apply(ctx, taskFixture, "stop", "drain"); !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("stop/drain acknowledged an active call: %v", err) } if hangups.Load() != 0 { t.Fatal("drain hung up an active call") } if _, err := calls.Register(taskFixture, "call-b", func() {}); !errors.Is(err, ErrTaskStopped) { t.Fatalf("stop barrier reopened after timeout: %v", err) } release() if err := calls.Apply(context.Background(), taskFixture, "stop", "drain"); err != nil { t.Fatalf("re-delivered stop did not complete after draining: %v", err) } if err := calls.Apply(context.Background(), taskFixture, "resume", ""); !errors.Is(err, ErrTaskStopped) { t.Fatalf("stopped task resumed: %v", err) } } func TestTaskCallsPreserveTaskIsolationAndValidateControls(t *testing.T) { var calls TaskCalls for _, tc := range []struct{ action, policy string }{ {"pause", ""}, {"resume", "hangup"}, {"restart", "hangup"}, {"stop", "unknown"}, } { if err := calls.Apply(context.Background(), taskFixture, tc.action, tc.policy); err == nil { t.Fatalf("invalid control %q/%q was accepted", tc.action, tc.policy) } } if _, err := calls.Register(TaskIdentity{}, "call", func() {}); err == nil { t.Fatal("missing task identity was accepted") } if err := calls.Apply(context.Background(), TaskIdentity{}, "stop", "hangup"); err == nil { t.Fatal("control without an approved task identity was accepted") } if _, err := calls.Register(taskFixture, "call", nil); err == nil { t.Fatal("missing call cancellation was accepted") } other := taskFixture other.TaskID = "task-b" releaseOther, err := calls.Register(other, "call-a", func() {}) if err != nil { t.Fatal(err) } releaseA, err := calls.Register(taskFixture, "call-a", func() {}) if err != nil { t.Fatal(err) } if _, err := calls.Register(taskFixture, "call-a", func() {}); err == nil { t.Fatal("duplicate live execution was admitted") } releaseA() releaseA() // repeated terminal observation must not close the channel twice if err := calls.Apply(context.Background(), taskFixture, "pause", "drain"); err != nil { t.Fatalf("released call remained active: %v", err) } releaseOtherB, err := calls.Register(other, "call-b", func() {}) if err != nil { t.Fatalf("other task was fenced by a different task: %v", err) } releaseOtherB() releaseOther() }