feat(multimodal): 视觉分层路由(多模态检测→主/视觉/OCR 工具)+ 语音 STT→TTS 降级链(异步)
This commit is contained in:
1 parent
02aae2bbb4
commit
0a936a3202
19 files changed
+2121
No files matched your search
@@ -0,0 +1,108 @@
|
||||
package voice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestService_STTAndTTS(t *testing.T) {
|
||||
t.Parallel()
|
||||
chain := NewChain(
|
||||
NewProvider("cloud", &stubSTT{text: "transcribed"}, &stubTTS{audio: Audio{Format: "mp3", Data: []byte("a")}}),
|
||||
)
|
||||
svc := NewService(chain)
|
||||
defer svc.Close()
|
||||
|
||||
text, err := svc.STT(context.Background(), Audio{Format: "wav", Data: []byte("x")})
|
||||
if err != nil {
|
||||
t.Fatalf("STT error: %v", err)
|
||||
}
|
||||
if text != "transcribed" {
|
||||
t.Errorf("text = %q, want transcribed", text)
|
||||
}
|
||||
|
||||
audio, err := svc.TTS(context.Background(), "hello")
|
||||
if err != nil {
|
||||
t.Fatalf("TTS error: %v", err)
|
||||
}
|
||||
if string(audio.Data) != "a" || audio.Format != "mp3" {
|
||||
t.Errorf("audio = %+v, want mp3/a", audio)
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_CloseStopsNewCalls(t *testing.T) {
|
||||
t.Parallel()
|
||||
svc := NewService(NewChain(SilentProvider()))
|
||||
svc.Close()
|
||||
_, err := svc.STT(context.Background(), Audio{})
|
||||
if !errors.Is(err, ErrClosed) {
|
||||
t.Fatalf("err = %v, want ErrClosed", err)
|
||||
}
|
||||
}
|
||||
|
||||
// blockingSTT blocks until its context is canceled, marking that it started.
|
||||
type blockingSTT struct {
|
||||
started chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (b *blockingSTT) STT(ctx context.Context, _ Audio) (string, error) {
|
||||
b.once.Do(func() { close(b.started) })
|
||||
<-ctx.Done()
|
||||
return "", ctx.Err()
|
||||
}
|
||||
|
||||
func TestService_CloseCancelsInFlightWorker(t *testing.T) {
|
||||
t.Parallel()
|
||||
blk := &blockingSTT{started: make(chan struct{})}
|
||||
chain := NewChain(NewProvider("cloud", blk, nil))
|
||||
svc := NewService(chain)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
_, _ = svc.STT(context.Background(), Audio{Format: "wav", Data: []byte("x")})
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-blk.started:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("provider never started")
|
||||
}
|
||||
|
||||
svc.Close()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("STT call did not return after Close; worker likely leaked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_CallerCancelUnblocks(t *testing.T) {
|
||||
t.Parallel()
|
||||
blk := &blockingSTT{started: make(chan struct{})}
|
||||
svc := NewService(NewChain(NewProvider("cloud", blk, nil)))
|
||||
defer svc.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
_, _ = svc.STT(ctx, Audio{Format: "wav", Data: []byte("x")})
|
||||
}()
|
||||
|
||||
<-blk.started
|
||||
cancel()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("caller cancellation did not unblock STT")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user