Files
vibe-bot/vibe_bot/tests/test_tts.py
T

173 lines
5.4 KiB
Python

"""Tests for the tts module."""
from __future__ import annotations
from unittest.mock import MagicMock
import numpy as np
import pytest
def test_tts_engine_init(mock_kokoro_tts: MagicMock) -> None:
"""Test TTSEngine initialization."""
from vibe_bot.tts import TTSEngine
engine = TTSEngine("/tmp/test-model.onnx", "/tmp/test-voices.bin")
assert engine.model_path == "/tmp/test-model.onnx"
assert engine.voices_path == "/tmp/test-voices.bin"
def test_generate_audio(mock_kokoro_tts: MagicMock) -> None:
"""Test audio generation returns a BytesIO object."""
from io import BytesIO
from vibe_bot.tts import TTSEngine
engine = TTSEngine("/tmp/test-model.onnx", "/tmp/test-voices.bin")
result = engine.generate_audio("hello world this is a test")
assert isinstance(result, BytesIO)
result.seek(0)
data = result.read()
assert len(data) > 0
def test_generate_audio_empty_text(mock_kokoro_tts: MagicMock) -> None:
"""Test that generating audio with empty text raises ValueError."""
from vibe_bot.tts import TTSEngine
mock_kokoro_tts["chunk_text"].return_value = []
engine = TTSEngine("/tmp/test-model.onnx", "/tmp/test-voices.bin")
with pytest.raises(ValueError, match="No audio samples generated"):
engine.generate_audio("")
def test_generate_audio_single_chunk(mock_kokoro_tts: MagicMock) -> None:
"""Test audio generation with a single chunk."""
from io import BytesIO
from vibe_bot.tts import TTSEngine
mock_kokoro_tts["chunk_text"].return_value = ["single chunk text"]
engine = TTSEngine("/tmp/test-model.onnx", "/tmp/test-voices.bin")
result = engine.generate_audio("single chunk text")
assert isinstance(result, BytesIO)
mock_kokoro_tts["process_chunk_sequential"].assert_called_once()
def test_generate_audio_multiple_chunks(mock_kokoro_tts: MagicMock) -> None:
"""Test audio generation with multiple chunks."""
from io import BytesIO
from vibe_bot.tts import TTSEngine
mock_kokoro_tts["chunk_text"].return_value = [
"chunk one",
"chunk two",
"chunk three",
]
engine = TTSEngine("/tmp/test-model.onnx", "/tmp/test-voices.bin")
result = engine.generate_audio(
"this text is long enough to be split into multiple chunks",
)
assert isinstance(result, BytesIO)
assert mock_kokoro_tts["process_chunk_sequential"].call_count == 3
def test_generate_audio_chunk_failure(mock_kokoro_tts: MagicMock) -> None:
"""Test that failed chunks are skipped but audio is still generated."""
from io import BytesIO
from vibe_bot.tts import TTSEngine
def process_with_failure(
chunk: str,
kokoro: MagicMock,
voice: str,
speed: float,
lang: str,
) -> tuple[np.ndarray, int]:
if chunk == "bad chunk":
raise Exception("processing error")
return np.array([0.1, 0.2], dtype=np.float32), 24000
mock_kokoro_tts["chunk_text"].return_value = [
"good chunk",
"bad chunk",
"another good",
]
mock_kokoro_tts["process_chunk_sequential"].side_effect = process_with_failure
engine = TTSEngine("/tmp/test-model.onnx", "/tmp/test-voices.bin")
result = engine.generate_audio("good chunk bad chunk another good")
assert isinstance(result, BytesIO)
def test_generate_audio_all_chunks_fail(mock_kokoro_tts: MagicMock) -> None:
"""Test that ValueError is raised when all chunks fail."""
from vibe_bot.tts import TTSEngine
mock_kokoro_tts["chunk_text"].return_value = ["chunk1", "chunk2"]
mock_kokoro_tts["process_chunk_sequential"].side_effect = Exception("always fails")
engine = TTSEngine("/tmp/test-model.onnx", "/tmp/test-voices.bin")
with pytest.raises(ValueError, match="No audio samples generated"):
engine.generate_audio("all chunks fail")
def test_generate_audio_with_custom_voice(mock_kokoro_tts: MagicMock) -> None:
"""Test audio generation with custom voice parameter."""
from vibe_bot.tts import TTSEngine
engine = TTSEngine("/tmp/test-model.onnx", "/tmp/test-voices.bin")
engine.generate_audio("hello", voice="af_bella", speed=1.5, lang="en-us")
call_args = mock_kokoro_tts["process_chunk_sequential"].call_args
# Called with positional args: chunk, kokoro, voice, speed, lang
assert call_args[0][2] == "af_bella"
assert call_args[0][3] == 1.5
assert call_args[0][4] == "en-us"
def test_generate_audio_returns_seekable(mock_kokoro_tts: MagicMock) -> None:
"""Test that the returned BytesIO is seekable."""
from vibe_bot.tts import TTSEngine
engine = TTSEngine("/tmp/test-model.onnx", "/tmp/test-voices.bin")
result = engine.generate_audio("hello world")
result.seek(0)
data = result.read()
assert len(data) > 0
# Should be able to seek and read again
result.seek(0)
data2 = result.read()
assert data == data2
def test_default_voice_constant() -> None:
"""Test that DEFAULT_VOICE has expected value."""
from vibe_bot.tts import DEFAULT_VOICE
assert DEFAULT_VOICE == "af_sarah"
def test_default_speed_constant() -> None:
"""Test that DEFAULT_SPEED has expected value."""
from vibe_bot.tts import DEFAULT_SPEED
assert DEFAULT_SPEED == 1.0
def test_default_lang_constant() -> None:
"""Test that DEFAULT_LANG has expected value."""
from vibe_bot.tts import DEFAULT_LANG
assert DEFAULT_LANG == "en-us"