teachingAssistant / tests /unit /domain /models /test_speech_synthesis_request.py
Michael Hu
refactor based on DDD
5009cb8
raw
history blame
15 kB
"""Unit tests for SpeechSynthesisRequest value object."""
import pytest
from src.domain.models.speech_synthesis_request import SpeechSynthesisRequest
from src.domain.models.text_content import TextContent
from src.domain.models.voice_settings import VoiceSettings
class TestSpeechSynthesisRequest:
"""Test cases for SpeechSynthesisRequest value object."""
def test_valid_speech_synthesis_request_creation(self):
"""Test creating valid SpeechSynthesisRequest instance."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.2, language="en")
request = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
output_format="wav",
sample_rate=44100
)
assert request.text == text
assert request.voice_settings == voice_settings
assert request.output_format == "wav"
assert request.sample_rate == 44100
assert request.effective_sample_rate == 44100
def test_speech_synthesis_request_with_defaults(self):
"""Test creating SpeechSynthesisRequest with default values."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
request = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings
)
assert request.output_format == "wav"
assert request.sample_rate is None
assert request.effective_sample_rate == 22050 # Default
def test_non_text_content_raises_error(self):
"""Test that non-TextContent text raises TypeError."""
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
with pytest.raises(TypeError, match="Text must be a TextContent instance"):
SpeechSynthesisRequest(
text="Hello, world!", # type: ignore
voice_settings=voice_settings
)
def test_non_voice_settings_raises_error(self):
"""Test that non-VoiceSettings voice_settings raises TypeError."""
text = TextContent(text="Hello, world!", language="en")
with pytest.raises(TypeError, match="Voice settings must be a VoiceSettings instance"):
SpeechSynthesisRequest(
text=text,
voice_settings={"voice_id": "en_male_001", "speed": 1.0} # type: ignore
)
def test_non_string_output_format_raises_error(self):
"""Test that non-string output_format raises TypeError."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
with pytest.raises(TypeError, match="Output format must be a string"):
SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
output_format=123 # type: ignore
)
def test_unsupported_output_format_raises_error(self):
"""Test that unsupported output_format raises ValueError."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
with pytest.raises(ValueError, match="Unsupported output format: xyz"):
SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
output_format="xyz"
)
def test_supported_output_formats(self):
"""Test all supported output formats."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
supported_formats = ['wav', 'mp3', 'flac', 'ogg']
for fmt in supported_formats:
request = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
output_format=fmt
)
assert request.output_format == fmt
def test_non_integer_sample_rate_raises_error(self):
"""Test that non-integer sample_rate raises TypeError."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
with pytest.raises(TypeError, match="Sample rate must be an integer"):
SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=44100.5 # type: ignore
)
def test_negative_sample_rate_raises_error(self):
"""Test that negative sample_rate raises ValueError."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
with pytest.raises(ValueError, match="Sample rate must be positive"):
SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=-1
)
def test_zero_sample_rate_raises_error(self):
"""Test that zero sample_rate raises ValueError."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
with pytest.raises(ValueError, match="Sample rate must be positive"):
SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=0
)
def test_sample_rate_too_low_raises_error(self):
"""Test that sample rate below 8000 raises ValueError."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
with pytest.raises(ValueError, match="Sample rate must be between 8000 and 192000 Hz"):
SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=7999
)
def test_sample_rate_too_high_raises_error(self):
"""Test that sample rate above 192000 raises ValueError."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
with pytest.raises(ValueError, match="Sample rate must be between 8000 and 192000 Hz"):
SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=192001
)
def test_valid_sample_rate_boundaries(self):
"""Test valid sample rate boundaries."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
# Test minimum valid sample rate
request_min = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=8000
)
assert request_min.sample_rate == 8000
# Test maximum valid sample rate
request_max = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=192000
)
assert request_max.sample_rate == 192000
def test_mismatched_languages_raises_error(self):
"""Test that mismatched text and voice languages raise ValueError."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="fr_male_001", speed=1.0, language="fr")
with pytest.raises(ValueError, match="Text language \\(en\\) must match voice language \\(fr\\)"):
SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings
)
def test_matching_languages_success(self):
"""Test that matching text and voice languages work correctly."""
text = TextContent(text="Bonjour le monde!", language="fr")
voice_settings = VoiceSettings(voice_id="fr_male_001", speed=1.0, language="fr")
request = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings
)
assert request.text.language == "fr"
assert request.voice_settings.language == "fr"
def test_estimated_duration_seconds_property(self):
"""Test estimated_duration_seconds property calculation."""
text = TextContent(text="Hello world test", language="en") # 3 words
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
request = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings
)
# 3 words at 175 words per minute = 3/175 * 60 ≈ 1.03 seconds
estimated = request.estimated_duration_seconds
assert 1.0 <= estimated <= 1.1
def test_estimated_duration_with_speed_adjustment(self):
"""Test estimated duration with different speed settings."""
text = TextContent(text="Hello world test", language="en") # 3 words
voice_settings_slow = VoiceSettings(voice_id="en_male_001", speed=0.5, language="en")
voice_settings_fast = VoiceSettings(voice_id="en_male_001", speed=2.0, language="en")
request_slow = SpeechSynthesisRequest(text=text, voice_settings=voice_settings_slow)
request_fast = SpeechSynthesisRequest(text=text, voice_settings=voice_settings_fast)
# Slower speed should result in longer duration
assert request_slow.estimated_duration_seconds > request_fast.estimated_duration_seconds
def test_is_long_text_property(self):
"""Test is_long_text property."""
short_text = TextContent(text="Hello world", language="en")
long_text = TextContent(text="a" * 5001, language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
request_short = SpeechSynthesisRequest(text=short_text, voice_settings=voice_settings)
request_long = SpeechSynthesisRequest(text=long_text, voice_settings=voice_settings)
assert request_short.is_long_text is False
assert request_long.is_long_text is True
def test_effective_sample_rate_property(self):
"""Test effective_sample_rate property."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
# With explicit sample rate
request_explicit = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=44100
)
assert request_explicit.effective_sample_rate == 44100
# Without explicit sample rate (default)
request_default = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings
)
assert request_default.effective_sample_rate == 22050
def test_with_output_format_method(self):
"""Test with_output_format method creates new instance."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
original = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
output_format="wav",
sample_rate=44100
)
new_request = original.with_output_format("mp3")
assert new_request.output_format == "mp3"
assert new_request.text == original.text
assert new_request.voice_settings == original.voice_settings
assert new_request.sample_rate == original.sample_rate
assert new_request is not original # Different instances
def test_with_sample_rate_method(self):
"""Test with_sample_rate method creates new instance."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
original = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=44100
)
new_request = original.with_sample_rate(22050)
assert new_request.sample_rate == 22050
assert new_request.text == original.text
assert new_request.voice_settings == original.voice_settings
assert new_request.output_format == original.output_format
assert new_request is not original # Different instances
def test_with_sample_rate_none(self):
"""Test with_sample_rate method with None value."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
original = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings,
sample_rate=44100
)
new_request = original.with_sample_rate(None)
assert new_request.sample_rate is None
assert new_request.effective_sample_rate == 22050
def test_with_voice_settings_method(self):
"""Test with_voice_settings method creates new instance."""
text = TextContent(text="Hello, world!", language="en")
original_voice = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
new_voice = VoiceSettings(voice_id="en_female_001", speed=1.5, language="en")
original = SpeechSynthesisRequest(
text=text,
voice_settings=original_voice
)
new_request = original.with_voice_settings(new_voice)
assert new_request.voice_settings == new_voice
assert new_request.text == original.text
assert new_request.output_format == original.output_format
assert new_request.sample_rate == original.sample_rate
assert new_request is not original # Different instances
def test_speech_synthesis_request_is_immutable(self):
"""Test that SpeechSynthesisRequest is immutable (frozen dataclass)."""
text = TextContent(text="Hello, world!", language="en")
voice_settings = VoiceSettings(voice_id="en_male_001", speed=1.0, language="en")
request = SpeechSynthesisRequest(
text=text,
voice_settings=voice_settings
)
with pytest.raises(AttributeError):
request.output_format = "mp3" # type: ignore