package asterisk import ( "context" "errors" "strings" "testing" "time" "github.com/CyCoreSystems/ari/v5" ) type delayedTeardownChannel struct { ari.Channel reads, hangups int } func (c *delayedTeardownChannel) Hangup(key *ari.Key, _ string) error { if key.ID != "owned-call" { return errors.New("wrong channel") } c.hangups++ return nil } func (c *delayedTeardownChannel) Data(key *ari.Key) (*ari.ChannelData, error) { if key.ID != "owned-call" { return nil, errors.New("wrong channel") } c.reads++ if c.reads < 3 { return &ari.ChannelData{ID: key.ID}, nil } return nil, nativeHTTPStatusError{code: 404} } func TestNativeCloseWaitsForExactChannelAsynchronousTeardown(t *testing.T) { channel := &delayedTeardownChannel{} call := &NativeCall{verifyEnd: true, outbound: ari.NewChannelHandle(ari.NewKey(ari.ChannelKey, "owned-call"), channel, nil)} if err := call.Close(); err != nil { t.Fatal(err) } if !call.EndConfirmed() || channel.reads != 3 || channel.hangups != 1 { t.Fatalf("only the exact channel 404 confirms end; reads=%d hangups=%d confirmed=%v", channel.reads, channel.hangups, call.EndConfirmed()) } if err := call.Close(); err != nil || channel.hangups != 1 { t.Fatal("Close must never repeat Hangup") } } func TestNativeEndReadbackKeepsUncertaintyVisible(t *testing.T) { for _, scenario := range []string{"still-present", "HTTP-error", "transport-error"} { t.Run(scenario, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() reads, err := awaitNativeChannelEnd(ctx, func() error { switch scenario { case "HTTP-error": return nativeHTTPStatusError{code: 503} case "transport-error": return errors.New("read unavailable") } return nil }) if err == nil || reads < 1 { t.Fatalf("missing terminal evidence must fail explicitly: reads=%d error=%v", reads, err) } if scenario == "still-present" && (!errors.Is(err, context.DeadlineExceeded) || !strings.Contains(err.Error(), "channel still present")) { t.Fatalf("deadline must retain pending-channel evidence: %v", err) } if scenario != "still-present" && reads != 1 { t.Fatal("failed read is not silently retried") } }) } }