Add tests for extraction API endpoints and enhance existing tests
Some checks failed
Backend CI / lint (push) Successful in 9m25s
Backend CI / test (push) Failing after 4m48s

- 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:
JSC
2025-08-25 21:40:31 +02:00
parent d3ce17f10d
commit 7dee6e320e
15 changed files with 1560 additions and 721 deletions

View File

@@ -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()