549 lines
20 KiB
Python
549 lines
20 KiB
Python
"""Tests for favorite service."""
|
|
|
|
from collections.abc import AsyncGenerator
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
|
|
from app.models.favorite import Favorite
|
|
from app.models.playlist import Playlist
|
|
from app.models.sound import Sound
|
|
from app.models.user import User
|
|
from app.services.favorite import FavoriteService
|
|
|
|
|
|
class TestFavoriteService:
|
|
"""Test favorite service operations."""
|
|
|
|
@pytest_asyncio.fixture
|
|
async def mock_session_factory(self) -> AsyncGenerator[MagicMock]:
|
|
"""Create a mock session factory."""
|
|
mock_session = AsyncMock(spec=AsyncSession)
|
|
mock_factory = MagicMock(return_value=mock_session)
|
|
mock_factory.return_value.__aenter__ = AsyncMock(return_value=mock_session)
|
|
mock_factory.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
yield mock_factory
|
|
|
|
@pytest_asyncio.fixture
|
|
async def favorite_service(
|
|
self,
|
|
mock_session_factory: MagicMock,
|
|
) -> AsyncGenerator[FavoriteService]:
|
|
"""Create a favorite service instance with mocked dependencies."""
|
|
yield FavoriteService(mock_session_factory)
|
|
|
|
@pytest_asyncio.fixture
|
|
async def test_sound(self) -> Sound:
|
|
"""Create a test sound."""
|
|
return Sound(
|
|
id=1,
|
|
filename="test_sound.mp3",
|
|
name="Test Sound",
|
|
type="SDB",
|
|
duration=5000,
|
|
size=1024000,
|
|
hash="abcdef123456789",
|
|
)
|
|
|
|
@pytest_asyncio.fixture
|
|
async def test_playlist(self, test_user: User) -> Playlist:
|
|
"""Create a test playlist."""
|
|
return Playlist(
|
|
id=1,
|
|
user_id=test_user.id,
|
|
name="Test Playlist",
|
|
description="A test playlist",
|
|
genre="test",
|
|
is_main=False,
|
|
is_current=False,
|
|
is_deletable=True,
|
|
)
|
|
|
|
@pytest_asyncio.fixture
|
|
async def mock_repositories(self) -> dict:
|
|
"""Create mock repositories."""
|
|
return {
|
|
"favorite_repo": AsyncMock(),
|
|
"user_repo": AsyncMock(),
|
|
"sound_repo": AsyncMock(),
|
|
"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.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,
|
|
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
|
|
|
|
expected_favorite = Favorite(
|
|
id=1,
|
|
user_id=test_user.id,
|
|
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
|
|
|
|
# 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({
|
|
"user_id": test_user.id,
|
|
"sound_id": test_sound.id,
|
|
"playlist_id": None,
|
|
})
|
|
mock_socket_manager.broadcast_to_all.assert_called_once()
|
|
|
|
@patch("app.services.favorite.UserRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_add_sound_favorite_user_not_found(
|
|
self,
|
|
mock_user_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test adding sound favorite when user not found."""
|
|
mock_user_repo = AsyncMock()
|
|
mock_user_repo_class.return_value = mock_user_repo
|
|
mock_user_repo.get_by_id.return_value = None
|
|
|
|
with pytest.raises(ValueError, match="User with ID 1 not found"):
|
|
await favorite_service.add_sound_favorite(1, 1)
|
|
|
|
@patch("app.services.favorite.SoundRepository")
|
|
@patch("app.services.favorite.UserRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_add_sound_favorite_sound_not_found(
|
|
self,
|
|
mock_user_repo_class: AsyncMock,
|
|
mock_sound_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
test_user: User,
|
|
) -> None:
|
|
"""Test adding sound favorite when sound not found."""
|
|
mock_user_repo = AsyncMock()
|
|
mock_sound_repo = AsyncMock()
|
|
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 = None
|
|
|
|
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,
|
|
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
|
|
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
|
|
|
|
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,
|
|
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
|
|
|
|
expected_favorite = Favorite(
|
|
id=1,
|
|
user_id=test_user.id,
|
|
sound_id=None,
|
|
playlist_id=test_playlist.id,
|
|
)
|
|
mock_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({
|
|
"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,
|
|
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
|
|
|
|
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
|
|
|
|
# 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()
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_remove_sound_favorite_not_found(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test removing sound favorite that doesn't exist."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
mock_favorite_repo.get_by_user_and_sound.return_value = None
|
|
|
|
with pytest.raises(ValueError, match="is not favorited"):
|
|
await favorite_service.remove_sound_favorite(1, 1)
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_remove_playlist_favorite_success(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
test_user: User,
|
|
test_playlist: Playlist,
|
|
) -> None:
|
|
"""Test successfully removing a playlist favorite."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
|
|
existing_favorite = Favorite(user_id=test_user.id, playlist_id=test_playlist.id)
|
|
mock_favorite_repo.get_by_user_and_playlist.return_value = existing_favorite
|
|
|
|
# Execute
|
|
await favorite_service.remove_playlist_favorite(test_user.id, test_playlist.id)
|
|
|
|
# Verify
|
|
mock_favorite_repo.get_by_user_and_playlist.assert_called_once_with(test_user.id, test_playlist.id)
|
|
mock_favorite_repo.delete.assert_called_once_with(existing_favorite)
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_remove_playlist_favorite_not_found(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test removing playlist favorite that doesn't exist."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
mock_favorite_repo.get_by_user_and_playlist.return_value = None
|
|
|
|
with pytest.raises(ValueError, match="is not favorited"):
|
|
await favorite_service.remove_playlist_favorite(1, 1)
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_favorites(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test getting user favorites."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
|
|
expected_favorites = [
|
|
Favorite(id=1, user_id=1, sound_id=1),
|
|
Favorite(id=2, user_id=1, playlist_id=1),
|
|
]
|
|
mock_favorite_repo.get_user_favorites.return_value = expected_favorites
|
|
|
|
# Execute
|
|
result = await favorite_service.get_user_favorites(1, 10, 0)
|
|
|
|
# Verify
|
|
assert result == expected_favorites
|
|
mock_favorite_repo.get_user_favorites.assert_called_once_with(1, 10, 0)
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_sound_favorites(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test getting user sound favorites."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
|
|
expected_favorites = [Favorite(id=1, user_id=1, sound_id=1)]
|
|
mock_favorite_repo.get_user_sound_favorites.return_value = expected_favorites
|
|
|
|
# Execute
|
|
result = await favorite_service.get_user_sound_favorites(1, 10, 0)
|
|
|
|
# Verify
|
|
assert result == expected_favorites
|
|
mock_favorite_repo.get_user_sound_favorites.assert_called_once_with(1, 10, 0)
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_playlist_favorites(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test getting user playlist favorites."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
|
|
expected_favorites = [Favorite(id=1, user_id=1, playlist_id=1)]
|
|
mock_favorite_repo.get_user_playlist_favorites.return_value = expected_favorites
|
|
|
|
# Execute
|
|
result = await favorite_service.get_user_playlist_favorites(1, 10, 0)
|
|
|
|
# Verify
|
|
assert result == expected_favorites
|
|
mock_favorite_repo.get_user_playlist_favorites.assert_called_once_with(1, 10, 0)
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_is_sound_favorited(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test checking if sound is favorited."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
|
|
mock_favorite_repo.is_sound_favorited.return_value = True
|
|
|
|
# Execute
|
|
result = await favorite_service.is_sound_favorited(1, 1)
|
|
|
|
# Verify
|
|
assert result is True
|
|
mock_favorite_repo.is_sound_favorited.assert_called_once_with(1, 1)
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_is_playlist_favorited(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test checking if playlist is favorited."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
|
|
mock_favorite_repo.is_playlist_favorited.return_value = False
|
|
|
|
# Execute
|
|
result = await favorite_service.is_playlist_favorited(1, 1)
|
|
|
|
# Verify
|
|
assert result is False
|
|
mock_favorite_repo.is_playlist_favorited.assert_called_once_with(1, 1)
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_get_favorite_counts(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test getting favorite counts."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
|
|
mock_favorite_repo.count_user_favorites.return_value = 3
|
|
mock_favorite_repo.get_user_sound_favorites.return_value = [1, 2] # 2 sounds
|
|
mock_favorite_repo.get_user_playlist_favorites.return_value = [1] # 1 playlist
|
|
|
|
# Execute
|
|
result = await favorite_service.get_favorite_counts(1)
|
|
|
|
# Verify
|
|
expected = {
|
|
"total": 3,
|
|
"sounds": 2,
|
|
"playlists": 1,
|
|
}
|
|
assert result == expected
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_get_sound_favorite_count(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test getting sound favorite count."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
|
|
mock_favorite_repo.count_sound_favorites.return_value = 5
|
|
|
|
# Execute
|
|
result = await favorite_service.get_sound_favorite_count(1)
|
|
|
|
# Verify
|
|
assert result == 5
|
|
mock_favorite_repo.count_sound_favorites.assert_called_once_with(1)
|
|
|
|
@patch("app.services.favorite.FavoriteRepository")
|
|
@pytest.mark.asyncio
|
|
async def test_get_playlist_favorite_count(
|
|
self,
|
|
mock_favorite_repo_class: AsyncMock,
|
|
favorite_service: FavoriteService,
|
|
) -> None:
|
|
"""Test getting playlist favorite count."""
|
|
mock_favorite_repo = AsyncMock()
|
|
mock_favorite_repo_class.return_value = mock_favorite_repo
|
|
|
|
mock_favorite_repo.count_playlist_favorites.return_value = 3
|
|
|
|
# Execute
|
|
result = await favorite_service.get_playlist_favorite_count(1)
|
|
|
|
# Verify
|
|
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,
|
|
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
|
|
|
|
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
|
|
|
|
# Make socket broadcast raise an exception
|
|
mock_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()
|
|
|