package rpc import ( "context" "errors" "os" "path/filepath" "testing" "time" agentpb "git.ipao.vip/rogee/go-sip/gen/agent" "git.ipao.vip/rogee/go-sip/internal/agent" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func TestApprovedAgentServerRegistersCallsBeforeExecuteAckAndDrainsOnControl(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) req := approvedTestRequest(t, now) process, stopProcess := context.WithCancel(context.Background()) defer stopProcess() calls := &agent.TaskCalls{} started := make(chan context.Context, 1) hangupFinished := make(chan struct{}) defer func() { select { case <-hangupFinished: default: close(hangupFinished) } }() 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() <-hangupFinished return nil }), OnFailure: func(_ ApprovedExecution, err error) error { failures <- err; return nil }, } server, err := NewApprovedAgentServer(ServerOptions{ Mode: "mock", StatePath: filepath.Join(t.TempDir(), "agent-session.json"), ApprovedDispatcherID: req.DispatcherId, Now: func() time.Time { return now }, Status: &agentpb.AgentStatus{AgentId: "agent-1", CellId: "cell-1", BootId: "boot-1"}, LoadedSIP: func(context.Context) (map[string]int64, error) { return map[string]int64{req.SelectedTrunkId: req.SipRevision}, nil }, }, worker) if err != nil { t.Fatal(err) } if _, err := server.ActivateAgent(context.Background(), &agentpb.ActivateAgentRequest{ Meta: testMeta("activate-approved", "", 0), Binding: &agentpb.AgentBinding{AgentId: "agent-1", CellId: "cell-1", ExpectedBootId: "boot-1", DispatcherEpoch: "epoch-1", SessionGeneration: 1, DispatcherId: req.DispatcherId}, ActivationOperationId: "activate-approved", }); err != nil { t.Fatal(err) } request, closeRequest := context.WithCancel(context.Background()) response, err := server.ExecuteApproved(request, req) if err != nil || response == nil || !response.Accepted { t.Fatalf("registered call did not receive dispatch ACK: response=%v err=%v", response, err) } closeRequest() // the outbound gRPC request lifetime has ended var callContext context.Context select { case callContext = <-started: case <-time.After(2 * time.Second): t.Fatal("approved worker did not begin after ACK") } if callContext.Err() != nil { t.Fatal("active call inherited the completed unary context") } control := &agentpb.ApplyApprovedTaskControlRequest{ Meta: testMeta("control", "", 1), DispatcherId: req.DispatcherId, TenantId: req.TenantId, TaskId: req.TaskId, Action: agentpb.ControlAction_CONTROL_ACTION_STOP, ActiveCallPolicy: agentpb.ActiveCallPolicy_ACTIVE_CALL_POLICY_HANGUP, } controlResult := make(chan error, 1) go func() { _, err := server.ApplyApprovedTaskControl(context.Background(), control); controlResult <- err }() select { case <-callContext.Done(): case <-time.After(2 * time.Second): t.Fatal("stop did not reach the registered call") } select { case err := <-controlResult: t.Fatalf("stop was acknowledged before media hangup: %v", err) default: } close(hangupFinished) if err := <-controlResult; err != nil { t.Fatalf("stop did not wait for the actual runner: %v", err) } if _, err := server.ExecuteApproved(context.Background(), req); status.Code(err) != codes.FailedPrecondition { t.Fatalf("completed or unknown durable identity was redialed: %v", err) } select { case err := <-failures: t.Fatalf("successful controlled hangup fabricated an execution failure: %v", err) default: } } func TestApprovedAgentServerFreshStartAndRestartDoNotCreateLegacyExecutionState(t *testing.T) { state := filepath.Join(t.TempDir(), "agent-session.json") options := ServerOptions{ Mode: "mock", StatePath: state, ApprovedDispatcherID: "dispatcher-1", Status: &agentpb.AgentStatus{AgentId: "agent-1", CellId: "cell-1", BootId: "boot-1"}, LoadedSIP: func(context.Context) (map[string]int64, error) { return map[string]int64{"trunk-mock": 8}, nil }, } worker := &ApprovedCallWorker{Lifecycle: context.Background(), Calls: &agent.TaskCalls{}, Prepare: prepareWorkerRun(func(context.Context, ApprovedExecution) error { return nil }), OnFailure: func(ApprovedExecution, error) error { return nil }, } server, err := NewApprovedAgentServer(options, worker) if err != nil { t.Fatal(err) } if _, err := server.ActivateAgent(context.Background(), &agentpb.ActivateAgentRequest{ Meta: testMeta("activate-without-legacy", "", 0), Binding: &agentpb.AgentBinding{AgentId: "agent-1", CellId: "cell-1", ExpectedBootId: "boot-1", DispatcherEpoch: "epoch-1", SessionGeneration: 1, DispatcherId: "dispatcher-1"}, ActivationOperationId: "activate-without-legacy", }); err != nil { t.Fatal(err) } if _, err := os.Lstat(state + ".executions"); !errors.Is(err, os.ErrNotExist) { t.Fatalf("current Agent wrote an obsolete execution journal: %v", err) } if _, err := NewApprovedAgentServer(options, worker); err != nil { t.Fatalf("approved Agent could not restart with its current session: %v", err) } } func TestApprovedAgentServerRefusesMissingOrCompetingAdapters(t *testing.T) { process := context.Background() calls := &agent.TaskCalls{} worker := &ApprovedCallWorker{Lifecycle: process, Calls: calls, Prepare: prepareWorkerRun(func(context.Context, ApprovedExecution) error { return nil }), OnFailure: func(ApprovedExecution, error) error { return nil }, } valid := ServerOptions{Mode: "mock", StatePath: filepath.Join(t.TempDir(), "agent-session.json"), ApprovedDispatcherID: "dispatcher-1", Status: &agentpb.AgentStatus{AgentId: "agent-1", CellId: "cell-1", BootId: "boot-1"}, LoadedSIP: func(context.Context) (map[string]int64, error) { return map[string]int64{"trunk-mock": 8}, nil }, } for _, tc := range []struct { name string edit func(*ServerOptions, *ApprovedCallWorker) }{ {"missing state", func(o *ServerOptions, _ *ApprovedCallWorker) { o.StatePath = "" }}, {"non-Mock mode", func(o *ServerOptions, _ *ApprovedCallWorker) { o.Mode = "real" }}, {"implicit Mock default", func(o *ServerOptions, _ *ApprovedCallWorker) { o.Mode = "" }}, {"missing SIP observation", func(o *ServerOptions, _ *ApprovedCallWorker) { o.LoadedSIP = nil }}, {"missing Agent identity", func(o *ServerOptions, _ *ApprovedCallWorker) { o.Status = nil }}, {"missing authorized Dispatcher", func(o *ServerOptions, _ *ApprovedCallWorker) { o.ApprovedDispatcherID = "" }}, {"competing originator", func(o *ServerOptions, _ *ApprovedCallWorker) { o.MockApprovedOriginate = func(context.Context, ApprovedExecution) error { return nil } }}, {"competing control registry", func(o *ServerOptions, _ *ApprovedCallWorker) { o.ApprovedTaskCalls = &agent.TaskCalls{} }}, {"missing worker process", func(_ *ServerOptions, w *ApprovedCallWorker) { w.Lifecycle = nil }}, {"missing failure handler", func(_ *ServerOptions, w *ApprovedCallWorker) { w.OnFailure = nil }}, } { t.Run(tc.name, func(t *testing.T) { options, isolated := valid, *worker tc.edit(&options, &isolated) if server, err := NewApprovedAgentServer(options, &isolated); err == nil || server != nil { t.Fatalf("unsafe approved server setup was admitted: %v", err) } }) } if server, err := NewApprovedAgentServer(valid, nil); err == nil || server != nil || !errors.Is(err, ErrApprovedWorkerRequired) { t.Fatalf("missing approved call worker was admitted: %v", err) } } func TestApprovedAgentServerBindsOnlyRealWorkerInNonprodMode(t *testing.T) { worker := &ApprovedCallWorker{Lifecycle: context.Background(), Calls: &agent.TaskCalls{}, Prepare: prepareWorkerRun(func(context.Context, ApprovedExecution) error { return nil }), OnFailure: func(ApprovedExecution, error) error { return nil }, } server, err := NewApprovedAgentServer(ServerOptions{ Mode: "nonprod-real", StatePath: filepath.Join(t.TempDir(), "agent-session.json"), ApprovedDispatcherID: "dispatcher-1", Status: &agentpb.AgentStatus{AgentId: "agent-1", CellId: "cell-1", BootId: "boot-1"}, LoadedSIP: func(context.Context) (map[string]int64, error) { return map[string]int64{"trunk-1": 8}, nil }, }, worker) if err != nil || server == nil || server.nonprodRealOriginate == nil || server.mockApprovedOriginate != nil { t.Fatalf("nonproduction real mode must bind only its real worker: server=%v err=%v", server, err) } } func TestApprovedAgentServerRefusesLegacyExecutionJournalWithoutMutation(t *testing.T) { state := filepath.Join(t.TempDir(), "agent-session.json") legacy := state + ".executions" original := []byte(`{"executions":{}}`) if err := os.WriteFile(legacy, original, 0600); err != nil { t.Fatal(err) } worker := &ApprovedCallWorker{Lifecycle: context.Background(), Calls: &agent.TaskCalls{}, Prepare: prepareWorkerRun(func(context.Context, ApprovedExecution) error { return nil }), OnFailure: func(ApprovedExecution, error) error { return nil }, } req := approvedTestRequest(t, time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC)) server, err := NewApprovedAgentServer(ServerOptions{ Mode: "mock", StatePath: state, ApprovedDispatcherID: req.DispatcherId, Status: &agentpb.AgentStatus{AgentId: "agent-1", CellId: "cell-1", BootId: "boot-1"}, LoadedSIP: func(context.Context) (map[string]int64, error) { return map[string]int64{req.SelectedTrunkId: req.SipRevision}, nil }, }, worker) if err == nil || server != nil { t.Fatalf("legacy execution journal must block approved Agent startup: %v", err) } retained, err := os.ReadFile(legacy) if err != nil || string(retained) != string(original) { t.Fatalf("legacy execution journal was changed: %v", err) } if _, err := os.Stat(state); !os.IsNotExist(err) { t.Fatalf("new session state was written before legacy data review: %v", err) } } func TestApprovedAgentServerRejectsSelfReportedDispatcherBeforeSessionWrite(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) req := approvedTestRequest(t, now) state := filepath.Join(t.TempDir(), "agent-session.json") worker := &ApprovedCallWorker{Lifecycle: context.Background(), Calls: &agent.TaskCalls{}, Prepare: prepareWorkerRun(func(context.Context, ApprovedExecution) error { return nil }), OnFailure: func(ApprovedExecution, error) error { return nil }, } server, err := NewApprovedAgentServer(ServerOptions{ Mode: "mock", StatePath: state, ApprovedDispatcherID: req.DispatcherId, Now: func() time.Time { return now }, Status: &agentpb.AgentStatus{AgentId: "agent-1", CellId: "cell-1", BootId: "boot-1"}, LoadedSIP: func(context.Context) (map[string]int64, error) { return map[string]int64{req.SelectedTrunkId: req.SipRevision}, nil }, }, worker) if err != nil { t.Fatal(err) } activation := &agentpb.ActivateAgentRequest{ Meta: testMeta("activate-unapproved-d", "", 0), Binding: &agentpb.AgentBinding{AgentId: "agent-1", CellId: "cell-1", ExpectedBootId: "boot-1", DispatcherEpoch: "epoch-1", DispatcherId: req.DispatcherId + "-other"}, ActivationOperationId: "activate-unapproved-d", } if _, err := server.ActivateAgent(context.Background(), activation); status.Code(err) != codes.PermissionDenied { t.Fatalf("pinned peer could claim a different Dispatcher identity: %v", err) } if _, err := os.Stat(state); !os.IsNotExist(err) { t.Fatalf("rejected identity persisted a session: %v", err) } activation.Binding.DispatcherId = req.DispatcherId if _, err := server.ActivateAgent(context.Background(), activation); err != nil { t.Fatalf("approved Dispatcher could not activate: %v", err) } } func TestExecuteApprovedReturnsDefinitiveNoDialRefusalWithoutRetry(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) for _, tc := range []struct { name string cause error want codes.Code }{ {"paused task", agent.ErrTaskAdmissionClosed, codes.FailedPrecondition}, {"stopped task", agent.ErrTaskStopped, codes.FailedPrecondition}, {"expired before dispatch", ErrApprovedDialExpired, codes.DeadlineExceeded}, {"invalid per-call preparation", ErrApprovedCallPreparation, codes.FailedPrecondition}, } { t.Run(tc.name, func(t *testing.T) { req := approvedTestRequest(t, now) attempts := 0 server := activatedApprovedServer(t, now, filepath.Join(t.TempDir(), "agent-session.json"), req.DispatcherId, 1, func(context.Context, ApprovedExecution) error { attempts++; return tc.cause }) if _, err := server.ExecuteApproved(context.Background(), req); status.Code(err) != tc.want { t.Fatalf("known no-dial was treated as ambiguous: %v", err) } if _, err := server.ExecuteApproved(context.Background(), req); status.Code(err) != codes.FailedPrecondition || attempts != 1 { t.Fatalf("durable no-redial guard was bypassed: attempts=%d err=%v", attempts, err) } }) } }