package ai import ( "context" "encoding/base64" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "sync/atomic" "testing" ) func TestCurrentTTSRejectsExcessPendingAudioWithoutRetry(t *testing.T) { task, providers := currentFixture(t, "full_ai") task = changeCurrentAgent(t, task, func(agent map[string]any) { agent["conversation"].(map[string]any)["max_pending_audio_chunks"] = 1 }) var requests atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { requests.Add(1) for _, pcm := range [][]byte{{1, 0}, {2, 0}} { _, _ = fmt.Fprintf(w, `{"code":0,"data":%q}`+"\n", base64.StdEncoding.EncodeToString(pcm)) } _, _ = 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) } if _, err := bound.Synthesize(context.Background(), "批准回复"); err == nil || !strings.Contains(err.Error(), "pending audio") || strings.Contains(err.Error(), p.Credential) { t.Fatalf("explicit pending-audio bound was ignored or leaked credentials: %v", err) } if requests.Load() != 1 { t.Fatalf("SDK must not automatically retry/bill twice: requests=%d", requests.Load()) } } func TestCurrentReplyChunksUnicodeByApprovedSentenceLimit(t *testing.T) { task, providers := currentFixture(t, "full_ai") task = changeCurrentAgent(t, task, func(agent map[string]any) { conversation := agent["conversation"].(map[string]any) conversation["sentence_max_chars"] = 3 conversation["max_pending_audio_chunks"] = 3 }) texts := make(chan string, 3) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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 } texts <- payload.Params.Text _, _ = fmt.Fprintf(w, `{"code":0,"data":%q}`+"\n", base64.StdEncoding.EncodeToString([]byte{1, 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) } audio, err := bound.SynthesizeReply(context.Background(), "你好世界,再见") if err != nil || len(audio) != 6 { t.Fatalf("reply must synthesize all bounded chunks: len=%d err=%v", len(audio), err) } for _, want := range []string{"你好世", "界,再", "见"} { if got := <-texts; got != want { t.Fatalf("Unicode reply was lost/reordered while splitting: got=%q want=%q", got, want) } } }