Files
goclaw/internal/audio/elevenlabs/defaults_invariant_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

60 lines
1.9 KiB
Go

package elevenlabs_test
import (
"encoding/json"
"testing"
"github.com/nextlevelbuilder/goclaw/internal/audio"
"github.com/nextlevelbuilder/goclaw/internal/audio/elevenlabs"
)
// TestDefaults_PreserveLegacyBody verifies that populating opts.Params with
// all Capabilities defaults produces a body byte-equivalent to the nil-Params
// characterization fixture. Guards against silent default flip on save.
func TestDefaults_PreserveLegacyBody(t *testing.T) {
cfg := elevenlabs.Config{APIKey: "test-key", VoiceID: "test-voice"}
p := elevenlabs.NewTTSProvider(cfg)
caps := p.Capabilities()
if len(caps.Params) == 0 {
t.Skip("Capabilities.Params not yet populated (Phase C enrichment pending)")
}
// Build params map from all schema defaults (nested-key aware).
params := make(map[string]any)
for _, s := range caps.Params {
if s.Default != nil {
audio.SetNested(params, s.Key, s.Default)
}
}
bodyWithDefaults, pathWithDefaults := captureElevenLabsBody(t, cfg, audio.TTSOptions{Params: params})
bodyNilParams, pathNilParams := captureElevenLabsBody(t, cfg, audio.TTSOptions{})
// Re-encode both maps to canonical JSON for deterministic comparison.
wantJSON := canonicalJSON(t, bodyNilParams)
gotJSON := canonicalJSON(t, bodyWithDefaults)
if gotJSON != wantJSON {
t.Errorf("defaults-invariant body FAILED:\n with-defaults: %s\n nil-params: %s",
gotJSON, wantJSON)
}
// URL (output_format query param) must also be identical.
if pathWithDefaults != pathNilParams {
t.Errorf("defaults-invariant URL FAILED:\n with-defaults: %s\n nil-params: %s",
pathWithDefaults, pathNilParams)
}
}
// canonicalJSON re-marshals a decoded map to produce a deterministic JSON
// string suitable for equality comparison.
func canonicalJSON(t *testing.T, m map[string]any) string {
t.Helper()
b, err := json.Marshal(m)
if err != nil {
t.Fatalf("canonicalJSON marshal: %v", err)
}
return string(b)
}