package dispatcher import ( "context" "encoding/json" "testing" "time" agentpb "git.ipao.vip/rogee/go-sip/gen/agent" "google.golang.org/grpc" ) type fakeAuthorizedAgentClient struct { agentpb.AgentControlServiceClient attempts int mockOrigins int deadlineClosed bool last *agentpb.ExecuteAuthorizedRequest } func (f *fakeAuthorizedAgentClient) ExecuteAuthorized(_ context.Context, request *agentpb.ExecuteAuthorizedRequest, _ ...grpc.CallOption) (*agentpb.ExecuteAuthorizedResponse, error) { f.attempts++ f.last = request if f.deadlineClosed { return &agentpb.ExecuteAuthorizedResponse{ Receipt: &agentpb.OperationReceipt{Result: agentpb.ResultCode_RESULT_CODE_REJECTED}, State: agentpb.ExecutionState_EXECUTION_STATE_TERMINAL, }, nil } f.mockOrigins++ return &agentpb.ExecuteAuthorizedResponse{ Receipt: &agentpb.OperationReceipt{Result: agentpb.ResultCode_RESULT_CODE_APPLIED}, State: agentpb.ExecutionState_EXECUTION_STATE_TERMINAL, }, nil } func (f *fakeAuthorizedAgentClient) QueryExecution(_ context.Context, request *agentpb.QueryExecutionRequest, _ ...grpc.CallOption) (*agentpb.QueryExecutionResponse, error) { binding := request.Binding if f.last != nil && f.last.Binding.GetExecutionId() == request.Binding.GetExecutionId() { binding = f.last.Binding } callState := "mock_no_answer" if f.deadlineClosed { callState = "mock_deadline_closed_without_dial" } return &agentpb.QueryExecutionResponse{Snapshot: &agentpb.ExecutionSnapshot{ Binding: binding, State: agentpb.ExecutionState_EXECUTION_STATE_TERMINAL, CallState: callState, AttemptId: binding.AttemptId, ObservedAtUnixMs: time.Date(2026, 9, 21, 1, 30, 1, 0, time.UTC).UnixMilli(), }}, nil } func TestTaskConsumerIssuesOnlyOneAuthorizedMockInstruction(t *testing.T) { at := time.Date(2026, 9, 21, 1, 30, 0, 0, time.UTC) client := &fakeAuthorizedAgentClient{} coordinator := NewAgentCoordinator(func() time.Time { return at }) if err := coordinator.Register("agent-1", client); err != nil { t.Fatal(err) } coordinator.sessions["agent-1"] = AgentSession{AgentID: "agent-1", CellID: "cell-1", BootID: "boot-1", DispatcherEpoch: "epoch-1", SessionGeneration: 1} route := "d." + localTestDispatcherID + ".task." + localTestTaskID + ".in" for _, tc := range []struct { name string refused bool agentDeadline bool wantInstructions int wantMockOrigins int }{ {"approved call", false, false, 1, 1}, {"refused call", true, false, 0, 0}, {"Agent deadline closed without dialing", false, true, 1, 0}, } { t.Run(tc.name, func(t *testing.T) { client.deadlineClosed = tc.agentDeadline d, st, configServer := newLocalV01TestDispatcher(t, at) defer configServer.Close() defer st.Close() broker := &fakeQueueBroker{} queues := newTaskQueueController(d, broker, nil) if err := queues.EnableMockAuthorizedOrigination("agent-1", coordinator); err != nil { t.Fatal(err) } assignment, err := st.LocalTaskAssignment(localTestDispatcherID, localTestTaskID) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() if err := queues.StartTask(ctx, assignment); err != nil || broker.handler == nil { t.Fatalf("task consumer: handler=%v err=%v", broker.handler != nil, err) } body := localExecuteCommandBody(t, "command-queue-authorized", at.Add(2*time.Hour)) if tc.refused { if err := d.AcceptLocalV01Command(ctx, route, body); err != nil { t.Fatal(err) } d.now = func() time.Time { return at.Add(90 * time.Minute) } // Shanghai 11:00, right-open. } before := client.attempts beforeOrigins := client.mockOrigins if err := broker.handler(ctx, route, body); err != nil { t.Fatalf("accepted command must ACK after durable final decision: %v", err) } if client.attempts-before != tc.wantInstructions || client.mockOrigins-beforeOrigins != tc.wantMockOrigins { t.Fatalf("Agent instructions/originations=%d/%d want=%d/%d", client.attempts-before, client.mockOrigins-beforeOrigins, tc.wantInstructions, tc.wantMockOrigins) } if tc.refused { d.now = func() time.Time { return at.Add(time.Minute) } } if err := broker.handler(ctx, route, body); err != nil { t.Fatalf("duplicate delivery must ACK: %v", err) } if client.attempts-before != tc.wantInstructions || client.mockOrigins-beforeOrigins != tc.wantMockOrigins { t.Fatal("duplicate message retried or revived an Agent originate") } var decision string if err := st.DB().QueryRow(`SELECT decision FROM local_v02_origination_decisions LIMIT 1`).Scan(&decision); err != nil { t.Fatal(err) } wantDecision := "issued" if tc.refused { wantDecision = "refused" } if decision != wantDecision { t.Fatalf("decision=%q want=%q", decision, wantDecision) } var taskStatus, reservationState string if err := st.DB().QueryRow(`SELECT t.status,r.state FROM tasks t JOIN reservations r ON r.execution_id=t.execution_id WHERE t.task_item_id='command-queue-authorized'`).Scan(&taskStatus, &reservationState); err != nil || taskStatus != "finished" || reservationState != "released" { t.Fatalf("ended or refused Mock execution held quota: task=%q reservation=%q err=%v", taskStatus, reservationState, err) } messages, err := st.ClaimOutbox(10) if err != nil { t.Fatal(err) } results := 0 for _, message := range messages { var event struct { EventType string `json:"event_type"` Payload struct { Outcome string `json:"outcome"` Recording struct { Status string `json:"status"` ReasonCode string `json:"reason_code"` } `json:"recording"` } `json:"payload"` } if err := json.Unmarshal(message.Body, &event); err != nil { t.Fatal(err) } if event.EventType == "call.result" { results++ wantOutcome, wantReason := "no_answer", "no_answer" if tc.refused || tc.agentDeadline { wantOutcome, wantReason = "failed", "not_attempted" } if event.Payload.Outcome != wantOutcome || event.Payload.Recording.Status != "not_created" || event.Payload.Recording.ReasonCode != wantReason { t.Fatalf("incorrect Mock final outcome: %+v", event.Payload) } } } if results != 1 { t.Fatalf("one accepted execution produced %d final results", results) } }) } }