voiceforge / backend /tests /unit /test_tts_service.py
lordofgaming
Initial VoiceForge deployment (clean)
673435a
"""
VoiceForge - TTS Service Unit Tests
------------------------------------
Tests for Text-to-Speech service functions:
- Voice listing
- Audio synthesis
- SSML parsing
- Streaming audio
"""
import pytest
import asyncio
from unittest.mock import Mock, patch, AsyncMock
import struct
class TestEdgeTTSService:
"""Test suite for Edge TTS service"""
@pytest.mark.asyncio
async def test_list_voices_returns_list(self):
"""Test that voice listing returns non-empty list"""
with patch('app.services.edge_tts_service.EdgeTTSService') as MockService:
mock_service = MockService.return_value
mock_service.list_voices = AsyncMock(return_value=[
{"name": "en-US-Neural2-F", "gender": "Female", "language": "en-US"},
{"name": "en-US-Neural2-M", "gender": "Male", "language": "en-US"},
])
voices = await mock_service.list_voices()
assert isinstance(voices, list)
assert len(voices) > 0
assert "name" in voices[0]
@pytest.mark.asyncio
async def test_synthesize_returns_audio_bytes(self):
"""Test that synthesis returns audio data"""
with patch('app.services.edge_tts_service.EdgeTTSService') as MockService:
mock_service = MockService.return_value
# Return minimal valid audio bytes
mock_audio = b'RIFF' + b'\x00' * 100
mock_service.synthesize = AsyncMock(return_value=mock_audio)
audio = await mock_service.synthesize("Hello world", voice="en-US-Neural2-F")
assert isinstance(audio, bytes)
assert len(audio) > 0
@pytest.mark.asyncio
async def test_synthesize_with_ssml(self):
"""Test SSML input processing"""
ssml_input = """
<speak>
<prosody rate="slow" pitch="+2st">
Hello, welcome to VoiceForge.
</prosody>
</speak>
"""
with patch('app.services.edge_tts_service.EdgeTTSService') as MockService:
mock_service = MockService.return_value
mock_service.synthesize_ssml = AsyncMock(return_value=b'AUDIO_DATA')
audio = await mock_service.synthesize_ssml(ssml_input, voice="en-US-Neural2-F")
assert audio is not None
@pytest.mark.asyncio
async def test_streaming_synthesis(self):
"""Test streaming audio generation"""
with patch('app.services.edge_tts_service.EdgeTTSService') as MockService:
mock_service = MockService.return_value
# Simulate streaming chunks - must accept text parameter
async def mock_stream(text):
for i in range(3):
yield b'chunk_' + str(i).encode()
mock_service.stream_synthesize = mock_stream
chunks = []
async for chunk in mock_service.stream_synthesize("Test text"):
chunks.append(chunk)
assert len(chunks) == 3
class TestGoogleTTSService:
"""Test suite for Google Cloud TTS"""
@pytest.mark.asyncio
async def test_google_tts_synthesize(self):
"""Test Google TTS synthesis"""
with patch('app.services.tts_service.TTSService') as MockService:
mock_service = MockService.return_value
mock_service.synthesize_google = AsyncMock(return_value=b'MP3_AUDIO_DATA')
audio = await mock_service.synthesize_google(
text="Hello world",
voice="en-US-Wavenet-D",
audio_format="MP3"
)
assert audio is not None
assert isinstance(audio, bytes)
class TestVoiceParameters:
"""Test voice parameter validation"""
def test_valid_speed_range(self):
"""Test speed parameter is within valid range"""
valid_speeds = [0.5, 1.0, 1.5, 2.0]
for speed in valid_speeds:
assert 0.25 <= speed <= 4.0
def test_valid_pitch_range(self):
"""Test pitch parameter is within valid range"""
valid_pitches = [-5.0, 0.0, 5.0]
for pitch in valid_pitches:
assert -20.0 <= pitch <= 20.0
def test_invalid_speed_rejected(self):
"""Test that invalid speed values are caught"""
invalid_speeds = [0.0, -1.0, 5.0]
for speed in invalid_speeds:
assert not (0.25 <= speed <= 4.0)
# Run tests
if __name__ == "__main__":
pytest.main([__file__, "-v"])