feat: Refactor audio utility functions and update sound services to use shared methods
This commit is contained in:
@@ -82,7 +82,8 @@ class TestSoundNormalizerService:
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
hash_value = normalizer_service._get_file_hash(temp_path)
|
||||
from app.utils.audio import get_file_hash
|
||||
hash_value = get_file_hash(temp_path)
|
||||
assert len(hash_value) == 64 # SHA-256 hash length
|
||||
assert isinstance(hash_value, str)
|
||||
finally:
|
||||
@@ -96,30 +97,33 @@ class TestSoundNormalizerService:
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
size = normalizer_service._get_file_size(temp_path)
|
||||
from app.utils.audio import get_file_size
|
||||
size = get_file_size(temp_path)
|
||||
assert size > 0
|
||||
assert isinstance(size, int)
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
@patch("app.services.sound_normalizer.ffmpeg.probe")
|
||||
@patch("app.utils.audio.ffmpeg.probe")
|
||||
def test_get_audio_duration_success(self, mock_probe, normalizer_service):
|
||||
"""Test successful audio duration extraction."""
|
||||
mock_probe.return_value = {"format": {"duration": "123.456"}}
|
||||
|
||||
temp_path = Path("/fake/path/test.mp3")
|
||||
duration = normalizer_service._get_audio_duration(temp_path)
|
||||
from app.utils.audio import get_audio_duration
|
||||
duration = get_audio_duration(temp_path)
|
||||
|
||||
assert duration == 123456 # 123.456 seconds * 1000 = 123456 ms
|
||||
mock_probe.assert_called_once_with(str(temp_path))
|
||||
|
||||
@patch("app.services.sound_normalizer.ffmpeg.probe")
|
||||
@patch("app.utils.audio.ffmpeg.probe")
|
||||
def test_get_audio_duration_failure(self, mock_probe, normalizer_service):
|
||||
"""Test audio duration extraction failure."""
|
||||
mock_probe.side_effect = Exception("FFmpeg error")
|
||||
|
||||
temp_path = Path("/fake/path/test.mp3")
|
||||
duration = normalizer_service._get_audio_duration(temp_path)
|
||||
from app.utils.audio import get_audio_duration
|
||||
duration = get_audio_duration(temp_path)
|
||||
|
||||
assert duration == 0
|
||||
mock_probe.assert_called_once_with(str(temp_path))
|
||||
@@ -163,9 +167,9 @@ class TestSoundNormalizerService:
|
||||
with patch.object(normalizer_service, "_get_original_path") as mock_orig_path, \
|
||||
patch.object(normalizer_service, "_get_normalized_path") as mock_norm_path, \
|
||||
patch.object(normalizer_service, "_normalize_audio_two_pass") as mock_normalize, \
|
||||
patch.object(normalizer_service, "_get_audio_duration", return_value=6000), \
|
||||
patch.object(normalizer_service, "_get_file_size", return_value=2048), \
|
||||
patch.object(normalizer_service, "_get_file_hash", return_value="new_hash"):
|
||||
patch("app.services.sound_normalizer.get_audio_duration", return_value=6000), \
|
||||
patch("app.services.sound_normalizer.get_file_size", return_value=2048), \
|
||||
patch("app.services.sound_normalizer.get_file_hash", return_value="new_hash"):
|
||||
|
||||
# Setup path mocks
|
||||
mock_orig_path.return_value = Path("/fake/original.mp3")
|
||||
@@ -229,9 +233,9 @@ class TestSoundNormalizerService:
|
||||
with patch.object(normalizer_service, "_get_original_path") as mock_orig_path, \
|
||||
patch.object(normalizer_service, "_get_normalized_path") as mock_norm_path, \
|
||||
patch.object(normalizer_service, "_normalize_audio_one_pass") as mock_normalize, \
|
||||
patch.object(normalizer_service, "_get_audio_duration", return_value=5500), \
|
||||
patch.object(normalizer_service, "_get_file_size", return_value=1500), \
|
||||
patch.object(normalizer_service, "_get_file_hash", return_value="norm_hash"):
|
||||
patch("app.services.sound_normalizer.get_audio_duration", return_value=5500), \
|
||||
patch("app.services.sound_normalizer.get_file_size", return_value=1500), \
|
||||
patch("app.services.sound_normalizer.get_file_hash", return_value="norm_hash"):
|
||||
|
||||
# Setup path mocks
|
||||
mock_orig_path.return_value = Path("/fake/original.mp3")
|
||||
|
||||
@@ -40,7 +40,8 @@ class TestSoundScannerService:
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
hash_value = scanner_service.get_file_hash(temp_path)
|
||||
from app.utils.audio import get_file_hash
|
||||
hash_value = get_file_hash(temp_path)
|
||||
assert len(hash_value) == 64 # SHA-256 hash length
|
||||
assert isinstance(hash_value, str)
|
||||
finally:
|
||||
@@ -54,7 +55,8 @@ class TestSoundScannerService:
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
size = scanner_service.get_file_size(temp_path)
|
||||
from app.utils.audio import get_file_size
|
||||
size = get_file_size(temp_path)
|
||||
assert size > 0
|
||||
assert isinstance(size, int)
|
||||
finally:
|
||||
@@ -74,24 +76,26 @@ class TestSoundScannerService:
|
||||
result = scanner_service.extract_name_from_filename(filename)
|
||||
assert result == expected_name
|
||||
|
||||
@patch("app.services.sound_scanner.ffmpeg.probe")
|
||||
@patch("app.utils.audio.ffmpeg.probe")
|
||||
def test_get_audio_duration_success(self, mock_probe, scanner_service):
|
||||
"""Test successful audio duration extraction."""
|
||||
mock_probe.return_value = {"format": {"duration": "123.456"}}
|
||||
|
||||
temp_path = Path("/fake/path/test.mp3")
|
||||
duration = scanner_service.get_audio_duration(temp_path)
|
||||
from app.utils.audio import get_audio_duration
|
||||
duration = get_audio_duration(temp_path)
|
||||
|
||||
assert duration == 123456 # 123.456 seconds * 1000 = 123456 ms
|
||||
mock_probe.assert_called_once_with(str(temp_path))
|
||||
|
||||
@patch("app.services.sound_scanner.ffmpeg.probe")
|
||||
@patch("app.utils.audio.ffmpeg.probe")
|
||||
def test_get_audio_duration_failure(self, mock_probe, scanner_service):
|
||||
"""Test audio duration extraction failure."""
|
||||
mock_probe.side_effect = Exception("FFmpeg error")
|
||||
|
||||
temp_path = Path("/fake/path/test.mp3")
|
||||
duration = scanner_service.get_audio_duration(temp_path)
|
||||
from app.utils.audio import get_audio_duration
|
||||
duration = get_audio_duration(temp_path)
|
||||
|
||||
assert duration == 0
|
||||
mock_probe.assert_called_once_with(str(temp_path))
|
||||
@@ -125,36 +129,36 @@ class TestSoundScannerService:
|
||||
)
|
||||
|
||||
# Mock file operations to return same hash
|
||||
scanner_service.get_file_hash = Mock(return_value="same_hash")
|
||||
scanner_service.get_audio_duration = Mock(return_value=120000)
|
||||
scanner_service.get_file_size = Mock(return_value=1024)
|
||||
with patch("app.services.sound_scanner.get_file_hash", return_value="same_hash"), \
|
||||
patch("app.services.sound_scanner.get_audio_duration", return_value=120000), \
|
||||
patch("app.services.sound_scanner.get_file_size", return_value=1024):
|
||||
|
||||
# Create a temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
||||
temp_path = Path(f.name)
|
||||
# Create a temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
results = {
|
||||
"scanned": 0,
|
||||
"added": 0,
|
||||
"updated": 0,
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"errors": 0,
|
||||
"files": [],
|
||||
}
|
||||
await scanner_service._sync_audio_file(
|
||||
temp_path, "SDB", existing_sound, results
|
||||
)
|
||||
try:
|
||||
results = {
|
||||
"scanned": 0,
|
||||
"added": 0,
|
||||
"updated": 0,
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"errors": 0,
|
||||
"files": [],
|
||||
}
|
||||
await scanner_service._sync_audio_file(
|
||||
temp_path, "SDB", existing_sound, results
|
||||
)
|
||||
|
||||
assert results["skipped"] == 1
|
||||
assert results["added"] == 0
|
||||
assert results["updated"] == 0
|
||||
assert len(results["files"]) == 1
|
||||
assert results["files"][0]["status"] == "skipped"
|
||||
assert results["files"][0]["reason"] == "file unchanged"
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
assert results["skipped"] == 1
|
||||
assert results["added"] == 0
|
||||
assert results["updated"] == 0
|
||||
assert len(results["files"]) == 1
|
||||
assert results["files"][0]["status"] == "skipped"
|
||||
assert results["files"][0]["reason"] == "file unchanged"
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sync_audio_file_new(self, scanner_service):
|
||||
@@ -171,42 +175,42 @@ class TestSoundScannerService:
|
||||
scanner_service.sound_repo.create = AsyncMock(return_value=created_sound)
|
||||
|
||||
# Mock file operations
|
||||
scanner_service.get_file_hash = Mock(return_value="test_hash")
|
||||
scanner_service.get_audio_duration = Mock(return_value=120000) # Duration in ms
|
||||
scanner_service.get_file_size = Mock(return_value=1024)
|
||||
with patch("app.services.sound_scanner.get_file_hash", return_value="test_hash"), \
|
||||
patch("app.services.sound_scanner.get_audio_duration", return_value=120000), \
|
||||
patch("app.services.sound_scanner.get_file_size", return_value=1024):
|
||||
|
||||
# Create a temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
||||
temp_path = Path(f.name)
|
||||
# Create a temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
results = {
|
||||
"scanned": 0,
|
||||
"added": 0,
|
||||
"updated": 0,
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"errors": 0,
|
||||
"files": [],
|
||||
}
|
||||
await scanner_service._sync_audio_file(temp_path, "SDB", None, results)
|
||||
try:
|
||||
results = {
|
||||
"scanned": 0,
|
||||
"added": 0,
|
||||
"updated": 0,
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"errors": 0,
|
||||
"files": [],
|
||||
}
|
||||
await scanner_service._sync_audio_file(temp_path, "SDB", None, results)
|
||||
|
||||
assert results["added"] == 1
|
||||
assert results["skipped"] == 0
|
||||
assert results["updated"] == 0
|
||||
assert len(results["files"]) == 1
|
||||
assert results["files"][0]["status"] == "added"
|
||||
assert results["added"] == 1
|
||||
assert results["skipped"] == 0
|
||||
assert results["updated"] == 0
|
||||
assert len(results["files"]) == 1
|
||||
assert results["files"][0]["status"] == "added"
|
||||
|
||||
# Verify sound_repo.create was called with correct data
|
||||
call_args = scanner_service.sound_repo.create.call_args[0][0]
|
||||
assert call_args["type"] == "SDB"
|
||||
assert call_args["filename"] == temp_path.name
|
||||
assert call_args["duration"] == 120000 # Duration in ms
|
||||
assert call_args["size"] == 1024
|
||||
assert call_args["hash"] == "test_hash"
|
||||
assert call_args["is_deletable"] is False # SDB sounds are not deletable
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
# Verify sound_repo.create was called with correct data
|
||||
call_args = scanner_service.sound_repo.create.call_args[0][0]
|
||||
assert call_args["type"] == "SDB"
|
||||
assert call_args["filename"] == temp_path.name
|
||||
assert call_args["duration"] == 120000 # Duration in ms
|
||||
assert call_args["size"] == 1024
|
||||
assert call_args["hash"] == "test_hash"
|
||||
assert call_args["is_deletable"] is False # SDB sounds are not deletable
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sync_audio_file_updated(self, scanner_service):
|
||||
@@ -225,44 +229,44 @@ class TestSoundScannerService:
|
||||
scanner_service.sound_repo.update = AsyncMock(return_value=existing_sound)
|
||||
|
||||
# Mock file operations to return new values
|
||||
scanner_service.get_file_hash = Mock(return_value="new_hash")
|
||||
scanner_service.get_audio_duration = Mock(return_value=120000) # New duration
|
||||
scanner_service.get_file_size = Mock(return_value=1024) # New size
|
||||
with patch("app.services.sound_scanner.get_file_hash", return_value="new_hash"), \
|
||||
patch("app.services.sound_scanner.get_audio_duration", return_value=120000), \
|
||||
patch("app.services.sound_scanner.get_file_size", return_value=1024):
|
||||
|
||||
# Create a temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
||||
temp_path = Path(f.name)
|
||||
# Create a temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
results = {
|
||||
"scanned": 0,
|
||||
"added": 0,
|
||||
"updated": 0,
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"errors": 0,
|
||||
"files": [],
|
||||
}
|
||||
await scanner_service._sync_audio_file(
|
||||
temp_path, "SDB", existing_sound, results
|
||||
)
|
||||
try:
|
||||
results = {
|
||||
"scanned": 0,
|
||||
"added": 0,
|
||||
"updated": 0,
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"errors": 0,
|
||||
"files": [],
|
||||
}
|
||||
await scanner_service._sync_audio_file(
|
||||
temp_path, "SDB", existing_sound, results
|
||||
)
|
||||
|
||||
assert results["updated"] == 1
|
||||
assert results["added"] == 0
|
||||
assert results["skipped"] == 0
|
||||
assert len(results["files"]) == 1
|
||||
assert results["files"][0]["status"] == "updated"
|
||||
assert results["files"][0]["reason"] == "file was modified"
|
||||
assert results["updated"] == 1
|
||||
assert results["added"] == 0
|
||||
assert results["skipped"] == 0
|
||||
assert len(results["files"]) == 1
|
||||
assert results["files"][0]["status"] == "updated"
|
||||
assert results["files"][0]["reason"] == "file was modified"
|
||||
|
||||
# Verify sound_repo.update was called with correct data
|
||||
call_args = scanner_service.sound_repo.update.call_args[0][1] # update_data
|
||||
assert call_args["duration"] == 120000
|
||||
assert call_args["size"] == 1024
|
||||
assert call_args["hash"] == "new_hash"
|
||||
# Name is extracted from temp filename, should be capitalized
|
||||
assert call_args["name"].endswith("mp3") is False # Should be cleaned
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
# Verify sound_repo.update was called with correct data
|
||||
call_args = scanner_service.sound_repo.update.call_args[0][1] # update_data
|
||||
assert call_args["duration"] == 120000
|
||||
assert call_args["size"] == 1024
|
||||
assert call_args["hash"] == "new_hash"
|
||||
# Name is extracted from temp filename, should be capitalized
|
||||
assert call_args["name"].endswith("mp3") is False # Should be cleaned
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sync_audio_file_custom_type(self, scanner_service):
|
||||
@@ -279,40 +283,40 @@ class TestSoundScannerService:
|
||||
scanner_service.sound_repo.create = AsyncMock(return_value=created_sound)
|
||||
|
||||
# Mock file operations
|
||||
scanner_service.get_file_hash = Mock(return_value="custom_hash")
|
||||
scanner_service.get_audio_duration = Mock(return_value=60000) # Duration in ms
|
||||
scanner_service.get_file_size = Mock(return_value=2048)
|
||||
with patch("app.services.sound_scanner.get_file_hash", return_value="custom_hash"), \
|
||||
patch("app.services.sound_scanner.get_audio_duration", return_value=60000), \
|
||||
patch("app.services.sound_scanner.get_file_size", return_value=2048):
|
||||
|
||||
# Create a temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
|
||||
temp_path = Path(f.name)
|
||||
# Create a temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
results = {
|
||||
"scanned": 0,
|
||||
"added": 0,
|
||||
"updated": 0,
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"errors": 0,
|
||||
"files": [],
|
||||
}
|
||||
await scanner_service._sync_audio_file(temp_path, "CUSTOM", None, results)
|
||||
try:
|
||||
results = {
|
||||
"scanned": 0,
|
||||
"added": 0,
|
||||
"updated": 0,
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"errors": 0,
|
||||
"files": [],
|
||||
}
|
||||
await scanner_service._sync_audio_file(temp_path, "CUSTOM", None, results)
|
||||
|
||||
assert results["added"] == 1
|
||||
assert results["skipped"] == 0
|
||||
assert len(results["files"]) == 1
|
||||
assert results["files"][0]["status"] == "added"
|
||||
assert results["added"] == 1
|
||||
assert results["skipped"] == 0
|
||||
assert len(results["files"]) == 1
|
||||
assert results["files"][0]["status"] == "added"
|
||||
|
||||
# Verify sound_repo.create was called with correct data for custom type
|
||||
call_args = scanner_service.sound_repo.create.call_args[0][0]
|
||||
assert call_args["type"] == "CUSTOM"
|
||||
assert call_args["filename"] == temp_path.name
|
||||
assert call_args["duration"] == 60000 # Duration in ms
|
||||
assert call_args["size"] == 2048
|
||||
assert call_args["hash"] == "custom_hash"
|
||||
assert (
|
||||
call_args["is_deletable"] is False
|
||||
) # All sounds are set to not deletable
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
# Verify sound_repo.create was called with correct data for custom type
|
||||
call_args = scanner_service.sound_repo.create.call_args[0][0]
|
||||
assert call_args["type"] == "CUSTOM"
|
||||
assert call_args["filename"] == temp_path.name
|
||||
assert call_args["duration"] == 60000 # Duration in ms
|
||||
assert call_args["size"] == 2048
|
||||
assert call_args["hash"] == "custom_hash"
|
||||
assert (
|
||||
call_args["is_deletable"] is False
|
||||
) # All sounds are set to not deletable
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
Reference in New Issue
Block a user