report confirmed real calls whose media setup fails
This commit is contained in:
@@ -99,21 +99,43 @@ func (r *ApprovedRecordedRealCall) Prepare(approved ApprovedExecution) (func(con
|
||||
if err != nil {
|
||||
return err // unknown origination is never retried or reported as a completed call
|
||||
}
|
||||
if call.ctx == nil || call.session == nil || call.close == nil {
|
||||
if call.close != nil {
|
||||
return errors.Join(errors.New("real call media unavailable"), call.close())
|
||||
startedAt := time.Now().UTC() // Originate returns only after the actual channel entered Stasis.
|
||||
// A real channel may have answered even if media/AI setup subsequently
|
||||
// fails. Report a failed call only after its hangup is confirmed; never
|
||||
// keep a known-ended call occupying capacity or invent a recording.
|
||||
reportSetupFailure := func(reason string, cause error) error {
|
||||
if call.close == nil {
|
||||
return errors.Join(cause, errors.New("real call has no hangup function; outcome unknown"))
|
||||
}
|
||||
if err := call.close(); err != nil {
|
||||
return errors.Join(cause, err)
|
||||
}
|
||||
payload, err := callflow.FinalResultPayload(callflow.FinalCallFacts{
|
||||
TaskID: approved.TaskID, CallerProfileID: approved.CallerProfileID,
|
||||
Callee: approved.Callee, TrunkID: approved.SelectedTrunkID,
|
||||
StartedAt: startedAt, EndedAt: time.Now().UTC(), Outcome: "failed", ReasonMessage: reason,
|
||||
}, callflow.Result{})
|
||||
if err != nil {
|
||||
return errors.Join(cause, err)
|
||||
}
|
||||
reportCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), r.ReportTimeout)
|
||||
defer cancel()
|
||||
return errors.Join(cause, r.Delivery.Complete(reportCtx, agent.CompletedRecording{ResultPayload: payload}))
|
||||
}
|
||||
if call.ctx == nil || call.session == nil {
|
||||
return reportSetupFailure("real media was unavailable", errors.New("real call media unavailable"))
|
||||
}
|
||||
if call.close == nil {
|
||||
return errors.New("real call has no hangup function; outcome unknown")
|
||||
}
|
||||
capture, err := callflow.NewRecordingSession(call.session, r.MaxWAVBytes)
|
||||
if err != nil {
|
||||
return errors.Join(err, call.close())
|
||||
return reportSetupFailure("real recording setup failed", err)
|
||||
}
|
||||
startedAt := time.Now().UTC() // Originate returns only after the actual channel entered Stasis.
|
||||
hangup := func(context.Context) error { return call.close() }
|
||||
pipeline, err := ai.NewCall(approved.AI, hangup)
|
||||
if err != nil {
|
||||
return errors.Join(err, call.close())
|
||||
return reportSetupFailure("real AI instance setup failed", err)
|
||||
}
|
||||
observed, runErr := RunApprovedCall(call.ctx, approved, capture, hangup, pipeline)
|
||||
if err := call.close(); err != nil {
|
||||
|
||||
@@ -78,6 +78,47 @@ func TestApprovedRecordedRealCallReportsOnlyEndedObservedCall(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApprovedRecordedRealCallReportsConfirmedMediaSetupFailure(t *testing.T) {
|
||||
fixture, stub, puts, _, approved := recordedMockFixture(t, nil, time.Second)
|
||||
approved.CallerID, approved.DialedCallee, approved.RingTimeout = "BD93205882", "7089"+approved.Callee, time.Second
|
||||
var originate, hangup int
|
||||
runner := &ApprovedRecordedRealCall{
|
||||
MaxWAVBytes: 4096, MediaPayloadType: 118, ReportTimeout: time.Second,
|
||||
Delivery: fixture.Delivery,
|
||||
originator: func(ctx context.Context, _ asterisk.NativeDial) (realMedia, error) {
|
||||
originate++
|
||||
return realMedia{ctx: ctx, close: func() error { hangup++; return nil }}, nil // answered channel, but no RTP capture session
|
||||
},
|
||||
}
|
||||
err := runner.Run(context.Background(), approved)
|
||||
if err == nil || originate != 1 || hangup != 1 || strings.Join(stub.calls, ",") != "end,result" || puts.Load() != 0 {
|
||||
t.Fatalf("confirmed ended call without RTP still needs true failed result and empty recording: err=%v originate=%d hangup=%d calls=%v puts=%d", err, originate, hangup, stub.calls, puts.Load())
|
||||
}
|
||||
var result struct {
|
||||
Outcome string `json:"outcome"`
|
||||
Recording map[string]any `json:"recording"`
|
||||
ReasonMessage string `json:"reason_message"`
|
||||
}
|
||||
if err := json.Unmarshal(stub.result, &result); err != nil || result.Outcome != "failed" || len(result.Recording) != 0 || result.ReasonMessage == "" {
|
||||
t.Fatalf("false success after media setup failure: %+v %v", result, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApprovedRecordedRealCallMissingMediaWithUnknownHangupDoesNotReport(t *testing.T) {
|
||||
fixture, stub, _, _, approved := recordedMockFixture(t, nil, time.Second)
|
||||
approved.CallerID, approved.DialedCallee, approved.RingTimeout = "BD93205882", "7089"+approved.Callee, time.Second
|
||||
runner := &ApprovedRecordedRealCall{
|
||||
MaxWAVBytes: 4096, MediaPayloadType: 118, ReportTimeout: time.Second,
|
||||
Delivery: fixture.Delivery,
|
||||
originator: func(ctx context.Context, _ asterisk.NativeDial) (realMedia, error) {
|
||||
return realMedia{ctx: ctx, close: func() error { return errors.New("hangup response missing") }}, nil
|
||||
},
|
||||
}
|
||||
if err := runner.Run(context.Background(), approved); err == nil || len(stub.calls) != 0 {
|
||||
t.Fatalf("ambiguous hangup cannot be reported as a confirmed end: err=%v calls=%v", err, stub.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApprovedRecordedRealCallDoesNotReportUnknownHangup(t *testing.T) {
|
||||
fixture, stub, puts, _, approved := recordedMockFixture(t, nil, time.Second)
|
||||
approved.CallerID, approved.DialedCallee, approved.RingTimeout = "BD93205882", "7089"+approved.Callee, time.Second
|
||||
|
||||
Reference in New Issue
Block a user