Spaces:
Sleeping
Sleeping
| """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 |