Add comprehensive tests for player API endpoints and player service functionality
- Implemented tests for player API endpoints including play, pause, stop, next, previous, seek, set volume, set mode, reload playlist, and get state. - Added mock player service for testing API interactions. - Created tests for player service methods including play, pause, stop playback, next, previous, seek, set volume, set mode, and reload playlist. - Ensured proper handling of success, error, and edge cases in both API and service tests. - Verified state management and serialization in player state tests.
This commit is contained in:
228
app/api/v1/player.py
Normal file
228
app/api/v1/player.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""Player API endpoints."""
|
||||
|
||||
from typing import Annotated, Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.core.dependencies import get_current_active_user_flexible
|
||||
from app.core.logging import get_logger
|
||||
from app.models.user import User
|
||||
from app.services.player import PlayerMode, get_player_service
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/player", tags=["player"])
|
||||
|
||||
|
||||
class SeekRequest(BaseModel):
|
||||
"""Request model for seek operation."""
|
||||
|
||||
position_ms: int = Field(ge=0, description="Position in milliseconds")
|
||||
|
||||
|
||||
class VolumeRequest(BaseModel):
|
||||
"""Request model for volume control."""
|
||||
|
||||
volume: int = Field(ge=0, le=100, description="Volume level (0-100)")
|
||||
|
||||
|
||||
class ModeRequest(BaseModel):
|
||||
"""Request model for mode change."""
|
||||
|
||||
mode: PlayerMode = Field(description="Playback mode")
|
||||
|
||||
|
||||
@router.post("/play")
|
||||
async def play(
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Play current sound."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.play()
|
||||
return {"message": "Playback started"}
|
||||
except Exception as e:
|
||||
logger.exception("Error starting playback")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to start playback",
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/play/{index}")
|
||||
async def play_at_index(
|
||||
index: int,
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Play sound at specific index."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.play(index)
|
||||
return {"message": f"Playing sound at index {index}"}
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e),
|
||||
) from e
|
||||
except Exception as e:
|
||||
logger.exception("Error playing sound at index %s", index)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to play sound",
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/pause")
|
||||
async def pause(
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Pause playback."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.pause()
|
||||
return {"message": "Playback paused"}
|
||||
except Exception as e:
|
||||
logger.exception("Error pausing playback")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to pause playback",
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/stop")
|
||||
async def stop(
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Stop playback."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.stop_playback()
|
||||
return {"message": "Playback stopped"}
|
||||
except Exception as e:
|
||||
logger.exception("Error stopping playback")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to stop playback",
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/next")
|
||||
async def next_track(
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Skip to next track."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.next()
|
||||
return {"message": "Skipped to next track"}
|
||||
except Exception as e:
|
||||
logger.exception("Error skipping to next track")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to skip to next track",
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/previous")
|
||||
async def previous_track(
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Go to previous track."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.previous()
|
||||
return {"message": "Went to previous track"}
|
||||
except Exception as e:
|
||||
logger.exception("Error going to previous track")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to go to previous track",
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/seek")
|
||||
async def seek(
|
||||
request: SeekRequest,
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Seek to specific position in current track."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.seek(request.position_ms)
|
||||
return {"message": f"Seeked to position {request.position_ms}ms"}
|
||||
except Exception as e:
|
||||
logger.exception("Error seeking to position %s", request.position_ms)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to seek",
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/volume")
|
||||
async def set_volume(
|
||||
request: VolumeRequest,
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Set playback volume."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.set_volume(request.volume)
|
||||
return {"message": f"Volume set to {request.volume}"}
|
||||
except Exception as e:
|
||||
logger.exception("Error setting volume to %s", request.volume)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to set volume",
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/mode")
|
||||
async def set_mode(
|
||||
request: ModeRequest,
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Set playback mode."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.set_mode(request.mode)
|
||||
return {"message": f"Mode set to {request.mode.value}"}
|
||||
except Exception as e:
|
||||
logger.exception("Error setting mode to %s", request.mode)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to set mode",
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/reload-playlist")
|
||||
async def reload_playlist(
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, str]:
|
||||
"""Reload current playlist."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
await player.reload_playlist()
|
||||
return {"message": "Playlist reloaded"}
|
||||
except Exception as e:
|
||||
logger.exception("Error reloading playlist")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to reload playlist",
|
||||
) from e
|
||||
|
||||
|
||||
@router.get("/state")
|
||||
async def get_state(
|
||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
|
||||
) -> dict[str, Any]:
|
||||
"""Get current player state."""
|
||||
try:
|
||||
player = get_player_service()
|
||||
return player.get_state()
|
||||
except Exception as e:
|
||||
logger.exception("Error getting player state")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get player state",
|
||||
) from e
|
||||
Reference in New Issue
Block a user