125 lines
4.3 KiB
Go
125 lines
4.3 KiB
Go
package ai
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func keywordGroups() []HangupKeyword {
|
|
return []HangupKeyword{
|
|
{Name: "挂机1", Triggers: []string{"不需要", "再见"}, ClosingRemark: "好的,祝您生活愉快。"},
|
|
{Name: "挂机2", Triggers: []string{"再见", "不要联系"}, ClosingRemark: "好的,再见。"},
|
|
}
|
|
}
|
|
|
|
func TestKeywordHangupOnlyFinalUserLiteralAndOrderedGroups(t *testing.T) {
|
|
calls := 0
|
|
groups := keywordGroups()
|
|
stopper, err := NewKeywordHangup(groups, func(context.Context) error { calls++; return nil })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
groups[0].Triggers[1], groups[0].ClosingRemark = "改过的词", "改过的结束语"
|
|
for _, segment := range []ASRSegment{
|
|
{Source: "user", Text: "再见", Final: false},
|
|
{Source: "assistant", Text: "再见", Final: true},
|
|
{Source: "user", Text: "继续", Final: true},
|
|
{Source: "user", Text: "不 需要", Final: true},
|
|
} {
|
|
if group, err := stopper.Match(segment); err != nil || group != nil || calls != 0 {
|
|
t.Fatalf("nonfinal/nonliteral/assistant text triggered closing: %v", err)
|
|
}
|
|
}
|
|
group, err := stopper.Match(ASRSegment{Source: "user", Text: "再见,不要联系", Final: true})
|
|
if err != nil || group == nil || group.Name != "挂机1" || group.ClosingRemark != "好的,祝您生活愉快。" || calls != 0 {
|
|
t.Fatalf("first configured match must reserve frozen closing without hanging up: group=%+v calls=%d err=%v", group, calls, err)
|
|
}
|
|
if _, err := stopper.Match(ASRSegment{Source: "user", Text: "再见", Final: true}); err == nil {
|
|
t.Fatal("duplicate result allowed closing replay")
|
|
}
|
|
if err := stopper.Finish(context.Background()); err != nil || calls != 1 {
|
|
t.Fatalf("finish: %v calls=%d", err, calls)
|
|
}
|
|
if err := stopper.Finish(context.Background()); err == nil || calls != 1 {
|
|
t.Fatal("hangup retried")
|
|
}
|
|
}
|
|
|
|
func TestKeywordHangupFailureVisibleWithoutRetry(t *testing.T) {
|
|
want := errors.New("transport outcome unknown")
|
|
calls := 0
|
|
stopper, err := NewKeywordHangup(keywordGroups(), func(context.Context) error { calls++; return want })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := stopper.Match(ASRSegment{Source: "user", Text: "不需要", Final: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := stopper.Finish(context.Background()); !errors.Is(err, want) || calls != 1 {
|
|
t.Fatalf("failure hidden: %v", err)
|
|
}
|
|
if err := stopper.Finish(context.Background()); err == nil || calls != 1 {
|
|
t.Fatal("uncertain hangup retried")
|
|
}
|
|
}
|
|
|
|
func TestKeywordHangupConcurrentReservations(t *testing.T) {
|
|
stopper, err := NewKeywordHangup(keywordGroups(), func(context.Context) error { return nil })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var wg sync.WaitGroup
|
|
matched := make(chan bool, 20)
|
|
for range 20 {
|
|
wg.Go(func() {
|
|
group, _ := stopper.Match(ASRSegment{Source: "user", Text: "不需要", Final: true})
|
|
matched <- group != nil
|
|
})
|
|
}
|
|
wg.Wait()
|
|
close(matched)
|
|
count := 0
|
|
for ok := range matched {
|
|
if ok {
|
|
count++
|
|
}
|
|
}
|
|
if count != 1 {
|
|
t.Fatalf("closing reserved %d times", count)
|
|
}
|
|
}
|
|
|
|
func TestKeywordHangupRejectsInvalidInput(t *testing.T) {
|
|
for _, groups := range [][]HangupKeyword{
|
|
{{Name: "", Triggers: []string{"词"}, ClosingRemark: "结束"}},
|
|
{{Name: "组", Triggers: []string{""}, ClosingRemark: "结束"}},
|
|
{{Name: "组", Triggers: []string{string([]byte{0xff})}, ClosingRemark: "结束"}},
|
|
{{Name: "组", Triggers: []string{"词"}, ClosingRemark: " "}},
|
|
} {
|
|
if _, err := NewKeywordHangup(groups, func(context.Context) error { return nil }); err == nil {
|
|
t.Fatal("invalid group accepted")
|
|
}
|
|
}
|
|
if _, err := NewKeywordHangup(nil, nil); err == nil {
|
|
t.Fatal("missing hangup accepted")
|
|
}
|
|
stopper, _ := NewKeywordHangup(keywordGroups(), func(context.Context) error { return nil })
|
|
for _, segment := range []ASRSegment{{Source: "unknown", Final: true}, {Source: "user", Text: string([]byte{0xff}), Final: true}} {
|
|
if _, err := stopper.Match(segment); err == nil {
|
|
t.Fatal("invalid ASR input accepted")
|
|
}
|
|
}
|
|
if err := stopper.Finish(context.Background()); err == nil {
|
|
t.Fatal("hangup without keyword reservation")
|
|
}
|
|
var unavailable *KeywordHangup
|
|
if _, err := unavailable.Match(ASRSegment{}); err == nil {
|
|
t.Fatal("nil matcher accepted")
|
|
}
|
|
if err := unavailable.Finish(context.Background()); err == nil {
|
|
t.Fatal("nil finish accepted")
|
|
}
|
|
}
|