Files
sdb2-backend/tests/api/v1/test_favorite_endpoints.py
JSC 821093f64f
Some checks failed
Backend CI / lint (push) Failing after 4m51s
Backend CI / test (push) Failing after 4m35s
Refactor code structure for improved readability and maintainability
2025-08-20 11:37:28 +02:00

577 lines
21 KiB
Python

"""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