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