test_printers_api.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. """Integration tests for Printers API endpoints.
  2. Tests the full request/response cycle for /api/v1/printers/ endpoints.
  3. """
  4. from unittest.mock import AsyncMock, MagicMock, patch
  5. import pytest
  6. from httpx import AsyncClient
  7. class TestPrintersAPI:
  8. """Integration tests for /api/v1/printers/ endpoints."""
  9. # ========================================================================
  10. # List endpoints
  11. # ========================================================================
  12. @pytest.mark.asyncio
  13. @pytest.mark.integration
  14. async def test_list_printers_empty(self, async_client: AsyncClient):
  15. """Verify empty list is returned when no printers exist."""
  16. response = await async_client.get("/api/v1/printers/")
  17. assert response.status_code == 200
  18. assert response.json() == []
  19. @pytest.mark.asyncio
  20. @pytest.mark.integration
  21. async def test_list_printers_with_data(self, async_client: AsyncClient, printer_factory, db_session):
  22. """Verify list returns existing printers."""
  23. await printer_factory(name="Test Printer")
  24. response = await async_client.get("/api/v1/printers/")
  25. assert response.status_code == 200
  26. data = response.json()
  27. assert len(data) >= 1
  28. assert any(p["name"] == "Test Printer" for p in data)
  29. # ========================================================================
  30. # Create endpoints
  31. # ========================================================================
  32. @pytest.mark.asyncio
  33. @pytest.mark.integration
  34. async def test_create_printer(self, async_client: AsyncClient):
  35. """Verify printer can be created."""
  36. data = {
  37. "name": "New Printer",
  38. "serial_number": "00M09A111111111",
  39. "ip_address": "192.168.1.100",
  40. "access_code": "12345678",
  41. "is_active": True,
  42. "model": "X1C",
  43. }
  44. response = await async_client.post("/api/v1/printers/", json=data)
  45. assert response.status_code == 200
  46. result = response.json()
  47. assert result["name"] == "New Printer"
  48. assert result["serial_number"] == "00M09A111111111"
  49. assert result["model"] == "X1C"
  50. @pytest.mark.asyncio
  51. @pytest.mark.integration
  52. async def test_create_printer_duplicate_serial(self, async_client: AsyncClient, printer_factory, db_session):
  53. """Verify duplicate serial number is rejected."""
  54. await printer_factory(serial_number="00M09A222222222")
  55. data = {
  56. "name": "Duplicate Printer",
  57. "serial_number": "00M09A222222222",
  58. "ip_address": "192.168.1.101",
  59. "access_code": "12345678",
  60. }
  61. response = await async_client.post("/api/v1/printers/", json=data)
  62. # Should fail due to duplicate serial
  63. assert response.status_code in [400, 409, 422, 500]
  64. # ========================================================================
  65. # Get single endpoint
  66. # ========================================================================
  67. @pytest.mark.asyncio
  68. @pytest.mark.integration
  69. async def test_get_printer(self, async_client: AsyncClient, printer_factory, db_session):
  70. """Verify single printer can be retrieved."""
  71. printer = await printer_factory(name="Get Test Printer")
  72. response = await async_client.get(f"/api/v1/printers/{printer.id}")
  73. assert response.status_code == 200
  74. result = response.json()
  75. assert result["id"] == printer.id
  76. assert result["name"] == "Get Test Printer"
  77. @pytest.mark.asyncio
  78. @pytest.mark.integration
  79. async def test_get_printer_not_found(self, async_client: AsyncClient):
  80. """Verify 404 for non-existent printer."""
  81. response = await async_client.get("/api/v1/printers/9999")
  82. assert response.status_code == 404
  83. # ========================================================================
  84. # Update endpoints
  85. # ========================================================================
  86. @pytest.mark.asyncio
  87. @pytest.mark.integration
  88. async def test_update_printer_name(self, async_client: AsyncClient, printer_factory, db_session):
  89. """Verify printer name can be updated."""
  90. printer = await printer_factory(name="Original Name")
  91. response = await async_client.patch(f"/api/v1/printers/{printer.id}", json={"name": "Updated Name"})
  92. assert response.status_code == 200
  93. assert response.json()["name"] == "Updated Name"
  94. @pytest.mark.asyncio
  95. @pytest.mark.integration
  96. async def test_update_printer_active_status(self, async_client: AsyncClient, printer_factory, db_session):
  97. """Verify printer active status can be updated."""
  98. printer = await printer_factory(is_active=True)
  99. response = await async_client.patch(f"/api/v1/printers/{printer.id}", json={"is_active": False})
  100. assert response.status_code == 200
  101. assert response.json()["is_active"] is False
  102. @pytest.mark.asyncio
  103. @pytest.mark.integration
  104. async def test_update_printer_auto_archive(self, async_client: AsyncClient, printer_factory, db_session):
  105. """Verify auto_archive setting can be updated."""
  106. printer = await printer_factory(auto_archive=True)
  107. response = await async_client.patch(f"/api/v1/printers/{printer.id}", json={"auto_archive": False})
  108. assert response.status_code == 200
  109. assert response.json()["auto_archive"] is False
  110. @pytest.mark.asyncio
  111. @pytest.mark.integration
  112. async def test_update_nonexistent_printer(self, async_client: AsyncClient):
  113. """Verify updating non-existent printer returns 404."""
  114. response = await async_client.patch("/api/v1/printers/9999", json={"name": "New Name"})
  115. assert response.status_code == 404
  116. # ========================================================================
  117. # Delete endpoints
  118. # ========================================================================
  119. @pytest.mark.asyncio
  120. @pytest.mark.integration
  121. async def test_delete_printer(self, async_client: AsyncClient, printer_factory, db_session):
  122. """Verify printer can be deleted."""
  123. printer = await printer_factory()
  124. printer_id = printer.id
  125. response = await async_client.delete(f"/api/v1/printers/{printer_id}")
  126. assert response.status_code == 200
  127. # Verify deleted
  128. response = await async_client.get(f"/api/v1/printers/{printer_id}")
  129. assert response.status_code == 404
  130. @pytest.mark.asyncio
  131. @pytest.mark.integration
  132. async def test_delete_nonexistent_printer(self, async_client: AsyncClient):
  133. """Verify deleting non-existent printer returns 404."""
  134. response = await async_client.delete("/api/v1/printers/9999")
  135. assert response.status_code == 404
  136. # ========================================================================
  137. # Status endpoint
  138. # ========================================================================
  139. @pytest.mark.asyncio
  140. @pytest.mark.integration
  141. async def test_get_printer_status(
  142. self, async_client: AsyncClient, printer_factory, mock_printer_manager, db_session
  143. ):
  144. """Verify printer status can be retrieved."""
  145. printer = await printer_factory()
  146. response = await async_client.get(f"/api/v1/printers/{printer.id}/status")
  147. assert response.status_code == 200
  148. result = response.json()
  149. assert "connected" in result
  150. assert "state" in result
  151. @pytest.mark.asyncio
  152. @pytest.mark.integration
  153. async def test_get_printer_status_not_found(self, async_client: AsyncClient):
  154. """Verify 404 for status of non-existent printer."""
  155. response = await async_client.get("/api/v1/printers/9999/status")
  156. assert response.status_code == 404
  157. # ========================================================================
  158. # Test connection endpoint
  159. # ========================================================================
  160. class TestPrinterDataIntegrity:
  161. """Tests for printer data integrity."""
  162. @pytest.mark.asyncio
  163. @pytest.mark.integration
  164. async def test_printer_stores_all_fields(self, async_client: AsyncClient, printer_factory, db_session):
  165. """Verify printer stores all fields correctly."""
  166. printer = await printer_factory(
  167. name="Full Test Printer",
  168. serial_number="00M09A444444444",
  169. ip_address="192.168.1.150",
  170. model="P1S",
  171. is_active=True,
  172. auto_archive=False,
  173. )
  174. response = await async_client.get(f"/api/v1/printers/{printer.id}")
  175. assert response.status_code == 200
  176. result = response.json()
  177. assert result["name"] == "Full Test Printer"
  178. assert result["serial_number"] == "00M09A444444444"
  179. assert result["ip_address"] == "192.168.1.150"
  180. assert result["model"] == "P1S"
  181. assert result["is_active"] is True
  182. assert result["auto_archive"] is False
  183. @pytest.mark.asyncio
  184. @pytest.mark.integration
  185. async def test_printer_update_persists(self, async_client: AsyncClient, printer_factory, db_session):
  186. """CRITICAL: Verify printer updates persist."""
  187. printer = await printer_factory(name="Original", is_active=True)
  188. # Update
  189. await async_client.patch(f"/api/v1/printers/{printer.id}", json={"name": "Updated", "is_active": False})
  190. # Verify persistence
  191. response = await async_client.get(f"/api/v1/printers/{printer.id}")
  192. result = response.json()
  193. assert result["name"] == "Updated"
  194. assert result["is_active"] is False
  195. # ========================================================================
  196. # Refresh status endpoint
  197. # ========================================================================
  198. @pytest.mark.asyncio
  199. @pytest.mark.integration
  200. async def test_refresh_status_not_found(self, async_client: AsyncClient):
  201. """Verify 404 for non-existent printer."""
  202. response = await async_client.post("/api/v1/printers/99999/refresh-status")
  203. assert response.status_code == 404
  204. @pytest.mark.asyncio
  205. @pytest.mark.integration
  206. async def test_refresh_status_not_connected(self, async_client: AsyncClient, printer_factory):
  207. """Verify 400 when printer is not connected."""
  208. printer = await printer_factory(name="Disconnected Printer")
  209. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  210. mock_pm.request_status_update.return_value = False
  211. response = await async_client.post(f"/api/v1/printers/{printer.id}/refresh-status")
  212. assert response.status_code == 400
  213. assert "not connected" in response.json()["detail"].lower()
  214. @pytest.mark.asyncio
  215. @pytest.mark.integration
  216. async def test_refresh_status_success(self, async_client: AsyncClient, printer_factory):
  217. """Verify successful refresh request."""
  218. printer = await printer_factory(name="Connected Printer")
  219. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  220. mock_pm.request_status_update.return_value = True
  221. response = await async_client.post(f"/api/v1/printers/{printer.id}/refresh-status")
  222. assert response.status_code == 200
  223. assert response.json()["status"] == "refresh_requested"
  224. mock_pm.request_status_update.assert_called_once_with(printer.id)
  225. class TestPrintControlAPI:
  226. """Integration tests for print control endpoints (stop, pause, resume)."""
  227. # ========================================================================
  228. # Stop print endpoint
  229. # ========================================================================
  230. @pytest.mark.asyncio
  231. @pytest.mark.integration
  232. async def test_stop_print_not_found(self, async_client: AsyncClient):
  233. """Verify 404 for non-existent printer."""
  234. response = await async_client.post("/api/v1/printers/99999/print/stop")
  235. assert response.status_code == 404
  236. @pytest.mark.asyncio
  237. @pytest.mark.integration
  238. async def test_stop_print_not_connected(self, async_client: AsyncClient, printer_factory):
  239. """Verify error when printer is not connected."""
  240. printer = await printer_factory(name="Disconnected Printer")
  241. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  242. mock_pm.get_client.return_value = None
  243. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/stop")
  244. assert response.status_code == 400
  245. assert "not connected" in response.json()["detail"].lower()
  246. @pytest.mark.asyncio
  247. @pytest.mark.integration
  248. async def test_stop_print_success(self, async_client: AsyncClient, printer_factory):
  249. """Verify successful stop print request."""
  250. printer = await printer_factory(name="Printing Printer")
  251. mock_client = MagicMock()
  252. mock_client.stop_print.return_value = True
  253. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  254. mock_pm.get_client.return_value = mock_client
  255. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/stop")
  256. assert response.status_code == 200
  257. assert response.json()["success"] is True
  258. mock_client.stop_print.assert_called_once()
  259. # ========================================================================
  260. # Pause print endpoint
  261. # ========================================================================
  262. @pytest.mark.asyncio
  263. @pytest.mark.integration
  264. async def test_pause_print_not_found(self, async_client: AsyncClient):
  265. """Verify 404 for non-existent printer."""
  266. response = await async_client.post("/api/v1/printers/99999/print/pause")
  267. assert response.status_code == 404
  268. @pytest.mark.asyncio
  269. @pytest.mark.integration
  270. async def test_pause_print_not_connected(self, async_client: AsyncClient, printer_factory):
  271. """Verify error when printer is not connected."""
  272. printer = await printer_factory(name="Disconnected Printer")
  273. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  274. mock_pm.get_client.return_value = None
  275. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/pause")
  276. assert response.status_code == 400
  277. assert "not connected" in response.json()["detail"].lower()
  278. @pytest.mark.asyncio
  279. @pytest.mark.integration
  280. async def test_pause_print_success(self, async_client: AsyncClient, printer_factory):
  281. """Verify successful pause print request."""
  282. printer = await printer_factory(name="Printing Printer")
  283. mock_client = MagicMock()
  284. mock_client.pause_print.return_value = True
  285. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  286. mock_pm.get_client.return_value = mock_client
  287. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/pause")
  288. assert response.status_code == 200
  289. assert response.json()["success"] is True
  290. mock_client.pause_print.assert_called_once()
  291. # ========================================================================
  292. # Resume print endpoint
  293. # ========================================================================
  294. @pytest.mark.asyncio
  295. @pytest.mark.integration
  296. async def test_resume_print_not_found(self, async_client: AsyncClient):
  297. """Verify 404 for non-existent printer."""
  298. response = await async_client.post("/api/v1/printers/99999/print/resume")
  299. assert response.status_code == 404
  300. @pytest.mark.asyncio
  301. @pytest.mark.integration
  302. async def test_resume_print_not_connected(self, async_client: AsyncClient, printer_factory):
  303. """Verify error when printer is not connected."""
  304. printer = await printer_factory(name="Disconnected Printer")
  305. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  306. mock_pm.get_client.return_value = None
  307. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/resume")
  308. assert response.status_code == 400
  309. assert "not connected" in response.json()["detail"].lower()
  310. @pytest.mark.asyncio
  311. @pytest.mark.integration
  312. async def test_resume_print_success(self, async_client: AsyncClient, printer_factory):
  313. """Verify successful resume print request."""
  314. printer = await printer_factory(name="Paused Printer")
  315. mock_client = MagicMock()
  316. mock_client.resume_print.return_value = True
  317. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  318. mock_pm.get_client.return_value = mock_client
  319. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/resume")
  320. assert response.status_code == 200
  321. assert response.json()["success"] is True
  322. mock_client.resume_print.assert_called_once()
  323. class TestAMSRefreshAPI:
  324. """Integration tests for AMS slot refresh endpoint."""
  325. @pytest.mark.asyncio
  326. @pytest.mark.integration
  327. async def test_ams_refresh_not_found(self, async_client: AsyncClient):
  328. """Verify 404 for non-existent printer."""
  329. response = await async_client.post("/api/v1/printers/99999/ams/0/slot/0/refresh")
  330. assert response.status_code == 404
  331. @pytest.mark.asyncio
  332. @pytest.mark.integration
  333. async def test_ams_refresh_not_connected(self, async_client: AsyncClient, printer_factory):
  334. """Verify error when printer is not connected."""
  335. printer = await printer_factory(name="Disconnected Printer")
  336. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  337. mock_pm.get_client.return_value = None
  338. response = await async_client.post(f"/api/v1/printers/{printer.id}/ams/0/slot/0/refresh")
  339. assert response.status_code == 400
  340. assert "not connected" in response.json()["detail"].lower()
  341. @pytest.mark.asyncio
  342. @pytest.mark.integration
  343. async def test_ams_refresh_success(self, async_client: AsyncClient, printer_factory):
  344. """Verify successful AMS refresh request."""
  345. printer = await printer_factory(name="Printer with AMS")
  346. mock_client = MagicMock()
  347. mock_client.ams_refresh_tray.return_value = (True, "Refreshing AMS 0 tray 1")
  348. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  349. mock_pm.get_client.return_value = mock_client
  350. response = await async_client.post(f"/api/v1/printers/{printer.id}/ams/0/slot/1/refresh")
  351. assert response.status_code == 200
  352. result = response.json()
  353. assert result["success"] is True
  354. mock_client.ams_refresh_tray.assert_called_once_with(0, 1)
  355. @pytest.mark.asyncio
  356. @pytest.mark.integration
  357. async def test_ams_refresh_filament_loaded(self, async_client: AsyncClient, printer_factory):
  358. """Verify error when filament is loaded (can't refresh while loaded)."""
  359. printer = await printer_factory(name="Printer with AMS")
  360. mock_client = MagicMock()
  361. mock_client.ams_refresh_tray.return_value = (False, "Please unload filament first")
  362. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  363. mock_pm.get_client.return_value = mock_client
  364. response = await async_client.post(f"/api/v1/printers/{printer.id}/ams/0/slot/0/refresh")
  365. assert response.status_code == 400
  366. assert "unload" in response.json()["detail"].lower()
  367. class TestSkipObjectsAPI:
  368. """Integration tests for skip objects endpoints."""
  369. # ========================================================================
  370. # Get printable objects endpoint
  371. # ========================================================================
  372. @pytest.mark.asyncio
  373. @pytest.mark.integration
  374. async def test_get_objects_not_found(self, async_client: AsyncClient):
  375. """Verify 404 for non-existent printer."""
  376. response = await async_client.get("/api/v1/printers/99999/print/objects")
  377. assert response.status_code == 404
  378. @pytest.mark.asyncio
  379. @pytest.mark.integration
  380. async def test_get_objects_not_connected(self, async_client: AsyncClient, printer_factory):
  381. """Verify error when printer is not connected."""
  382. printer = await printer_factory(name="Disconnected Printer")
  383. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  384. mock_pm.get_client.return_value = None
  385. response = await async_client.get(f"/api/v1/printers/{printer.id}/print/objects")
  386. assert response.status_code == 400
  387. assert "not connected" in response.json()["detail"].lower()
  388. @pytest.mark.asyncio
  389. @pytest.mark.integration
  390. async def test_get_objects_empty(self, async_client: AsyncClient, printer_factory):
  391. """Verify empty objects list when no print is active."""
  392. printer = await printer_factory(name="Idle Printer")
  393. mock_client = MagicMock()
  394. mock_client.state.printable_objects = {}
  395. mock_client.state.skipped_objects = []
  396. mock_client.state.state = "IDLE"
  397. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  398. mock_pm.get_client.return_value = mock_client
  399. response = await async_client.get(f"/api/v1/printers/{printer.id}/print/objects")
  400. assert response.status_code == 200
  401. result = response.json()
  402. assert result["objects"] == []
  403. assert result["total"] == 0
  404. assert result["skipped_count"] == 0
  405. assert result["is_printing"] is False
  406. @pytest.mark.asyncio
  407. @pytest.mark.integration
  408. async def test_get_objects_with_data(self, async_client: AsyncClient, printer_factory):
  409. """Verify objects list when print is active."""
  410. printer = await printer_factory(name="Printing Printer")
  411. mock_client = MagicMock()
  412. mock_client.state.printable_objects = {100: "Part A", 200: "Part B", 300: "Part C"}
  413. mock_client.state.skipped_objects = [200]
  414. mock_client.state.state = "RUNNING"
  415. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  416. mock_pm.get_client.return_value = mock_client
  417. response = await async_client.get(f"/api/v1/printers/{printer.id}/print/objects")
  418. assert response.status_code == 200
  419. result = response.json()
  420. assert result["total"] == 3
  421. assert result["skipped_count"] == 1
  422. assert result["is_printing"] is True
  423. # Check objects have correct structure
  424. objects_by_id = {obj["id"]: obj for obj in result["objects"]}
  425. assert objects_by_id[100]["name"] == "Part A"
  426. assert objects_by_id[100]["skipped"] is False
  427. assert objects_by_id[200]["name"] == "Part B"
  428. assert objects_by_id[200]["skipped"] is True
  429. assert objects_by_id[300]["name"] == "Part C"
  430. assert objects_by_id[300]["skipped"] is False
  431. # ========================================================================
  432. # Skip objects endpoint
  433. # ========================================================================
  434. @pytest.mark.asyncio
  435. @pytest.mark.integration
  436. async def test_get_objects_with_positions(self, async_client: AsyncClient, printer_factory):
  437. """Verify objects list includes position data when available."""
  438. printer = await printer_factory(name="Printing Printer")
  439. # New format with position data
  440. mock_client = MagicMock()
  441. mock_client.state.printable_objects = {
  442. 100: {"name": "Part A", "x": 50.0, "y": 100.0},
  443. 200: {"name": "Part B", "x": 150.0, "y": 100.0},
  444. }
  445. mock_client.state.skipped_objects = []
  446. mock_client.state.state = "RUNNING"
  447. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  448. mock_pm.get_client.return_value = mock_client
  449. response = await async_client.get(f"/api/v1/printers/{printer.id}/print/objects")
  450. assert response.status_code == 200
  451. result = response.json()
  452. assert result["total"] == 2
  453. # Check objects have position data
  454. objects_by_id = {obj["id"]: obj for obj in result["objects"]}
  455. assert objects_by_id[100]["name"] == "Part A"
  456. assert objects_by_id[100]["x"] == 50.0
  457. assert objects_by_id[100]["y"] == 100.0
  458. assert objects_by_id[200]["name"] == "Part B"
  459. assert objects_by_id[200]["x"] == 150.0
  460. assert objects_by_id[200]["y"] == 100.0
  461. # ========================================================================
  462. # Skip objects endpoint
  463. # ========================================================================
  464. @pytest.mark.asyncio
  465. @pytest.mark.integration
  466. async def test_skip_objects_not_found(self, async_client: AsyncClient):
  467. """Verify 404 for non-existent printer."""
  468. response = await async_client.post("/api/v1/printers/99999/print/skip-objects", json=[100])
  469. assert response.status_code == 404
  470. @pytest.mark.asyncio
  471. @pytest.mark.integration
  472. async def test_skip_objects_not_connected(self, async_client: AsyncClient, printer_factory):
  473. """Verify error when printer is not connected."""
  474. printer = await printer_factory(name="Disconnected Printer")
  475. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  476. mock_pm.get_client.return_value = None
  477. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/skip-objects", json=[100])
  478. assert response.status_code == 400
  479. assert "not connected" in response.json()["detail"].lower()
  480. @pytest.mark.asyncio
  481. @pytest.mark.integration
  482. async def test_skip_objects_empty_list(self, async_client: AsyncClient, printer_factory):
  483. """Verify error when no object IDs provided."""
  484. printer = await printer_factory(name="Printing Printer")
  485. mock_client = MagicMock()
  486. mock_client.state.printable_objects = {100: "Part A"}
  487. mock_client.state.skipped_objects = []
  488. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  489. mock_pm.get_client.return_value = mock_client
  490. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/skip-objects", json=[])
  491. assert response.status_code == 400
  492. assert "no object" in response.json()["detail"].lower()
  493. @pytest.mark.asyncio
  494. @pytest.mark.integration
  495. async def test_skip_objects_invalid_id(self, async_client: AsyncClient, printer_factory):
  496. """Verify error when object ID doesn't exist."""
  497. printer = await printer_factory(name="Printing Printer")
  498. mock_client = MagicMock()
  499. mock_client.state.printable_objects = {100: "Part A"}
  500. mock_client.state.skipped_objects = []
  501. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  502. mock_pm.get_client.return_value = mock_client
  503. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/skip-objects", json=[999])
  504. assert response.status_code == 400
  505. assert "invalid" in response.json()["detail"].lower()
  506. @pytest.mark.asyncio
  507. @pytest.mark.integration
  508. async def test_skip_objects_success(self, async_client: AsyncClient, printer_factory):
  509. """Verify successful skip objects request."""
  510. printer = await printer_factory(name="Printing Printer")
  511. mock_client = MagicMock()
  512. mock_client.state.printable_objects = {100: "Part A", 200: "Part B"}
  513. mock_client.state.skipped_objects = []
  514. mock_client.skip_objects.return_value = True
  515. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  516. mock_pm.get_client.return_value = mock_client
  517. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/skip-objects", json=[100])
  518. assert response.status_code == 200
  519. result = response.json()
  520. assert result["success"] is True
  521. assert 100 in result["skipped_objects"]
  522. mock_client.skip_objects.assert_called_once_with([100])
  523. @pytest.mark.asyncio
  524. @pytest.mark.integration
  525. async def test_skip_objects_multiple(self, async_client: AsyncClient, printer_factory):
  526. """Verify skipping multiple objects at once."""
  527. printer = await printer_factory(name="Printing Printer")
  528. mock_client = MagicMock()
  529. mock_client.state.printable_objects = {100: "Part A", 200: "Part B", 300: "Part C"}
  530. mock_client.state.skipped_objects = []
  531. mock_client.skip_objects.return_value = True
  532. with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
  533. mock_pm.get_client.return_value = mock_client
  534. response = await async_client.post(f"/api/v1/printers/{printer.id}/print/skip-objects", json=[100, 200])
  535. assert response.status_code == 200
  536. result = response.json()
  537. assert result["success"] is True
  538. assert 100 in result["skipped_objects"]
  539. assert 200 in result["skipped_objects"]
  540. mock_client.skip_objects.assert_called_once_with([100, 200])