feat: Add VLC player API endpoints and associated tests

- Implemented VLC player API endpoints for playing and stopping sounds.
- Added tests for successful playback, error handling, and authentication scenarios.
- Created utility function to get sound file paths based on sound properties.
- Refactored player service to utilize shared sound path utility.
- Enhanced test coverage for sound file path utility with various sound types.
- Introduced tests for VLC player service, including subprocess handling and play count tracking.
This commit is contained in:
JSC
2025-07-30 20:46:49 +02:00
parent 1b0d291ad3
commit dd10ef5d41
9 changed files with 1413 additions and 134 deletions

View File

@@ -21,6 +21,7 @@ from app.services.player import (
initialize_player_service,
shutdown_player_service,
)
from app.utils.audio import get_sound_file_path
class TestPlayerState:
@@ -196,7 +197,7 @@ class TestPlayerService:
)
player_service.state.playlist_sounds = [sound]
with patch.object(player_service, "_get_sound_file_path") as mock_path:
with patch("app.services.player.get_sound_file_path") as mock_path:
mock_file_path = Mock(spec=Path)
mock_file_path.exists.return_value = True
mock_path.return_value = mock_file_path
@@ -385,51 +386,6 @@ class TestPlayerService:
assert player_service.state.playlist_length == 2
assert player_service.state.playlist_duration == 75000
def test_get_sound_file_path_normalized(self, player_service):
"""Test getting file path for normalized sound."""
sound = Sound(
id=1,
name="Test Song",
filename="original.mp3",
normalized_filename="normalized.mp3",
is_normalized=True,
type="SDB",
)
result = player_service._get_sound_file_path(sound)
expected = Path("sounds/normalized/sdb/normalized.mp3")
assert result == expected
def test_get_sound_file_path_original(self, player_service):
"""Test getting file path for original sound."""
sound = Sound(
id=1,
name="Test Song",
filename="original.mp3",
is_normalized=False,
type="SDB",
)
result = player_service._get_sound_file_path(sound)
expected = Path("sounds/originals/sdb/original.mp3")
assert result == expected
def test_get_sound_file_path_ext_type(self, player_service):
"""Test getting file path for EXT type sound."""
sound = Sound(
id=1,
name="Test Song",
filename="extracted.mp3",
is_normalized=False,
type="EXT",
)
result = player_service._get_sound_file_path(sound)
expected = Path("sounds/originals/extracted/extracted.mp3")
assert result == expected
def test_get_next_index_continuous_mode(self, player_service):
"""Test getting next index in continuous mode."""
@@ -538,36 +494,24 @@ class TestPlayerService:
# Mock repositories
with patch("app.services.player.SoundRepository") as mock_sound_repo_class:
with patch("app.services.player.UserRepository") as mock_user_repo_class:
mock_sound_repo = AsyncMock()
mock_user_repo = AsyncMock()
mock_sound_repo_class.return_value = mock_sound_repo
mock_user_repo_class.return_value = mock_user_repo
mock_sound_repo = AsyncMock()
mock_sound_repo_class.return_value = mock_sound_repo
# Mock sound and user
mock_sound = Mock()
mock_sound.play_count = 5
mock_sound_repo.get_by_id.return_value = mock_sound
# Mock sound
mock_sound = Mock()
mock_sound.play_count = 5
mock_sound_repo.get_by_id.return_value = mock_sound
mock_user = Mock()
mock_user.id = 1
mock_user_repo.get_by_id.return_value = mock_user
await player_service._record_play_count(1)
# Mock no existing SoundPlayed record
mock_result = Mock()
mock_result.first.return_value = None
mock_session.exec.return_value = mock_result
# Verify sound play count was updated
mock_sound_repo.update.assert_called_once_with(
mock_sound, {"play_count": 6}
)
await player_service._record_play_count(1)
# Verify sound play count was updated
mock_sound_repo.update.assert_called_once_with(
mock_sound, {"play_count": 6}
)
# Verify SoundPlayed record was created
mock_session.add.assert_called_once()
mock_session.commit.assert_called_once()
# Verify SoundPlayed record was created with None user_id for player
mock_session.add.assert_called_once()
mock_session.commit.assert_called_once()
def test_get_state(self, player_service):
"""Test getting current player state."""
@@ -577,6 +521,27 @@ class TestPlayerService:
assert "mode" in result
assert "volume" in result
def test_uses_shared_sound_path_utility(self, player_service):
"""Test that player service uses the shared sound path utility."""
sound = Sound(
id=1,
name="Test Song",
filename="test.mp3",
type="SDB",
is_normalized=False,
)
player_service.state.playlist_sounds = [sound]
with patch("app.services.player.get_sound_file_path") as mock_path:
mock_file_path = Mock(spec=Path)
mock_file_path.exists.return_value = False # File doesn't exist
mock_path.return_value = mock_file_path
# This should fail because file doesn't exist
result = asyncio.run(player_service.play(0))
# Verify the utility was called
mock_path.assert_called_once_with(sound)
class TestPlayerServiceGlobalFunctions:
"""Test global player service functions."""