package rpc import ( "context" "encoding/json" "errors" "os" "path/filepath" "strings" "testing" "time" agentpb "git.ipao.vip/rogee/go-sip/gen/agent" "git.ipao.vip/rogee/go-sip/internal/configread" "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func TestAgentDefiniteNoDialStatusHasExplicitMachineReadableEvidence(t *testing.T) { err := approvedNoIssueError(codes.FailedPrecondition, "call was not ready") st, ok := status.FromError(err) if !ok || st.Code() != codes.FailedPrecondition { t.Fatalf("explicit refusal must retain gRPC code: %v", err) } if len(st.Details()) != 1 { t.Fatalf("missing signed no-dial fact: %v", st.Details()) } info, ok := st.Details()[0].(*errdetails.ErrorInfo) if !ok || info.Reason != "GO_SIP_CALL_NOT_ISSUED" || info.Domain != "agent.go-sip" { t.Fatalf("wrong no-call fact: %v", st.Details()) } } func approvedTestRequest(t *testing.T, now time.Time) *agentpb.ExecuteApprovedRequest { t.Helper() taskJSON, err := os.ReadFile("../../contracts/schema/examples/config-read-task-asr.json") if err != nil { t.Fatal(err) } var task configread.Task if err := json.Unmarshal(taskJSON, &task); err != nil { t.Fatal(err) } rawProviders, err := os.ReadFile("../../contracts/schema/examples/config-read-providers.json") if err != nil { t.Fatal(err) } var payload struct { Providers []configread.Provider `json:"providers"` } if err := json.Unmarshal(rawProviders, &payload); err != nil { t.Fatal(err) } providers := make(map[string]configread.Provider) for _, p := range payload.Providers { providers[p.Code] = p } for _, code := range []string{"openai_compatible", "ali_bailian"} { providers[code] = configread.Provider{ProviderRef: "test-" + code, Code: code, Name: "Example only", Endpoint: "https://example.invalid", WSEndpoint: "wss://example.invalid", Credential: "example-only-not-a-real-secret"} } providerJSON, err := json.Marshal(providers) if err != nil { t.Fatal(err) } hash, err := configread.ExecutionBindingSHA256(taskJSON, providerJSON, 8) if err != nil { t.Fatal(err) } return &agentpb.ExecuteApprovedRequest{ Meta: testMeta("event-1", "event-1", 1), DispatcherId: task.DispatcherID, TenantId: task.TenantID, TaskId: task.TaskID, SourceEventId: "event-1", CallId: "event-1", SelectedTrunkId: task.AllowedTrunks[0].TrunkID, CallerId: "BD93205882", Callee: "15003164745", DialedCallee: "708915003164745", RingTimeoutMs: 10000, MaxCallDurationMs: 20000, DialBeforeUnixMs: now.Add(time.Minute).UnixMilli(), SipRevision: 8, TaskConfigJson: taskJSON, ProvidersJson: providerJSON, BindingSha256: hash, } } func activatedApprovedServer(t *testing.T, now time.Time, statePath string, dispatcherID string, generation uint64, originate func(context.Context, ApprovedExecution) error) *Server { t.Helper() s := NewServer(ServerOptions{ Mode: "mock", StatePath: statePath, 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{"trunk-mock": 8}, nil }, MockApprovedOriginate: originate, }) _, err := s.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: generation, DispatcherId: dispatcherID}, ActivationOperationId: "activate-approved", }) if err != nil { t.Fatal(err) } return s } func TestExecuteApprovedRealModeNeverUsesMockAdapter(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) req := approvedTestRequest(t, now) s := activatedApprovedServer(t, now, filepath.Join(t.TempDir(), "agent-session.json"), req.DispatcherId, 1, func(context.Context, ApprovedExecution) error { t.Fatal("real mode used mock adapter") return nil }) s.mode = "nonprod-real" if _, err := s.ExecuteApproved(context.Background(), req); status.Code(err) != codes.FailedPrecondition { t.Fatalf("real mode without its own adapter must refuse before journaling: %v", err) } attempts := 0 s.nonprodRealOriginate = func(_ context.Context, approved ApprovedExecution) error { attempts++ if approved.DialedCallee != req.DialedCallee || approved.CallerID != req.CallerId || approved.SelectedTrunkID != req.SelectedTrunkId { t.Fatal("signed real dial identity was altered") } return nil } response, err := s.ExecuteApproved(context.Background(), req) if err != nil || response == nil || !response.Accepted || attempts != 1 { t.Fatalf("real mode should dispatch exactly once through its adapter: response=%v err=%v attempts=%d", response, err, attempts) } if _, err := s.ExecuteApproved(context.Background(), req); status.Code(err) != codes.FailedPrecondition || attempts != 1 { t.Fatalf("real mode must not redial an accepted identity: err=%v attempts=%d", err, attempts) } } func TestExecuteApprovedBindsConfigAndPreventsRedialAcrossRestart(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) req := approvedTestRequest(t, now) statePath := filepath.Join(t.TempDir(), "agent-session.json") attempts := 0 originate := func(context.Context, ApprovedExecution) error { attempts++ return errors.New("mock originated; outcome unknown") } s := activatedApprovedServer(t, now, statePath, req.DispatcherId, 1, originate) if _, err := s.ExecuteApproved(context.Background(), req); err == nil || attempts != 1 { t.Fatalf("first attempt must preserve unknown outcome: attempts=%d err=%v", attempts, err) } if _, err := s.ExecuteApproved(context.Background(), req); status.Code(err) != codes.FailedPrecondition || attempts != 1 { t.Fatalf("repeat must not redial: attempts=%d err=%v", attempts, err) } restarted := activatedApprovedServer(t, now, statePath, req.DispatcherId, 2, originate) req.Meta.SessionGeneration = 2 if _, err := restarted.ExecuteApproved(context.Background(), req); status.Code(err) != codes.FailedPrecondition || attempts != 1 { t.Fatalf("restart must retain unknown outcome: attempts=%d err=%v", attempts, err) } entries, err := os.ReadDir(statePath + ".approved") if err != nil || len(entries) != 1 { t.Fatalf("one durable execution identity expected: %d %v", len(entries), err) } body, err := os.ReadFile(filepath.Join(statePath+".approved", entries[0].Name())) if err != nil || strings.Contains(string(body), "example-only-not-a-real-secret") { t.Fatalf("journal must not persist raw provider credentials: %v", err) } } func TestExecuteApprovedJournalWriteFailureCannotReplayFromMemory(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") attempts := 0 s := activatedApprovedServer(t, now, state, req.DispatcherId, 1, func(context.Context, ApprovedExecution) error { attempts++ return errors.New("mock originated; outcome unknown") }) blocker := state + ".approved" if err := os.WriteFile(blocker, []byte("not a directory"), 0600); err != nil { t.Fatal(err) } for i := 0; i < 2; i++ { response, err := s.ExecuteApproved(context.Background(), req) if err == nil || response != nil || attempts != 0 { t.Fatalf("failed journal write acknowledged or originated call: attempt=%d response=%v err=%v", i, response, err) } } if err := os.Remove(blocker); err != nil { t.Fatal(err) } response, err := s.ExecuteApproved(context.Background(), req) if err == nil || response != nil || attempts != 1 { t.Fatalf("recovered journal must originate only once: attempts=%d response=%v err=%v", attempts, response, err) } if _, err := s.ExecuteApproved(context.Background(), req); status.Code(err) != codes.FailedPrecondition || attempts != 1 { t.Fatalf("unknown outcome must not redial: attempts=%d err=%v", attempts, err) } } func TestExecuteApprovedRejectsMismatchedIdentityAndCredentialsBeforeAttempt(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) req := approvedTestRequest(t, now) attempts := 0 s := activatedApprovedServer(t, now, filepath.Join(t.TempDir(), "agent-session.json"), req.DispatcherId, 1, func(context.Context, ApprovedExecution) error { attempts++; return nil }) original := req.BindingSha256 req.ProvidersJson = append(append([]byte(nil), req.ProvidersJson...), ' ') if _, err := s.ExecuteApproved(context.Background(), req); status.Code(err) != codes.FailedPrecondition || attempts != 0 { t.Fatalf("altered provider bytes must fail binding: attempts=%d err=%v", attempts, err) } req.ProvidersJson = req.ProvidersJson[:len(req.ProvidersJson)-1] req.DispatcherId = "unauthorized-dispatcher" if _, err := s.ExecuteApproved(context.Background(), req); status.Code(err) != codes.PermissionDenied || attempts != 0 { t.Fatalf("unauthorized Dispatcher must fail session binding: attempts=%d err=%v", attempts, err) } req.DispatcherId = approvedTestRequest(t, now).DispatcherId req.BindingSha256 = original req.SipRevision = 9 if _, err := s.ExecuteApproved(context.Background(), req); status.Code(err) != codes.FailedPrecondition || attempts != 0 { t.Fatalf("unloaded SIP revision must reject: attempts=%d err=%v", attempts, err) } } func TestGetLoadedSIPRequiresActiveSessionAndReportsActualRevision(t *testing.T) { now := time.Date(2026, 9, 20, 10, 0, 0, 0, time.UTC) req := approvedTestRequest(t, now) s := NewServer(ServerOptions{Mode: "mock", StatePath: filepath.Join(t.TempDir(), "agent-session.json"), 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: 8}, nil }, }) if _, err := s.GetLoadedSIP(context.Background(), &agentpb.GetLoadedSIPRequest{Meta: req.Meta}); err == nil { t.Fatal("unactivated caller must not inspect loaded SIP") } _, err := s.ActivateAgent(context.Background(), &agentpb.ActivateAgentRequest{Meta: testMeta("activate-loaded", "", 0), Binding: &agentpb.AgentBinding{AgentId: "agent-1", CellId: "cell-1", ExpectedBootId: "boot-1", DispatcherEpoch: "epoch-1", SessionGeneration: 1, DispatcherId: req.DispatcherId}, ActivationOperationId: "activate-loaded"}) if err != nil { t.Fatal(err) } result, err := s.GetLoadedSIP(context.Background(), &agentpb.GetLoadedSIPRequest{Meta: req.Meta}) if err != nil || result.TrunkRevision[req.SelectedTrunkId] != 8 { t.Fatalf("must report live loaded revision: %v %v", result, err) } }