1
0

test_api_playlists.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. """
  2. Unit tests for playlist API endpoints.
  3. Tests the following endpoints:
  4. - GET /list_all_playlists
  5. - GET /get_playlist
  6. - POST /create_playlist
  7. - POST /modify_playlist
  8. - DELETE /delete_playlist
  9. - POST /rename_playlist
  10. - POST /add_to_playlist
  11. - POST /run_playlist (when disconnected)
  12. """
  13. import pytest
  14. from unittest.mock import patch, MagicMock, AsyncMock
  15. class TestListAllPlaylists:
  16. """Tests for /list_all_playlists endpoint."""
  17. @pytest.mark.asyncio
  18. async def test_list_all_playlists(self, async_client):
  19. """Test list_all_playlists returns list of playlist names."""
  20. mock_playlists = ["favorites", "evening", "morning"]
  21. with patch("main.playlist_manager.list_all_playlists", return_value=mock_playlists):
  22. response = await async_client.get("/list_all_playlists")
  23. assert response.status_code == 200
  24. data = response.json()
  25. assert isinstance(data, list)
  26. assert len(data) == 3
  27. assert "favorites" in data
  28. @pytest.mark.asyncio
  29. async def test_list_all_playlists_empty(self, async_client):
  30. """Test list_all_playlists returns empty list when no playlists."""
  31. with patch("main.playlist_manager.list_all_playlists", return_value=[]):
  32. response = await async_client.get("/list_all_playlists")
  33. assert response.status_code == 200
  34. data = response.json()
  35. assert data == []
  36. class TestGetPlaylist:
  37. """Tests for /get_playlist endpoint."""
  38. @pytest.mark.asyncio
  39. async def test_get_playlist_exists(self, async_client):
  40. """Test get_playlist returns playlist data."""
  41. mock_playlist = {
  42. "name": "favorites",
  43. "files": ["circle.thr", "spiral.thr"]
  44. }
  45. with patch("main.playlist_manager.get_playlist", return_value=mock_playlist):
  46. response = await async_client.get("/get_playlist", params={"name": "favorites"})
  47. assert response.status_code == 200
  48. data = response.json()
  49. assert data["name"] == "favorites"
  50. assert data["files"] == ["circle.thr", "spiral.thr"]
  51. @pytest.mark.asyncio
  52. async def test_get_playlist_creates_empty_if_not_found(self, async_client):
  53. """Test get_playlist auto-creates empty playlist if not found.
  54. Note: This is the actual behavior - the endpoint auto-creates empty playlists.
  55. """
  56. with patch("main.playlist_manager.get_playlist", return_value=None):
  57. with patch("main.playlist_manager.create_playlist", return_value=True):
  58. response = await async_client.get("/get_playlist", params={"name": "nonexistent"})
  59. assert response.status_code == 200
  60. data = response.json()
  61. assert data["name"] == "nonexistent"
  62. assert data["files"] == []
  63. class TestCreatePlaylist:
  64. """Tests for /create_playlist endpoint."""
  65. @pytest.mark.asyncio
  66. async def test_create_playlist(self, async_client):
  67. """Test creating a new playlist."""
  68. with patch("main.playlist_manager.create_playlist", return_value=True):
  69. response = await async_client.post(
  70. "/create_playlist",
  71. json={
  72. "playlist_name": "new_playlist", # API uses playlist_name, not name
  73. "files": ["circle.thr", "spiral.thr"]
  74. }
  75. )
  76. assert response.status_code == 200
  77. data = response.json()
  78. assert data["success"] is True
  79. class TestModifyPlaylist:
  80. """Tests for /modify_playlist endpoint."""
  81. @pytest.mark.asyncio
  82. async def test_modify_playlist(self, async_client):
  83. """Test modifying an existing playlist."""
  84. with patch("main.playlist_manager.modify_playlist", return_value=True):
  85. response = await async_client.post(
  86. "/modify_playlist",
  87. json={
  88. "playlist_name": "favorites", # API uses playlist_name
  89. "files": ["new_pattern.thr"]
  90. }
  91. )
  92. assert response.status_code == 200
  93. data = response.json()
  94. assert data["success"] is True
  95. class TestDeletePlaylist:
  96. """Tests for /delete_playlist endpoint."""
  97. @pytest.mark.asyncio
  98. async def test_delete_playlist(self, async_client):
  99. """Test deleting a playlist."""
  100. with patch("main.playlist_manager.delete_playlist", return_value=True):
  101. response = await async_client.request(
  102. "DELETE",
  103. "/delete_playlist",
  104. json={"playlist_name": "to_delete"} # DELETE with body
  105. )
  106. assert response.status_code == 200
  107. data = response.json()
  108. assert data["success"] is True
  109. @pytest.mark.asyncio
  110. async def test_delete_playlist_not_found(self, async_client):
  111. """Test deleting a non-existent playlist returns 404."""
  112. with patch("main.playlist_manager.delete_playlist", return_value=False):
  113. response = await async_client.request(
  114. "DELETE",
  115. "/delete_playlist",
  116. json={"playlist_name": "nonexistent"}
  117. )
  118. assert response.status_code == 404
  119. class TestRenamePlaylist:
  120. """Tests for /rename_playlist endpoint."""
  121. @pytest.mark.asyncio
  122. async def test_rename_playlist_success(self, async_client):
  123. """Test renaming a playlist."""
  124. with patch("main.playlist_manager.rename_playlist", return_value=(True, "Renamed")):
  125. response = await async_client.post(
  126. "/rename_playlist",
  127. json={
  128. "old_name": "old_playlist",
  129. "new_name": "new_playlist"
  130. }
  131. )
  132. assert response.status_code == 200
  133. data = response.json()
  134. assert data["success"] is True
  135. @pytest.mark.asyncio
  136. async def test_rename_playlist_not_found(self, async_client):
  137. """Test renaming a non-existent playlist fails."""
  138. with patch("main.playlist_manager.rename_playlist", return_value=(False, "Playlist not found")):
  139. response = await async_client.post(
  140. "/rename_playlist",
  141. json={
  142. "old_name": "nonexistent",
  143. "new_name": "new_name"
  144. }
  145. )
  146. # Returns 400 with message (not 404)
  147. assert response.status_code == 400
  148. class TestAddToPlaylist:
  149. """Tests for /add_to_playlist endpoint."""
  150. @pytest.mark.asyncio
  151. async def test_add_to_playlist(self, async_client):
  152. """Test adding a pattern to a playlist."""
  153. with patch("main.playlist_manager.add_to_playlist", return_value=True):
  154. response = await async_client.post(
  155. "/add_to_playlist",
  156. json={
  157. "playlist_name": "favorites", # API uses playlist_name
  158. "pattern": "new_pattern.thr" # API uses pattern, not file
  159. }
  160. )
  161. assert response.status_code == 200
  162. data = response.json()
  163. assert data["success"] is True
  164. @pytest.mark.asyncio
  165. async def test_add_to_playlist_not_found(self, async_client):
  166. """Test adding to a non-existent playlist fails."""
  167. with patch("main.playlist_manager.add_to_playlist", return_value=False):
  168. response = await async_client.post(
  169. "/add_to_playlist",
  170. json={
  171. "playlist_name": "nonexistent",
  172. "pattern": "pattern.thr"
  173. }
  174. )
  175. assert response.status_code == 404
  176. class TestRunPlaylist:
  177. """Tests for /run_playlist endpoint."""
  178. @pytest.mark.asyncio
  179. async def test_run_playlist_when_disconnected(self, async_client, mock_state):
  180. """Test run_playlist fails when not connected.
  181. Note: The endpoint catches HTTPException and re-raises as 500.
  182. """
  183. mock_state.conn = None
  184. mock_state.is_homing = False
  185. with patch("main.state", mock_state):
  186. response = await async_client.post(
  187. "/run_playlist",
  188. json={
  189. "playlist_name": "test",
  190. "pause_time": 5,
  191. "clear_pattern": None,
  192. "run_mode": "single"
  193. }
  194. )
  195. # Endpoint wraps in try/except and returns 500
  196. assert response.status_code == 500
  197. data = response.json()
  198. assert "not established" in data["detail"].lower()
  199. @pytest.mark.asyncio
  200. async def test_run_playlist_during_homing(self, async_client, mock_state):
  201. """Test run_playlist fails during homing.
  202. Note: The endpoint catches HTTPException and re-raises as 500.
  203. """
  204. mock_state.is_homing = True
  205. mock_state.conn = MagicMock()
  206. mock_state.conn.is_connected.return_value = True
  207. with patch("main.state", mock_state):
  208. response = await async_client.post(
  209. "/run_playlist",
  210. json={
  211. "playlist_name": "test",
  212. "pause_time": 5,
  213. "clear_pattern": None,
  214. "run_mode": "single"
  215. }
  216. )
  217. # Endpoint wraps in try/except and returns 500
  218. assert response.status_code == 500
  219. data = response.json()
  220. assert "homing" in data["detail"].lower()
  221. class TestSkipPattern:
  222. """Tests for /skip_pattern endpoint."""
  223. @pytest.mark.asyncio
  224. async def test_skip_pattern(self, async_client, mock_state):
  225. """Test skip_pattern during playlist execution."""
  226. mock_state.current_playlist = ["a.thr", "b.thr"]
  227. mock_state.current_playlist_index = 0
  228. mock_state.skip_requested = False
  229. with patch("main.state", mock_state):
  230. response = await async_client.post("/skip_pattern")
  231. assert response.status_code == 200
  232. data = response.json()
  233. assert data["success"] is True
  234. # Endpoint sets skip_requested directly
  235. assert mock_state.skip_requested is True
  236. @pytest.mark.asyncio
  237. async def test_skip_pattern_no_playlist(self, async_client, mock_state):
  238. """Test skip_pattern fails when no playlist is running."""
  239. mock_state.current_playlist = None
  240. with patch("main.state", mock_state):
  241. response = await async_client.post("/skip_pattern")
  242. assert response.status_code == 400
  243. data = response.json()
  244. assert "no playlist" in data["detail"].lower()