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 会阻塞到它的 context 被取消,同时标记它已启动。 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") } }