"""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.""" @pytest.fixture def sample_text_content(self): """Sample text content for testing.""" return TextContent( text="Hello, world!", language="en" ) @pytest.fixture def sample_voice_settings(self): """Sample voice settings for testing.""" return VoiceSettings( voice_id="en_male_001", speed=1.0, language="en" ) def test_valid_speech_synthesis_request_creation(self, sample_text_content, sample_voice_settings): """Test creating valid SpeechSynthesisRequest instance.""" request = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, output_format="wav", sample_rate=22050 ) assert request.text_content == sample_text_content assert request.voice_settings == sample_voice_settings assert request.output_format == "wav" assert request.sample_rate == 22050 assert request.effective_sample_rate == 22050 def test_speech_synthesis_request_with_defaults(self, sample_text_content, sample_voice_settings): """Test creating SpeechSynthesisRequest with default values.""" request = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_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, sample_voice_settings): """Test that non-TextContent raises TypeError.""" with pytest.raises(TypeError, match="Text must be a TextContent instance"): SpeechSynthesisRequest( text_content="not a TextContent", # type: ignore voice_settings=sample_voice_settings ) def test_non_voice_settings_raises_error(self, sample_text_content): """Test that non-VoiceSettings raises TypeError.""" with pytest.raises(TypeError, match="Voice settings must be a VoiceSettings instance"): SpeechSynthesisRequest( text_content=sample_text_content, voice_settings="not voice settings" # type: ignore ) def test_non_string_output_format_raises_error(self, sample_text_content, sample_voice_settings): """Test that non-string output format raises TypeError.""" with pytest.raises(TypeError, match="Output format must be a string"): SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, output_format=123 # type: ignore ) def test_unsupported_output_format_raises_error(self, sample_text_content, sample_voice_settings): """Test that unsupported output format raises ValueError.""" with pytest.raises(ValueError, match="Unsupported output format: xyz"): SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, output_format="xyz" ) def test_supported_output_formats(self, sample_text_content, sample_voice_settings): """Test all supported output formats.""" supported_formats = ['wav', 'mp3', 'flac', 'ogg'] for fmt in supported_formats: request = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, output_format=fmt ) assert request.output_format == fmt def test_non_integer_sample_rate_raises_error(self, sample_text_content, sample_voice_settings): """Test that non-integer sample rate raises TypeError.""" with pytest.raises(TypeError, match="Sample rate must be an integer"): SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=22050.5 # type: ignore ) def test_negative_sample_rate_raises_error(self, sample_text_content, sample_voice_settings): """Test that negative sample rate raises ValueError.""" with pytest.raises(ValueError, match="Sample rate must be positive"): SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=-1 ) def test_zero_sample_rate_raises_error(self, sample_text_content, sample_voice_settings): """Test that zero sample rate raises ValueError.""" with pytest.raises(ValueError, match="Sample rate must be positive"): SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=0 ) def test_sample_rate_too_low_raises_error(self, sample_text_content, sample_voice_settings): """Test that sample rate below 8000 raises ValueError.""" with pytest.raises(ValueError, match="Sample rate must be between 8000 and 192000 Hz"): SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=7999 ) def test_sample_rate_too_high_raises_error(self, sample_text_content, sample_voice_settings): """Test that sample rate above 192000 raises ValueError.""" with pytest.raises(ValueError, match="Sample rate must be between 8000 and 192000 Hz"): SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=192001 ) def test_valid_sample_rate_boundaries(self, sample_text_content, sample_voice_settings): """Test valid sample rate boundaries.""" # Test minimum valid sample rate request_min = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=8000 ) assert request_min.sample_rate == 8000 # Test maximum valid sample rate request_max = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=192000 ) assert request_max.sample_rate == 192000 def test_language_mismatch_raises_error(self, sample_voice_settings): """Test that language mismatch between text and voice raises ValueError.""" text_content = TextContent(text="Hola mundo", language="es") with pytest.raises(ValueError, match="Text language \\(es\\) must match voice language \\(en\\)"): SpeechSynthesisRequest( text_content=text_content, voice_settings=sample_voice_settings # language="en" ) def test_matching_languages_success(self): """Test that matching languages between text and voice works.""" text_content = TextContent(text="Hola mundo", language="es") voice_settings = VoiceSettings(voice_id="es_female_001", speed=1.0, language="es") request = SpeechSynthesisRequest( text_content=text_content, voice_settings=voice_settings ) assert request.text_content.language == request.voice_settings.language def test_estimated_duration_seconds_property(self, sample_text_content, sample_voice_settings): """Test estimated_duration_seconds property calculation.""" request = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings ) # Should be based on word count and speed expected_duration = (sample_text_content.word_count / (175 / sample_voice_settings.speed)) * 60 assert abs(request.estimated_duration_seconds - expected_duration) < 0.01 def test_estimated_duration_with_different_speed(self, sample_text_content): """Test estimated duration with different speech speed.""" fast_voice = VoiceSettings(voice_id="test", speed=2.0, language="en") slow_voice = VoiceSettings(voice_id="test", speed=0.5, language="en") fast_request = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=fast_voice ) slow_request = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=slow_voice ) # Faster speed should result in shorter duration assert fast_request.estimated_duration_seconds < slow_request.estimated_duration_seconds def test_is_long_text_property(self, sample_voice_settings): """Test is_long_text property.""" # Short text short_text = TextContent(text="Hello", language="en") short_request = SpeechSynthesisRequest( text_content=short_text, voice_settings=sample_voice_settings ) assert short_request.is_long_text is False # Long text (over 5000 characters) long_text = TextContent(text="a" * 5001, language="en") long_request = SpeechSynthesisRequest( text_content=long_text, voice_settings=sample_voice_settings ) assert long_request.is_long_text is True def test_with_output_format_method(self, sample_text_content, sample_voice_settings): """Test with_output_format method creates new instance.""" original = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, output_format="wav", sample_rate=22050 ) new_request = original.with_output_format("mp3") assert new_request.output_format == "mp3" assert new_request.text_content == original.text_content 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, sample_text_content, sample_voice_settings): """Test with_sample_rate method creates new instance.""" original = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=22050 ) new_request = original.with_sample_rate(44100) assert new_request.sample_rate == 44100 assert new_request.text_content == original.text_content 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, sample_text_content, sample_voice_settings): """Test with_sample_rate method with None value.""" original = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=22050 ) new_request = original.with_sample_rate(None) assert new_request.sample_rate is None assert new_request.effective_sample_rate == 22050 # Default def test_with_voice_settings_method(self, sample_text_content, sample_voice_settings): """Test with_voice_settings method creates new instance.""" new_voice_settings = VoiceSettings(voice_id="en_female_001", speed=1.5, language="en") original = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings ) new_request = original.with_voice_settings(new_voice_settings) assert new_request.voice_settings == new_voice_settings assert new_request.text_content == original.text_content 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, sample_text_content, sample_voice_settings): """Test that SpeechSynthesisRequest is immutable (frozen dataclass).""" request = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings ) with pytest.raises(AttributeError): request.output_format = "mp3" # type: ignore def test_effective_sample_rate_with_none(self, sample_text_content, sample_voice_settings): """Test effective_sample_rate when sample_rate is None.""" request = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=None ) assert request.effective_sample_rate == 22050 # Default value def test_effective_sample_rate_with_value(self, sample_text_content, sample_voice_settings): """Test effective_sample_rate when sample_rate has a value.""" request = SpeechSynthesisRequest( text_content=sample_text_content, voice_settings=sample_voice_settings, sample_rate=44100 ) assert request.effective_sample_rate == 44100