Files

174 lines
5.7 KiB
Go

package call
import (
"context"
"testing"
"time"
"ai-operator/internal/asterisk/ari"
)
type fakeActions struct{ answered, hungup, played []string }
func (f *fakeActions) AnswerChannel(ctx context.Context, channelID string) error {
f.answered = append(f.answered, channelID)
return nil
}
func (f *fakeActions) HangupChannel(ctx context.Context, channelID string) error {
f.hungup = append(f.hungup, channelID)
return nil
}
func (f *fakeActions) GetChannel(ctx context.Context, channelID string) (*ari.ARIChannel, error) {
return &ari.ARIChannel{ID: channelID}, nil
}
func (f *fakeActions) PlayChannel(ctx context.Context, channelID string, media string) error {
f.played = append(f.played, channelID+" "+media)
return nil
}
func startEvent(id string, args []string) ari.StasisStartEvent {
return ari.StasisStartEvent{BaseEvent: ari.BaseEvent{Type: ari.EventStasisStart}, Args: args, Channel: ari.ARIChannel{ID: id, Caller: ari.ARICallerID{Number: "+77771234567"}}}
}
func TestManagerObserveOnly(t *testing.T) {
fake := &fakeActions{}
store := NewSessionStore()
m := NewManager(DefaultManagerConfig(ManagerModeObserveOnly, time.Millisecond), fake, store, nil)
if err := m.HandleEvent(context.Background(), startEvent("c1", []string{"test"})); err != nil {
t.Fatal(err)
}
if len(fake.answered) != 0 || len(fake.hungup) != 0 {
t.Fatal("observe-only controlled channel")
}
if store.Count() != 1 {
t.Fatalf("count=%d", store.Count())
}
}
func TestManagerCallControlTestRoute(t *testing.T) {
fake := &fakeActions{}
m := NewManager(DefaultManagerConfig(ManagerModeCallControl, 5*time.Millisecond), fake, NewSessionStore(), nil)
if err := m.HandleEvent(context.Background(), startEvent("c1", []string{"test"})); err != nil {
t.Fatal(err)
}
if len(fake.answered) != 1 {
t.Fatalf("answered=%v", fake.answered)
}
time.Sleep(20 * time.Millisecond)
if len(fake.hungup) != 1 {
t.Fatalf("hungup=%v", fake.hungup)
}
}
func TestManagerPlaybackDiagnostic(t *testing.T) {
fake := &fakeActions{}
cfg := DefaultManagerConfig(ManagerModeCallControl, 5*time.Millisecond)
cfg.MediaTestMode = "playback"
m := NewManager(cfg, fake, NewSessionStore(), nil)
if err := m.HandleEvent(context.Background(), startEvent("c1", []string{"test"})); err != nil {
t.Fatal(err)
}
if len(fake.answered) != 1 {
t.Fatalf("answered=%v", fake.answered)
}
if len(fake.played) != 1 || fake.played[0] != "c1 sound:hello-world" {
t.Fatalf("played=%v", fake.played)
}
}
func TestManagerRejectsProductionAndCleans(t *testing.T) {
fake := &fakeActions{}
store := NewSessionStore()
m := NewManager(DefaultManagerConfig(ManagerModeCallControl, time.Millisecond), fake, store, nil)
if err := m.HandleEvent(context.Background(), startEvent("c1", []string{"production"})); err != nil {
t.Fatal(err)
}
if len(fake.answered) != 0 || len(fake.hungup) != 1 {
t.Fatalf("answered=%v hungup=%v", fake.answered, fake.hungup)
}
_ = m.HandleEvent(context.Background(), ari.StasisEndEvent{BaseEvent: ari.BaseEvent{Type: ari.EventStasisEnd}, Channel: ari.ARIChannel{ID: "c1"}})
if store.Count() != 0 {
t.Fatalf("count=%d", store.Count())
}
_ = m.HandleEvent(context.Background(), startEvent("c2", []string{"test"}))
_ = m.HandleEvent(context.Background(), ari.ChannelDestroyedEvent{BaseEvent: ari.BaseEvent{Type: ari.EventChannelDestroyed}, Channel: ari.ARIChannel{ID: "c2"}})
if store.Count() != 0 {
t.Fatalf("count=%d", store.Count())
}
}
type fakeMediaStarter struct{ calls int }
func (f *fakeMediaStarter) StartCallMedia(ctx context.Context, session *CallSession) (MediaClient, error) {
f.calls++
session.MediaConnected = true
return nil, nil
}
func TestManagerMediaIntegrationAndMediaChannelIgnored(t *testing.T) {
fake := &fakeActions{}
media := &fakeMediaStarter{}
cfg := DefaultManagerConfig(ManagerModeCallControl, time.Millisecond)
cfg.MediaEnabled = true
cfg.MediaStarter = media
m := NewManager(cfg, fake, NewSessionStore(), nil)
if err := m.HandleEvent(context.Background(), startEvent("c1", []string{"test"})); err != nil {
t.Fatal(err)
}
if media.calls != 1 {
t.Fatalf("media calls=%d", media.calls)
}
if err := m.HandleEvent(context.Background(), startEvent("aiop-media-c1", []string{"test"})); err != nil {
t.Fatal(err)
}
if m.Store().Count() != 1 {
t.Fatalf("media channel treated as caller, count=%d", m.Store().Count())
}
}
func TestManagerDialogueIntegration(t *testing.T) {
fake := &fakeActions{}
started := false
ended := false
cfg := DefaultManagerConfig(ManagerModeCallControl, time.Millisecond)
cfg.StartDialogue = func(ctx context.Context, session *CallSession) (string, error) {
started = true
if session.Route != "test" {
t.Fatalf("route=%s", session.Route)
}
return "state prompt", nil
}
cfg.EndDialogue = func(ctx context.Context, callID string, reason string) error {
ended = true
return nil
}
m := NewManager(cfg, fake, NewSessionStore(), nil)
if err := m.HandleEvent(context.Background(), startEvent("c1", []string{"test"})); err != nil {
t.Fatal(err)
}
if !started {
t.Fatal("dialogue not started")
}
_ = m.HandleEvent(context.Background(), ari.StasisEndEvent{BaseEvent: ari.BaseEvent{Type: ari.EventStasisEnd}, Channel: ari.ARIChannel{ID: "c1"}})
if !ended {
t.Fatal("dialogue not ended")
}
}
func TestManagerDoesNotStartDialogueForProduction(t *testing.T) {
fake := &fakeActions{}
started := false
cfg := DefaultManagerConfig(ManagerModeCallControl, time.Millisecond)
cfg.StartDialogue = func(ctx context.Context, session *CallSession) (string, error) {
started = true
return "", nil
}
m := NewManager(cfg, fake, NewSessionStore(), nil)
if err := m.HandleEvent(context.Background(), startEvent("c1", []string{"production"})); err != nil {
t.Fatal(err)
}
if started {
t.Fatal("dialogue started for production route")
}
}