93 lines
3.3 KiB
Go
93 lines
3.3 KiB
Go
package ai
|
|
|
|
import (
|
|
"context"
|
|
"github.com/coder/websocket"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestFunASRFailureNeverPublishesPartialFinalTextOrRetries(t *testing.T) {
|
|
for _, scenario := range []string{"failed-after-final", "partial-only", "malformed-final", "conflicting-final", "binary", "closed"} {
|
|
t.Run(scenario, func(t *testing.T) {
|
|
bound := bailianFixture(t)
|
|
var requests atomic.Int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
requests.Add(1)
|
|
c, err := websocket.Accept(w, r, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer c.CloseNow()
|
|
ctx, cancel := context.WithTimeout(r.Context(), time.Second)
|
|
defer cancel()
|
|
cmd := mockRead(t, c, ctx)
|
|
mockEvent(c, ctx, cmd.Header.TaskID, "task-started", nil)
|
|
for {
|
|
kind, _, err := c.Read(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if kind == websocket.MessageText {
|
|
break
|
|
}
|
|
}
|
|
sentence := map[string]any{"text": "模拟最终文本", "sentence_end": true, "begin_time": 0, "end_time": 100}
|
|
if scenario == "partial-only" {
|
|
sentence["sentence_end"] = false
|
|
}
|
|
if scenario == "malformed-final" {
|
|
delete(sentence, "end_time")
|
|
}
|
|
if scenario == "binary" {
|
|
c.Write(ctx, websocket.MessageBinary, []byte{1, 2})
|
|
return
|
|
}
|
|
mockEvent(c, ctx, cmd.Header.TaskID, "result-generated", map[string]any{"output": map[string]any{"sentence": sentence}})
|
|
if scenario == "closed" {
|
|
return
|
|
}
|
|
if scenario == "failed-after-final" {
|
|
mockEvent(c, ctx, cmd.Header.TaskID, "task-failed", nil)
|
|
return
|
|
}
|
|
if scenario == "conflicting-final" {
|
|
sentence["text"] = "修改最终文本"
|
|
mockEvent(c, ctx, cmd.Header.TaskID, "result-generated", map[string]any{"output": map[string]any{"sentence": sentence}})
|
|
}
|
|
mockEvent(c, ctx, cmd.Header.TaskID, "task-finished", nil)
|
|
}))
|
|
defer server.Close()
|
|
bound.ASR.Provider.Endpoint = "ws" + strings.TrimPrefix(server.URL, "http")
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
text, err := bound.Recognize(ctx, make([]byte, 3200))
|
|
if err == nil || text != "" || requests.Load() != 1 {
|
|
t.Fatalf("ASR failure returned text or retried: text=%q err=%v calls=%d", text, err, requests.Load())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBailianHandshakeDoesNotFollowRedirectOrExposeCredentials(t *testing.T) {
|
|
var targetCalls atomic.Int32
|
|
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { targetCalls.Add(1) }))
|
|
defer target.Close()
|
|
source := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Redirect(w, r, target.URL, http.StatusTemporaryRedirect)
|
|
}))
|
|
defer source.Close()
|
|
bound := bailianFixture(t)
|
|
bound.TTS.Provider.WSEndpoint = "ws" + strings.TrimPrefix(source.URL, "http")
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
audio, err := bound.Synthesize(ctx, "本地模拟")
|
|
if err == nil || len(audio) != 0 || targetCalls.Load() != 0 || !strings.Contains(err.Error(), "307") || strings.Contains(err.Error(), "mock-key") {
|
|
t.Fatalf("redirect/failure hidden: audio=%d err=%v requests=%d", len(audio), err, targetCalls.Load())
|
|
}
|
|
}
|