package llamaswap import ( "context" "io" "net/http" "net/http/httptest" "testing" "gitea.stevedudenhoeffer.com/steve/majordomo/audio" ) func TestSpeakWithReferenceUsesCloneRoute(t *testing.T) { var gotPath, gotInput, gotVoice, gotFilename string var gotRef []byte srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path if err := r.ParseMultipartForm(1 << 20); err != nil { t.Fatalf("parse multipart: %v", err) } gotInput = r.FormValue("input") gotVoice = r.FormValue("voice") f, hdr, err := r.FormFile("voice_file") if err != nil { t.Fatalf("voice_file part: %v", err) } defer f.Close() gotRef, _ = io.ReadAll(f) gotFilename = hdr.Filename w.Header().Set("Content-Type", "audio/wav") _, _ = w.Write(wavFixture()) })) defer srv.Close() p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client())) sm, _ := p.SpeechModel("chatterbox") res, err := sm.Speak(context.Background(), audio.SpeechRequest{Input: "hello in my voice", Voice: "narrator"}, audio.WithReferenceAudio([]byte("REFWAV"), "audio/wav")) if err != nil { t.Fatalf("Speak: %v", err) } if gotPath != "/upstream/chatterbox/v1/audio/speech/upload" { t.Errorf("path = %q, want clone route", gotPath) } if gotInput != "hello in my voice" || gotVoice != "narrator" { t.Errorf("input/voice = %q/%q", gotInput, gotVoice) } if string(gotRef) != "REFWAV" || gotFilename != "audio.wav" { t.Errorf("ref = %q name = %q", gotRef, gotFilename) } if res.MIME != "audio/wav" || len(res.Audio) == 0 { t.Errorf("result = %q/%d bytes", res.MIME, len(res.Audio)) } } func TestSpeakWithReferenceDefaultsToWavMIME(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(1 << 20); err != nil { t.Fatalf("parse multipart: %v", err) } for _, k := range []string{"voice", "response_format", "speed"} { if v, ok := r.MultipartForm.Value[k]; ok { t.Errorf("unset request sent %q = %v, want omitted", k, v) } } w.Header()["Content-Type"] = nil // no declared type at all _, _ = w.Write([]byte("RAWAUDIO")) })) defer srv.Close() p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client())) sm, _ := p.SpeechModel("chatterbox") res, err := sm.Speak(context.Background(), audio.SpeechRequest{Input: "hi"}, audio.WithReferenceAudio([]byte("REF"), "")) if err != nil { t.Fatalf("Speak: %v", err) } // The clone route's default container is wav, not the JSON route's mp3. if res.MIME != "audio/wav" { t.Errorf("MIME = %q, want audio/wav fallback", res.MIME) } } func TestSpeakWithoutReferenceKeepsJSONRoute(t *testing.T) { var gotPath, gotContentType string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path gotContentType = r.Header.Get("Content-Type") _, _ = w.Write([]byte("MP3")) })) defer srv.Close() p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client())) sm, _ := p.SpeechModel("chatterbox") if _, err := sm.Speak(context.Background(), audio.SpeechRequest{Input: "hi"}); err != nil { t.Fatalf("Speak: %v", err) } if gotPath != "/v1/audio/speech" || gotContentType != "application/json" { t.Errorf("path/content-type = %q/%q, want JSON route", gotPath, gotContentType) } } func TestTranscribeTranslateForcesAutoLanguage(t *testing.T) { var gotTranslate, gotLanguage string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(1 << 20); err != nil { t.Fatalf("parse multipart: %v", err) } gotTranslate = r.FormValue("translate") gotLanguage = r.FormValue("language") _, _ = w.Write([]byte(`{"text":"hello"}`)) })) defer srv.Close() p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client())) tm, _ := p.TranscriptionModel("whisper-large-v3-turbo") res, err := tm.Transcribe(context.Background(), audio.TranscriptionRequest{Audio: []byte("AUDIO")}, audio.WithTranslate()) if err != nil { t.Fatalf("Transcribe: %v", err) } // whisper.cpp's server default language is "en", which would skip // translation — translate must force auto-detection when no explicit // language hint was given. if gotTranslate != "true" || gotLanguage != "auto" { t.Errorf("translate/language = %q/%q, want true/auto", gotTranslate, gotLanguage) } if res.Text != "hello" { t.Errorf("text = %q", res.Text) } } func TestTranscribeTranslateKeepsExplicitLanguage(t *testing.T) { var gotTranslate, gotLanguage string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(1 << 20); err != nil { t.Fatalf("parse multipart: %v", err) } gotTranslate = r.FormValue("translate") gotLanguage = r.FormValue("language") _, _ = w.Write([]byte(`{"text":"hello"}`)) })) defer srv.Close() p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client())) tm, _ := p.TranscriptionModel("whisper-large-v3-turbo") if _, err := tm.Transcribe(context.Background(), audio.TranscriptionRequest{Audio: []byte("AUDIO"), Language: "de", Translate: true}); err != nil { t.Fatalf("Transcribe: %v", err) } if gotTranslate != "true" || gotLanguage != "de" { t.Errorf("translate/language = %q/%q, want true/de", gotTranslate, gotLanguage) } } func TestTranscribeWithoutTranslateOmitsFields(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(1 << 20); err != nil { t.Fatalf("parse multipart: %v", err) } for _, k := range []string{"translate", "language"} { if v, ok := r.MultipartForm.Value[k]; ok { t.Errorf("unset request sent %q = %v, want omitted", k, v) } } _, _ = w.Write([]byte(`{"text":"hi"}`)) })) defer srv.Close() p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client())) tm, _ := p.TranscriptionModel("whisper-large-v3-turbo") if _, err := tm.Transcribe(context.Background(), audio.TranscriptionRequest{Audio: []byte("AUDIO")}); err != nil { t.Fatalf("Transcribe: %v", err) } }