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

110 lines
3.8 KiB
Go

package ai
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os/exec"
"strings"
"sync/atomic"
"testing"
"git.ipao.vip/rogee/go-sip/internal/media"
)
func mockBailianTTS(t *testing.T, pcm []byte, observe func(string)) (string, *atomic.Int32) {
t.Helper()
wav, _, err := media.EncodeMonoWAV(pcm, 1024)
if err != nil {
t.Fatal(err)
}
calls := &atomic.Int32{}
var server *httptest.Server
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/audio" && r.Method == http.MethodGet {
_, _ = w.Write(wav)
return
}
if r.URL.Path != "/api/v1/services/aigc/multimodal-generation/generation" || r.Method != http.MethodPost {
http.Error(w, "unexpected endpoint", http.StatusNotFound)
return
}
calls.Add(1)
var request struct {
Input struct {
Text string `json:"text"`
} `json:"input"`
}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
if observe != nil {
observe(request.Input.Text)
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"output": map[string]any{"audio": map[string]any{"url": server.URL + "/audio?sig=fixture"}}})
}))
t.Cleanup(server.Close)
return server.URL + "/api/v1/services/aigc/multimodal-generation/generation", calls
}
func TestBailianTTSDownloadFailureDoesNotLeakSignedURLOrRetry(t *testing.T) {
if _, err := exec.LookPath("ffmpeg"); err != nil {
t.Skip("Bailian TTS conversion requires ffmpeg")
}
var calls, downloads atomic.Int32
var server *httptest.Server
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/audio" {
downloads.Add(1)
http.Error(w, "private-data", http.StatusForbidden)
return
}
calls.Add(1)
_ = json.NewEncoder(w).Encode(map[string]any{"output": map[string]any{"audio": map[string]any{"url": server.URL + "/audio?sig=private-signature"}}})
}))
defer server.Close()
task, providers := currentFixture(t, "full_ai")
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)
}
_, err = bound.Synthesize(context.Background(), "批准的回复")
if err == nil || !strings.Contains(err.Error(), "HTTP 403") || strings.Contains(err.Error(), "private-signature") || strings.Contains(err.Error(), "private-data") || strings.Contains(err.Error(), p.Credential) || calls.Load() != 1 || downloads.Load() != 1 {
t.Fatalf("download failure must be explicit without leaking/retrying: calls=%d downloads=%d err=%v", calls.Load(), downloads.Load(), err)
}
}
func TestBailianTTSDoesNotFollowChargeableRedirect(t *testing.T) {
if _, err := exec.LookPath("ffmpeg"); err != nil {
t.Skip("Bailian TTS conversion requires ffmpeg")
}
var calls, redirected atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/charged-again" {
redirected.Add(1)
return
}
calls.Add(1)
http.Redirect(w, r, "/charged-again", http.StatusTemporaryRedirect)
}))
defer server.Close()
task, providers := currentFixture(t, "full_ai")
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)
}
_, err = bound.Synthesize(context.Background(), "批准的回复")
if err == nil || !strings.Contains(err.Error(), "HTTP 307") || calls.Load() != 1 || redirected.Load() != 0 {
t.Fatalf("TTS redirect cannot create a second provider request: calls=%d redirected=%d err=%v", calls.Load(), redirected.Load(), err)
}
}