Update desktop app to android app capabilities.
This commit is contained in:
+81
-13
@@ -3,13 +3,16 @@ no network, no audio device, no ONNX model."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from bolt_pet import config as tts_config
|
||||
from bolt_pet.audio.tts import chunks_to_int16, model_for, voice_for
|
||||
from bolt_pet.audio import tts
|
||||
from bolt_pet.audio.tts import TtsError, chunks_to_int16, voice_for
|
||||
from bolt_pet.audio.wake_word import NearMissLog
|
||||
|
||||
|
||||
@@ -83,7 +86,7 @@ def test_clear_resets_peak_and_entries():
|
||||
assert log.entries() == [] and log.peak == 0.0
|
||||
|
||||
|
||||
# ── voice / model selection (server speak_as) ───────────────────────────────
|
||||
# ── voice selection (server speak_as) ───────────────────────────────────────
|
||||
|
||||
def test_the_override_voice_wins_over_the_configured_one(monkeypatch):
|
||||
monkeypatch.setattr(tts_config, "ELEVENLABS_VOICE_ID", "DEFAULT")
|
||||
@@ -92,16 +95,81 @@ def test_the_override_voice_wins_over_the_configured_one(monkeypatch):
|
||||
assert voice_for(None) == "DEFAULT"
|
||||
|
||||
|
||||
def test_english_replies_in_the_default_voice_use_the_default_model(monkeypatch):
|
||||
monkeypatch.setattr(tts_config, "ELEVENLABS_MODEL_ID", "eleven_flash_v2")
|
||||
monkeypatch.setattr(tts_config, "ELEVENLABS_MULTILINGUAL_MODEL_ID", "eleven_flash_v2_5")
|
||||
assert model_for("all good here", None) == "eleven_flash_v2"
|
||||
# ── server-hosted synthesis (/desk/tts) ─────────────────────────────────────
|
||||
# No local ElevenLabs account: both the whole-clip and streaming paths post
|
||||
# to this pet's own server, same as server_client.py's other endpoints.
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _configured(monkeypatch):
|
||||
monkeypatch.setattr(tts_config, "SERVER_URL", "http://test-server:5002")
|
||||
monkeypatch.setattr(tts_config, "API_KEY", "test-key")
|
||||
monkeypatch.setattr(tts_config, "SESSION_ID", "pet-test")
|
||||
monkeypatch.setattr(tts_config, "ELEVENLABS_VOICE_ID", "default-voice")
|
||||
|
||||
|
||||
def test_a_picked_voice_or_non_english_text_uses_the_multilingual_model(monkeypatch):
|
||||
# eleven_flash_v2 is English-only: it would read either of these as
|
||||
# mangled phonetic English rather than failing outright.
|
||||
monkeypatch.setattr(tts_config, "ELEVENLABS_MODEL_ID", "eleven_flash_v2")
|
||||
monkeypatch.setattr(tts_config, "ELEVENLABS_MULTILINGUAL_MODEL_ID", "eleven_flash_v2_5")
|
||||
assert model_for("all good here", "VOICE1") == "eleven_flash_v2_5"
|
||||
assert model_for("こんにちは", None) == "eleven_flash_v2_5"
|
||||
def _mock_response(content=b"", ok=True):
|
||||
resp = MagicMock()
|
||||
resp.content = content
|
||||
resp.raise_for_status = MagicMock() if ok else MagicMock(side_effect=Exception("boom"))
|
||||
return resp
|
||||
|
||||
|
||||
def test_synthesize_pcm_posts_to_the_servers_tts_endpoint():
|
||||
pcm_bytes = np.array([1, 2, 3], dtype=np.int16).tobytes()
|
||||
with patch.object(tts.requests, "post") as post:
|
||||
post.return_value = _mock_response(pcm_bytes)
|
||||
pcm, rate = tts.synthesize_pcm("hello there")
|
||||
post.assert_called_once()
|
||||
args, kwargs = post.call_args
|
||||
assert args[0] == "http://test-server:5002/desk/tts"
|
||||
assert kwargs["headers"] == {"X-Desk-Api-Key": "test-key"}
|
||||
assert kwargs["json"] == {
|
||||
"session_id": "pet-test", "text": "hello there", "voice_id": "default-voice",
|
||||
}
|
||||
assert pcm.tolist() == [1, 2, 3]
|
||||
assert rate == tts_config.TTS_SAMPLE_RATE
|
||||
|
||||
|
||||
def test_synthesize_pcm_uses_the_override_voice():
|
||||
with patch.object(tts.requests, "post") as post:
|
||||
post.return_value = _mock_response(b"\x01\x00")
|
||||
tts.synthesize_pcm("hi", voice_id="picked-voice")
|
||||
assert post.call_args.kwargs["json"]["voice_id"] == "picked-voice"
|
||||
|
||||
|
||||
def test_synthesize_pcm_raises_when_not_configured(monkeypatch):
|
||||
monkeypatch.setattr(tts_config, "API_KEY", "")
|
||||
with pytest.raises(TtsError, match="BOLT_SERVER_URL"):
|
||||
tts.synthesize_pcm("hi")
|
||||
|
||||
|
||||
def test_synthesize_pcm_raises_on_empty_audio():
|
||||
with patch.object(tts.requests, "post") as post:
|
||||
post.return_value = _mock_response(b"")
|
||||
with pytest.raises(TtsError, match="no audio"):
|
||||
tts.synthesize_pcm("hi")
|
||||
|
||||
|
||||
def test_synthesize_pcm_raises_when_the_request_fails():
|
||||
with patch.object(tts.requests, "post") as post:
|
||||
post.return_value = _mock_response(b"", ok=False)
|
||||
with pytest.raises(TtsError, match="server tts request failed"):
|
||||
tts.synthesize_pcm("hi")
|
||||
|
||||
|
||||
def test_stream_pcm_posts_to_the_same_endpoint_with_stream_true():
|
||||
with patch.object(tts.requests, "post") as post:
|
||||
response = MagicMock()
|
||||
response.raise_for_status = MagicMock()
|
||||
response.iter_content.return_value = [np.array([4, 5], dtype=np.int16).tobytes()]
|
||||
post.return_value = response
|
||||
chunks = list(tts.stream_pcm("hi"))
|
||||
assert post.call_args.kwargs["stream"] is True
|
||||
assert post.call_args[0][0] == "http://test-server:5002/desk/tts"
|
||||
assert np.concatenate(chunks).tolist() == [4, 5]
|
||||
|
||||
|
||||
def test_stream_pcm_raises_when_not_configured(monkeypatch):
|
||||
monkeypatch.setattr(tts_config, "SERVER_URL", "")
|
||||
with pytest.raises(TtsError, match="BOLT_SERVER_URL"):
|
||||
list(tts.stream_pcm("hi"))
|
||||
|
||||
Reference in New Issue
Block a user