Add tests for extraction API endpoints and enhance existing tests
- Implement tests for admin extraction API endpoints including status retrieval, deletion of extractions, and permission checks. - Add tests for user extraction deletion, ensuring proper handling of permissions and non-existent extractions. - Enhance sound endpoint tests to include duplicate handling in responses. - Refactor favorite service tests to utilize mock dependencies for better maintainability and clarity. - Update sound scanner tests to improve file handling and ensure proper deletion of associated files.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""Tests for favorite service."""
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
from dataclasses import dataclass
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -14,6 +15,31 @@ from app.models.user import User
|
||||
from app.services.favorite import FavoriteService
|
||||
|
||||
|
||||
@dataclass
|
||||
class MockRepositories:
|
||||
"""Container for all mock repositories."""
|
||||
|
||||
user_repo: AsyncMock
|
||||
favorite_repo: AsyncMock
|
||||
sound_repo: AsyncMock
|
||||
socket_manager: AsyncMock
|
||||
|
||||
|
||||
@dataclass
|
||||
class MockServiceDependencies:
|
||||
"""Container for all mock service dependencies."""
|
||||
|
||||
sound_repo_class: AsyncMock
|
||||
user_repo_class: AsyncMock
|
||||
favorite_repo_class: AsyncMock
|
||||
socket_manager: AsyncMock
|
||||
sound_repo: AsyncMock
|
||||
user_repo: AsyncMock
|
||||
favorite_repo: AsyncMock
|
||||
playlist_repo_class: AsyncMock | None = None
|
||||
playlist_repo: AsyncMock | None = None
|
||||
|
||||
|
||||
class TestFavoriteService:
|
||||
"""Test favorite service operations."""
|
||||
|
||||
@@ -71,34 +97,75 @@ class TestFavoriteService:
|
||||
"playlist_repo": AsyncMock(),
|
||||
}
|
||||
|
||||
@patch("app.services.favorite.socket_manager")
|
||||
@patch("app.services.favorite.FavoriteRepository")
|
||||
@patch("app.services.favorite.UserRepository")
|
||||
@patch("app.services.favorite.SoundRepository")
|
||||
@pytest_asyncio.fixture
|
||||
async def mock_sound_favorite_dependencies(self) -> MockServiceDependencies:
|
||||
"""Create mock dependencies for sound favorite operations."""
|
||||
with (
|
||||
patch("app.services.favorite.SoundRepository") as mock_sound_repo_class,
|
||||
patch("app.services.favorite.UserRepository") as mock_user_repo_class,
|
||||
patch("app.services.favorite.FavoriteRepository") as mock_favorite_repo_class,
|
||||
patch("app.services.favorite.socket_manager") as mock_socket_manager,
|
||||
):
|
||||
mock_sound_repo = AsyncMock()
|
||||
mock_user_repo = AsyncMock()
|
||||
mock_favorite_repo = AsyncMock()
|
||||
|
||||
mock_sound_repo_class.return_value = mock_sound_repo
|
||||
mock_user_repo_class.return_value = mock_user_repo
|
||||
mock_favorite_repo_class.return_value = mock_favorite_repo
|
||||
|
||||
yield MockServiceDependencies(
|
||||
sound_repo_class=mock_sound_repo_class,
|
||||
user_repo_class=mock_user_repo_class,
|
||||
favorite_repo_class=mock_favorite_repo_class,
|
||||
socket_manager=mock_socket_manager,
|
||||
sound_repo=mock_sound_repo,
|
||||
user_repo=mock_user_repo,
|
||||
favorite_repo=mock_favorite_repo,
|
||||
)
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def mock_playlist_favorite_dependencies(self) -> MockServiceDependencies:
|
||||
"""Create mock dependencies for playlist favorite operations."""
|
||||
with (
|
||||
patch("app.services.favorite.UserRepository") as mock_user_repo_class,
|
||||
patch("app.services.favorite.PlaylistRepository") as mock_playlist_repo_class,
|
||||
patch("app.services.favorite.FavoriteRepository") as mock_favorite_repo_class,
|
||||
):
|
||||
mock_user_repo = AsyncMock()
|
||||
mock_playlist_repo = AsyncMock()
|
||||
mock_favorite_repo = AsyncMock()
|
||||
|
||||
mock_user_repo_class.return_value = mock_user_repo
|
||||
mock_playlist_repo_class.return_value = mock_playlist_repo
|
||||
mock_favorite_repo_class.return_value = mock_favorite_repo
|
||||
|
||||
yield MockServiceDependencies(
|
||||
sound_repo_class=AsyncMock(), # Not used in playlist tests
|
||||
user_repo_class=mock_user_repo_class,
|
||||
favorite_repo_class=mock_favorite_repo_class,
|
||||
socket_manager=AsyncMock(), # Not used in playlist tests
|
||||
sound_repo=AsyncMock(), # Not used in playlist tests
|
||||
user_repo=mock_user_repo,
|
||||
favorite_repo=mock_favorite_repo,
|
||||
playlist_repo_class=mock_playlist_repo_class,
|
||||
playlist_repo=mock_playlist_repo,
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_sound_favorite_success(
|
||||
self,
|
||||
mock_sound_repo_class: AsyncMock,
|
||||
mock_user_repo_class: AsyncMock,
|
||||
mock_favorite_repo_class: AsyncMock,
|
||||
mock_socket_manager: AsyncMock,
|
||||
mock_sound_favorite_dependencies: MockServiceDependencies,
|
||||
favorite_service: FavoriteService,
|
||||
test_user: User,
|
||||
test_sound: Sound,
|
||||
) -> None:
|
||||
"""Test successfully adding a sound favorite."""
|
||||
# Setup mocks
|
||||
mock_favorite_repo = AsyncMock()
|
||||
mock_user_repo = AsyncMock()
|
||||
mock_sound_repo = AsyncMock()
|
||||
|
||||
mock_favorite_repo_class.return_value = mock_favorite_repo
|
||||
mock_user_repo_class.return_value = mock_user_repo
|
||||
mock_sound_repo_class.return_value = mock_sound_repo
|
||||
|
||||
mock_user_repo.get_by_id.return_value = test_user
|
||||
mock_sound_repo.get_by_id.return_value = test_sound
|
||||
mock_favorite_repo.get_by_user_and_sound.return_value = None
|
||||
mocks = mock_sound_favorite_dependencies
|
||||
mocks.user_repo.get_by_id.return_value = test_user
|
||||
mocks.sound_repo.get_by_id.return_value = test_sound
|
||||
mocks.favorite_repo.get_by_user_and_sound.return_value = None
|
||||
|
||||
expected_favorite = Favorite(
|
||||
id=1,
|
||||
@@ -106,23 +173,23 @@ class TestFavoriteService:
|
||||
sound_id=test_sound.id,
|
||||
playlist_id=None,
|
||||
)
|
||||
mock_favorite_repo.create.return_value = expected_favorite
|
||||
mock_favorite_repo.count_sound_favorites.return_value = 1
|
||||
mocks.favorite_repo.create.return_value = expected_favorite
|
||||
mocks.favorite_repo.count_sound_favorites.return_value = 1
|
||||
|
||||
# Execute
|
||||
result = await favorite_service.add_sound_favorite(test_user.id, test_sound.id)
|
||||
|
||||
# Verify
|
||||
assert result == expected_favorite
|
||||
mock_user_repo.get_by_id.assert_called_once_with(test_user.id)
|
||||
mock_sound_repo.get_by_id.assert_called_once_with(test_sound.id)
|
||||
mock_favorite_repo.get_by_user_and_sound.assert_called_once_with(test_user.id, test_sound.id)
|
||||
mock_favorite_repo.create.assert_called_once_with({
|
||||
mocks.user_repo.get_by_id.assert_called_once_with(test_user.id)
|
||||
mocks.sound_repo.get_by_id.assert_called_once_with(test_sound.id)
|
||||
mocks.favorite_repo.get_by_user_and_sound.assert_called_once_with(test_user.id, test_sound.id)
|
||||
mocks.favorite_repo.create.assert_called_once_with({
|
||||
"user_id": test_user.id,
|
||||
"sound_id": test_sound.id,
|
||||
"playlist_id": None,
|
||||
})
|
||||
mock_socket_manager.broadcast_to_all.assert_called_once()
|
||||
mocks.socket_manager.broadcast_to_all.assert_called_once()
|
||||
|
||||
@patch("app.services.favorite.UserRepository")
|
||||
@pytest.mark.asyncio
|
||||
@@ -161,62 +228,38 @@ class TestFavoriteService:
|
||||
with pytest.raises(ValueError, match="Sound with ID 1 not found"):
|
||||
await favorite_service.add_sound_favorite(test_user.id, 1)
|
||||
|
||||
@patch("app.services.favorite.FavoriteRepository")
|
||||
@patch("app.services.favorite.SoundRepository")
|
||||
@patch("app.services.favorite.UserRepository")
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_sound_favorite_already_exists(
|
||||
self,
|
||||
mock_user_repo_class: AsyncMock,
|
||||
mock_sound_repo_class: AsyncMock,
|
||||
mock_favorite_repo_class: AsyncMock,
|
||||
mock_sound_favorite_dependencies: MockServiceDependencies,
|
||||
favorite_service: FavoriteService,
|
||||
test_user: User,
|
||||
test_sound: Sound,
|
||||
) -> None:
|
||||
"""Test adding sound favorite that already exists."""
|
||||
mock_user_repo = AsyncMock()
|
||||
mock_sound_repo = AsyncMock()
|
||||
mock_favorite_repo = AsyncMock()
|
||||
|
||||
mock_user_repo_class.return_value = mock_user_repo
|
||||
mock_sound_repo_class.return_value = mock_sound_repo
|
||||
mock_favorite_repo_class.return_value = mock_favorite_repo
|
||||
|
||||
mock_user_repo.get_by_id.return_value = test_user
|
||||
mock_sound_repo.get_by_id.return_value = test_sound
|
||||
mocks = mock_sound_favorite_dependencies
|
||||
mocks.user_repo.get_by_id.return_value = test_user
|
||||
mocks.sound_repo.get_by_id.return_value = test_sound
|
||||
existing_favorite = Favorite(user_id=test_user.id, sound_id=test_sound.id)
|
||||
mock_favorite_repo.get_by_user_and_sound.return_value = existing_favorite
|
||||
mocks.favorite_repo.get_by_user_and_sound.return_value = existing_favorite
|
||||
|
||||
with pytest.raises(ValueError, match="already favorited"):
|
||||
await favorite_service.add_sound_favorite(test_user.id, test_sound.id)
|
||||
|
||||
@patch("app.services.favorite.FavoriteRepository")
|
||||
@patch("app.services.favorite.PlaylistRepository")
|
||||
@patch("app.services.favorite.UserRepository")
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_playlist_favorite_success(
|
||||
self,
|
||||
mock_user_repo_class: AsyncMock,
|
||||
mock_playlist_repo_class: AsyncMock,
|
||||
mock_favorite_repo_class: AsyncMock,
|
||||
mock_playlist_favorite_dependencies: MockServiceDependencies,
|
||||
favorite_service: FavoriteService,
|
||||
test_user: User,
|
||||
test_playlist: Playlist,
|
||||
) -> None:
|
||||
"""Test successfully adding a playlist favorite."""
|
||||
# Setup mocks
|
||||
mock_favorite_repo = AsyncMock()
|
||||
mock_user_repo = AsyncMock()
|
||||
mock_playlist_repo = AsyncMock()
|
||||
|
||||
mock_favorite_repo_class.return_value = mock_favorite_repo
|
||||
mock_user_repo_class.return_value = mock_user_repo
|
||||
mock_playlist_repo_class.return_value = mock_playlist_repo
|
||||
|
||||
mock_user_repo.get_by_id.return_value = test_user
|
||||
mock_playlist_repo.get_by_id.return_value = test_playlist
|
||||
mock_favorite_repo.get_by_user_and_playlist.return_value = None
|
||||
mocks = mock_playlist_favorite_dependencies
|
||||
mocks.user_repo.get_by_id.return_value = test_user
|
||||
mocks.playlist_repo.get_by_id.return_value = test_playlist
|
||||
mocks.favorite_repo.get_by_user_and_playlist.return_value = None
|
||||
|
||||
expected_favorite = Favorite(
|
||||
id=1,
|
||||
@@ -224,59 +267,45 @@ class TestFavoriteService:
|
||||
sound_id=None,
|
||||
playlist_id=test_playlist.id,
|
||||
)
|
||||
mock_favorite_repo.create.return_value = expected_favorite
|
||||
mocks.favorite_repo.create.return_value = expected_favorite
|
||||
|
||||
# Execute
|
||||
result = await favorite_service.add_playlist_favorite(test_user.id, test_playlist.id)
|
||||
|
||||
# Verify
|
||||
assert result == expected_favorite
|
||||
mock_user_repo.get_by_id.assert_called_once_with(test_user.id)
|
||||
mock_playlist_repo.get_by_id.assert_called_once_with(test_playlist.id)
|
||||
mock_favorite_repo.get_by_user_and_playlist.assert_called_once_with(test_user.id, test_playlist.id)
|
||||
mock_favorite_repo.create.assert_called_once_with({
|
||||
mocks.user_repo.get_by_id.assert_called_once_with(test_user.id)
|
||||
mocks.playlist_repo.get_by_id.assert_called_once_with(test_playlist.id)
|
||||
mocks.favorite_repo.get_by_user_and_playlist.assert_called_once_with(test_user.id, test_playlist.id)
|
||||
mocks.favorite_repo.create.assert_called_once_with({
|
||||
"user_id": test_user.id,
|
||||
"sound_id": None,
|
||||
"playlist_id": test_playlist.id,
|
||||
})
|
||||
|
||||
@patch("app.services.favorite.socket_manager")
|
||||
@patch("app.services.favorite.FavoriteRepository")
|
||||
@patch("app.services.favorite.SoundRepository")
|
||||
@patch("app.services.favorite.UserRepository")
|
||||
@pytest.mark.asyncio
|
||||
async def test_remove_sound_favorite_success(
|
||||
self,
|
||||
mock_user_repo_class: AsyncMock,
|
||||
mock_sound_repo_class: AsyncMock,
|
||||
mock_favorite_repo_class: AsyncMock,
|
||||
mock_socket_manager: AsyncMock,
|
||||
mock_sound_favorite_dependencies: MockServiceDependencies,
|
||||
favorite_service: FavoriteService,
|
||||
test_user: User,
|
||||
test_sound: Sound,
|
||||
) -> None:
|
||||
"""Test successfully removing a sound favorite."""
|
||||
mock_favorite_repo = AsyncMock()
|
||||
mock_user_repo = AsyncMock()
|
||||
mock_sound_repo = AsyncMock()
|
||||
|
||||
mock_favorite_repo_class.return_value = mock_favorite_repo
|
||||
mock_user_repo_class.return_value = mock_user_repo
|
||||
mock_sound_repo_class.return_value = mock_sound_repo
|
||||
|
||||
mocks = mock_sound_favorite_dependencies
|
||||
existing_favorite = Favorite(user_id=test_user.id, sound_id=test_sound.id)
|
||||
mock_favorite_repo.get_by_user_and_sound.return_value = existing_favorite
|
||||
mock_user_repo.get_by_id.return_value = test_user
|
||||
mock_sound_repo.get_by_id.return_value = test_sound
|
||||
mock_favorite_repo.count_sound_favorites.return_value = 0
|
||||
mocks.favorite_repo.get_by_user_and_sound.return_value = existing_favorite
|
||||
mocks.user_repo.get_by_id.return_value = test_user
|
||||
mocks.sound_repo.get_by_id.return_value = test_sound
|
||||
mocks.favorite_repo.count_sound_favorites.return_value = 0
|
||||
|
||||
# Execute
|
||||
await favorite_service.remove_sound_favorite(test_user.id, test_sound.id)
|
||||
|
||||
# Verify
|
||||
mock_favorite_repo.get_by_user_and_sound.assert_called_once_with(test_user.id, test_sound.id)
|
||||
mock_favorite_repo.delete.assert_called_once_with(existing_favorite)
|
||||
mock_socket_manager.broadcast_to_all.assert_called_once()
|
||||
mocks.favorite_repo.get_by_user_and_sound.assert_called_once_with(test_user.id, test_sound.id)
|
||||
mocks.favorite_repo.delete.assert_called_once_with(existing_favorite)
|
||||
mocks.socket_manager.broadcast_to_all.assert_called_once()
|
||||
|
||||
@patch("app.services.favorite.FavoriteRepository")
|
||||
@pytest.mark.asyncio
|
||||
@@ -503,46 +532,31 @@ class TestFavoriteService:
|
||||
assert result == 3
|
||||
mock_favorite_repo.count_playlist_favorites.assert_called_once_with(1)
|
||||
|
||||
@patch("app.services.favorite.socket_manager")
|
||||
@patch("app.services.favorite.FavoriteRepository")
|
||||
@patch("app.services.favorite.SoundRepository")
|
||||
@patch("app.services.favorite.UserRepository")
|
||||
@pytest.mark.asyncio
|
||||
async def test_socket_broadcast_error_handling(
|
||||
self,
|
||||
mock_user_repo_class: AsyncMock,
|
||||
mock_sound_repo_class: AsyncMock,
|
||||
mock_favorite_repo_class: AsyncMock,
|
||||
mock_socket_manager: AsyncMock,
|
||||
mock_sound_favorite_dependencies: MockServiceDependencies,
|
||||
favorite_service: FavoriteService,
|
||||
test_user: User,
|
||||
test_sound: Sound,
|
||||
) -> None:
|
||||
"""Test that socket broadcast errors don't affect the operation."""
|
||||
# Setup mocks
|
||||
mock_favorite_repo = AsyncMock()
|
||||
mock_user_repo = AsyncMock()
|
||||
mock_sound_repo = AsyncMock()
|
||||
|
||||
mock_favorite_repo_class.return_value = mock_favorite_repo
|
||||
mock_user_repo_class.return_value = mock_user_repo
|
||||
mock_sound_repo_class.return_value = mock_sound_repo
|
||||
|
||||
mock_user_repo.get_by_id.return_value = test_user
|
||||
mock_sound_repo.get_by_id.return_value = test_sound
|
||||
mock_favorite_repo.get_by_user_and_sound.return_value = None
|
||||
mocks = mock_sound_favorite_dependencies
|
||||
mocks.user_repo.get_by_id.return_value = test_user
|
||||
mocks.sound_repo.get_by_id.return_value = test_sound
|
||||
mocks.favorite_repo.get_by_user_and_sound.return_value = None
|
||||
|
||||
expected_favorite = Favorite(id=1, user_id=test_user.id, sound_id=test_sound.id)
|
||||
mock_favorite_repo.create.return_value = expected_favorite
|
||||
mock_favorite_repo.count_sound_favorites.return_value = 1
|
||||
mocks.favorite_repo.create.return_value = expected_favorite
|
||||
mocks.favorite_repo.count_sound_favorites.return_value = 1
|
||||
|
||||
# Make socket broadcast raise an exception
|
||||
mock_socket_manager.broadcast_to_all.side_effect = Exception("Socket error")
|
||||
mocks.socket_manager.broadcast_to_all.side_effect = Exception("Socket error")
|
||||
|
||||
# Execute - should not raise exception despite socket error
|
||||
result = await favorite_service.add_sound_favorite(test_user.id, test_sound.id)
|
||||
|
||||
# Verify operation still succeeded
|
||||
assert result == expected_favorite
|
||||
mock_favorite_repo.create.assert_called_once()
|
||||
mocks.favorite_repo.create.assert_called_once()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user