Files
go-sip/internal/ai/opening_test.go
T

124 lines
3.9 KiB
Go

package ai
import (
"context"
"net/http"
"net/http/httptest"
"os/exec"
"strings"
"sync/atomic"
"testing"
)
func TestCallOpeningUsesApprovedTTSOnce(t *testing.T) {
if _, err := exec.LookPath("ffmpeg"); err != nil {
t.Skip("Bailian audio conversion requires ffmpeg")
}
task, providers := currentFixture(t, "full_ai")
text := make(chan string, 1)
endpoint, calls := mockBailianTTS(t, []byte{1, 0, 2, 0}, func(value string) { text <- value })
p := providers["tts-example"]
p.Endpoint = endpoint
providers[p.ProviderRef] = p
bound, err := Bind(task, providers)
if err != nil {
t.Fatal(err)
}
call, err := NewCall(bound, func(context.Context) error { return nil })
if err != nil {
t.Fatal(err)
}
audio, err := call.Open(context.Background())
if err != nil || len(audio) != 4 || <-text != bound.Opening {
t.Fatalf("approved opening was not synthesized once: len=%d err=%v", len(audio), err)
}
if _, err := call.Open(context.Background()); err == nil || !strings.Contains(err.Error(), "already") || calls.Load() != 1 {
t.Fatalf("opening cannot be replayed: requests=%d err=%v", calls.Load(), err)
}
}
func TestCallOpeningFailureIsVisibleAndNeverRetried(t *testing.T) {
task, providers := currentFixture(t, "full_ai")
var calls atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls.Add(1)
http.Error(w, "mock provider unavailable", http.StatusServiceUnavailable)
}))
defer server.Close()
p := providers["tts-example"]
p.Endpoint = server.URL + "/api/v1/services/aigc/multimodal-generation/generation"
providers[p.ProviderRef] = p
bound, err := Bind(task, providers)
if err != nil {
t.Fatal(err)
}
call, err := NewCall(bound, func(context.Context) error { return nil })
if err != nil {
t.Fatal(err)
}
if _, err := call.Open(context.Background()); err == nil {
t.Fatal("provider failure must be visible")
}
if _, err := call.Open(context.Background()); err == nil || calls.Load() != 1 {
t.Fatalf("uncertain opening must not replay: requests=%d err=%v", calls.Load(), err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
if _, err := call.RunTurn(ctx, []byte{1, 0}); err == nil || !strings.Contains(err.Error(), "opening") {
t.Fatalf("an unsuccessful opening cannot be silently skipped: %v", err)
}
}
func TestCallASROnlyNeverRequestsOpeningTTS(t *testing.T) {
task, providers := currentFixture(t, "asr_only")
bound, err := Bind(task, providers)
if err != nil {
t.Fatal(err)
}
call, err := NewCall(bound, func(context.Context) error { return nil })
if err != nil {
t.Fatal(err)
}
if _, err := call.Open(context.Background()); err == nil {
t.Fatal("ASR-only call cannot synthesize an opening")
}
}
func TestCallOptionalEmptyOpeningMakesNoTTSRequest(t *testing.T) {
task, providers := currentFixture(t, "full_ai")
task = changeCurrentAgent(t, task, func(agent map[string]any) {
agent["conversation"].(map[string]any)["opening"] = ""
})
bound, err := Bind(task, providers)
if err != nil {
t.Fatal(err)
}
call, err := NewCall(bound, func(context.Context) error { return nil })
if err != nil {
t.Fatal(err)
}
if audio, err := call.Open(context.Background()); err != nil || len(audio) != 0 {
t.Fatalf("an approved empty opening must not call TTS: len=%d err=%v", len(audio), err)
}
if _, err := call.Open(context.Background()); err == nil {
t.Fatal("opening stage cannot run twice even if no audio was configured")
}
}
func TestCallRequiresApprovedOpeningBeforeFullAITurn(t *testing.T) {
task, providers := currentFixture(t, "full_ai")
bound, err := Bind(task, providers)
if err != nil {
t.Fatal(err)
}
call, err := NewCall(bound, func(context.Context) error { return nil })
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
if _, err := call.RunTurn(ctx, []byte{1, 0}); err == nil || !strings.Contains(err.Error(), "opening") {
t.Fatalf("a full-AI turn cannot skip its configured opening: %v", err)
}
}