Files
tts-module/main_test.go
Billy D. 147b685645 feat: rewrite tts-module in Go
Replace Python streaming TTS service with Go for smaller container images.
- VoiceRegistry: discovers custom voices from model store
- NATS subscriptions: TTS requests, voice list, voice refresh
- JetStream AI_VOICE_TTS stream setup
- Chunked audio streaming over NATS
- Dockerfile: multi-stage golang:1.25-alpine → scratch
- CI: Gitea Actions with lint/test/release/docker/notify
2026-02-19 17:56:06 -05:00

148 lines
3.8 KiB
Go

package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestVoiceRegistryRefresh(t *testing.T) {
dir := t.TempDir()
// Create a voice directory
voiceDir := filepath.Join(dir, "test-voice")
os.MkdirAll(voiceDir, 0o755)
info := map[string]string{"name": "test-voice", "language": "en", "type": "coqui-tts", "created_at": "2024-01-01"}
infoData, _ := json.Marshal(info)
os.WriteFile(filepath.Join(voiceDir, "model_info.json"), infoData, 0o644)
os.WriteFile(filepath.Join(voiceDir, "model.pth"), []byte("fake"), 0o644)
vr := newVoiceRegistry(dir)
count := vr.refresh()
if count != 1 {
t.Errorf("refresh() = %d, want 1", count)
}
voice := vr.get("test-voice")
if voice == nil {
t.Fatal("expected voice 'test-voice'")
}
if voice.Language != "en" {
t.Errorf("language = %q, want %q", voice.Language, "en")
}
if voice.ModelPath != filepath.Join(voiceDir, "model.pth") {
t.Errorf("model_path = %q", voice.ModelPath)
}
voices := vr.listVoices()
if len(voices) != 1 {
t.Errorf("listVoices() len = %d, want 1", len(voices))
}
}
func TestVoiceRegistryMissing(t *testing.T) {
vr := newVoiceRegistry("/nonexistent/path")
count := vr.refresh()
if count != 0 {
t.Errorf("refresh() = %d, want 0", count)
}
}
func TestVoiceRegistryNoModel(t *testing.T) {
dir := t.TempDir()
voiceDir := filepath.Join(dir, "bad-voice")
os.MkdirAll(voiceDir, 0o755)
info := map[string]string{"name": "bad-voice"}
infoData, _ := json.Marshal(info)
os.WriteFile(filepath.Join(voiceDir, "model_info.json"), infoData, 0o644)
// No model.pth
vr := newVoiceRegistry(dir)
count := vr.refresh()
if count != 0 {
t.Errorf("refresh() = %d, want 0", count)
}
}
func TestSynthesizeHTTP(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/audio/speech" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
var payload map[string]any
json.NewDecoder(r.Body).Decode(&payload)
if payload["text"] != "hello" {
t.Errorf("unexpected text: %v", payload["text"])
}
w.Write([]byte{0x01, 0x02, 0x03, 0x04})
}))
defer ts.Close()
// Test the XTTS synthesis HTTP call directly
client := ts.Client()
body := `{"text":"hello","speaker":"default","language":"en"}`
resp, err := client.Post(ts.URL+"/v1/audio/speech", "application/json", strings.NewReader(body))
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("status = %d, want 200", resp.StatusCode)
}
}
func TestHelperFunctions(t *testing.T) {
m := map[string]any{"text": "hello", "stream": true, "count": 42}
if got := strVal(m, "text", ""); got != "hello" {
t.Errorf("strVal(text) = %q", got)
}
if got := strVal(m, "missing", "def"); got != "def" {
t.Errorf("strVal(missing) = %q", got)
}
if got := boolVal(m, "stream", false); !got {
t.Errorf("boolVal(stream) = %v", got)
}
if got := boolVal(m, "missing", true); !got {
t.Errorf("boolVal(missing) = %v", got)
}
}
func TestGetEnv(t *testing.T) {
t.Setenv("MY_TEST_VAR", "value")
if got := getEnv("MY_TEST_VAR", "fallback"); got != "value" {
t.Errorf("getEnv = %q", got)
}
if got := getEnv("NONEXISTENT_XYZ", "fallback"); got != "fallback" {
t.Errorf("getEnv = %q", got)
}
}
func TestGetEnvInt(t *testing.T) {
t.Setenv("MY_PORT", "8080")
if got := getEnvInt("MY_PORT", 3000); got != 8080 {
t.Errorf("getEnvInt = %d", got)
}
if got := getEnvInt("NONEXISTENT_XYZ", 3000); got != 3000 {
t.Errorf("getEnvInt = %d", got)
}
}
func TestOrDefault(t *testing.T) {
if got := orDefault("", "en"); got != "en" {
t.Errorf("orDefault('', 'en') = %q", got)
}
if got := orDefault("fr", "en"); got != "fr" {
t.Errorf("orDefault('fr', 'en') = %q", got)
}
}