Files
goclaw/tests/integration/tts_capabilities_test.go
viettranx 613b6e38d7 feat(tts): provider capabilities schema + Gemini TTS + dynamic param forms
Baseline groundwork for TTS expansion plan. Introduces a capabilities
system that the follow-up plan (phase-01..04) builds on.

Backend:
- `audio.ParamSchema` / `ProviderCapabilities` types + per-provider
  capabilities.go for edge, elevenlabs, minimax, openai, gemini.
- Gemini TTS provider (client, models, voices, wav encoder,
  multi-speaker via SpeakerVoice, audio-tag aware prompts).
- TTSOptions gains `Params map[string]any` (read-only) + `Speakers`.
- `VoiceListProvider` interface decouples HTTP voice handler from
  provider-specific impls.
- `nested_keys.go` resolves dot-separated param paths for nested
  provider bodies (voice_settings.stability etc.).
- Characterization + defaults-invariant tests per provider lock
  nil-params byte-equivalence before new params land.
- `/v1/tts/capabilities` HTTP endpoint + integration coverage.
- Dual-read tests (PG + SQLite) for tts_config.

Frontend (web + desktop):
- `DynamicParamForm` with depends-on evaluation, split into
  fields/logic modules. Slider primitive added.
- `AudioTagPicker` + `MultiSpeakerEditor` for Gemini.
- `voice-picker` refactored toward portal UX; combobox tightened.
- tts-capabilities API client + typed hooks.
- i18n catalogs (en/vi/zh) expanded; parity tests guard key drift.
- TTS page reorganised (voice-model-section removed; playground +
  credentials + provider-setup split cleanly).

Docs: codebase-summary, project-changelog, tts-provider-capabilities.
2026-04-20 00:05:19 +07:00

117 lines
3.3 KiB
Go

//go:build integration
package integration
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/nextlevelbuilder/goclaw/internal/audio"
httphandlers "github.com/nextlevelbuilder/goclaw/internal/http"
)
// TestTTSCapabilities_ListProviders verifies that the audio manager correctly
// lists capabilities for all registered TTS providers.
func TestTTSCapabilities_ListProviders(t *testing.T) {
// Create manager with mock providers
mgr := audio.NewManager(audio.ManagerConfig{Primary: "openai"})
registerTestProviders(mgr)
// List capabilities from manager
caps := mgr.ListCapabilities()
if len(caps) < 4 {
t.Errorf("expected at least 4 providers, got %d", len(caps))
}
// Find OpenAI provider
var openaiCap *audio.ProviderCapabilities
for i := range caps {
if caps[i].Provider == "openai" {
openaiCap = &caps[i]
break
}
}
if openaiCap == nil {
t.Fatal("OpenAI provider not found in capabilities")
}
if openaiCap.DisplayName == "" {
t.Error("OpenAI capability missing DisplayName")
}
}
// TestTTSCapabilities_Handler_GetRoute verifies that GET /v1/tts/capabilities
// endpoint is properly registered on the TTS handler.
func TestTTSCapabilities_Handler_GetRoute(t *testing.T) {
mgr := audio.NewManager(audio.ManagerConfig{Primary: "openai"})
registerTestProviders(mgr)
handler := httphandlers.NewTTSHandler(mgr)
mux := http.NewServeMux()
handler.RegisterRoutes(mux)
// GET /v1/tts/capabilities (without auth, should fail on auth check)
req := httptest.NewRequest("GET", "/v1/tts/capabilities", nil)
rr := httptest.NewRecorder()
mux.ServeHTTP(rr, req)
// Should get 401 (unauthorized) since no Bearer token provided
// Note: The auth middleware requires valid token
if rr.Code != http.StatusUnauthorized && rr.Code != http.StatusBadRequest {
t.Logf("GET without auth returned %d (expected 401 or 400)", rr.Code)
}
}
// TestTTSCapabilities_ManagerRegistration verifies that providers can be
// registered and listed through the manager.
func TestTTSCapabilities_ManagerRegistration(t *testing.T) {
mgr := audio.NewManager(audio.ManagerConfig{Primary: "openai"})
// Register providers
mgr.RegisterTTS(&capTestProvider{name: "openai"})
mgr.RegisterTTS(&capTestProvider{name: "elevenlabs"})
// Check count
caps := mgr.ListCapabilities()
if len(caps) < 2 {
t.Errorf("expected at least 2 providers after registration, got %d", len(caps))
}
// Verify names present
names := make(map[string]bool)
for _, cap := range caps {
names[cap.Provider] = true
}
if !names["openai"] {
t.Error("openai not in capabilities after registration")
}
if !names["elevenlabs"] {
t.Error("elevenlabs not in capabilities after registration")
}
}
// registerTestProviders registers the standard test providers with the manager.
func registerTestProviders(mgr *audio.Manager) {
// Register mock providers for testing
mgr.RegisterTTS(&capTestProvider{name: "openai"})
mgr.RegisterTTS(&capTestProvider{name: "elevenlabs"})
mgr.RegisterTTS(&capTestProvider{name: "edge"})
mgr.RegisterTTS(&capTestProvider{name: "minimax"})
}
// capTestProvider is a test implementation of audio.TTSProvider.
type capTestProvider struct {
name string
}
func (p *capTestProvider) Name() string { return p.name }
func (p *capTestProvider) Synthesize(context.Context, string, audio.TTSOptions) (*audio.SynthResult, error) {
return nil, nil
}