Refactor test cases for improved readability and consistency
All checks were successful
Backend CI / lint (push) Successful in 9m49s
Backend CI / test (push) Successful in 6m15s

- Adjusted function signatures in various test files to enhance clarity by aligning parameters.
- Updated patching syntax for better readability across test cases.
- Improved formatting and spacing in test assertions and mock setups.
- Ensured consistent use of async/await patterns in async test functions.
- Enhanced comments for better understanding of test intentions.
This commit is contained in:
JSC
2025-08-01 20:53:30 +02:00
parent d926779fe4
commit 6068599a47
39 changed files with 691 additions and 286 deletions

View File

@@ -48,7 +48,9 @@ class TestCreditService:
mock_repo_class.return_value = mock_repo
mock_repo.get_by_id.return_value = sample_user
result = await credit_service.check_credits(1, CreditActionType.VLC_PLAY_SOUND)
result = await credit_service.check_credits(
1, CreditActionType.VLC_PLAY_SOUND,
)
assert result is True
mock_repo.get_by_id.assert_called_once_with(1)
@@ -72,7 +74,9 @@ class TestCreditService:
mock_repo_class.return_value = mock_repo
mock_repo.get_by_id.return_value = poor_user
result = await credit_service.check_credits(1, CreditActionType.VLC_PLAY_SOUND)
result = await credit_service.check_credits(
1, CreditActionType.VLC_PLAY_SOUND,
)
assert result is False
mock_session.close.assert_called_once()
@@ -87,13 +91,17 @@ class TestCreditService:
mock_repo_class.return_value = mock_repo
mock_repo.get_by_id.return_value = None
result = await credit_service.check_credits(999, CreditActionType.VLC_PLAY_SOUND)
result = await credit_service.check_credits(
999, CreditActionType.VLC_PLAY_SOUND,
)
assert result is False
mock_session.close.assert_called_once()
@pytest.mark.asyncio
async def test_validate_and_reserve_credits_success(self, credit_service, sample_user) -> None:
async def test_validate_and_reserve_credits_success(
self, credit_service, sample_user,
) -> None:
"""Test successful credit validation and reservation."""
mock_session = credit_service.db_session_factory()
@@ -103,7 +111,8 @@ class TestCreditService:
mock_repo.get_by_id.return_value = sample_user
user, action = await credit_service.validate_and_reserve_credits(
1, CreditActionType.VLC_PLAY_SOUND,
1,
CreditActionType.VLC_PLAY_SOUND,
)
assert user == sample_user
@@ -112,7 +121,9 @@ class TestCreditService:
mock_session.close.assert_called_once()
@pytest.mark.asyncio
async def test_validate_and_reserve_credits_insufficient(self, credit_service) -> None:
async def test_validate_and_reserve_credits_insufficient(
self, credit_service,
) -> None:
"""Test credit validation with insufficient credits."""
mock_session = credit_service.db_session_factory()
poor_user = User(
@@ -131,7 +142,8 @@ class TestCreditService:
with pytest.raises(InsufficientCreditsError) as exc_info:
await credit_service.validate_and_reserve_credits(
1, CreditActionType.VLC_PLAY_SOUND,
1,
CreditActionType.VLC_PLAY_SOUND,
)
assert exc_info.value.required == 1
@@ -139,7 +151,9 @@ class TestCreditService:
mock_session.close.assert_called_once()
@pytest.mark.asyncio
async def test_validate_and_reserve_credits_user_not_found(self, credit_service) -> None:
async def test_validate_and_reserve_credits_user_not_found(
self, credit_service,
) -> None:
"""Test credit validation when user is not found."""
mock_session = credit_service.db_session_factory()
@@ -150,7 +164,8 @@ class TestCreditService:
with pytest.raises(ValueError, match="User 999 not found"):
await credit_service.validate_and_reserve_credits(
999, CreditActionType.VLC_PLAY_SOUND,
999,
CreditActionType.VLC_PLAY_SOUND,
)
mock_session.close.assert_called_once()
@@ -160,15 +175,20 @@ class TestCreditService:
"""Test successful credit deduction."""
mock_session = credit_service.db_session_factory()
with patch("app.services.credit.UserRepository") as mock_repo_class, \
patch("app.services.credit.socket_manager") as mock_socket_manager:
with (
patch("app.services.credit.UserRepository") as mock_repo_class,
patch("app.services.credit.socket_manager") as mock_socket_manager,
):
mock_repo = AsyncMock()
mock_repo_class.return_value = mock_repo
mock_repo.get_by_id.return_value = sample_user
mock_socket_manager.send_to_user = AsyncMock()
await credit_service.deduct_credits(
1, CreditActionType.VLC_PLAY_SOUND, success=True, metadata={"test": "data"},
1,
CreditActionType.VLC_PLAY_SOUND,
success=True,
metadata={"test": "data"},
)
# Verify user credits were updated
@@ -180,7 +200,9 @@ class TestCreditService:
# Verify socket event was emitted
mock_socket_manager.send_to_user.assert_called_once_with(
"1", "user_credits_changed", {
"1",
"user_credits_changed",
{
"user_id": "1",
"credits_before": 10,
"credits_after": 9,
@@ -202,19 +224,25 @@ class TestCreditService:
assert json.loads(added_transaction.metadata_json) == {"test": "data"}
@pytest.mark.asyncio
async def test_deduct_credits_failed_action_requires_success(self, credit_service, sample_user) -> None:
async def test_deduct_credits_failed_action_requires_success(
self, credit_service, sample_user,
) -> None:
"""Test credit deduction when action failed but requires success."""
mock_session = credit_service.db_session_factory()
with patch("app.services.credit.UserRepository") as mock_repo_class, \
patch("app.services.credit.socket_manager") as mock_socket_manager:
with (
patch("app.services.credit.UserRepository") as mock_repo_class,
patch("app.services.credit.socket_manager") as mock_socket_manager,
):
mock_repo = AsyncMock()
mock_repo_class.return_value = mock_repo
mock_repo.get_by_id.return_value = sample_user
mock_socket_manager.send_to_user = AsyncMock()
await credit_service.deduct_credits(
1, CreditActionType.VLC_PLAY_SOUND, success=False, # Action failed
1,
CreditActionType.VLC_PLAY_SOUND,
success=False, # Action failed
)
# Verify user credits were NOT updated (action requires success)
@@ -247,8 +275,10 @@ class TestCreditService:
plan_id=1,
)
with patch("app.services.credit.UserRepository") as mock_repo_class, \
patch("app.services.credit.socket_manager") as mock_socket_manager:
with (
patch("app.services.credit.UserRepository") as mock_repo_class,
patch("app.services.credit.socket_manager") as mock_socket_manager,
):
mock_repo = AsyncMock()
mock_repo_class.return_value = mock_repo
mock_repo.get_by_id.return_value = poor_user
@@ -256,7 +286,9 @@ class TestCreditService:
with pytest.raises(InsufficientCreditsError):
await credit_service.deduct_credits(
1, CreditActionType.VLC_PLAY_SOUND, success=True,
1,
CreditActionType.VLC_PLAY_SOUND,
success=True,
)
# Verify no socket event was emitted since credits could not be deducted
@@ -270,15 +302,20 @@ class TestCreditService:
"""Test adding credits to user account."""
mock_session = credit_service.db_session_factory()
with patch("app.services.credit.UserRepository") as mock_repo_class, \
patch("app.services.credit.socket_manager") as mock_socket_manager:
with (
patch("app.services.credit.UserRepository") as mock_repo_class,
patch("app.services.credit.socket_manager") as mock_socket_manager,
):
mock_repo = AsyncMock()
mock_repo_class.return_value = mock_repo
mock_repo.get_by_id.return_value = sample_user
mock_socket_manager.send_to_user = AsyncMock()
await credit_service.add_credits(
1, 5, "Bonus credits", {"reason": "signup"},
1,
5,
"Bonus credits",
{"reason": "signup"},
)
# Verify user credits were updated
@@ -290,7 +327,9 @@ class TestCreditService:
# Verify socket event was emitted
mock_socket_manager.send_to_user.assert_called_once_with(
"1", "user_credits_changed", {
"1",
"user_credits_changed",
{
"user_id": "1",
"credits_before": 10,
"credits_after": 15,

View File

@@ -53,7 +53,9 @@ class TestExtractionService:
@patch("app.services.extraction.yt_dlp.YoutubeDL")
@pytest.mark.asyncio
async def test_detect_service_info_youtube(
self, mock_ydl_class, extraction_service,
self,
mock_ydl_class,
extraction_service,
) -> None:
"""Test service detection for YouTube."""
mock_ydl = Mock()
@@ -78,7 +80,9 @@ class TestExtractionService:
@patch("app.services.extraction.yt_dlp.YoutubeDL")
@pytest.mark.asyncio
async def test_detect_service_info_failure(
self, mock_ydl_class, extraction_service,
self,
mock_ydl_class,
extraction_service,
) -> None:
"""Test service detection failure."""
mock_ydl = Mock()
@@ -170,7 +174,9 @@ class TestExtractionService:
assert result["status"] == "pending"
@pytest.mark.asyncio
async def test_process_extraction_with_service_detection(self, extraction_service) -> None:
async def test_process_extraction_with_service_detection(
self, extraction_service,
) -> None:
"""Test extraction processing with service detection."""
extraction_id = 1
@@ -202,14 +208,18 @@ class TestExtractionService:
with (
patch.object(
extraction_service, "_detect_service_info", return_value=service_info,
extraction_service,
"_detect_service_info",
return_value=service_info,
),
patch.object(extraction_service, "_extract_media") as mock_extract,
patch.object(
extraction_service, "_move_files_to_final_location",
extraction_service,
"_move_files_to_final_location",
) as mock_move,
patch.object(
extraction_service, "_create_sound_record",
extraction_service,
"_create_sound_record",
) as mock_create_sound,
patch.object(extraction_service, "_normalize_sound"),
patch.object(extraction_service, "_add_to_main_playlist"),
@@ -289,11 +299,13 @@ class TestExtractionService:
with (
patch(
"app.services.extraction.get_audio_duration", return_value=240000,
"app.services.extraction.get_audio_duration",
return_value=240000,
),
patch("app.services.extraction.get_file_size", return_value=1024),
patch(
"app.services.extraction.get_file_hash", return_value="test_hash",
"app.services.extraction.get_file_hash",
return_value="test_hash",
),
):
extraction_service.sound_repo.create = AsyncMock(

View File

@@ -29,7 +29,9 @@ class TestExtractionProcessor:
"""Test starting and stopping the processor."""
# Mock the _process_queue method to avoid actual processing
with patch.object(
processor, "_process_queue", new_callable=AsyncMock,
processor,
"_process_queue",
new_callable=AsyncMock,
):
# Start the processor
await processor.start()
@@ -229,7 +231,9 @@ class TestExtractionProcessor:
"app.services.extraction_processor.AsyncSession",
) as mock_session_class,
patch.object(
processor, "_process_single_extraction", new_callable=AsyncMock,
processor,
"_process_single_extraction",
new_callable=AsyncMock,
),
patch(
"app.services.extraction_processor.ExtractionService",
@@ -274,7 +278,9 @@ class TestExtractionProcessor:
"app.services.extraction_processor.AsyncSession",
) as mock_session_class,
patch.object(
processor, "_process_single_extraction", new_callable=AsyncMock,
processor,
"_process_single_extraction",
new_callable=AsyncMock,
),
patch(
"app.services.extraction_processor.ExtractionService",

View File

@@ -131,11 +131,15 @@ class TestPlayerService:
yield mock
@pytest.fixture
def player_service(self, mock_db_session_factory, mock_vlc_instance, mock_socket_manager):
def player_service(
self, mock_db_session_factory, mock_vlc_instance, mock_socket_manager,
):
"""Create a player service instance for testing."""
return PlayerService(mock_db_session_factory)
def test_init_creates_player_service(self, mock_db_session_factory, mock_vlc_instance) -> None:
def test_init_creates_player_service(
self, mock_db_session_factory, mock_vlc_instance,
) -> None:
"""Test that player service initializes correctly."""
with patch("app.services.player.socket_manager"):
service = PlayerService(mock_db_session_factory)
@@ -152,7 +156,9 @@ class TestPlayerService:
assert service._loop is None
@pytest.mark.asyncio
async def test_start_initializes_service(self, player_service, mock_vlc_instance) -> None:
async def test_start_initializes_service(
self, player_service, mock_vlc_instance,
) -> None:
"""Test that start method initializes the service."""
with patch.object(player_service, "reload_playlist", new_callable=AsyncMock):
await player_service.start()
@@ -197,7 +203,9 @@ class TestPlayerService:
mock_file_path.exists.return_value = True
mock_path.return_value = mock_file_path
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock):
with patch.object(
player_service, "_broadcast_state", new_callable=AsyncMock,
):
mock_media = Mock()
player_service._vlc_instance.media_new.return_value = mock_media
player_service._player.play.return_value = 0 # Success
@@ -252,7 +260,9 @@ class TestPlayerService:
"""Test pausing when not playing does nothing."""
player_service.state.status = PlayerStatus.STOPPED
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock) as mock_broadcast:
with patch.object(
player_service, "_broadcast_state", new_callable=AsyncMock,
) as mock_broadcast:
await player_service.pause()
assert player_service.state.status == PlayerStatus.STOPPED
@@ -264,8 +274,12 @@ class TestPlayerService:
player_service.state.status = PlayerStatus.PLAYING
player_service.state.current_sound_position = 5000
with patch.object(player_service, "_process_play_count", new_callable=AsyncMock):
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock):
with patch.object(
player_service, "_process_play_count", new_callable=AsyncMock,
):
with patch.object(
player_service, "_broadcast_state", new_callable=AsyncMock,
):
await player_service.stop_playback()
assert player_service.state.status == PlayerStatus.STOPPED
@@ -314,7 +328,9 @@ class TestPlayerService:
"""Test seeking when stopped does nothing."""
player_service.state.status = PlayerStatus.STOPPED
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock) as mock_broadcast:
with patch.object(
player_service, "_broadcast_state", new_callable=AsyncMock,
) as mock_broadcast:
await player_service.seek(15000)
player_service._player.set_position.assert_not_called()
@@ -364,7 +380,9 @@ class TestPlayerService:
mock_playlist = Mock()
mock_playlist.id = 1
mock_playlist.name = "Test Playlist"
mock_repo.get_current_playlist.return_value = mock_playlist # Return current playlist directly
mock_repo.get_current_playlist.return_value = (
mock_playlist # Return current playlist directly
)
# Mock sounds
sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000)
@@ -372,7 +390,9 @@ class TestPlayerService:
mock_sounds = [sound1, sound2]
mock_repo.get_playlist_sounds.return_value = mock_sounds
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock):
with patch.object(
player_service, "_broadcast_state", new_callable=AsyncMock,
):
await player_service.reload_playlist()
assert player_service.state.playlist_id == 1
@@ -394,7 +414,9 @@ class TestPlayerService:
sound2 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
sounds = [sound1, sound2]
with patch.object(player_service, "_stop_playback", new_callable=AsyncMock) as mock_stop:
with patch.object(
player_service, "_stop_playback", new_callable=AsyncMock,
) as mock_stop:
await player_service._handle_playlist_id_changed(1, 2, sounds)
# Should stop playback and set first track as current
@@ -404,11 +426,15 @@ class TestPlayerService:
assert player_service.state.current_sound_id == 1
@pytest.mark.asyncio
async def test_handle_playlist_id_changed_empty_playlist(self, player_service) -> None:
async def test_handle_playlist_id_changed_empty_playlist(
self, player_service,
) -> None:
"""Test handling playlist ID change with empty playlist."""
player_service.state.status = PlayerStatus.PLAYING
with patch.object(player_service, "_stop_playback", new_callable=AsyncMock) as mock_stop:
with patch.object(
player_service, "_stop_playback", new_callable=AsyncMock,
) as mock_stop:
await player_service._handle_playlist_id_changed(1, 2, [])
mock_stop.assert_called_once()
@@ -417,7 +443,9 @@ class TestPlayerService:
assert player_service.state.current_sound_id is None
@pytest.mark.asyncio
async def test_handle_same_playlist_track_exists_same_index(self, player_service) -> None:
async def test_handle_same_playlist_track_exists_same_index(
self, player_service,
) -> None:
"""Test handling same playlist when track exists at same index."""
sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000)
sound2 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
@@ -426,11 +454,15 @@ class TestPlayerService:
await player_service._handle_same_playlist_track_check(1, 0, sounds)
# Should update sound object reference but keep same index
assert player_service.state.current_sound_index == 0 # Should be set to 0 from new_index
assert (
player_service.state.current_sound_index == 0
) # Should be set to 0 from new_index
assert player_service.state.current_sound == sound1
@pytest.mark.asyncio
async def test_handle_same_playlist_track_exists_different_index(self, player_service) -> None:
async def test_handle_same_playlist_track_exists_different_index(
self, player_service,
) -> None:
"""Test handling same playlist when track exists at different index."""
sound1 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
sound2 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000)
@@ -450,7 +482,9 @@ class TestPlayerService:
sound2 = Sound(id=3, name="Song 3", filename="song3.mp3", duration=60000)
sounds = [sound1, sound2] # Track with ID 1 is missing
with patch.object(player_service, "_handle_track_removed", new_callable=AsyncMock) as mock_removed:
with patch.object(
player_service, "_handle_track_removed", new_callable=AsyncMock,
) as mock_removed:
await player_service._handle_same_playlist_track_check(1, 0, sounds)
mock_removed.assert_called_once_with(1, sounds)
@@ -461,7 +495,9 @@ class TestPlayerService:
sound1 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
sounds = [sound1]
with patch.object(player_service, "_stop_playback", new_callable=AsyncMock) as mock_stop:
with patch.object(
player_service, "_stop_playback", new_callable=AsyncMock,
) as mock_stop:
await player_service._handle_track_removed(1, sounds)
mock_stop.assert_called_once()
@@ -474,7 +510,9 @@ class TestPlayerService:
"""Test handling when current track is removed with empty playlist."""
player_service.state.status = PlayerStatus.PLAYING
with patch.object(player_service, "_stop_playback", new_callable=AsyncMock) as mock_stop:
with patch.object(
player_service, "_stop_playback", new_callable=AsyncMock,
) as mock_stop:
await player_service._handle_track_removed(1, [])
mock_stop.assert_called_once()
@@ -562,14 +600,20 @@ class TestPlayerService:
mock_playlist = Mock()
mock_playlist.id = 2 # Different ID
mock_playlist.name = "New Playlist"
mock_repo.get_current_playlist.return_value = mock_playlist # Return current playlist directly
mock_repo.get_current_playlist.return_value = (
mock_playlist # Return current playlist directly
)
sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000)
mock_sounds = [sound1]
mock_repo.get_playlist_sounds.return_value = mock_sounds
with patch.object(player_service, "_stop_playback", new_callable=AsyncMock) as mock_stop:
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock):
with patch.object(
player_service, "_stop_playback", new_callable=AsyncMock,
) as mock_stop:
with patch.object(
player_service, "_broadcast_state", new_callable=AsyncMock,
):
await player_service.reload_playlist()
# Should stop and reset to first track
@@ -597,7 +641,9 @@ class TestPlayerService:
mock_playlist = Mock()
mock_playlist.id = 1
mock_playlist.name = "Same Playlist"
mock_repo.get_current_playlist.return_value = mock_playlist # Return current playlist directly
mock_repo.get_current_playlist.return_value = (
mock_playlist # Return current playlist directly
)
# Track 2 moved to index 0
sound1 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
@@ -605,7 +651,9 @@ class TestPlayerService:
mock_sounds = [sound1, sound2] # Track 2 now at index 0
mock_repo.get_playlist_sounds.return_value = mock_sounds
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock):
with patch.object(
player_service, "_broadcast_state", new_callable=AsyncMock,
):
await player_service.reload_playlist()
# Should update index but keep same track
@@ -614,7 +662,6 @@ class TestPlayerService:
assert player_service.state.current_sound_id == 2 # Same track
assert player_service.state.current_sound == sound1
def test_get_next_index_continuous_mode(self, player_service) -> None:
"""Test getting next index in continuous mode."""
player_service.state.mode = PlayerMode.CONTINUOUS
@@ -734,7 +781,8 @@ class TestPlayerService:
# Verify sound play count was updated
mock_sound_repo.update.assert_called_once_with(
mock_sound, {"play_count": 6},
mock_sound,
{"play_count": 6},
)
# Verify SoundPlayed record was created with None user_id for player

View File

@@ -98,7 +98,11 @@ class TestSocketManager:
@patch("app.services.socket.extract_access_token_from_cookies")
@patch("app.services.socket.JWTUtils.decode_access_token")
async def test_connect_handler_success(
self, mock_decode, mock_extract_token, socket_manager, mock_sio,
self,
mock_decode,
mock_extract_token,
socket_manager,
mock_sio,
) -> None:
"""Test successful connection with valid token."""
# Setup mocks
@@ -132,7 +136,10 @@ class TestSocketManager:
@pytest.mark.asyncio
@patch("app.services.socket.extract_access_token_from_cookies")
async def test_connect_handler_no_token(
self, mock_extract_token, socket_manager, mock_sio,
self,
mock_extract_token,
socket_manager,
mock_sio,
) -> None:
"""Test connection with no access token."""
# Setup mocks
@@ -165,7 +172,11 @@ class TestSocketManager:
@patch("app.services.socket.extract_access_token_from_cookies")
@patch("app.services.socket.JWTUtils.decode_access_token")
async def test_connect_handler_invalid_token(
self, mock_decode, mock_extract_token, socket_manager, mock_sio,
self,
mock_decode,
mock_extract_token,
socket_manager,
mock_sio,
) -> None:
"""Test connection with invalid token."""
# Setup mocks
@@ -199,7 +210,11 @@ class TestSocketManager:
@patch("app.services.socket.extract_access_token_from_cookies")
@patch("app.services.socket.JWTUtils.decode_access_token")
async def test_connect_handler_missing_user_id(
self, mock_decode, mock_extract_token, socket_manager, mock_sio,
self,
mock_decode,
mock_extract_token,
socket_manager,
mock_sio,
) -> None:
"""Test connection with token missing user ID."""
# Setup mocks
@@ -254,7 +269,9 @@ class TestSocketManager:
assert "123" not in socket_manager.user_rooms
@pytest.mark.asyncio
async def test_disconnect_handler_unknown_socket(self, socket_manager, mock_sio) -> None:
async def test_disconnect_handler_unknown_socket(
self, socket_manager, mock_sio,
) -> None:
"""Test disconnect handler with unknown socket."""
# Access the disconnect handler directly
handlers = {}

View File

@@ -154,7 +154,9 @@ class TestSoundNormalizerService:
assert result["id"] == 1
@pytest.mark.asyncio
async def test_normalize_sound_force_already_normalized(self, normalizer_service) -> None:
async def test_normalize_sound_force_already_normalized(
self, normalizer_service,
) -> None:
"""Test force normalizing a sound that's already normalized."""
sound = Sound(
id=1,
@@ -172,14 +174,17 @@ class TestSoundNormalizerService:
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",
normalizer_service,
"_normalize_audio_two_pass",
),
patch(
"app.services.sound_normalizer.get_audio_duration", return_value=6000,
"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",
"app.services.sound_normalizer.get_file_hash",
return_value="new_hash",
),
):
# Setup path mocks
@@ -245,14 +250,17 @@ class TestSoundNormalizerService:
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",
normalizer_service,
"_normalize_audio_one_pass",
) as mock_normalize,
patch(
"app.services.sound_normalizer.get_audio_duration", return_value=5500,
"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",
"app.services.sound_normalizer.get_file_hash",
return_value="norm_hash",
),
):
# Setup path mocks
@@ -275,7 +283,9 @@ class TestSoundNormalizerService:
mock_normalize.assert_called_once()
@pytest.mark.asyncio
async def test_normalize_sound_normalization_error(self, normalizer_service) -> None:
async def test_normalize_sound_normalization_error(
self, normalizer_service,
) -> None:
"""Test handling normalization errors."""
sound = Sound(
id=1,
@@ -300,7 +310,8 @@ class TestSoundNormalizerService:
with (
patch("pathlib.Path.exists", return_value=True),
patch.object(
normalizer_service, "_normalize_audio_two_pass",
normalizer_service,
"_normalize_audio_two_pass",
) as mock_normalize,
):
mock_normalize.side_effect = Exception("Normalization failed")
@@ -529,7 +540,11 @@ class TestSoundNormalizerService:
# Verify ffmpeg chain was called correctly
mock_ffmpeg.input.assert_called_once_with(str(input_path))
mock_ffmpeg.filter.assert_called_once_with(
mock_stream, "loudnorm", I=-23, TP=-2, LRA=7,
mock_stream,
"loudnorm",
I=-23,
TP=-2,
LRA=7,
)
mock_ffmpeg.output.assert_called_once()
mock_ffmpeg.run.assert_called_once()

View File

@@ -153,7 +153,10 @@ class TestSoundScannerService:
"files": [],
}
await scanner_service._sync_audio_file(
temp_path, "SDB", existing_sound, results,
temp_path,
"SDB",
existing_sound,
results,
)
assert results["skipped"] == 1
@@ -257,7 +260,10 @@ class TestSoundScannerService:
"files": [],
}
await scanner_service._sync_audio_file(
temp_path, "SDB", existing_sound, results,
temp_path,
"SDB",
existing_sound,
results,
)
assert results["updated"] == 1
@@ -296,7 +302,8 @@ class TestSoundScannerService:
# Mock file operations
with (
patch(
"app.services.sound_scanner.get_file_hash", return_value="custom_hash",
"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),
@@ -316,7 +323,10 @@ class TestSoundScannerService:
"files": [],
}
await scanner_service._sync_audio_file(
temp_path, "CUSTOM", None, results,
temp_path,
"CUSTOM",
None,
results,
)
assert results["added"] == 1

View File

@@ -80,6 +80,7 @@ class TestVLCPlayerService:
# Mock Path to return True for the first absolute path
with patch("app.services.vlc_player.Path") as mock_path:
def path_side_effect(path_str):
mock_instance = Mock()
mock_instance.exists.return_value = str(path_str) == "/usr/bin/vlc"
@@ -105,11 +106,13 @@ class TestVLCPlayerService:
service = VLCPlayerService()
assert service.vlc_executable == "vlc"
@pytest.mark.asyncio
@patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_play_sound_success(
self, mock_subprocess, vlc_service, sample_sound,
self,
mock_subprocess,
vlc_service,
sample_sound,
) -> None:
"""Test successful sound playback."""
# Mock subprocess
@@ -142,7 +145,9 @@ class TestVLCPlayerService:
@pytest.mark.asyncio
async def test_play_sound_file_not_found(
self, vlc_service, sample_sound,
self,
vlc_service,
sample_sound,
) -> None:
"""Test sound playback when file doesn't exist."""
# Mock the file path utility to return a non-existent path
@@ -158,7 +163,10 @@ class TestVLCPlayerService:
@pytest.mark.asyncio
@patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_play_sound_subprocess_error(
self, mock_subprocess, vlc_service, sample_sound,
self,
mock_subprocess,
vlc_service,
sample_sound,
) -> None:
"""Test sound playback when subprocess fails."""
# Mock the file path utility to return an existing path
@@ -176,7 +184,9 @@ class TestVLCPlayerService:
@pytest.mark.asyncio
@patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_stop_all_vlc_instances_success(self, mock_subprocess, vlc_service) -> None:
async def test_stop_all_vlc_instances_success(
self, mock_subprocess, vlc_service,
) -> None:
"""Test successful stopping of all VLC instances."""
# Mock pgrep process (find VLC processes)
mock_find_process = Mock()
@@ -212,7 +222,9 @@ class TestVLCPlayerService:
@pytest.mark.asyncio
@patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_stop_all_vlc_instances_no_processes(
self, mock_subprocess, vlc_service,
self,
mock_subprocess,
vlc_service,
) -> None:
"""Test stopping VLC instances when none are running."""
# Mock pgrep process (no VLC processes found)
@@ -232,7 +244,9 @@ class TestVLCPlayerService:
@pytest.mark.asyncio
@patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_stop_all_vlc_instances_partial_kill(
self, mock_subprocess, vlc_service,
self,
mock_subprocess,
vlc_service,
) -> None:
"""Test stopping VLC instances when some processes remain."""
# Mock pgrep process (find VLC processes)
@@ -266,7 +280,9 @@ class TestVLCPlayerService:
@pytest.mark.asyncio
@patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_stop_all_vlc_instances_error(self, mock_subprocess, vlc_service) -> None:
async def test_stop_all_vlc_instances_error(
self, mock_subprocess, vlc_service,
) -> None:
"""Test stopping VLC instances when an error occurs."""
# Mock subprocess exception
mock_subprocess.side_effect = Exception("Command failed")
@@ -287,6 +303,7 @@ class TestVLCPlayerService:
# Clear the global instance
import app.services.vlc_player
app.services.vlc_player.vlc_player_service = None
# First call should create new instance
@@ -304,7 +321,10 @@ class TestVLCPlayerService:
@pytest.mark.asyncio
@patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_play_sound_with_play_count_tracking(
self, mock_subprocess, vlc_service_with_db, sample_sound,
self,
mock_subprocess,
vlc_service_with_db,
sample_sound,
) -> None:
"""Test sound playback with play count tracking."""
# Mock subprocess
@@ -320,11 +340,17 @@ class TestVLCPlayerService:
mock_sound_repo = AsyncMock()
mock_user_repo = AsyncMock()
with patch("app.services.vlc_player.SoundRepository", return_value=mock_sound_repo):
with patch("app.services.vlc_player.UserRepository", return_value=mock_user_repo):
with patch(
"app.services.vlc_player.SoundRepository", return_value=mock_sound_repo,
):
with patch(
"app.services.vlc_player.UserRepository", return_value=mock_user_repo,
):
with patch("app.services.vlc_player.socket_manager") as mock_socket:
# Mock the file path utility
with patch("app.services.vlc_player.get_sound_file_path") as mock_get_path:
with patch(
"app.services.vlc_player.get_sound_file_path",
) as mock_get_path:
mock_path = Mock()
mock_path.exists.return_value = True
mock_get_path.return_value = mock_path
@@ -397,8 +423,12 @@ class TestVLCPlayerService:
role="admin",
)
with patch("app.services.vlc_player.SoundRepository", return_value=mock_sound_repo):
with patch("app.services.vlc_player.UserRepository", return_value=mock_user_repo):
with patch(
"app.services.vlc_player.SoundRepository", return_value=mock_sound_repo,
):
with patch(
"app.services.vlc_player.UserRepository", return_value=mock_user_repo,
):
with patch("app.services.vlc_player.socket_manager") as mock_socket:
# Setup mocks
mock_sound_repo.get_by_id.return_value = test_sound
@@ -412,7 +442,8 @@ class TestVLCPlayerService:
# Verify sound repository calls
mock_sound_repo.get_by_id.assert_called_once_with(1)
mock_sound_repo.update.assert_called_once_with(
test_sound, {"play_count": 1},
test_sound,
{"play_count": 1},
)
# Verify user repository calls
@@ -442,7 +473,9 @@ class TestVLCPlayerService:
# The method should return early without doing anything
@pytest.mark.asyncio
async def test_record_play_count_always_creates_record(self, vlc_service_with_db) -> None:
async def test_record_play_count_always_creates_record(
self, vlc_service_with_db,
) -> None:
"""Test play count recording always creates a new SoundPlayed record."""
# Mock session and repositories
mock_session = AsyncMock()
@@ -469,28 +502,33 @@ class TestVLCPlayerService:
role="admin",
)
with patch("app.services.vlc_player.SoundRepository", return_value=mock_sound_repo):
with patch("app.services.vlc_player.UserRepository", return_value=mock_user_repo):
with patch(
"app.services.vlc_player.SoundRepository", return_value=mock_sound_repo,
):
with patch(
"app.services.vlc_player.UserRepository", return_value=mock_user_repo,
):
with patch("app.services.vlc_player.socket_manager") as mock_socket:
# Setup mocks
mock_sound_repo.get_by_id.return_value = test_sound
mock_user_repo.get_by_id.return_value = admin_user
# Setup mocks
mock_sound_repo.get_by_id.return_value = test_sound
mock_user_repo.get_by_id.return_value = admin_user
# Mock socket broadcast
mock_socket.broadcast_to_all = AsyncMock()
# Mock socket broadcast
mock_socket.broadcast_to_all = AsyncMock()
await vlc_service_with_db._record_play_count(1, "Test Sound")
await vlc_service_with_db._record_play_count(1, "Test Sound")
# Verify sound play count was updated
mock_sound_repo.update.assert_called_once_with(
test_sound, {"play_count": 6},
)
# Verify sound play count was updated
mock_sound_repo.update.assert_called_once_with(
test_sound,
{"play_count": 6},
)
# Verify new SoundPlayed record was always added
mock_session.add.assert_called_once()
# Verify new SoundPlayed record was always added
mock_session.add.assert_called_once()
# Verify commit happened
mock_session.commit.assert_called_once()
# Verify commit happened
mock_session.commit.assert_called_once()
def test_uses_shared_sound_path_utility(self, vlc_service, sample_sound) -> None:
"""Test that VLC service uses the shared sound path utility."""