|
|
@@ -0,0 +1,304 @@
|
|
|
+"""
|
|
|
+Unit tests for playlist API endpoints.
|
|
|
+
|
|
|
+Tests the following endpoints:
|
|
|
+- GET /list_all_playlists
|
|
|
+- GET /get_playlist
|
|
|
+- POST /create_playlist
|
|
|
+- POST /modify_playlist
|
|
|
+- DELETE /delete_playlist
|
|
|
+- POST /rename_playlist
|
|
|
+- POST /add_to_playlist
|
|
|
+- POST /run_playlist (when disconnected)
|
|
|
+"""
|
|
|
+import pytest
|
|
|
+from unittest.mock import patch, MagicMock, AsyncMock
|
|
|
+
|
|
|
+
|
|
|
+class TestListAllPlaylists:
|
|
|
+ """Tests for /list_all_playlists endpoint."""
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_list_all_playlists(self, async_client):
|
|
|
+ """Test list_all_playlists returns list of playlist names."""
|
|
|
+ mock_playlists = ["favorites", "evening", "morning"]
|
|
|
+
|
|
|
+ with patch("main.playlist_manager.list_all_playlists", return_value=mock_playlists):
|
|
|
+ response = await async_client.get("/list_all_playlists")
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert isinstance(data, list)
|
|
|
+ assert len(data) == 3
|
|
|
+ assert "favorites" in data
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_list_all_playlists_empty(self, async_client):
|
|
|
+ """Test list_all_playlists returns empty list when no playlists."""
|
|
|
+ with patch("main.playlist_manager.list_all_playlists", return_value=[]):
|
|
|
+ response = await async_client.get("/list_all_playlists")
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert data == []
|
|
|
+
|
|
|
+
|
|
|
+class TestGetPlaylist:
|
|
|
+ """Tests for /get_playlist endpoint."""
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_get_playlist_exists(self, async_client):
|
|
|
+ """Test get_playlist returns playlist data."""
|
|
|
+ mock_playlist = {
|
|
|
+ "name": "favorites",
|
|
|
+ "files": ["circle.thr", "spiral.thr"]
|
|
|
+ }
|
|
|
+
|
|
|
+ with patch("main.playlist_manager.get_playlist", return_value=mock_playlist):
|
|
|
+ response = await async_client.get("/get_playlist", params={"name": "favorites"})
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert data["name"] == "favorites"
|
|
|
+ assert data["files"] == ["circle.thr", "spiral.thr"]
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_get_playlist_creates_empty_if_not_found(self, async_client):
|
|
|
+ """Test get_playlist auto-creates empty playlist if not found.
|
|
|
+
|
|
|
+ Note: This is the actual behavior - the endpoint auto-creates empty playlists.
|
|
|
+ """
|
|
|
+ with patch("main.playlist_manager.get_playlist", return_value=None):
|
|
|
+ with patch("main.playlist_manager.create_playlist", return_value=True):
|
|
|
+ response = await async_client.get("/get_playlist", params={"name": "nonexistent"})
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert data["name"] == "nonexistent"
|
|
|
+ assert data["files"] == []
|
|
|
+
|
|
|
+
|
|
|
+class TestCreatePlaylist:
|
|
|
+ """Tests for /create_playlist endpoint."""
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_create_playlist(self, async_client):
|
|
|
+ """Test creating a new playlist."""
|
|
|
+ with patch("main.playlist_manager.create_playlist", return_value=True):
|
|
|
+ response = await async_client.post(
|
|
|
+ "/create_playlist",
|
|
|
+ json={
|
|
|
+ "playlist_name": "new_playlist", # API uses playlist_name, not name
|
|
|
+ "files": ["circle.thr", "spiral.thr"]
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert data["success"] is True
|
|
|
+
|
|
|
+
|
|
|
+class TestModifyPlaylist:
|
|
|
+ """Tests for /modify_playlist endpoint."""
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_modify_playlist(self, async_client):
|
|
|
+ """Test modifying an existing playlist."""
|
|
|
+ with patch("main.playlist_manager.modify_playlist", return_value=True):
|
|
|
+ response = await async_client.post(
|
|
|
+ "/modify_playlist",
|
|
|
+ json={
|
|
|
+ "playlist_name": "favorites", # API uses playlist_name
|
|
|
+ "files": ["new_pattern.thr"]
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert data["success"] is True
|
|
|
+
|
|
|
+
|
|
|
+class TestDeletePlaylist:
|
|
|
+ """Tests for /delete_playlist endpoint."""
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_delete_playlist(self, async_client):
|
|
|
+ """Test deleting a playlist."""
|
|
|
+ with patch("main.playlist_manager.delete_playlist", return_value=True):
|
|
|
+ response = await async_client.request(
|
|
|
+ "DELETE",
|
|
|
+ "/delete_playlist",
|
|
|
+ json={"playlist_name": "to_delete"} # DELETE with body
|
|
|
+ )
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert data["success"] is True
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_delete_playlist_not_found(self, async_client):
|
|
|
+ """Test deleting a non-existent playlist returns 404."""
|
|
|
+ with patch("main.playlist_manager.delete_playlist", return_value=False):
|
|
|
+ response = await async_client.request(
|
|
|
+ "DELETE",
|
|
|
+ "/delete_playlist",
|
|
|
+ json={"playlist_name": "nonexistent"}
|
|
|
+ )
|
|
|
+
|
|
|
+ assert response.status_code == 404
|
|
|
+
|
|
|
+
|
|
|
+class TestRenamePlaylist:
|
|
|
+ """Tests for /rename_playlist endpoint."""
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_rename_playlist_success(self, async_client):
|
|
|
+ """Test renaming a playlist."""
|
|
|
+ with patch("main.playlist_manager.rename_playlist", return_value=(True, "Renamed")):
|
|
|
+ response = await async_client.post(
|
|
|
+ "/rename_playlist",
|
|
|
+ json={
|
|
|
+ "old_name": "old_playlist",
|
|
|
+ "new_name": "new_playlist"
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert data["success"] is True
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_rename_playlist_not_found(self, async_client):
|
|
|
+ """Test renaming a non-existent playlist fails."""
|
|
|
+ with patch("main.playlist_manager.rename_playlist", return_value=(False, "Playlist not found")):
|
|
|
+ response = await async_client.post(
|
|
|
+ "/rename_playlist",
|
|
|
+ json={
|
|
|
+ "old_name": "nonexistent",
|
|
|
+ "new_name": "new_name"
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ # Returns 400 with message (not 404)
|
|
|
+ assert response.status_code == 400
|
|
|
+
|
|
|
+
|
|
|
+class TestAddToPlaylist:
|
|
|
+ """Tests for /add_to_playlist endpoint."""
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_add_to_playlist(self, async_client):
|
|
|
+ """Test adding a pattern to a playlist."""
|
|
|
+ with patch("main.playlist_manager.add_to_playlist", return_value=True):
|
|
|
+ response = await async_client.post(
|
|
|
+ "/add_to_playlist",
|
|
|
+ json={
|
|
|
+ "playlist_name": "favorites", # API uses playlist_name
|
|
|
+ "pattern": "new_pattern.thr" # API uses pattern, not file
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert data["success"] is True
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_add_to_playlist_not_found(self, async_client):
|
|
|
+ """Test adding to a non-existent playlist fails."""
|
|
|
+ with patch("main.playlist_manager.add_to_playlist", return_value=False):
|
|
|
+ response = await async_client.post(
|
|
|
+ "/add_to_playlist",
|
|
|
+ json={
|
|
|
+ "playlist_name": "nonexistent",
|
|
|
+ "pattern": "pattern.thr"
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ assert response.status_code == 404
|
|
|
+
|
|
|
+
|
|
|
+class TestRunPlaylist:
|
|
|
+ """Tests for /run_playlist endpoint."""
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_run_playlist_when_disconnected(self, async_client, mock_state):
|
|
|
+ """Test run_playlist fails when not connected.
|
|
|
+
|
|
|
+ Note: The endpoint catches HTTPException and re-raises as 500.
|
|
|
+ """
|
|
|
+ mock_state.conn = None
|
|
|
+ mock_state.is_homing = False
|
|
|
+
|
|
|
+ with patch("main.state", mock_state):
|
|
|
+ response = await async_client.post(
|
|
|
+ "/run_playlist",
|
|
|
+ json={
|
|
|
+ "playlist_name": "test",
|
|
|
+ "pause_time": 5,
|
|
|
+ "clear_pattern": None,
|
|
|
+ "run_mode": "single"
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ # Endpoint wraps in try/except and returns 500
|
|
|
+ assert response.status_code == 500
|
|
|
+ data = response.json()
|
|
|
+ assert "not established" in data["detail"].lower()
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_run_playlist_during_homing(self, async_client, mock_state):
|
|
|
+ """Test run_playlist fails during homing.
|
|
|
+
|
|
|
+ Note: The endpoint catches HTTPException and re-raises as 500.
|
|
|
+ """
|
|
|
+ mock_state.is_homing = True
|
|
|
+ mock_state.conn = MagicMock()
|
|
|
+ mock_state.conn.is_connected.return_value = True
|
|
|
+
|
|
|
+ with patch("main.state", mock_state):
|
|
|
+ response = await async_client.post(
|
|
|
+ "/run_playlist",
|
|
|
+ json={
|
|
|
+ "playlist_name": "test",
|
|
|
+ "pause_time": 5,
|
|
|
+ "clear_pattern": None,
|
|
|
+ "run_mode": "single"
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ # Endpoint wraps in try/except and returns 500
|
|
|
+ assert response.status_code == 500
|
|
|
+ data = response.json()
|
|
|
+ assert "homing" in data["detail"].lower()
|
|
|
+
|
|
|
+
|
|
|
+class TestSkipPattern:
|
|
|
+ """Tests for /skip_pattern endpoint."""
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_skip_pattern(self, async_client, mock_state):
|
|
|
+ """Test skip_pattern during playlist execution."""
|
|
|
+ mock_state.current_playlist = ["a.thr", "b.thr"]
|
|
|
+ mock_state.current_playlist_index = 0
|
|
|
+ mock_state.skip_requested = False
|
|
|
+
|
|
|
+ with patch("main.state", mock_state):
|
|
|
+ response = await async_client.post("/skip_pattern")
|
|
|
+
|
|
|
+ assert response.status_code == 200
|
|
|
+ data = response.json()
|
|
|
+ assert data["success"] is True
|
|
|
+ # Endpoint sets skip_requested directly
|
|
|
+ assert mock_state.skip_requested is True
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ async def test_skip_pattern_no_playlist(self, async_client, mock_state):
|
|
|
+ """Test skip_pattern fails when no playlist is running."""
|
|
|
+ mock_state.current_playlist = None
|
|
|
+
|
|
|
+ with patch("main.state", mock_state):
|
|
|
+ response = await async_client.post("/skip_pattern")
|
|
|
+
|
|
|
+ assert response.status_code == 400
|
|
|
+ data = response.json()
|
|
|
+ assert "no playlist" in data["detail"].lower()
|