Files
sdb2-backend/tests/utils/test_audio.py
JSC 8847131f24 Refactor test files for improved readability and consistency
- Removed unnecessary blank lines and adjusted formatting in test files.
- Ensured consistent use of commas in function calls and assertions across various test cases.
- Updated import statements for better organization and clarity.
- Enhanced mock setups in tests for better isolation and reliability.
- Improved assertions to follow a consistent style for better readability.
2025-07-31 21:37:04 +02:00

416 lines
15 KiB
Python

"""Tests for audio utility functions."""
import hashlib
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
from app.models.sound import Sound
from app.utils.audio import (
get_audio_duration,
get_file_hash,
get_file_size,
get_sound_file_path,
)
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)
def test_get_sound_file_path_sdb_original(self):
"""Test getting sound file path for SDB type original file."""
sound = Sound(
id=1,
name="Test Sound",
filename="test.mp3",
type="SDB",
is_normalized=False,
)
result = get_sound_file_path(sound)
expected = Path("sounds/originals/soundboard/test.mp3")
assert result == expected
def test_get_sound_file_path_sdb_normalized(self):
"""Test getting sound file path for SDB type normalized file."""
sound = Sound(
id=1,
name="Test Sound",
filename="original.mp3",
normalized_filename="normalized.mp3",
type="SDB",
is_normalized=True,
)
result = get_sound_file_path(sound)
expected = Path("sounds/normalized/soundboard/normalized.mp3")
assert result == expected
def test_get_sound_file_path_tts_original(self):
"""Test getting sound file path for TTS type original file."""
sound = Sound(
id=2,
name="TTS Sound",
filename="tts_file.wav",
type="TTS",
is_normalized=False,
)
result = get_sound_file_path(sound)
expected = Path("sounds/originals/text_to_speech/tts_file.wav")
assert result == expected
def test_get_sound_file_path_tts_normalized(self):
"""Test getting sound file path for TTS type normalized file."""
sound = Sound(
id=2,
name="TTS Sound",
filename="original.wav",
normalized_filename="normalized.mp3",
type="TTS",
is_normalized=True,
)
result = get_sound_file_path(sound)
expected = Path("sounds/normalized/text_to_speech/normalized.mp3")
assert result == expected
def test_get_sound_file_path_ext_original(self):
"""Test getting sound file path for EXT type original file."""
sound = Sound(
id=3,
name="Extracted Sound",
filename="extracted.mp3",
type="EXT",
is_normalized=False,
)
result = get_sound_file_path(sound)
expected = Path("sounds/originals/extracted/extracted.mp3")
assert result == expected
def test_get_sound_file_path_ext_normalized(self):
"""Test getting sound file path for EXT type normalized file."""
sound = Sound(
id=3,
name="Extracted Sound",
filename="original.mp3",
normalized_filename="normalized.mp3",
type="EXT",
is_normalized=True,
)
result = get_sound_file_path(sound)
expected = Path("sounds/normalized/extracted/normalized.mp3")
assert result == expected
def test_get_sound_file_path_unknown_type_fallback(self):
"""Test getting sound file path for unknown type falls back to lowercase."""
sound = Sound(
id=4,
name="Unknown Type Sound",
filename="unknown.mp3",
type="CUSTOM",
is_normalized=False,
)
result = get_sound_file_path(sound)
expected = Path("sounds/originals/custom/unknown.mp3")
assert result == expected
def test_get_sound_file_path_normalized_without_filename(self):
"""Test getting sound file path when normalized but no normalized_filename."""
sound = Sound(
id=5,
name="Test Sound",
filename="original.mp3",
normalized_filename=None,
type="SDB",
is_normalized=True, # True but no normalized_filename
)
result = get_sound_file_path(sound)
# Should fall back to original file
expected = Path("sounds/originals/soundboard/original.mp3")
assert result == expected