139 lines
4.4 KiB
Go
139 lines
4.4 KiB
Go
package ai
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
)
|
|
|
|
func TestCurrentCallOpeningUsesApprovedTTSOnce(t *testing.T) {
|
|
task, providers := currentFixture(t, "full_ai")
|
|
var calls atomic.Int32
|
|
text := make(chan string, 1)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls.Add(1)
|
|
var payload struct {
|
|
Params struct {
|
|
Text string `json:"text"`
|
|
} `json:"req_params"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
|
http.Error(w, "invalid SDK request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
text <- payload.Params.Text
|
|
_, _ = fmt.Fprintf(w, `{"code":0,"data":%q}`+"\n", base64.StdEncoding.EncodeToString([]byte{1, 0, 2, 0}))
|
|
_, _ = fmt.Fprintln(w, `{"code":20000000,"message":"ok","data":null}`)
|
|
}))
|
|
defer server.Close()
|
|
p := providers["tts-example"]
|
|
p.Endpoint = server.URL
|
|
providers[p.ProviderRef] = p
|
|
bound, err := BindCurrent(task, providers)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
call, err := NewCurrentCall(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 TestCurrentCallOpeningFailureIsVisibleAndNeverRetried(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
|
|
providers[p.ProviderRef] = p
|
|
bound, err := BindCurrent(task, providers)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
call, err := NewCurrentCall(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 TestCurrentCallASROnlyNeverRequestsOpeningTTS(t *testing.T) {
|
|
task, providers := currentFixture(t, "asr_only")
|
|
bound, err := BindCurrent(task, providers)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
call, err := NewCurrentCall(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 TestCurrentCallOptionalEmptyOpeningMakesNoTTSRequest(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 := BindCurrent(task, providers)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
call, err := NewCurrentCall(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 TestCurrentCallRequiresApprovedOpeningBeforeFullAITurn(t *testing.T) {
|
|
task, providers := currentFixture(t, "full_ai")
|
|
bound, err := BindCurrent(task, providers)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
call, err := NewCurrentCall(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)
|
|
}
|
|
}
|