Refactor code structure for improved readability and maintainability
Some checks failed
Backend CI / lint (push) Failing after 4m51s
Backend CI / test (push) Failing after 4m35s

This commit is contained in:
JSC
2025-08-20 11:37:28 +02:00
parent 9653062003
commit 821093f64f
15 changed files with 1897 additions and 217 deletions

View File

@@ -0,0 +1,576 @@
"""Tests for favorite API endpoints."""
import pytest
import pytest_asyncio
from httpx import AsyncClient
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
class TestFavoriteEndpoints:
"""Test favorite API endpoints."""
@pytest_asyncio.fixture
async def test_sound(
self,
test_session: AsyncSession,
) -> Sound:
"""Create a test sound."""
sound = Sound(
filename="test_sound.mp3",
name="Test Sound",
type="SDB",
duration=5000, # 5 seconds in ms
size=1024000, # 1MB
hash="abcdef123456789",
)
test_session.add(sound)
await test_session.commit()
await test_session.refresh(sound)
return sound
@pytest_asyncio.fixture
async def test_sound2(
self,
test_session: AsyncSession,
) -> Sound:
"""Create a second test sound."""
sound = Sound(
filename="test_sound2.mp3",
name="Test Sound 2",
type="TTS",
duration=3000, # 3 seconds in ms
size=512000, # 512KB
hash="xyz789456123abc",
)
test_session.add(sound)
await test_session.commit()
await test_session.refresh(sound)
return sound
@pytest_asyncio.fixture
async def test_playlist(
self,
test_session: AsyncSession,
) -> Playlist:
"""Create a test playlist."""
playlist = Playlist(
user_id=1, # Use hardcoded ID
name="Test Playlist",
description="A test playlist",
genre="test",
is_main=False,
is_current=False,
is_deletable=True,
)
test_session.add(playlist)
await test_session.commit()
await test_session.refresh(playlist)
return playlist
@pytest_asyncio.fixture
async def test_playlist2(
self,
test_session: AsyncSession,
) -> Playlist:
"""Create a second test playlist."""
playlist = Playlist(
user_id=1, # Use hardcoded ID
name="Test Playlist 2",
description="Another test playlist",
genre="test2",
is_main=False,
is_current=False,
is_deletable=True,
)
test_session.add(playlist)
await test_session.commit()
await test_session.refresh(playlist)
return playlist
@pytest_asyncio.fixture
async def existing_sound_favorite(
self,
test_session: AsyncSession,
) -> Favorite:
"""Create an existing sound favorite."""
favorite = Favorite(
user_id=1, # Use hardcoded ID
sound_id=1, # Use hardcoded ID
)
test_session.add(favorite)
await test_session.commit()
await test_session.refresh(favorite)
return favorite
@pytest_asyncio.fixture
async def existing_playlist_favorite(
self,
test_session: AsyncSession,
) -> Favorite:
"""Create an existing playlist favorite."""
favorite = Favorite(
user_id=1, # Use hardcoded ID
playlist_id=1, # Use hardcoded ID
)
test_session.add(favorite)
await test_session.commit()
await test_session.refresh(favorite)
return favorite
@pytest.mark.asyncio
async def test_add_sound_favorite_success(
self,
authenticated_client: AsyncClient,
test_sound: Sound,
) -> None:
"""Test successfully adding a sound to favorites."""
# Clean up any existing favorite first
try:
await authenticated_client.delete("/api/v1/favorites/sounds/1")
except:
pass # It's ok if it doesn't exist
response = await authenticated_client.post("/api/v1/favorites/sounds/1")
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["sound_id"] == 1
assert data["playlist_id"] is None
assert "created_at" in data
assert "updated_at" in data
@pytest.mark.asyncio
async def test_add_sound_favorite_not_found(
self,
authenticated_client: AsyncClient,
) -> None:
"""Test adding a favorite for non-existent sound."""
response = await authenticated_client.post("/api/v1/favorites/sounds/99999")
assert response.status_code == 404
assert "Sound with ID 99999 not found" in response.json()["detail"]
@pytest.mark.asyncio
async def test_add_sound_favorite_already_exists(
self,
authenticated_client: AsyncClient,
existing_sound_favorite: Favorite,
test_sound: Sound,
) -> None:
"""Test adding a sound favorite that already exists."""
response = await authenticated_client.post(f"/api/v1/favorites/sounds/{test_sound.id}")
assert response.status_code == 409
assert "already favorited" in response.json()["detail"]
@pytest.mark.asyncio
async def test_add_playlist_favorite_success(
self,
authenticated_client: AsyncClient,
test_playlist: Playlist,
) -> None:
"""Test successfully adding a playlist to favorites."""
# Clean up any existing favorite first
try:
await authenticated_client.delete("/api/v1/favorites/playlists/1")
except:
pass # It's ok if it doesn't exist
response = await authenticated_client.post("/api/v1/favorites/playlists/1")
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["sound_id"] is None
assert data["playlist_id"] == 1
assert "created_at" in data
assert "updated_at" in data
@pytest.mark.asyncio
async def test_add_playlist_favorite_not_found(
self,
authenticated_client: AsyncClient,
) -> None:
"""Test adding a favorite for non-existent playlist."""
response = await authenticated_client.post("/api/v1/favorites/playlists/99999")
assert response.status_code == 404
assert "Playlist with ID 99999 not found" in response.json()["detail"]
@pytest.mark.asyncio
async def test_add_playlist_favorite_already_exists(
self,
authenticated_client: AsyncClient,
existing_playlist_favorite: Favorite,
test_playlist: Playlist,
) -> None:
"""Test adding a playlist favorite that already exists."""
response = await authenticated_client.post(f"/api/v1/favorites/playlists/{test_playlist.id}")
assert response.status_code == 409
assert "already favorited" in response.json()["detail"]
@pytest.mark.asyncio
async def test_remove_sound_favorite_success(
self,
authenticated_client: AsyncClient,
existing_sound_favorite: Favorite,
test_sound: Sound,
) -> None:
"""Test successfully removing a sound from favorites."""
response = await authenticated_client.delete(f"/api/v1/favorites/sounds/{test_sound.id}")
assert response.status_code == 200
data = response.json()
assert data["message"] == "Sound removed from favorites"
@pytest.mark.asyncio
async def test_remove_sound_favorite_not_found(
self,
authenticated_client: AsyncClient,
test_sound: Sound,
) -> None:
"""Test removing a sound favorite that doesn't exist."""
response = await authenticated_client.delete(f"/api/v1/favorites/sounds/{test_sound.id}")
assert response.status_code == 404
assert "is not favorited" in response.json()["detail"]
@pytest.mark.asyncio
async def test_remove_playlist_favorite_success(
self,
authenticated_client: AsyncClient,
existing_playlist_favorite: Favorite,
test_playlist: Playlist,
) -> None:
"""Test successfully removing a playlist from favorites."""
response = await authenticated_client.delete(f"/api/v1/favorites/playlists/{test_playlist.id}")
assert response.status_code == 200
data = response.json()
assert data["message"] == "Playlist removed from favorites"
@pytest.mark.asyncio
async def test_remove_playlist_favorite_not_found(
self,
authenticated_client: AsyncClient,
test_playlist: Playlist,
) -> None:
"""Test removing a playlist favorite that doesn't exist."""
response = await authenticated_client.delete(f"/api/v1/favorites/playlists/{test_playlist.id}")
assert response.status_code == 404
assert "is not favorited" in response.json()["detail"]
@pytest.mark.asyncio
async def test_get_all_favorites_empty(
self,
authenticated_client: AsyncClient,
) -> None:
"""Test getting all favorites when user has none."""
# First get all existing favorites for the test user
response = await authenticated_client.get("/api/v1/favorites/")
assert response.status_code == 200
existing_data = response.json()
# Clean up any existing favorites for this test user
for favorite in existing_data["favorites"]:
if favorite.get("sound_id"):
await authenticated_client.delete(f"/api/v1/favorites/sounds/{favorite['sound_id']}")
elif favorite.get("playlist_id"):
await authenticated_client.delete(f"/api/v1/favorites/playlists/{favorite['playlist_id']}")
# Now test that the user has no favorites
response = await authenticated_client.get("/api/v1/favorites/")
assert response.status_code == 200
data = response.json()
assert data["favorites"] == []
@pytest.mark.asyncio
async def test_get_all_favorites_with_data(
self,
authenticated_client: AsyncClient,
existing_sound_favorite: Favorite,
existing_playlist_favorite: Favorite,
) -> None:
"""Test getting all favorites with mixed data."""
# Clean up any existing favorites first
response = await authenticated_client.get("/api/v1/favorites/")
assert response.status_code == 200
existing_data = response.json()
for favorite in existing_data["favorites"]:
if favorite.get("sound_id"):
await authenticated_client.delete(f"/api/v1/favorites/sounds/{favorite['sound_id']}")
elif favorite.get("playlist_id"):
await authenticated_client.delete(f"/api/v1/favorites/playlists/{favorite['playlist_id']}")
# Add the test favorites using hardcoded IDs
await authenticated_client.post("/api/v1/favorites/sounds/1")
await authenticated_client.post("/api/v1/favorites/playlists/1")
response = await authenticated_client.get("/api/v1/favorites/")
assert response.status_code == 200
data = response.json()
assert len(data["favorites"]) == 2
# Check that we have both types
sound_favorite = next((f for f in data["favorites"] if f["sound_id"] is not None), None)
playlist_favorite = next((f for f in data["favorites"] if f["playlist_id"] is not None), None)
assert sound_favorite is not None
assert playlist_favorite is not None
assert sound_favorite["sound_id"] == 1
assert playlist_favorite["playlist_id"] == 1
@pytest.mark.asyncio
async def test_get_all_favorites_pagination(
self,
authenticated_client: AsyncClient,
test_session: AsyncSession,
) -> None:
"""Test pagination for get all favorites."""
# Create multiple favorites
favorite1 = Favorite(user_id=1, sound_id=1)
favorite2 = Favorite(user_id=1, sound_id=2)
test_session.add(favorite1)
test_session.add(favorite2)
await test_session.commit()
# Test limit
response = await authenticated_client.get("/api/v1/favorites/?limit=1")
assert response.status_code == 200
data = response.json()
assert len(data["favorites"]) == 1
# Test offset
response = await authenticated_client.get("/api/v1/favorites/?offset=1&limit=1")
assert response.status_code == 200
data = response.json()
assert len(data["favorites"]) == 1
@pytest.mark.asyncio
async def test_get_sound_favorites(
self,
authenticated_client: AsyncClient,
existing_sound_favorite: Favorite,
existing_playlist_favorite: Favorite,
) -> None:
"""Test getting only sound favorites."""
# Clean up any existing favorites first
response = await authenticated_client.get("/api/v1/favorites/")
assert response.status_code == 200
existing_data = response.json()
for favorite in existing_data["favorites"]:
if favorite.get("sound_id"):
await authenticated_client.delete(f"/api/v1/favorites/sounds/{favorite['sound_id']}")
elif favorite.get("playlist_id"):
await authenticated_client.delete(f"/api/v1/favorites/playlists/{favorite['playlist_id']}")
# Add test favorites using hardcoded IDs
await authenticated_client.post("/api/v1/favorites/sounds/1")
await authenticated_client.post("/api/v1/favorites/playlists/1")
response = await authenticated_client.get("/api/v1/favorites/sounds")
assert response.status_code == 200
data = response.json()
assert len(data["favorites"]) == 1
assert data["favorites"][0]["sound_id"] == 1
assert data["favorites"][0]["playlist_id"] is None
@pytest.mark.asyncio
async def test_get_playlist_favorites(
self,
authenticated_client: AsyncClient,
existing_sound_favorite: Favorite,
existing_playlist_favorite: Favorite,
) -> None:
"""Test getting only playlist favorites."""
# Clean up any existing favorites first
response = await authenticated_client.get("/api/v1/favorites/")
assert response.status_code == 200
existing_data = response.json()
for favorite in existing_data["favorites"]:
if favorite.get("sound_id"):
await authenticated_client.delete(f"/api/v1/favorites/sounds/{favorite['sound_id']}")
elif favorite.get("playlist_id"):
await authenticated_client.delete(f"/api/v1/favorites/playlists/{favorite['playlist_id']}")
# Add test favorites using hardcoded IDs
await authenticated_client.post("/api/v1/favorites/sounds/1")
await authenticated_client.post("/api/v1/favorites/playlists/1")
response = await authenticated_client.get("/api/v1/favorites/playlists")
assert response.status_code == 200
data = response.json()
assert len(data["favorites"]) == 1
assert data["favorites"][0]["playlist_id"] == 1
assert data["favorites"][0]["sound_id"] is None
@pytest.mark.asyncio
async def test_get_favorite_counts(
self,
authenticated_client: AsyncClient,
existing_sound_favorite: Favorite,
existing_playlist_favorite: Favorite,
) -> None:
"""Test getting favorite counts."""
# Clean up any existing favorites first
response = await authenticated_client.get("/api/v1/favorites/")
assert response.status_code == 200
existing_data = response.json()
for favorite in existing_data["favorites"]:
if favorite.get("sound_id"):
await authenticated_client.delete(f"/api/v1/favorites/sounds/{favorite['sound_id']}")
elif favorite.get("playlist_id"):
await authenticated_client.delete(f"/api/v1/favorites/playlists/{favorite['playlist_id']}")
# Add test favorites using hardcoded IDs
await authenticated_client.post("/api/v1/favorites/sounds/1")
await authenticated_client.post("/api/v1/favorites/playlists/1")
response = await authenticated_client.get("/api/v1/favorites/counts")
assert response.status_code == 200
data = response.json()
assert data["total"] == 2
assert data["sounds"] == 1
assert data["playlists"] == 1
@pytest.mark.asyncio
async def test_check_sound_favorited_true(
self,
authenticated_client: AsyncClient,
existing_sound_favorite: Favorite,
test_sound: Sound,
) -> None:
"""Test checking if a sound is favorited (true case)."""
# Add a favorite for sound ID 1 using hardcoded ID
await authenticated_client.post("/api/v1/favorites/sounds/1")
response = await authenticated_client.get("/api/v1/favorites/sounds/1/check")
assert response.status_code == 200
data = response.json()
assert data["is_favorited"] is True
@pytest.mark.asyncio
async def test_check_sound_favorited_false(
self,
authenticated_client: AsyncClient,
test_sound: Sound,
) -> None:
"""Test checking if a sound is favorited (false case)."""
# Make sure sound 1 is not favorited
try:
await authenticated_client.delete("/api/v1/favorites/sounds/1")
except:
pass # It's ok if it doesn't exist
response = await authenticated_client.get("/api/v1/favorites/sounds/1/check")
assert response.status_code == 200
data = response.json()
assert data["is_favorited"] is False
@pytest.mark.asyncio
async def test_check_playlist_favorited_true(
self,
authenticated_client: AsyncClient,
existing_playlist_favorite: Favorite,
test_playlist: Playlist,
) -> None:
"""Test checking if a playlist is favorited (true case)."""
# Add a favorite for playlist ID 1 using hardcoded ID
await authenticated_client.post("/api/v1/favorites/playlists/1")
response = await authenticated_client.get("/api/v1/favorites/playlists/1/check")
assert response.status_code == 200
data = response.json()
assert data["is_favorited"] is True
@pytest.mark.asyncio
async def test_check_playlist_favorited_false(
self,
authenticated_client: AsyncClient,
test_playlist: Playlist,
) -> None:
"""Test checking if a playlist is favorited (false case)."""
# Make sure playlist 1 is not favorited
try:
await authenticated_client.delete("/api/v1/favorites/playlists/1")
except:
pass # It's ok if it doesn't exist
response = await authenticated_client.get("/api/v1/favorites/playlists/1/check")
assert response.status_code == 200
data = response.json()
assert data["is_favorited"] is False
@pytest.mark.asyncio
async def test_endpoints_require_authentication(
self,
test_client: AsyncClient,
test_sound: Sound,
test_playlist: Playlist,
) -> None:
"""Test that all endpoints require authentication."""
# Use hardcoded IDs to avoid session context issues
endpoints = [
("GET", "/api/v1/favorites/"),
("GET", "/api/v1/favorites/sounds"),
("GET", "/api/v1/favorites/playlists"),
("GET", "/api/v1/favorites/counts"),
("POST", "/api/v1/favorites/sounds/1"),
("POST", "/api/v1/favorites/playlists/1"),
("DELETE", "/api/v1/favorites/sounds/1"),
("DELETE", "/api/v1/favorites/playlists/1"),
("GET", "/api/v1/favorites/sounds/1/check"),
("GET", "/api/v1/favorites/playlists/1/check"),
]
for method, endpoint in endpoints:
if method == "GET":
response = await test_client.get(endpoint)
elif method == "POST":
response = await test_client.post(endpoint)
elif method == "DELETE":
response = await test_client.delete(endpoint)
assert response.status_code == 401, f"Endpoint {method} {endpoint} should require authentication"
@pytest.mark.asyncio
async def test_query_parameter_validation(
self,
authenticated_client: AsyncClient,
) -> None:
"""Test query parameter validation."""
# Test invalid limit (too small)
response = await authenticated_client.get("/api/v1/favorites/?limit=0")
assert response.status_code == 422
# Test invalid limit (too large)
response = await authenticated_client.get("/api/v1/favorites/?limit=101")
assert response.status_code == 422
# Test invalid offset (negative)
response = await authenticated_client.get("/api/v1/favorites/?offset=-1")
assert response.status_code == 422
# Test valid parameters
response = await authenticated_client.get("/api/v1/favorites/?limit=50&offset=0")
assert response.status_code == 200

View File

@@ -16,8 +16,19 @@ from sqlmodel.ext.asyncio.session import AsyncSession
from app.api import api_router
from app.core.database import get_db
from app.middleware.logging import LoggingMiddleware
# Import all models to ensure SQLAlchemy relationships are properly resolved
from app.models.credit_action import CreditAction # noqa: F401
from app.models.credit_transaction import CreditTransaction # noqa: F401
from app.models.extraction import Extraction # noqa: F401
from app.models.favorite import Favorite # noqa: F401
from app.models.plan import Plan
from app.models.playlist import Playlist # noqa: F401
from app.models.playlist_sound import PlaylistSound # noqa: F401
from app.models.sound import Sound # noqa: F401
from app.models.sound_played import SoundPlayed # noqa: F401
from app.models.user import User
from app.models.user_oauth import UserOauth # noqa: F401
from app.utils.auth import JWTUtils, PasswordUtils
@@ -48,7 +59,7 @@ async def test_engine() -> Any:
@pytest_asyncio.fixture
async def test_session(test_engine: Any) -> AsyncGenerator[AsyncSession, None]:
async def test_session(test_engine: Any) -> AsyncGenerator[AsyncSession]:
"""Create a test database session."""
connection = await test_engine.connect()
transaction = await connection.begin()
@@ -80,7 +91,7 @@ async def test_app(test_session: AsyncSession) -> FastAPI:
app.include_router(api_router)
# Override the database dependency
async def override_get_db() -> AsyncGenerator[AsyncSession, None]:
async def override_get_db() -> AsyncGenerator[AsyncSession]:
yield test_session
app.dependency_overrides[get_db] = override_get_db
@@ -89,7 +100,7 @@ async def test_app(test_session: AsyncSession) -> FastAPI:
@pytest_asyncio.fixture
async def test_client(test_app) -> AsyncGenerator[AsyncClient, None]:
async def test_client(test_app) -> AsyncGenerator[AsyncClient]:
"""Create a test HTTP client."""
async with AsyncClient(
transport=ASGITransport(app=test_app),
@@ -102,7 +113,7 @@ async def test_client(test_app) -> AsyncGenerator[AsyncClient, None]:
async def authenticated_client(
test_app: FastAPI,
auth_cookies: dict[str, str],
) -> AsyncGenerator[AsyncClient, None]:
) -> AsyncGenerator[AsyncClient]:
"""Create a test HTTP client with authentication cookies."""
async with AsyncClient(
transport=ASGITransport(app=test_app),
@@ -116,7 +127,7 @@ async def authenticated_client(
async def authenticated_admin_client(
test_app: FastAPI,
admin_cookies: dict[str, str],
) -> AsyncGenerator[AsyncClient, None]:
) -> AsyncGenerator[AsyncClient]:
"""Create a test HTTP client with admin authentication cookies."""
async with AsyncClient(
transport=ASGITransport(app=test_app),

View File

@@ -32,7 +32,7 @@ class TestCreditTransactionRepository:
async def credit_transaction_repository(
self,
test_session: AsyncSession,
) -> AsyncGenerator[CreditTransactionRepository, None]:
) -> AsyncGenerator[CreditTransactionRepository]:
"""Create a credit transaction repository instance."""
yield CreditTransactionRepository(test_session)
@@ -50,7 +50,7 @@ class TestCreditTransactionRepository:
self,
test_session: AsyncSession,
test_user_id: int,
) -> AsyncGenerator[list[CreditTransaction], None]:
) -> AsyncGenerator[list[CreditTransaction]]:
"""Create test credit transactions."""
transactions = []
user_id = test_user_id
@@ -115,7 +115,7 @@ class TestCreditTransactionRepository:
self,
test_session: AsyncSession,
ensure_plans: tuple[Any, ...],
) -> AsyncGenerator[CreditTransaction, None]:
) -> AsyncGenerator[CreditTransaction]:
"""Create a transaction for a different user."""
# Create another user
user_repo = UserRepository(test_session)

View File

@@ -0,0 +1,653 @@
"""Tests for favorite repository."""
from collections.abc import AsyncGenerator
import pytest
import pytest_asyncio
from sqlmodel.ext.asyncio.session import AsyncSession
from app.models.playlist import Playlist
from app.models.sound import Sound
from app.models.user import User
from app.repositories.favorite import FavoriteRepository
class TestFavoriteRepository:
"""Test favorite repository operations."""
@pytest_asyncio.fixture
async def favorite_repository(
self,
test_session: AsyncSession,
) -> AsyncGenerator[FavoriteRepository]:
"""Create a favorite repository instance."""
yield FavoriteRepository(test_session)
@pytest_asyncio.fixture
async def test_sound(
self,
test_session: AsyncSession,
) -> Sound:
"""Create a test sound."""
sound = Sound(
filename="test_sound.mp3",
name="Test Sound",
type="SDB",
duration=5000,
size=1024000,
hash="abcdef123456789",
)
test_session.add(sound)
await test_session.commit()
await test_session.refresh(sound)
return sound
@pytest_asyncio.fixture
async def test_sound2(
self,
test_session: AsyncSession,
) -> Sound:
"""Create a second test sound."""
sound = Sound(
filename="test_sound2.mp3",
name="Test Sound 2",
type="TTS",
duration=3000,
size=512000,
hash="xyz789456123abc",
)
test_session.add(sound)
await test_session.commit()
await test_session.refresh(sound)
return sound
@pytest_asyncio.fixture
async def test_playlist(
self,
test_session: AsyncSession,
) -> Playlist:
"""Create a test playlist."""
playlist = Playlist(
user_id=1, # Use hardcoded ID
name="Test Playlist",
description="A test playlist",
genre="test",
is_main=False,
is_current=False,
is_deletable=True,
)
test_session.add(playlist)
await test_session.commit()
await test_session.refresh(playlist)
return playlist
@pytest_asyncio.fixture
async def test_playlist2(
self,
test_session: AsyncSession,
) -> Playlist:
"""Create a second test playlist."""
playlist = Playlist(
user_id=1, # Use hardcoded ID
name="Test Playlist 2",
description="Another test playlist",
genre="test2",
is_main=False,
is_current=False,
is_deletable=True,
)
test_session.add(playlist)
await test_session.commit()
await test_session.refresh(playlist)
return playlist
@pytest_asyncio.fixture
async def other_user(
self,
test_session: AsyncSession,
) -> User:
"""Create another test user."""
from app.utils.auth import PasswordUtils
user = User(
email="other@example.com",
name="Other User",
password_hash=PasswordUtils.hash_password("otherpassword123"),
role="user",
is_active=True,
plan_id=1, # Use hardcoded plan ID
credits=100,
)
test_session.add(user)
await test_session.commit()
await test_session.refresh(user)
return user
@pytest.mark.asyncio
async def test_create_sound_favorite(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test creating a sound favorite."""
# Use hardcoded IDs to avoid session issues
favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
favorite = await favorite_repository.create(favorite_data)
assert favorite.id is not None
assert favorite.user_id == 1
assert favorite.sound_id == 1
assert favorite.playlist_id is None
assert favorite.created_at is not None
assert favorite.updated_at is not None
@pytest.mark.asyncio
async def test_create_playlist_favorite(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test creating a playlist favorite."""
favorite_data = {
"user_id": 1,
"sound_id": None,
"playlist_id": 1,
}
favorite = await favorite_repository.create(favorite_data)
assert favorite.id is not None
assert favorite.user_id == 1
assert favorite.sound_id is None
assert favorite.playlist_id == 1
assert favorite.created_at is not None
assert favorite.updated_at is not None
@pytest.mark.asyncio
async def test_get_by_id(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test getting a favorite by ID."""
# Create favorite
favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
created_favorite = await favorite_repository.create(favorite_data)
favorite_id = created_favorite.id
# Get by ID
retrieved_favorite = await favorite_repository.get_by_id(favorite_id)
assert retrieved_favorite is not None
assert retrieved_favorite.id == favorite_id
assert retrieved_favorite.user_id == 1
assert retrieved_favorite.sound_id == 1
@pytest.mark.asyncio
async def test_get_by_id_not_found(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test getting a favorite by non-existent ID."""
result = await favorite_repository.get_by_id(99999)
assert result is None
@pytest.mark.asyncio
async def test_get_user_favorites_empty(
self,
favorite_repository: FavoriteRepository,
test_user: User,
) -> None:
"""Test getting favorites for user with no favorites."""
favorites = await favorite_repository.get_user_favorites(test_user.id)
assert favorites == []
@pytest.mark.asyncio
async def test_get_user_favorites_mixed(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test getting mixed favorites (sounds and playlists)."""
# Create sound favorite
sound_favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
await favorite_repository.create(sound_favorite_data)
# Create playlist favorite
playlist_favorite_data = {
"user_id": 1,
"sound_id": None,
"playlist_id": 1,
}
await favorite_repository.create(playlist_favorite_data)
# Get all favorites
favorites = await favorite_repository.get_user_favorites(1)
assert len(favorites) == 2
# Should be ordered by created_at desc (most recent first)
sound_favorite = next((f for f in favorites if f.sound_id is not None), None)
playlist_favorite = next((f for f in favorites if f.playlist_id is not None), None)
assert sound_favorite is not None
assert playlist_favorite is not None
assert sound_favorite.sound_id == 1
assert playlist_favorite.playlist_id == 1
@pytest.mark.asyncio
async def test_get_user_favorites_pagination(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test pagination for get user favorites."""
# Create multiple favorites
for sound_id in [1, 2]:
favorite_data = {
"user_id": 1,
"sound_id": sound_id,
"playlist_id": None,
}
await favorite_repository.create(favorite_data)
# Test limit
favorites = await favorite_repository.get_user_favorites(1, limit=1)
assert len(favorites) == 1
# Test offset
favorites_page1 = await favorite_repository.get_user_favorites(1, limit=1, offset=0)
favorites_page2 = await favorite_repository.get_user_favorites(1, limit=1, offset=1)
assert len(favorites_page1) == 1
assert len(favorites_page2) == 1
assert favorites_page1[0].id != favorites_page2[0].id
@pytest.mark.asyncio
async def test_get_user_sound_favorites(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test getting only sound favorites."""
# Create sound favorite
sound_favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
await favorite_repository.create(sound_favorite_data)
# Create playlist favorite
playlist_favorite_data = {
"user_id": 1,
"sound_id": None,
"playlist_id": 1,
}
await favorite_repository.create(playlist_favorite_data)
# Get only sound favorites
sound_favorites = await favorite_repository.get_user_sound_favorites(1)
assert len(sound_favorites) == 1
assert sound_favorites[0].sound_id == 1
assert sound_favorites[0].playlist_id is None
@pytest.mark.asyncio
async def test_get_user_playlist_favorites(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test getting only playlist favorites."""
# Create sound favorite
sound_favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
await favorite_repository.create(sound_favorite_data)
# Create playlist favorite
playlist_favorite_data = {
"user_id": 1,
"sound_id": None,
"playlist_id": 1,
}
await favorite_repository.create(playlist_favorite_data)
# Get only playlist favorites
playlist_favorites = await favorite_repository.get_user_playlist_favorites(1)
assert len(playlist_favorites) == 1
assert playlist_favorites[0].playlist_id == 1
assert playlist_favorites[0].sound_id is None
@pytest.mark.asyncio
async def test_get_by_user_and_sound_found(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test getting favorite by user and sound when it exists."""
# Create favorite
favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
created_favorite = await favorite_repository.create(favorite_data)
favorite_id = created_favorite.id
# Find by user and sound
found_favorite = await favorite_repository.get_by_user_and_sound(1, 1)
assert found_favorite is not None
assert found_favorite.id == favorite_id
assert found_favorite.user_id == 1
assert found_favorite.sound_id == 1
@pytest.mark.asyncio
async def test_get_by_user_and_sound_not_found(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test getting favorite by user and sound when it doesn't exist."""
result = await favorite_repository.get_by_user_and_sound(1, 1)
assert result is None
@pytest.mark.asyncio
async def test_get_by_user_and_playlist_found(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test getting favorite by user and playlist when it exists."""
# Create favorite
favorite_data = {
"user_id": 1,
"sound_id": None,
"playlist_id": 1,
}
created_favorite = await favorite_repository.create(favorite_data)
favorite_id = created_favorite.id
# Find by user and playlist
found_favorite = await favorite_repository.get_by_user_and_playlist(1, 1)
assert found_favorite is not None
assert found_favorite.id == favorite_id
assert found_favorite.user_id == 1
assert found_favorite.playlist_id == 1
@pytest.mark.asyncio
async def test_get_by_user_and_playlist_not_found(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test getting favorite by user and playlist when it doesn't exist."""
result = await favorite_repository.get_by_user_and_playlist(1, 1)
assert result is None
@pytest.mark.asyncio
async def test_is_sound_favorited_true(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test checking if sound is favorited when it is."""
# Create favorite
favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
await favorite_repository.create(favorite_data)
result = await favorite_repository.is_sound_favorited(1, 1)
assert result is True
@pytest.mark.asyncio
async def test_is_sound_favorited_false(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test checking if sound is favorited when it's not."""
result = await favorite_repository.is_sound_favorited(1, 1)
assert result is False
@pytest.mark.asyncio
async def test_is_playlist_favorited_true(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test checking if playlist is favorited when it is."""
# Create favorite
favorite_data = {
"user_id": 1,
"sound_id": None,
"playlist_id": 1,
}
await favorite_repository.create(favorite_data)
result = await favorite_repository.is_playlist_favorited(1, 1)
assert result is True
@pytest.mark.asyncio
async def test_is_playlist_favorited_false(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test checking if playlist is favorited when it's not."""
result = await favorite_repository.is_playlist_favorited(1, 1)
assert result is False
@pytest.mark.asyncio
async def test_count_user_favorites(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test counting user favorites."""
# Initially no favorites
count = await favorite_repository.count_user_favorites(1)
assert count == 0
# Create sound favorite
sound_favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
await favorite_repository.create(sound_favorite_data)
count = await favorite_repository.count_user_favorites(1)
assert count == 1
# Create playlist favorite
playlist_favorite_data = {
"user_id": 1,
"sound_id": None,
"playlist_id": 1,
}
await favorite_repository.create(playlist_favorite_data)
count = await favorite_repository.count_user_favorites(1)
assert count == 2
@pytest.mark.asyncio
async def test_count_sound_favorites(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test counting favorites for a sound."""
# Initially no favorites
count = await favorite_repository.count_sound_favorites(1)
assert count == 0
# Create favorite by first user
favorite_data1 = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
await favorite_repository.create(favorite_data1)
count = await favorite_repository.count_sound_favorites(1)
assert count == 1
# Create favorite by second user
favorite_data2 = {
"user_id": 2,
"sound_id": 1,
"playlist_id": None,
}
await favorite_repository.create(favorite_data2)
count = await favorite_repository.count_sound_favorites(1)
assert count == 2
@pytest.mark.asyncio
async def test_count_playlist_favorites(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test counting favorites for a playlist."""
# Initially no favorites
count = await favorite_repository.count_playlist_favorites(1)
assert count == 0
# Create favorite by first user
favorite_data1 = {
"user_id": 1,
"sound_id": None,
"playlist_id": 1,
}
await favorite_repository.create(favorite_data1)
count = await favorite_repository.count_playlist_favorites(1)
assert count == 1
# Create favorite by second user
favorite_data2 = {
"user_id": 2,
"sound_id": None,
"playlist_id": 1,
}
await favorite_repository.create(favorite_data2)
count = await favorite_repository.count_playlist_favorites(1)
assert count == 2
@pytest.mark.asyncio
async def test_update_favorite(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test updating a favorite."""
# Create favorite
favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
created_favorite = await favorite_repository.create(favorite_data)
favorite_id = created_favorite.id
original_updated_at = created_favorite.updated_at
# Update (note: there's not much to update in a favorite, but we can test the mechanism)
updated_favorite = await favorite_repository.update(created_favorite, {})
assert updated_favorite.id == favorite_id
assert updated_favorite.updated_at >= original_updated_at
@pytest.mark.asyncio
async def test_delete_favorite(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test deleting a favorite."""
# Create favorite
favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
created_favorite = await favorite_repository.create(favorite_data)
favorite_id = created_favorite.id
# Verify it exists
found_favorite = await favorite_repository.get_by_id(favorite_id)
assert found_favorite is not None
# Delete it
await favorite_repository.delete(created_favorite)
# Verify it's gone
deleted_favorite = await favorite_repository.get_by_id(favorite_id)
assert deleted_favorite is None
@pytest.mark.asyncio
async def test_unique_constraints(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test that unique constraints are enforced."""
# Create sound favorite
sound_favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
await favorite_repository.create(sound_favorite_data)
# Try to create duplicate sound favorite - should raise exception
from sqlalchemy.exc import IntegrityError
with pytest.raises(IntegrityError):
await favorite_repository.create(sound_favorite_data)
# Create playlist favorite
playlist_favorite_data = {
"user_id": 1,
"sound_id": None,
"playlist_id": 1,
}
await favorite_repository.create(playlist_favorite_data)
# Try to create duplicate playlist favorite - should raise exception
with pytest.raises(IntegrityError):
await favorite_repository.create(playlist_favorite_data)
@pytest.mark.asyncio
async def test_user_isolation(
self,
favorite_repository: FavoriteRepository,
) -> None:
"""Test that favorites are properly isolated by user."""
# Clean up any existing favorites for user 1 first
existing_favorites = await favorite_repository.get_user_favorites(1)
for favorite in existing_favorites:
await favorite_repository.delete(favorite)
# Create favorite for user 1
favorite_data = {
"user_id": 1,
"sound_id": 1,
"playlist_id": None,
}
await favorite_repository.create(favorite_data)
# Check that user 1 has the favorite
user1_favorites = await favorite_repository.get_user_favorites(1)
assert len(user1_favorites) == 1
# Check that user 2 doesn't have any favorites
user2_favorites = await favorite_repository.get_user_favorites(2)
assert len(user2_favorites) == 0
# Check that the sound is favorited by user 1 but not user 2
assert await favorite_repository.is_sound_favorited(1, 1) is True
assert await favorite_repository.is_sound_favorited(2, 1) is False

View File

@@ -30,7 +30,7 @@ class TestPlaylistRepository:
async def playlist_repository(
self,
test_session: AsyncSession,
) -> AsyncGenerator[PlaylistRepository, None]:
) -> AsyncGenerator[PlaylistRepository]:
"""Create a playlist repository instance."""
yield PlaylistRepository(test_session)

View File

@@ -21,7 +21,7 @@ class TestSoundRepository:
async def sound_repository(
self,
test_session: AsyncSession,
) -> AsyncGenerator[SoundRepository, None]:
) -> AsyncGenerator[SoundRepository]:
"""Create a sound repository instance."""
yield SoundRepository(test_session)
@@ -29,7 +29,7 @@ class TestSoundRepository:
async def test_sound(
self,
test_session: AsyncSession,
) -> AsyncGenerator[Sound, None]:
) -> AsyncGenerator[Sound]:
"""Create a test sound."""
sound_data = {
"name": "Test Sound",
@@ -51,7 +51,7 @@ class TestSoundRepository:
async def normalized_sound(
self,
test_session: AsyncSession,
) -> AsyncGenerator[Sound, None]:
) -> AsyncGenerator[Sound]:
"""Create a normalized test sound."""
sound_data = {
"name": "Normalized Sound",

View File

@@ -20,7 +20,7 @@ class TestUserRepository:
async def user_repository(
self,
test_session: AsyncSession,
) -> AsyncGenerator[UserRepository, None]: # type: ignore[misc]
) -> AsyncGenerator[UserRepository]: # type: ignore[misc]
"""Create a user repository instance."""
yield UserRepository(test_session)
@@ -209,6 +209,17 @@ class TestUserRepository:
assert user.id is not None
user_id = user.id
# Clean up any favorites that might reference this specific user to avoid foreign key constraint violations
from sqlmodel import select
from app.models.favorite import Favorite
# Only delete favorites for the user we're about to delete, not all favorites
existing_favorites = await test_session.exec(select(Favorite).where(Favorite.user_id == user_id))
for favorite in existing_favorites.all():
await test_session.delete(favorite)
await test_session.commit()
# Delete the user
await user_repository.delete(user)

View File

@@ -19,7 +19,7 @@ class TestUserOauthRepository:
async def user_oauth_repository(
self,
test_session: AsyncSession,
) -> AsyncGenerator[UserOauthRepository, None]:
) -> AsyncGenerator[UserOauthRepository]:
"""Create a user OAuth repository instance."""
yield UserOauthRepository(test_session)
@@ -37,7 +37,7 @@ class TestUserOauthRepository:
self,
test_session: AsyncSession,
test_user_id: int,
) -> AsyncGenerator[UserOauth, None]:
) -> AsyncGenerator[UserOauth]:
"""Create a test OAuth record."""
oauth_data = {
"user_id": test_user_id,

View File

@@ -0,0 +1,548 @@
"""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()

View File

@@ -20,7 +20,7 @@ class TestPlaylistService:
async def playlist_service(
self,
test_session: AsyncSession,
) -> AsyncGenerator[PlaylistService, None]:
) -> AsyncGenerator[PlaylistService]:
"""Create a playlist service instance."""
yield PlaylistService(test_session)