package gateway import ( "context" "encoding/base64" "net/http" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/auth" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/multimodal/voice" ) // audioStubVoice is a VoiceService returning fixed STT/TTS results for the // REST /api/v1/audio/* handler tests. type audioStubVoice struct { sttText string sttErr error ttsAudio voice.Audio ttsErr error gotAudio voice.Audio gotText string sttCalled bool ttsCalled bool } func (s *audioStubVoice) STT(ctx context.Context, audio voice.Audio) (string, error) { s.sttCalled = true s.gotAudio = audio return s.sttText, s.sttErr } func (s *audioStubVoice) TTS(ctx context.Context, text string) (voice.Audio, error) { s.ttsCalled = true s.gotText = text return s.ttsAudio, s.ttsErr } func TestHandleTranscribe(t *testing.T) { env := newEnv(t, nil, false) sv := &audioStubVoice{sttText: "你好世界"} env.srv.SetVoice(sv) token := env.token(t, auth.RoleOwner) resp, body := doReq(t, http.MethodPost, env.ts.URL+"/api/v1/audio/transcribe", map[string]any{ "audio": base64.StdEncoding.EncodeToString([]byte("raw")), "format": "wav", }, token) if resp.StatusCode != http.StatusOK { t.Fatalf("status = %d, body = %s", resp.StatusCode, body) } if !sv.sttCalled { t.Fatal("STT not called") } if string(sv.gotAudio.Data) != "raw" || sv.gotAudio.Format != "wav" { t.Errorf("audio = %+v, want format wav / data raw", sv.gotAudio) } env2 := decodeBody(t, body) if got := string(env2["data"]); got == "" { t.Errorf("data envelope empty: %s", body) } } func TestHandleTranscribe_AuthRequired(t *testing.T) { env := newEnv(t, nil, false) sv := &audioStubVoice{sttText: "x"} env.srv.SetVoice(sv) resp, body := doReq(t, http.MethodPost, env.ts.URL+"/api/v1/audio/transcribe", map[string]any{ "audio": base64.StdEncoding.EncodeToString([]byte("raw")), }, "") if resp.StatusCode != http.StatusUnauthorized { t.Fatalf("status = %d, want 401 (body=%s)", resp.StatusCode, body) } } func TestHandleTranscribe_EmptyAudio(t *testing.T) { env := newEnv(t, nil, false) sv := &audioStubVoice{sttText: "x"} env.srv.SetVoice(sv) token := env.token(t, auth.RoleOwner) resp, body := doReq(t, http.MethodPost, env.ts.URL+"/api/v1/audio/transcribe", map[string]any{ "audio": base64.StdEncoding.EncodeToString([]byte("")), }, token) if resp.StatusCode != http.StatusBadRequest { t.Fatalf("status = %d, want 400 (body=%s)", resp.StatusCode, body) } } func TestHandleSpeech(t *testing.T) { env := newEnv(t, nil, false) sv := &audioStubVoice{ttsAudio: voice.Audio{Format: "wav", Data: []byte("audio-bytes")}} env.srv.SetVoice(sv) token := env.token(t, auth.RoleOwner) resp, body := doReq(t, http.MethodPost, env.ts.URL+"/api/v1/audio/speech", map[string]any{ "text": "你好", }, token) if resp.StatusCode != http.StatusOK { t.Fatalf("status = %d, body = %s", resp.StatusCode, body) } if ct := resp.Header.Get("Content-Type"); ct != "audio/wav" { t.Errorf("Content-Type = %q, want audio/wav", ct) } if string(body) != "audio-bytes" { t.Errorf("body = %q, want audio-bytes", body) } if !sv.ttsCalled || sv.gotText != "你好" { t.Errorf("TTS got text = %q (called=%v), want 你好", sv.gotText, sv.ttsCalled) } } func TestHandleSpeech_EmptyText(t *testing.T) { env := newEnv(t, nil, false) env.srv.SetVoice(&audioStubVoice{}) token := env.token(t, auth.RoleOwner) resp, body := doReq(t, http.MethodPost, env.ts.URL+"/api/v1/audio/speech", map[string]any{ "text": " ", }, token) if resp.StatusCode != http.StatusBadRequest { t.Fatalf("status = %d, want 400 (body=%s)", resp.StatusCode, body) } }