test_library_api.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. """Integration tests for Library API endpoints."""
  2. import pytest
  3. from httpx import AsyncClient
  4. class TestLibraryFoldersAPI:
  5. """Integration tests for library folders endpoints."""
  6. @pytest.fixture
  7. async def folder_factory(self, db_session):
  8. """Factory to create test folders."""
  9. _counter = [0]
  10. async def _create_folder(**kwargs):
  11. from backend.app.models.library import LibraryFolder
  12. _counter[0] += 1
  13. counter = _counter[0]
  14. defaults = {
  15. "name": f"Test Folder {counter}",
  16. }
  17. defaults.update(kwargs)
  18. folder = LibraryFolder(**defaults)
  19. db_session.add(folder)
  20. await db_session.commit()
  21. await db_session.refresh(folder)
  22. return folder
  23. return _create_folder
  24. @pytest.mark.asyncio
  25. @pytest.mark.integration
  26. async def test_list_folders_empty(self, async_client: AsyncClient, db_session):
  27. """Verify empty folder list returns empty array."""
  28. response = await async_client.get("/api/v1/library/folders")
  29. assert response.status_code == 200
  30. assert response.json() == []
  31. @pytest.mark.asyncio
  32. @pytest.mark.integration
  33. async def test_create_folder(self, async_client: AsyncClient, db_session):
  34. """Verify folder can be created."""
  35. data = {"name": "New Folder"}
  36. response = await async_client.post("/api/v1/library/folders", json=data)
  37. assert response.status_code == 200
  38. result = response.json()
  39. assert result["name"] == "New Folder"
  40. assert result["id"] is not None
  41. @pytest.mark.asyncio
  42. @pytest.mark.integration
  43. async def test_create_nested_folder(self, async_client: AsyncClient, folder_factory, db_session):
  44. """Verify nested folder can be created."""
  45. parent = await folder_factory(name="Parent")
  46. data = {"name": "Child", "parent_id": parent.id}
  47. response = await async_client.post("/api/v1/library/folders", json=data)
  48. assert response.status_code == 200
  49. result = response.json()
  50. assert result["name"] == "Child"
  51. assert result["parent_id"] == parent.id
  52. @pytest.mark.asyncio
  53. @pytest.mark.integration
  54. async def test_get_folder(self, async_client: AsyncClient, folder_factory, db_session):
  55. """Verify single folder can be retrieved."""
  56. folder = await folder_factory(name="Test Folder")
  57. response = await async_client.get(f"/api/v1/library/folders/{folder.id}")
  58. assert response.status_code == 200
  59. result = response.json()
  60. assert result["id"] == folder.id
  61. assert result["name"] == "Test Folder"
  62. @pytest.mark.asyncio
  63. @pytest.mark.integration
  64. async def test_get_folder_not_found(self, async_client: AsyncClient, db_session):
  65. """Verify 404 for non-existent folder."""
  66. response = await async_client.get("/api/v1/library/folders/9999")
  67. assert response.status_code == 404
  68. @pytest.mark.asyncio
  69. @pytest.mark.integration
  70. async def test_update_folder(self, async_client: AsyncClient, folder_factory, db_session):
  71. """Verify folder can be updated."""
  72. folder = await folder_factory(name="Old Name")
  73. data = {"name": "New Name"}
  74. response = await async_client.put(f"/api/v1/library/folders/{folder.id}", json=data)
  75. assert response.status_code == 200
  76. result = response.json()
  77. assert result["name"] == "New Name"
  78. @pytest.mark.asyncio
  79. @pytest.mark.integration
  80. async def test_delete_folder(self, async_client: AsyncClient, folder_factory, db_session):
  81. """Verify folder can be deleted."""
  82. folder = await folder_factory()
  83. response = await async_client.delete(f"/api/v1/library/folders/{folder.id}")
  84. assert response.status_code == 200
  85. result = response.json()
  86. assert result.get("message") or result.get("success", True)
  87. class TestLibraryFilesAPI:
  88. """Integration tests for library files endpoints."""
  89. @pytest.fixture
  90. async def folder_factory(self, db_session):
  91. """Factory to create test folders."""
  92. _counter = [0]
  93. async def _create_folder(**kwargs):
  94. from backend.app.models.library import LibraryFolder
  95. _counter[0] += 1
  96. counter = _counter[0]
  97. defaults = {"name": f"Test Folder {counter}"}
  98. defaults.update(kwargs)
  99. folder = LibraryFolder(**defaults)
  100. db_session.add(folder)
  101. await db_session.commit()
  102. await db_session.refresh(folder)
  103. return folder
  104. return _create_folder
  105. @pytest.fixture
  106. async def file_factory(self, db_session):
  107. """Factory to create test files."""
  108. _counter = [0]
  109. async def _create_file(**kwargs):
  110. from backend.app.models.library import LibraryFile
  111. _counter[0] += 1
  112. counter = _counter[0]
  113. defaults = {
  114. "filename": f"test_file_{counter}.3mf",
  115. "file_path": f"/test/path/test_file_{counter}.3mf",
  116. "file_size": 1024,
  117. "file_type": "3mf",
  118. }
  119. defaults.update(kwargs)
  120. lib_file = LibraryFile(**defaults)
  121. db_session.add(lib_file)
  122. await db_session.commit()
  123. await db_session.refresh(lib_file)
  124. return lib_file
  125. return _create_file
  126. @pytest.mark.asyncio
  127. @pytest.mark.integration
  128. async def test_list_files_empty(self, async_client: AsyncClient, db_session):
  129. """Verify empty file list returns empty array."""
  130. response = await async_client.get("/api/v1/library/files")
  131. assert response.status_code == 200
  132. assert response.json() == []
  133. @pytest.mark.asyncio
  134. @pytest.mark.integration
  135. async def test_list_files_in_folder(self, async_client: AsyncClient, folder_factory, file_factory, db_session):
  136. """Verify files can be filtered by folder."""
  137. folder = await folder_factory()
  138. file1 = await file_factory(folder_id=folder.id)
  139. await file_factory() # File in root (no folder)
  140. response = await async_client.get(f"/api/v1/library/files?folder_id={folder.id}")
  141. assert response.status_code == 200
  142. result = response.json()
  143. assert len(result) == 1
  144. assert result[0]["id"] == file1.id
  145. @pytest.mark.asyncio
  146. @pytest.mark.integration
  147. async def test_get_file(self, async_client: AsyncClient, file_factory, db_session):
  148. """Verify single file can be retrieved."""
  149. lib_file = await file_factory(filename="test.3mf")
  150. response = await async_client.get(f"/api/v1/library/files/{lib_file.id}")
  151. assert response.status_code == 200
  152. result = response.json()
  153. assert result["id"] == lib_file.id
  154. assert result["filename"] == "test.3mf"
  155. @pytest.mark.asyncio
  156. @pytest.mark.integration
  157. async def test_get_file_not_found(self, async_client: AsyncClient, db_session):
  158. """Verify 404 for non-existent file."""
  159. response = await async_client.get("/api/v1/library/files/9999")
  160. assert response.status_code == 404
  161. @pytest.mark.asyncio
  162. @pytest.mark.integration
  163. async def test_delete_file(self, async_client: AsyncClient, file_factory, db_session):
  164. """Verify file can be deleted."""
  165. lib_file = await file_factory()
  166. response = await async_client.delete(f"/api/v1/library/files/{lib_file.id}")
  167. assert response.status_code == 200
  168. result = response.json()
  169. assert result.get("message") or result.get("success", True)
  170. @pytest.mark.asyncio
  171. @pytest.mark.integration
  172. async def test_library_stats(self, async_client: AsyncClient, folder_factory, file_factory, db_session):
  173. """Verify library stats endpoint returns counts."""
  174. await folder_factory()
  175. await folder_factory()
  176. await file_factory()
  177. response = await async_client.get("/api/v1/library/stats")
  178. assert response.status_code == 200
  179. result = response.json()
  180. assert result["total_folders"] == 2
  181. assert result["total_files"] == 1
  182. class TestLibraryAddToQueueAPI:
  183. """Integration tests for /api/v1/library/files/add-to-queue endpoint."""
  184. @pytest.fixture
  185. async def printer_factory(self, db_session):
  186. """Factory to create test printers."""
  187. _counter = [0]
  188. async def _create_printer(**kwargs):
  189. from backend.app.models.printer import Printer
  190. _counter[0] += 1
  191. counter = _counter[0]
  192. defaults = {
  193. "name": f"Test Printer {counter}",
  194. "ip_address": f"192.168.1.{100 + counter}",
  195. "serial_number": f"TESTSERIAL{counter:04d}",
  196. "access_code": "12345678",
  197. "model": "X1C",
  198. }
  199. defaults.update(kwargs)
  200. printer = Printer(**defaults)
  201. db_session.add(printer)
  202. await db_session.commit()
  203. await db_session.refresh(printer)
  204. return printer
  205. return _create_printer
  206. @pytest.fixture
  207. async def library_file_factory(self, db_session):
  208. """Factory to create test library files."""
  209. _counter = [0]
  210. async def _create_library_file(**kwargs):
  211. from backend.app.models.library import LibraryFile
  212. _counter[0] += 1
  213. counter = _counter[0]
  214. defaults = {
  215. "filename": f"test_file_{counter}.gcode.3mf",
  216. "file_path": f"/test/path/test_file_{counter}.gcode.3mf",
  217. "file_size": 1024,
  218. "file_type": "3mf",
  219. }
  220. defaults.update(kwargs)
  221. lib_file = LibraryFile(**defaults)
  222. db_session.add(lib_file)
  223. await db_session.commit()
  224. await db_session.refresh(lib_file)
  225. return lib_file
  226. return _create_library_file
  227. @pytest.mark.asyncio
  228. @pytest.mark.integration
  229. async def test_add_to_queue_file_not_found(self, async_client: AsyncClient, printer_factory, db_session):
  230. """Verify error for non-existent file."""
  231. await printer_factory()
  232. data = {"file_ids": [9999]}
  233. response = await async_client.post("/api/v1/library/files/add-to-queue", json=data)
  234. assert response.status_code == 200
  235. result = response.json()
  236. assert len(result["added"]) == 0
  237. assert len(result["errors"]) == 1
  238. assert result["errors"][0]["file_id"] == 9999
  239. @pytest.mark.asyncio
  240. @pytest.mark.integration
  241. async def test_add_non_sliced_file_to_queue_fails(
  242. self, async_client: AsyncClient, printer_factory, library_file_factory, db_session
  243. ):
  244. """Verify non-sliced file cannot be added to queue."""
  245. await printer_factory()
  246. lib_file = await library_file_factory(
  247. filename="model.stl",
  248. file_path="/test/path/model.stl",
  249. file_type="stl",
  250. )
  251. data = {"file_ids": [lib_file.id]}
  252. response = await async_client.post("/api/v1/library/files/add-to-queue", json=data)
  253. assert response.status_code == 200
  254. result = response.json()
  255. assert len(result["added"]) == 0
  256. assert len(result["errors"]) == 1
  257. assert "sliced" in result["errors"][0]["error"].lower()