package asterisk import ( "context" "strings" "testing" "time" "github.com/CyCoreSystems/ari/v5" ) type answerEvents struct { ari.Subscription events chan ari.Event } func (f answerEvents) Events() <-chan ari.Event { return f.events } func (f answerEvents) Cancel() {} func TestAwaitNativeAnswerRequiresMatchingStasisStart(t *testing.T) { sub := answerEvents{events: make(chan ari.Event, 3)} sub.events <- &ari.StasisStart{EventData: ari.EventData{Type: "StasisStart"}, Channel: ari.ChannelData{ID: "someone-else"}} sub.events <- &ari.ChannelStateChange{EventData: ari.EventData{Type: "ChannelStateChange"}, Channel: ari.ChannelData{ID: "exec-1", State: "Up"}} sub.events <- &ari.StasisStart{EventData: ari.EventData{Type: "StasisStart"}, Channel: ari.ChannelData{ID: "exec-1"}} ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := awaitStasisStart(ctx, sub, "exec-1"); err != nil { t.Fatalf("actual answered channel must enter the approved Stasis application: %v", err) } } func TestAwaitNativeAnswerFailsClosedOnHangupOrEventLoss(t *testing.T) { for _, tc := range []struct { name string event ari.Event }{ {"hangup", &ari.ChannelHangupRequest{EventData: ari.EventData{Type: "ChannelHangupRequest"}, Channel: ari.ChannelData{ID: "exec-1"}, Cause: 17}}, {"destroyed", &ari.ChannelDestroyed{EventData: ari.EventData{Type: "ChannelDestroyed"}, Channel: ari.ChannelData{ID: "exec-1"}, Cause: 17}}, {"stasis-ended", &ari.StasisEnd{EventData: ari.EventData{Type: "StasisEnd"}, Channel: ari.ChannelData{ID: "exec-1"}}}, } { t.Run(tc.name, func(t *testing.T) { sub := answerEvents{events: make(chan ari.Event, 1)} sub.events <- tc.event ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := awaitStasisStart(ctx, sub, "exec-1"); err == nil || !strings.Contains(err.Error(), "before StasisStart") { t.Fatalf("failed origination must not count as answered: %v", err) } }) } t.Run("empty-event", func(t *testing.T) { sub := answerEvents{events: make(chan ari.Event, 1)} sub.events <- nil ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := awaitStasisStart(ctx, sub, "exec-1"); err == nil || !strings.Contains(err.Error(), "empty event") { t.Fatalf("empty ARI event cannot be ignored as if the stream was healthy: %v", err) } }) t.Run("subscription-lost", func(t *testing.T) { sub := answerEvents{events: make(chan ari.Event)} close(sub.events) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := awaitStasisStart(ctx, sub, "exec-1"); err == nil || !strings.Contains(err.Error(), "subscription closed") { t.Fatalf("event loss cannot be interpreted as answer: %v", err) } }) t.Run("deadline", func(t *testing.T) { sub := answerEvents{events: make(chan ari.Event)} ctx, cancel := context.WithCancel(context.Background()) cancel() if err := awaitStasisStart(ctx, sub, "exec-1"); err == nil || !strings.Contains(err.Error(), "deadline or cancellation") { t.Fatalf("expired answer window cannot be interpreted as answer: %v", err) } }) }