2026-06-14 10:05:56 +08:00
|
|
|
|
package tts
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"sync/atomic"
|
|
|
|
|
|
"testing"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// mockMiMoTTSServer 创建模拟 MiMo TTS API 的 HTTP 服务器。
|
|
|
|
|
|
func mockMiMoTTSServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
|
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
return httptest.NewServer(handler)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// buildMiMoTTSResponse 构造 MiMo TTS 非流式响应 JSON。
|
|
|
|
|
|
func buildMiMoTTSResponse(audioData string) []byte {
|
|
|
|
|
|
resp := mimoTTSResponse{
|
|
|
|
|
|
Choices: []struct {
|
|
|
|
|
|
Message struct {
|
|
|
|
|
|
Audio struct {
|
|
|
|
|
|
Data string `json:"data"`
|
|
|
|
|
|
} `json:"audio"`
|
|
|
|
|
|
} `json:"message"`
|
|
|
|
|
|
}{
|
|
|
|
|
|
{
|
|
|
|
|
|
Message: struct {
|
|
|
|
|
|
Audio struct {
|
|
|
|
|
|
Data string `json:"data"`
|
|
|
|
|
|
} `json:"audio"`
|
|
|
|
|
|
}{
|
|
|
|
|
|
Audio: struct {
|
|
|
|
|
|
Data string `json:"data"`
|
|
|
|
|
|
}{Data: audioData},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
data, _ := json.Marshal(resp)
|
|
|
|
|
|
return data
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMiMoService_SynthesizeStream_Success(t *testing.T) {
|
|
|
|
|
|
var callCount int32
|
|
|
|
|
|
srv := mockMiMoTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
atomic.AddInt32(&callCount, 1)
|
|
|
|
|
|
|
|
|
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
|
|
t.Errorf("method = %s, want POST", r.Method)
|
|
|
|
|
|
}
|
|
|
|
|
|
if !strings.Contains(r.URL.Path, "/chat/completions") {
|
|
|
|
|
|
t.Errorf("path = %s, should contain /chat/completions", r.URL.Path)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证 api-key 认证头
|
|
|
|
|
|
apiKey := r.Header.Get("api-key")
|
|
|
|
|
|
if apiKey != "test-key" {
|
|
|
|
|
|
t.Errorf("api-key = %q, want %q", apiKey, "test-key")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证请求体
|
|
|
|
|
|
body, _ := io.ReadAll(r.Body)
|
|
|
|
|
|
var req mimoTTSRequest
|
|
|
|
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
|
|
|
|
t.Errorf("unmarshal request: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.Model != "mimo-v2.5-tts" {
|
|
|
|
|
|
t.Errorf("model = %q, want %q", req.Model, "mimo-v2.5-tts")
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(req.Messages) != 1 || req.Messages[0].Role != "assistant" {
|
|
|
|
|
|
t.Errorf("expected 1 assistant message, got %d messages", len(req.Messages))
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.Audio.Voice != "冰糖" {
|
|
|
|
|
|
t.Errorf("voice = %q, want %q", req.Audio.Voice, "冰糖")
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.Audio.Format != "mp3" {
|
|
|
|
|
|
t.Errorf("format = %q, want %q", req.Audio.Format, "mp3")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 返回假音频数据(base64 编码)
|
|
|
|
|
|
audioB64 := base64.StdEncoding.EncodeToString([]byte("fake-mp3-data"))
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
w.Write(buildMiMoTTSResponse(audioB64))
|
|
|
|
|
|
})
|
|
|
|
|
|
defer srv.Close()
|
|
|
|
|
|
|
2026-06-14 11:55:29 +08:00
|
|
|
|
svc := NewMiMoService("test-key", "mimo-v2.5-tts", "冰糖", srv.URL, 5, 30, zap.NewNop().Sugar())
|
2026-06-14 10:05:56 +08:00
|
|
|
|
|
|
|
|
|
|
textStream := sendSentences("你好", "世界", "!")
|
|
|
|
|
|
|
|
|
|
|
|
ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{
|
|
|
|
|
|
Voice: "冰糖", OutputFmt: "mp3", SampleRate: 24000,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("SynthesizeStream() error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var chunks []Chunk
|
|
|
|
|
|
for c := range ch {
|
|
|
|
|
|
chunks = append(chunks, c)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 13:54:49 +08:00
|
|
|
|
// 应该有 3 个音频 chunk + 1 个 Final 标记
|
2026-06-14 10:05:56 +08:00
|
|
|
|
if len(chunks) != 4 {
|
|
|
|
|
|
t.Fatalf("got %d chunks, want 4", len(chunks))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 13:54:49 +08:00
|
|
|
|
// 验证前 3 个有音频数据,IsLast 为 true(每句结束)
|
2026-06-14 10:05:56 +08:00
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
|
|
if string(chunks[i].Audio) != "fake-mp3-data" {
|
|
|
|
|
|
t.Errorf("chunk[%d].Audio = %q, want %q", i, string(chunks[i].Audio), "fake-mp3-data")
|
|
|
|
|
|
}
|
2026-06-14 13:54:49 +08:00
|
|
|
|
if !chunks[i].IsLast {
|
|
|
|
|
|
t.Errorf("chunk[%d].IsLast should be true (sentence end)", i)
|
|
|
|
|
|
}
|
|
|
|
|
|
if chunks[i].Final {
|
|
|
|
|
|
t.Errorf("chunk[%d].Final should be false", i)
|
2026-06-14 10:05:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 13:54:49 +08:00
|
|
|
|
// 验证最后一个是 Final(整轮结束)
|
|
|
|
|
|
if !chunks[3].Final {
|
|
|
|
|
|
t.Error("last chunk should be Final")
|
2026-06-14 10:05:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
if chunks[3].Audio != nil {
|
|
|
|
|
|
t.Error("last chunk Audio should be nil")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证调用了 3 次 API(3 个句子)
|
|
|
|
|
|
if atomic.LoadInt32(&callCount) != 3 {
|
|
|
|
|
|
t.Errorf("API called %d times, want 3", callCount)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMiMoService_SynthesizeStream_APIError(t *testing.T) {
|
|
|
|
|
|
srv := mockMiMoTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
|
|
fmt.Fprintf(w, "internal error")
|
|
|
|
|
|
})
|
|
|
|
|
|
defer srv.Close()
|
|
|
|
|
|
|
2026-06-14 11:55:29 +08:00
|
|
|
|
svc := NewMiMoService("test-key", "mimo-v2.5-tts", "冰糖", srv.URL, 5, 30, zap.NewNop().Sugar())
|
2026-06-14 10:05:56 +08:00
|
|
|
|
|
|
|
|
|
|
textStream := sendSentences("你好")
|
|
|
|
|
|
|
|
|
|
|
|
ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("SynthesizeStream() error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 13:54:49 +08:00
|
|
|
|
// 应该只有一个 Final chunk(音频被跳过)
|
2026-06-14 10:05:56 +08:00
|
|
|
|
var chunks []Chunk
|
|
|
|
|
|
for c := range ch {
|
|
|
|
|
|
chunks = append(chunks, c)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if len(chunks) != 1 {
|
2026-06-14 13:54:49 +08:00
|
|
|
|
t.Fatalf("got %d chunks, want 1 (Final only)", len(chunks))
|
2026-06-14 10:05:56 +08:00
|
|
|
|
}
|
2026-06-14 13:54:49 +08:00
|
|
|
|
if !chunks[0].Final {
|
|
|
|
|
|
t.Error("chunk should be Final")
|
2026-06-14 10:05:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMiMoService_SynthesizeStream_Timeout(t *testing.T) {
|
|
|
|
|
|
srv := mockMiMoTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
time.Sleep(3 * time.Second)
|
|
|
|
|
|
audioB64 := base64.StdEncoding.EncodeToString([]byte("late-mp3"))
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
w.Write(buildMiMoTTSResponse(audioB64))
|
|
|
|
|
|
})
|
|
|
|
|
|
defer srv.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// 1 秒超时
|
2026-06-14 11:55:29 +08:00
|
|
|
|
svc := NewMiMoService("test-key", "mimo-v2.5-tts", "冰糖", srv.URL, 1, 30, zap.NewNop().Sugar())
|
2026-06-14 10:05:56 +08:00
|
|
|
|
|
|
|
|
|
|
textStream := sendSentences("很长的句子")
|
|
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
|
|
ch, err := svc.SynthesizeStream(ctx, textStream, Options{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("SynthesizeStream() error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var chunks []Chunk
|
|
|
|
|
|
for c := range ch {
|
|
|
|
|
|
chunks = append(chunks, c)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 13:54:49 +08:00
|
|
|
|
// 超时后音频被跳过,只有 Final
|
2026-06-14 10:05:56 +08:00
|
|
|
|
if len(chunks) != 1 {
|
|
|
|
|
|
t.Fatalf("got %d chunks, want 1", len(chunks))
|
|
|
|
|
|
}
|
2026-06-14 13:54:49 +08:00
|
|
|
|
if !chunks[0].Final {
|
|
|
|
|
|
t.Error("chunk should be Final")
|
2026-06-14 10:05:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMiMoService_SynthesizeStream_EmptyText(t *testing.T) {
|
|
|
|
|
|
var callCount int32
|
|
|
|
|
|
srv := mockMiMoTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
atomic.AddInt32(&callCount, 1)
|
|
|
|
|
|
audioB64 := base64.StdEncoding.EncodeToString([]byte("mp3"))
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
w.Write(buildMiMoTTSResponse(audioB64))
|
|
|
|
|
|
})
|
|
|
|
|
|
defer srv.Close()
|
|
|
|
|
|
|
2026-06-14 11:55:29 +08:00
|
|
|
|
svc := NewMiMoService("test-key", "mimo-v2.5-tts", "冰糖", srv.URL, 5, 30, zap.NewNop().Sugar())
|
2026-06-14 10:05:56 +08:00
|
|
|
|
|
|
|
|
|
|
// 空句子应该被跳过
|
|
|
|
|
|
textStream := sendSentences("", "你好", "")
|
|
|
|
|
|
|
|
|
|
|
|
ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("SynthesizeStream() error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var chunks []Chunk
|
|
|
|
|
|
for c := range ch {
|
|
|
|
|
|
chunks = append(chunks, c)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 只有 "你好" 应该被合成
|
|
|
|
|
|
if atomic.LoadInt32(&callCount) != 1 {
|
|
|
|
|
|
t.Errorf("API called %d times, want 1", callCount)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 13:54:49 +08:00
|
|
|
|
// 1 个音频(IsLast: true)+ 1 个 Final
|
2026-06-14 10:05:56 +08:00
|
|
|
|
if len(chunks) != 2 {
|
|
|
|
|
|
t.Fatalf("got %d chunks, want 2", len(chunks))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMiMoService_SynthesizeStream_ContextCancelled(t *testing.T) {
|
|
|
|
|
|
srv := mockMiMoTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
audioB64 := base64.StdEncoding.EncodeToString([]byte("mp3"))
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
w.Write(buildMiMoTTSResponse(audioB64))
|
|
|
|
|
|
})
|
|
|
|
|
|
defer srv.Close()
|
|
|
|
|
|
|
2026-06-14 11:55:29 +08:00
|
|
|
|
svc := NewMiMoService("test-key", "mimo-v2.5-tts", "冰糖", srv.URL, 5, 30, zap.NewNop().Sugar())
|
2026-06-14 10:05:56 +08:00
|
|
|
|
|
|
|
|
|
|
textStream := make(chan string, 3)
|
|
|
|
|
|
textStream <- "第一句"
|
|
|
|
|
|
textStream <- "第二句"
|
|
|
|
|
|
textStream <- "第三句"
|
|
|
|
|
|
close(textStream)
|
|
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
// 立即取消
|
|
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
|
|
|
|
ch, err := svc.SynthesizeStream(ctx, textStream, Options{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("SynthesizeStream() error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 消费 channel,应该很快结束
|
|
|
|
|
|
var count int
|
|
|
|
|
|
for range ch {
|
|
|
|
|
|
count++
|
|
|
|
|
|
}
|
|
|
|
|
|
// 可能收到 0 个或 1 个 chunk,取决于时序
|
|
|
|
|
|
t.Logf("received %d chunks after context cancel", count)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMiMoService_SynthesizeStream_PartialFailure(t *testing.T) {
|
|
|
|
|
|
var callCount int32
|
|
|
|
|
|
srv := mockMiMoTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
n := atomic.AddInt32(&callCount, 1)
|
|
|
|
|
|
if n == 2 {
|
|
|
|
|
|
// 第二个句子失败
|
|
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
|
|
fmt.Fprintf(w, "error")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
audioB64 := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("mp3-%d", n)))
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
w.Write(buildMiMoTTSResponse(audioB64))
|
|
|
|
|
|
})
|
|
|
|
|
|
defer srv.Close()
|
|
|
|
|
|
|
2026-06-14 11:55:29 +08:00
|
|
|
|
svc := NewMiMoService("test-key", "mimo-v2.5-tts", "冰糖", srv.URL, 5, 30, zap.NewNop().Sugar())
|
2026-06-14 10:05:56 +08:00
|
|
|
|
|
|
|
|
|
|
textStream := sendSentences("第一句", "第二句", "第三句")
|
|
|
|
|
|
|
|
|
|
|
|
ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("SynthesizeStream() error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var chunks []Chunk
|
|
|
|
|
|
for c := range ch {
|
|
|
|
|
|
chunks = append(chunks, c)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 13:54:49 +08:00
|
|
|
|
// 2 个成功音频(IsLast: true)+ 1 个 Final(第二句被跳过)
|
2026-06-14 10:05:56 +08:00
|
|
|
|
if len(chunks) != 3 {
|
|
|
|
|
|
t.Fatalf("got %d chunks, want 3", len(chunks))
|
|
|
|
|
|
}
|
2026-06-14 13:54:49 +08:00
|
|
|
|
if !chunks[0].IsLast {
|
|
|
|
|
|
t.Error("first audio chunk should be IsLast")
|
|
|
|
|
|
}
|
|
|
|
|
|
if !chunks[1].IsLast {
|
|
|
|
|
|
t.Error("second audio chunk should be IsLast")
|
|
|
|
|
|
}
|
|
|
|
|
|
if !chunks[len(chunks)-1].Final {
|
|
|
|
|
|
t.Error("last chunk should be Final")
|
2026-06-14 10:05:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMiMoService_SynthesizeStream_CustomVoice(t *testing.T) {
|
|
|
|
|
|
srv := mockMiMoTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
body, _ := io.ReadAll(r.Body)
|
|
|
|
|
|
var req mimoTTSRequest
|
|
|
|
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
|
|
|
|
t.Errorf("unmarshal request: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.Audio.Voice != "茉莉" {
|
|
|
|
|
|
t.Errorf("voice = %q, want %q", req.Audio.Voice, "茉莉")
|
|
|
|
|
|
}
|
|
|
|
|
|
audioB64 := base64.StdEncoding.EncodeToString([]byte("mp3"))
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
w.Write(buildMiMoTTSResponse(audioB64))
|
|
|
|
|
|
})
|
|
|
|
|
|
defer srv.Close()
|
|
|
|
|
|
|
2026-06-14 11:55:29 +08:00
|
|
|
|
svc := NewMiMoService("test-key", "mimo-v2.5-tts", "冰糖", srv.URL, 5, 30, zap.NewNop().Sugar())
|
2026-06-14 10:05:56 +08:00
|
|
|
|
|
|
|
|
|
|
textStream := sendSentences("你好")
|
|
|
|
|
|
|
|
|
|
|
|
ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{Voice: "茉莉"})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("SynthesizeStream() error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for range ch {
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMiMoService_SynthesizeStream_DefaultVoice(t *testing.T) {
|
|
|
|
|
|
srv := mockMiMoTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
body, _ := io.ReadAll(r.Body)
|
|
|
|
|
|
var req mimoTTSRequest
|
|
|
|
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
|
|
|
|
t.Errorf("unmarshal request: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 未指定 voice 时应使用默认 "冰糖"
|
|
|
|
|
|
if req.Audio.Voice != "冰糖" {
|
|
|
|
|
|
t.Errorf("voice = %q, want %q (default)", req.Audio.Voice, "冰糖")
|
|
|
|
|
|
}
|
|
|
|
|
|
audioB64 := base64.StdEncoding.EncodeToString([]byte("mp3"))
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
w.Write(buildMiMoTTSResponse(audioB64))
|
|
|
|
|
|
})
|
|
|
|
|
|
defer srv.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// 不指定 voice
|
2026-06-14 11:55:29 +08:00
|
|
|
|
svc := NewMiMoService("test-key", "mimo-v2.5-tts", "冰糖", srv.URL, 5, 30, zap.NewNop().Sugar())
|
2026-06-14 10:05:56 +08:00
|
|
|
|
|
|
|
|
|
|
textStream := sendSentences("你好")
|
|
|
|
|
|
|
|
|
|
|
|
ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("SynthesizeStream() error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for range ch {
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMiMoService_SynthesizeStream_EmptyAudioData(t *testing.T) {
|
|
|
|
|
|
srv := mockMiMoTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
// 返回空音频数据
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
w.Write(buildMiMoTTSResponse(""))
|
|
|
|
|
|
})
|
|
|
|
|
|
defer srv.Close()
|
|
|
|
|
|
|
2026-06-14 11:55:29 +08:00
|
|
|
|
svc := NewMiMoService("test-key", "mimo-v2.5-tts", "冰糖", srv.URL, 5, 30, zap.NewNop().Sugar())
|
2026-06-14 10:05:56 +08:00
|
|
|
|
|
|
|
|
|
|
textStream := sendSentences("你好")
|
|
|
|
|
|
|
|
|
|
|
|
ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("SynthesizeStream() error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var chunks []Chunk
|
|
|
|
|
|
for c := range ch {
|
|
|
|
|
|
chunks = append(chunks, c)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 13:54:49 +08:00
|
|
|
|
// 空音频数据导致错误,句子被跳过,只有 Final
|
2026-06-14 10:05:56 +08:00
|
|
|
|
if len(chunks) != 1 {
|
|
|
|
|
|
t.Fatalf("got %d chunks, want 1", len(chunks))
|
|
|
|
|
|
}
|
2026-06-14 13:54:49 +08:00
|
|
|
|
if !chunks[0].Final {
|
|
|
|
|
|
t.Error("chunk should be Final")
|
2026-06-14 10:05:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|