"""Tests for audio utility functions.""" import hashlib import tempfile from pathlib import Path from unittest.mock import patch import pytest from app.utils.audio import get_audio_duration, get_file_hash, get_file_size class TestAudioUtils: """Test audio utility functions.""" def test_get_file_hash(self): """Test file hash calculation.""" # Create a temporary file with known content test_content = "test content for hashing" with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: f.write(test_content) temp_path = Path(f.name) try: # Calculate hash using our function result_hash = get_file_hash(temp_path) # Calculate expected hash manually expected_hash = hashlib.sha256(test_content.encode()).hexdigest() # Verify the hash is correct assert result_hash == expected_hash assert len(result_hash) == 64 # SHA-256 hash length assert isinstance(result_hash, str) finally: temp_path.unlink() def test_get_file_hash_binary_content(self): """Test file hash calculation with binary content.""" # Create a temporary file with binary content test_bytes = b"\x00\x01\x02\x03\xff\xfe\xfd" with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f: f.write(test_bytes) temp_path = Path(f.name) try: # Calculate hash using our function result_hash = get_file_hash(temp_path) # Calculate expected hash manually expected_hash = hashlib.sha256(test_bytes).hexdigest() # Verify the hash is correct assert result_hash == expected_hash assert len(result_hash) == 64 # SHA-256 hash length assert isinstance(result_hash, str) finally: temp_path.unlink() def test_get_file_hash_empty_file(self): """Test file hash calculation for empty file.""" # Create an empty temporary file with tempfile.NamedTemporaryFile(delete=False) as f: temp_path = Path(f.name) try: # Calculate hash using our function result_hash = get_file_hash(temp_path) # Calculate expected hash for empty content expected_hash = hashlib.sha256(b"").hexdigest() # Verify the hash is correct assert result_hash == expected_hash assert len(result_hash) == 64 # SHA-256 hash length assert isinstance(result_hash, str) finally: temp_path.unlink() def test_get_file_hash_large_file(self): """Test file hash calculation for large file (tests chunked reading).""" # Create a large temporary file (larger than 4096 bytes chunk size) test_content = "A" * 10000 # 10KB of 'A' characters with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: f.write(test_content) temp_path = Path(f.name) try: # Calculate hash using our function result_hash = get_file_hash(temp_path) # Calculate expected hash manually expected_hash = hashlib.sha256(test_content.encode()).hexdigest() # Verify the hash is correct assert result_hash == expected_hash assert len(result_hash) == 64 # SHA-256 hash length assert isinstance(result_hash, str) finally: temp_path.unlink() def test_get_file_size(self): """Test file size calculation.""" # Create a temporary file with known content test_content = "test content for size calculation" with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: f.write(test_content) temp_path = Path(f.name) try: # Get size using our function result_size = get_file_size(temp_path) # Get expected size using pathlib directly expected_size = temp_path.stat().st_size # Verify the size is correct assert result_size == expected_size assert result_size > 0 assert isinstance(result_size, int) finally: temp_path.unlink() def test_get_file_size_empty_file(self): """Test file size calculation for empty file.""" # Create an empty temporary file with tempfile.NamedTemporaryFile(delete=False) as f: temp_path = Path(f.name) try: # Get size using our function result_size = get_file_size(temp_path) # Verify the size is zero assert result_size == 0 assert isinstance(result_size, int) finally: temp_path.unlink() def test_get_file_size_binary_file(self): """Test file size calculation for binary file.""" # Create a temporary file with binary content test_bytes = b"\x00\x01\x02\x03\xff\xfe\xfd" * 100 # 700 bytes with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f: f.write(test_bytes) temp_path = Path(f.name) try: # Get size using our function result_size = get_file_size(temp_path) # Verify the size is correct assert result_size == len(test_bytes) assert result_size == 700 assert isinstance(result_size, int) finally: temp_path.unlink() @patch("app.utils.audio.ffmpeg.probe") def test_get_audio_duration_success(self, mock_probe): """Test successful audio duration extraction.""" # Mock ffmpeg.probe to return duration mock_probe.return_value = {"format": {"duration": "123.456"}} temp_path = Path("/fake/path/test.mp3") duration = get_audio_duration(temp_path) # Verify duration is converted correctly (seconds to milliseconds) assert duration == 123456 # 123.456 seconds * 1000 = 123456 ms assert isinstance(duration, int) mock_probe.assert_called_once_with(str(temp_path)) @patch("app.utils.audio.ffmpeg.probe") def test_get_audio_duration_integer_duration(self, mock_probe): """Test audio duration extraction with integer duration.""" # Mock ffmpeg.probe to return integer duration mock_probe.return_value = {"format": {"duration": "60"}} temp_path = Path("/fake/path/test.wav") duration = get_audio_duration(temp_path) # Verify duration is converted correctly assert duration == 60000 # 60 seconds * 1000 = 60000 ms assert isinstance(duration, int) mock_probe.assert_called_once_with(str(temp_path)) @patch("app.utils.audio.ffmpeg.probe") def test_get_audio_duration_zero_duration(self, mock_probe): """Test audio duration extraction with zero duration.""" # Mock ffmpeg.probe to return zero duration mock_probe.return_value = {"format": {"duration": "0.0"}} temp_path = Path("/fake/path/silent.mp3") duration = get_audio_duration(temp_path) # Verify duration is zero assert duration == 0 assert isinstance(duration, int) mock_probe.assert_called_once_with(str(temp_path)) @patch("app.utils.audio.ffmpeg.probe") def test_get_audio_duration_fractional_duration(self, mock_probe): """Test audio duration extraction with fractional seconds.""" # Mock ffmpeg.probe to return fractional duration mock_probe.return_value = {"format": {"duration": "45.123"}} temp_path = Path("/fake/path/test.flac") duration = get_audio_duration(temp_path) # Verify duration is converted and rounded correctly assert duration == 45123 # 45.123 seconds * 1000 = 45123 ms assert isinstance(duration, int) mock_probe.assert_called_once_with(str(temp_path)) @patch("app.utils.audio.ffmpeg.probe") def test_get_audio_duration_ffmpeg_error(self, mock_probe): """Test audio duration extraction when ffmpeg fails.""" # Mock ffmpeg.probe to raise an exception mock_probe.side_effect = Exception("FFmpeg error: file not found") temp_path = Path("/fake/path/nonexistent.mp3") duration = get_audio_duration(temp_path) # Verify duration defaults to 0 on error assert duration == 0 assert isinstance(duration, int) mock_probe.assert_called_once_with(str(temp_path)) @patch("app.utils.audio.ffmpeg.probe") def test_get_audio_duration_missing_format(self, mock_probe): """Test audio duration extraction when format info is missing.""" # Mock ffmpeg.probe to return data without format info mock_probe.return_value = {"streams": []} temp_path = Path("/fake/path/corrupt.mp3") duration = get_audio_duration(temp_path) # Verify duration defaults to 0 when format info is missing assert duration == 0 assert isinstance(duration, int) mock_probe.assert_called_once_with(str(temp_path)) @patch("app.utils.audio.ffmpeg.probe") def test_get_audio_duration_missing_duration(self, mock_probe): """Test audio duration extraction when duration is missing.""" # Mock ffmpeg.probe to return format without duration mock_probe.return_value = {"format": {"size": "1024"}} temp_path = Path("/fake/path/noduration.mp3") duration = get_audio_duration(temp_path) # Verify duration defaults to 0 when duration is missing assert duration == 0 assert isinstance(duration, int) mock_probe.assert_called_once_with(str(temp_path)) @patch("app.utils.audio.ffmpeg.probe") def test_get_audio_duration_invalid_duration(self, mock_probe): """Test audio duration extraction with invalid duration value.""" # Mock ffmpeg.probe to return invalid duration mock_probe.return_value = {"format": {"duration": "invalid"}} temp_path = Path("/fake/path/invalid.mp3") duration = get_audio_duration(temp_path) # Verify duration defaults to 0 when duration is invalid assert duration == 0 assert isinstance(duration, int) mock_probe.assert_called_once_with(str(temp_path)) def test_get_file_hash_nonexistent_file(self): """Test file hash calculation for nonexistent file.""" nonexistent_path = Path("/fake/nonexistent/file.mp3") # Should raise FileNotFoundError for nonexistent file with pytest.raises(FileNotFoundError): get_file_hash(nonexistent_path) def test_get_file_size_nonexistent_file(self): """Test file size calculation for nonexistent file.""" nonexistent_path = Path("/fake/nonexistent/file.mp3") # Should raise FileNotFoundError for nonexistent file with pytest.raises(FileNotFoundError): get_file_size(nonexistent_path)