package rpc import ( "context" "errors" "strings" "sync/atomic" "testing" "time" "git.ipao.vip/rogee/go-sip/internal/agent" ) func workerTestExecution(now time.Time) ApprovedExecution { return ApprovedExecution{DispatcherID: "c046b893-8628-4589-ae50-619d049248a6", TenantID: 42, TaskID: "task-a", SourceEventID: "event-a", CallID: "call-a", DialBefore: now.Add(time.Minute), MaxCallDuration: time.Minute} } func prepareWorkerRun(run func(context.Context, ApprovedExecution) error) func(ApprovedExecution) (func(context.Context) error, error) { return func(call ApprovedExecution) (func(context.Context) error, error) { return func(ctx context.Context) error { return run(ctx, call) }, nil } } func TestApprovedCallWorkerOutlivesInitiatingUnaryAndWaitsForPhysicalEnd(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) process, stopProcess := context.WithCancel(context.Background()) defer stopProcess() request, closeRequest := context.WithCancel(context.Background()) defer closeRequest() calls := &agent.TaskCalls{} started := make(chan context.Context, 1) finish := make(chan struct{}) defer func() { select { case <-finish: default: close(finish) } }() failures := make(chan error, 1) worker := ApprovedCallWorker{Lifecycle: process, Calls: calls, Now: func() time.Time { return now }, Prepare: prepareWorkerRun(func(ctx context.Context, _ ApprovedExecution) error { started <- ctx <-ctx.Done() <-finish // simulated hangup must finish before TaskCalls releases the call return nil }), OnFailure: func(_ ApprovedExecution, err error) error { failures <- err; return nil }, } approved := workerTestExecution(now) if err := worker.Originate(request, approved); err != nil { t.Fatal(err) } var callContext context.Context select { case callContext = <-started: case <-time.After(2 * time.Second): t.Fatal("accepted call did not start") } closeRequest() // the unary response must not cancel the active call if callContext.Err() != nil { t.Fatal("call inherited the completed unary request deadline") } task := agent.TaskIdentity{DispatcherID: approved.DispatcherID, TenantID: approved.TenantID, TaskID: approved.TaskID} if _, err := calls.Register(task, approved.CallID, func() {}); err == nil { t.Fatal("accepted call was not registered before the RPC returned") } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() controlDone := make(chan error, 1) go func() { controlDone <- calls.Apply(ctx, task, "pause", "hangup") }() select { case <-callContext.Done(): case <-ctx.Done(): t.Fatal("Agent control did not cancel the active call") } select { case err := <-controlDone: t.Fatalf("control was acknowledged before physical hangup finished: %v", err) default: } close(finish) if err := <-controlDone; err != nil { t.Fatalf("control did not wait for the Agent runner: %v", err) } select { case err := <-failures: t.Fatalf("successful cancellation generated a false failure: %v", err) default: } } func TestApprovedCallWorkerRejectsExpiredOrClosedTaskBeforeIssuing(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) process, stopProcess := context.WithCancel(context.Background()) defer stopProcess() calls := &agent.TaskCalls{} var starts atomic.Int32 worker := ApprovedCallWorker{Lifecycle: process, Calls: calls, Now: func() time.Time { return now }, Prepare: prepareWorkerRun(func(context.Context, ApprovedExecution) error { starts.Add(1); return nil }), OnFailure: func(ApprovedExecution, error) error { return nil }, } approved := workerTestExecution(now) approved.DialBefore = now.Add(-time.Millisecond) if err := worker.Originate(context.Background(), approved); !errors.Is(err, ErrApprovedDialExpired) { t.Fatalf("expired authorization was admitted: %v", err) } approved.DialBefore = now.Add(time.Minute) task := agent.TaskIdentity{DispatcherID: approved.DispatcherID, TenantID: approved.TenantID, TaskID: approved.TaskID} if err := calls.Apply(context.Background(), task, "pause", "hangup"); err != nil { t.Fatal(err) } if err := worker.Originate(context.Background(), approved); !errors.Is(err, agent.ErrTaskAdmissionClosed) { t.Fatalf("paused task was admitted: %v", err) } if starts.Load() != 0 { t.Fatal("Agent issued a call despite expired authorization or task pause") } worker.OnFailure = nil if err := worker.Originate(context.Background(), approved); err == nil || !strings.Contains(err.Error(), "failure") { t.Fatalf("missing failure observability was accepted: %v", err) } } func TestApprovedCallWorkerProcessStopCancelsAndReportsFailureOnce(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) process, stopProcess := context.WithCancel(context.Background()) defer stopProcess() calls := &agent.TaskCalls{} started := make(chan struct{}) reported := make(chan error, 1) var attempts atomic.Int32 worker := ApprovedCallWorker{Lifecycle: process, Calls: calls, Now: func() time.Time { return now }, Prepare: prepareWorkerRun(func(ctx context.Context, _ ApprovedExecution) error { attempts.Add(1) close(started) <-ctx.Done() return ctx.Err() }), OnFailure: func(_ ApprovedExecution, err error) error { reported <- err; return nil }, } approved := workerTestExecution(now) if err := worker.Originate(context.Background(), approved); err != nil { t.Fatal(err) } select { case <-started: case <-time.After(2 * time.Second): t.Fatal("accepted worker did not start") } stopProcess() select { case err := <-reported: if !errors.Is(err, context.Canceled) { t.Fatalf("lost process-shutdown failure reason: %v", err) } case <-time.After(2 * time.Second): t.Fatal("worker failed silently after process shutdown") } task := agent.TaskIdentity{DispatcherID: approved.DispatcherID, TenantID: approved.TenantID, TaskID: approved.TaskID} if err := calls.Apply(context.Background(), task, "stop", "drain"); err != nil { t.Fatalf("failed worker was not released: %v", err) } if attempts.Load() != 1 { t.Fatalf("worker was replayed after an unknown outcome: %d", attempts.Load()) } } func TestApprovedCallWorkerRefusesExpiredAuthorizationBeforeDispatchAck(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) process, stopProcess := context.WithCancel(context.Background()) defer stopProcess() calls := &agent.TaskCalls{} var checks, starts, reports atomic.Int32 worker := ApprovedCallWorker{Lifecycle: process, Calls: calls, Now: func() time.Time { if checks.Add(1) == 1 { return now // approved when entering the worker } return now.Add(2 * time.Minute) // expired when the scheduled worker begins }, Prepare: prepareWorkerRun(func(context.Context, ApprovedExecution) error { starts.Add(1); return nil }), OnFailure: func(ApprovedExecution, error) error { reports.Add(1); return nil }, } approved := workerTestExecution(now) if err := worker.Originate(context.Background(), approved); !errors.Is(err, ErrApprovedDialExpired) { t.Fatalf("expired scheduled call received an execution ACK: %v", err) } if starts.Load() != 0 || reports.Load() != 0 { t.Fatal("no-dial pre-dispatch expiry started work or fabricated a call result") } task := agent.TaskIdentity{DispatcherID: approved.DispatcherID, TenantID: approved.TenantID, TaskID: approved.TaskID} if err := calls.Apply(context.Background(), task, "stop", "drain"); err != nil { t.Fatalf("refused call remained registered: %v", err) } } func TestApprovedCallWorkerPreparationRejectsBeforeDispatchAck(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) calls := &agent.TaskCalls{} cause := errors.New("synthetic Mock script does not match approved AI") var reports atomic.Int32 worker := ApprovedCallWorker{Lifecycle: context.Background(), Calls: calls, Now: func() time.Time { return now }, Prepare: func(ApprovedExecution) (func(context.Context) error, error) { return nil, cause }, OnFailure: func(ApprovedExecution, error) error { reports.Add(1); return nil }, } approved := workerTestExecution(now) if err := worker.Originate(context.Background(), approved); !errors.Is(err, ErrApprovedCallPreparation) || !errors.Is(err, cause) { t.Fatalf("invalid per-call Mock parameters were acknowledged: %v", err) } if reports.Load() != 0 { t.Fatal("no-dial preparation error fabricated a completed call") } task := agent.TaskIdentity{DispatcherID: approved.DispatcherID, TenantID: approved.TenantID, TaskID: approved.TaskID} if err := calls.Apply(context.Background(), task, "stop", "drain"); err != nil { t.Fatalf("failed preparation registered a call: %v", err) } }