Files
go-sip/internal/rpc/approved_server_test.go
T

211 lines
8.9 KiB
Go

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