Files
go-sip/internal/agent/task_calls_test.go
T

121 lines
4.0 KiB
Go

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()
}