"""Unit tests for AudioChunk value object.""" import pytest from src.domain.models.audio_chunk import AudioChunk class TestAudioChunk: """Test cases for AudioChunk value object.""" def test_valid_audio_chunk_creation(self): """Test creating valid AudioChunk instance.""" chunk = AudioChunk( data=b"fake_audio_chunk_data", format="wav", sample_rate=22050, chunk_index=0, is_final=False, timestamp=1.5 ) assert chunk.data == b"fake_audio_chunk_data" assert chunk.format == "wav" assert chunk.sample_rate == 22050 assert chunk.chunk_index == 0 assert chunk.is_final is False assert chunk.timestamp == 1.5 assert chunk.size_bytes == len(b"fake_audio_chunk_data") def test_audio_chunk_with_defaults(self): """Test creating AudioChunk with default values.""" chunk = AudioChunk( data=b"fake_audio_chunk_data", format="wav", sample_rate=22050, chunk_index=0 ) assert chunk.is_final is False assert chunk.timestamp is None def test_final_chunk_creation(self): """Test creating final AudioChunk.""" chunk = AudioChunk( data=b"final_chunk_data", format="wav", sample_rate=22050, chunk_index=5, is_final=True ) assert chunk.is_final is True assert chunk.chunk_index == 5 def test_non_bytes_data_raises_error(self): """Test that non-bytes data raises TypeError.""" with pytest.raises(TypeError, match="Audio data must be bytes"): AudioChunk( data="not_bytes", # type: ignore format="wav", sample_rate=22050, chunk_index=0 ) def test_empty_data_raises_error(self): """Test that empty data raises ValueError.""" with pytest.raises(ValueError, match="Audio data cannot be empty"): AudioChunk( data=b"", format="wav", sample_rate=22050, chunk_index=0 ) def test_unsupported_format_raises_error(self): """Test that unsupported format raises ValueError.""" with pytest.raises(ValueError, match="Unsupported audio format: xyz"): AudioChunk( data=b"fake_data", format="xyz", sample_rate=22050, chunk_index=0 ) def test_supported_formats(self): """Test all supported audio formats.""" supported_formats = ['wav', 'mp3', 'flac', 'ogg', 'raw'] for fmt in supported_formats: chunk = AudioChunk( data=b"fake_data", format=fmt, sample_rate=22050, chunk_index=0 ) assert chunk.format == fmt def test_non_integer_sample_rate_raises_error(self): """Test that non-integer sample rate raises ValueError.""" with pytest.raises(ValueError, match="Sample rate must be a positive integer"): AudioChunk( data=b"fake_data", format="wav", sample_rate=22050.5, # type: ignore chunk_index=0 ) def test_negative_sample_rate_raises_error(self): """Test that negative sample rate raises ValueError.""" with pytest.raises(ValueError, match="Sample rate must be a positive integer"): AudioChunk( data=b"fake_data", format="wav", sample_rate=-1, chunk_index=0 ) def test_zero_sample_rate_raises_error(self): """Test that zero sample rate raises ValueError.""" with pytest.raises(ValueError, match="Sample rate must be a positive integer"): AudioChunk( data=b"fake_data", format="wav", sample_rate=0, chunk_index=0 ) def test_non_integer_chunk_index_raises_error(self): """Test that non-integer chunk index raises ValueError.""" with pytest.raises(ValueError, match="Chunk index must be a non-negative integer"): AudioChunk( data=b"fake_data", format="wav", sample_rate=22050, chunk_index=1.5 # type: ignore ) def test_negative_chunk_index_raises_error(self): """Test that negative chunk index raises ValueError.""" with pytest.raises(ValueError, match="Chunk index must be a non-negative integer"): AudioChunk( data=b"fake_data", format="wav", sample_rate=22050, chunk_index=-1 ) def test_valid_chunk_index_zero(self): """Test that chunk index of zero is valid.""" chunk = AudioChunk( data=b"fake_data", format="wav", sample_rate=22050, chunk_index=0 ) assert chunk.chunk_index == 0 def test_non_boolean_is_final_raises_error(self): """Test that non-boolean is_final raises TypeError.""" with pytest.raises(TypeError, match="is_final must be a boolean"): AudioChunk( data=b"fake_data", format="wav", sample_rate=22050, chunk_index=0, is_final="true" # type: ignore ) def test_non_numeric_timestamp_raises_error(self): """Test that non-numeric timestamp raises ValueError.""" with pytest.raises(ValueError, match="Timestamp must be a non-negative number"): AudioChunk( data=b"fake_data", format="wav", sample_rate=22050, chunk_index=0, timestamp="1.5" # type: ignore ) def test_negative_timestamp_raises_error(self): """Test that negative timestamp raises ValueError.""" with pytest.raises(ValueError, match="Timestamp must be a non-negative number"): AudioChunk( data=b"fake_data", format="wav", sample_rate=22050, chunk_index=0, timestamp=-1.0 ) def test_valid_timestamp_zero(self): """Test that timestamp of zero is valid.""" chunk = AudioChunk( data=b"fake_data", format="wav", sample_rate=22050, chunk_index=0, timestamp=0.0 ) assert chunk.timestamp == 0.0 def test_valid_timestamp_values(self): """Test valid timestamp values.""" valid_timestamps = [0.0, 1.5, 10, 100.123] for timestamp in valid_timestamps: chunk = AudioChunk( data=b"fake_data", format="wav", sample_rate=22050, chunk_index=0, timestamp=timestamp ) assert chunk.timestamp == timestamp def test_size_bytes_property(self): """Test size_bytes property returns correct value.""" test_data = b"test_audio_chunk_data_123" chunk = AudioChunk( data=test_data, format="wav", sample_rate=22050, chunk_index=0 ) assert chunk.size_bytes == len(test_data) def test_duration_estimate_property(self): """Test duration_estimate property calculation.""" # Create chunk with known data size test_data = b"x" * 44100 # 44100 bytes chunk = AudioChunk( data=test_data, format="wav", sample_rate=22050, # 22050 samples per second chunk_index=0 ) # Expected duration: 44100 bytes / (22050 samples/sec * 2 bytes/sample) = 1.0 second expected_duration = 44100 / (22050 * 2) assert abs(chunk.duration_estimate - expected_duration) < 0.01 def test_duration_estimate_with_zero_sample_rate(self): """Test duration_estimate with edge case of zero calculation.""" # This shouldn't happen due to validation, but test the property logic chunk = AudioChunk( data=b"test_data", format="wav", sample_rate=22050, chunk_index=0 ) # Should return a reasonable estimate assert chunk.duration_estimate >= 0 def test_audio_chunk_is_immutable(self): """Test that AudioChunk is immutable (frozen dataclass).""" chunk = AudioChunk( data=b"fake_data", format="wav", sample_rate=22050, chunk_index=0 ) with pytest.raises(AttributeError): chunk.format = "mp3" # type: ignore def test_chunk_sequence_ordering(self): """Test that chunks can be ordered by chunk_index.""" chunks = [ AudioChunk(data=b"chunk2", format="wav", sample_rate=22050, chunk_index=2), AudioChunk(data=b"chunk0", format="wav", sample_rate=22050, chunk_index=0), AudioChunk(data=b"chunk1", format="wav", sample_rate=22050, chunk_index=1), ] # Sort by chunk_index sorted_chunks = sorted(chunks, key=lambda c: c.chunk_index) assert sorted_chunks[0].chunk_index == 0 assert sorted_chunks[1].chunk_index == 1 assert sorted_chunks[2].chunk_index == 2 def test_streaming_scenario(self): """Test typical streaming scenario with multiple chunks.""" # First chunk chunk1 = AudioChunk( data=b"first_chunk_data", format="wav", sample_rate=22050, chunk_index=0, is_final=False, timestamp=0.0 ) # Middle chunk chunk2 = AudioChunk( data=b"middle_chunk_data", format="wav", sample_rate=22050, chunk_index=1, is_final=False, timestamp=1.0 ) # Final chunk chunk3 = AudioChunk( data=b"final_chunk_data", format="wav", sample_rate=22050, chunk_index=2, is_final=True, timestamp=2.0 ) assert not chunk1.is_final assert not chunk2.is_final assert chunk3.is_final # Verify ordering chunks = [chunk1, chunk2, chunk3] for i, chunk in enumerate(chunks): assert chunk.chunk_index == i assert chunk.timestamp == float(i)