661 lines
22 KiB
Python
661 lines
22 KiB
Python
"""Tests for player API endpoints."""
|
|
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
|
|
from app.models.user import User
|
|
from app.services.player import PlayerMode, PlayerStatus
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_player_service():
|
|
"""Mock player service for testing."""
|
|
with patch("app.api.v1.player.get_player_service") as mock:
|
|
service = Mock()
|
|
service.play = AsyncMock()
|
|
service.pause = AsyncMock()
|
|
service.stop_playback = AsyncMock()
|
|
service.next = AsyncMock()
|
|
service.previous = AsyncMock()
|
|
service.seek = AsyncMock()
|
|
service.set_volume = AsyncMock()
|
|
service.set_mode = AsyncMock()
|
|
service.reload_playlist = AsyncMock()
|
|
service.get_state = Mock() # This should return a dict, not a coroutine
|
|
mock.return_value = service
|
|
yield service
|
|
|
|
|
|
class TestPlayerEndpoints:
|
|
"""Test player API endpoints."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_play_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test starting playback successfully."""
|
|
response = await authenticated_client.post("/api/v1/player/play")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Playback started"
|
|
|
|
mock_player_service.play.assert_called_once_with()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_play_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test starting playback without authentication."""
|
|
response = await client.post("/api/v1/player/play")
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_play_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test starting playback with service error."""
|
|
mock_player_service.play.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post("/api/v1/player/play")
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to start playback"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_play_at_index_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test playing sound at specific index successfully."""
|
|
index = 2
|
|
response = await authenticated_client.post(f"/api/v1/player/play/{index}")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == f"Playing sound at index {index}"
|
|
|
|
mock_player_service.play.assert_called_once_with(index)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_play_at_index_invalid_index(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test playing sound with invalid index."""
|
|
mock_player_service.play.side_effect = ValueError("Invalid sound index")
|
|
|
|
response = await authenticated_client.post("/api/v1/player/play/999")
|
|
|
|
assert response.status_code == 400
|
|
data = response.json()
|
|
assert data["detail"] == "Invalid sound index"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_play_at_index_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test playing sound at index with service error."""
|
|
mock_player_service.play.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post("/api/v1/player/play/0")
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to play sound"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_pause_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test pausing playback successfully."""
|
|
response = await authenticated_client.post("/api/v1/player/pause")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Playback paused"
|
|
|
|
mock_player_service.pause.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_pause_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test pausing playback without authentication."""
|
|
response = await client.post("/api/v1/player/pause")
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_pause_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test pausing playback with service error."""
|
|
mock_player_service.pause.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post("/api/v1/player/pause")
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to pause playback"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stop_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test stopping playback successfully."""
|
|
response = await authenticated_client.post("/api/v1/player/stop")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Playback stopped"
|
|
|
|
mock_player_service.stop_playback.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stop_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test stopping playback without authentication."""
|
|
response = await client.post("/api/v1/player/stop")
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stop_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test stopping playback with service error."""
|
|
mock_player_service.stop_playback.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post("/api/v1/player/stop")
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to stop playback"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_next_track_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test skipping to next track successfully."""
|
|
response = await authenticated_client.post("/api/v1/player/next")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Skipped to next track"
|
|
|
|
mock_player_service.next.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_next_track_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test skipping to next track without authentication."""
|
|
response = await client.post("/api/v1/player/next")
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_next_track_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test skipping to next track with service error."""
|
|
mock_player_service.next.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post("/api/v1/player/next")
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to skip to next track"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_previous_track_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test going to previous track successfully."""
|
|
response = await authenticated_client.post("/api/v1/player/previous")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Went to previous track"
|
|
|
|
mock_player_service.previous.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_previous_track_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test going to previous track without authentication."""
|
|
response = await client.post("/api/v1/player/previous")
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_previous_track_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test going to previous track with service error."""
|
|
mock_player_service.previous.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post("/api/v1/player/previous")
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to go to previous track"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_seek_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test seeking to position successfully."""
|
|
position = 5000
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/seek",
|
|
json={"position": position},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == f"Seeked to position {position}ms"
|
|
|
|
mock_player_service.seek.assert_called_once_with(position)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_seek_invalid_position(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test seeking with invalid position."""
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/seek",
|
|
json={"position": -1000}, # Negative position
|
|
)
|
|
|
|
assert response.status_code == 422 # Validation error
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_seek_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test seeking without authentication."""
|
|
response = await client.post(
|
|
"/api/v1/player/seek",
|
|
json={"position": 5000},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_seek_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test seeking with service error."""
|
|
mock_player_service.seek.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/seek",
|
|
json={"position": 5000},
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to seek"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_volume_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test setting volume successfully."""
|
|
volume = 75
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/volume",
|
|
json={"volume": volume},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == f"Volume set to {volume}"
|
|
|
|
mock_player_service.set_volume.assert_called_once_with(volume)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_volume_invalid_range(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test setting volume with invalid range."""
|
|
# Test volume too high
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/volume",
|
|
json={"volume": 150},
|
|
)
|
|
assert response.status_code == 422
|
|
|
|
# Test volume too low
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/volume",
|
|
json={"volume": -10},
|
|
)
|
|
assert response.status_code == 422
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_volume_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test setting volume without authentication."""
|
|
response = await client.post(
|
|
"/api/v1/player/volume",
|
|
json={"volume": 50},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_volume_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test setting volume with service error."""
|
|
mock_player_service.set_volume.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/volume",
|
|
json={"volume": 75},
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to set volume"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_mode_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test setting playback mode successfully."""
|
|
mode = PlayerMode.LOOP
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/mode",
|
|
json={"mode": mode.value},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == f"Mode set to {mode.value}"
|
|
|
|
mock_player_service.set_mode.assert_called_once_with(mode)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_mode_invalid_mode(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test setting invalid playback mode."""
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/mode",
|
|
json={"mode": "invalid_mode"},
|
|
)
|
|
|
|
assert response.status_code == 422 # Validation error
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_mode_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test setting mode without authentication."""
|
|
response = await client.post(
|
|
"/api/v1/player/mode",
|
|
json={"mode": "loop"},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_mode_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test setting mode with service error."""
|
|
mock_player_service.set_mode.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/mode",
|
|
json={"mode": "loop"},
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to set mode"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_reload_playlist_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test reloading playlist successfully."""
|
|
response = await authenticated_client.post("/api/v1/player/reload-playlist")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Playlist reloaded"
|
|
|
|
mock_player_service.reload_playlist.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_reload_playlist_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test reloading playlist without authentication."""
|
|
response = await client.post("/api/v1/player/reload-playlist")
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_reload_playlist_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test reloading playlist with service error."""
|
|
mock_player_service.reload_playlist.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.post("/api/v1/player/reload-playlist")
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to reload playlist"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_state_success(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test getting player state successfully."""
|
|
mock_state = {
|
|
"status": PlayerStatus.PLAYING.value,
|
|
"mode": PlayerMode.CONTINUOUS.value,
|
|
"volume": 50,
|
|
"previous_volume": 80,
|
|
"position": 5000,
|
|
"duration": 30000,
|
|
"index": 0,
|
|
"current_sound": {
|
|
"id": 1,
|
|
"name": "Test Song",
|
|
"filename": "test.mp3",
|
|
"duration": 30000,
|
|
"size": 1024,
|
|
"type": "SDB",
|
|
"thumbnail": None,
|
|
"play_count": 0,
|
|
},
|
|
"playlist": {
|
|
"id": 1,
|
|
"name": "Test Playlist",
|
|
"length": 1,
|
|
"duration": 30000,
|
|
"sounds": [],
|
|
},
|
|
}
|
|
mock_player_service.get_state.return_value = mock_state
|
|
|
|
response = await authenticated_client.get("/api/v1/player/state")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data == mock_state
|
|
|
|
mock_player_service.get_state.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_state_unauthenticated(self, client: AsyncClient) -> None:
|
|
"""Test getting player state without authentication."""
|
|
response = await client.get("/api/v1/player/state")
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_state_service_error(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test getting player state with service error."""
|
|
mock_player_service.get_state.side_effect = Exception("Service error")
|
|
|
|
response = await authenticated_client.get("/api/v1/player/state")
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"] == "Failed to get player state"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_seek_missing_body(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
) -> None:
|
|
"""Test seeking without request body."""
|
|
response = await authenticated_client.post("/api/v1/player/seek")
|
|
assert response.status_code == 422
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_volume_missing_body(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
) -> None:
|
|
"""Test setting volume without request body."""
|
|
response = await authenticated_client.post("/api/v1/player/volume")
|
|
assert response.status_code == 422
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mode_missing_body(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
) -> None:
|
|
"""Test setting mode without request body."""
|
|
response = await authenticated_client.post("/api/v1/player/mode")
|
|
assert response.status_code == 422
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_play_at_index_negative_index(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test playing sound with negative index."""
|
|
mock_player_service.play.side_effect = ValueError("Invalid sound index")
|
|
|
|
response = await authenticated_client.post("/api/v1/player/play/-1")
|
|
|
|
assert response.status_code == 400
|
|
data = response.json()
|
|
assert data["detail"] == "Invalid sound index"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_seek_zero_position(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test seeking to position zero."""
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/seek",
|
|
json={"position": 0},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Seeked to position 0ms"
|
|
|
|
mock_player_service.seek.assert_called_once_with(0)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_volume_boundary_values(
|
|
self,
|
|
authenticated_client: AsyncClient,
|
|
authenticated_user: User,
|
|
mock_player_service,
|
|
) -> None:
|
|
"""Test setting volume with boundary values."""
|
|
# Test minimum volume
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/volume",
|
|
json={"volume": 0},
|
|
)
|
|
assert response.status_code == 200
|
|
mock_player_service.set_volume.assert_called_with(0)
|
|
|
|
# Test maximum volume
|
|
response = await authenticated_client.post(
|
|
"/api/v1/player/volume",
|
|
json={"volume": 100},
|
|
)
|
|
assert response.status_code == 200
|
|
mock_player_service.set_volume.assert_called_with(100)
|