test_virtual_printer_api.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. """Integration tests for Virtual Printer API endpoints.
  2. Tests the full request/response cycle for /api/v1/settings/virtual-printer endpoints.
  3. """
  4. from unittest.mock import AsyncMock, MagicMock, patch
  5. import pytest
  6. from httpx import AsyncClient
  7. class TestVirtualPrinterSettingsAPI:
  8. """Integration tests for /api/v1/settings/virtual-printer endpoints."""
  9. # ========================================================================
  10. # Get settings
  11. # ========================================================================
  12. @pytest.mark.asyncio
  13. @pytest.mark.integration
  14. async def test_get_virtual_printer_settings(self, async_client: AsyncClient):
  15. """Verify virtual printer settings can be retrieved."""
  16. response = await async_client.get("/api/v1/settings/virtual-printer")
  17. assert response.status_code == 200
  18. result = response.json()
  19. assert "enabled" in result
  20. assert "access_code_set" in result
  21. assert "mode" in result
  22. assert "status" in result
  23. @pytest.mark.asyncio
  24. @pytest.mark.integration
  25. async def test_get_settings_has_status(self, async_client: AsyncClient):
  26. """Verify settings include status details."""
  27. response = await async_client.get("/api/v1/settings/virtual-printer")
  28. assert response.status_code == 200
  29. result = response.json()
  30. status = result["status"]
  31. assert "enabled" in status
  32. assert "running" in status
  33. assert "mode" in status
  34. assert "name" in status
  35. assert "serial" in status
  36. assert "pending_files" in status
  37. # ========================================================================
  38. # Update settings
  39. # ========================================================================
  40. @pytest.mark.asyncio
  41. @pytest.mark.integration
  42. async def test_update_mode(self, async_client: AsyncClient):
  43. """Verify mode can be updated."""
  44. response = await async_client.put("/api/v1/settings/virtual-printer?mode=review")
  45. assert response.status_code == 200
  46. result = response.json()
  47. assert result["mode"] == "review"
  48. @pytest.mark.asyncio
  49. @pytest.mark.integration
  50. async def test_update_mode_to_print_queue(self, async_client: AsyncClient):
  51. """Verify mode can be set to print_queue."""
  52. response = await async_client.put("/api/v1/settings/virtual-printer?mode=print_queue")
  53. assert response.status_code == 200
  54. result = response.json()
  55. assert result["mode"] == "print_queue"
  56. @pytest.mark.asyncio
  57. @pytest.mark.integration
  58. async def test_update_mode_legacy_queue_maps_to_review(self, async_client: AsyncClient):
  59. """Verify legacy 'queue' mode is normalized to 'review'."""
  60. response = await async_client.put("/api/v1/settings/virtual-printer?mode=queue")
  61. assert response.status_code == 200
  62. result = response.json()
  63. assert result["mode"] == "review" # Legacy queue maps to review
  64. @pytest.mark.asyncio
  65. @pytest.mark.integration
  66. async def test_update_mode_to_immediate(self, async_client: AsyncClient):
  67. """Verify mode can be set to immediate."""
  68. response = await async_client.put("/api/v1/settings/virtual-printer?mode=immediate")
  69. assert response.status_code == 200
  70. result = response.json()
  71. assert result["mode"] == "immediate"
  72. @pytest.mark.asyncio
  73. @pytest.mark.integration
  74. async def test_update_access_code(self, async_client: AsyncClient):
  75. """Verify access code can be set."""
  76. response = await async_client.put("/api/v1/settings/virtual-printer?access_code=12345678")
  77. assert response.status_code == 200
  78. result = response.json()
  79. assert result["access_code_set"] is True
  80. @pytest.mark.asyncio
  81. @pytest.mark.integration
  82. async def test_update_access_code_wrong_length(self, async_client: AsyncClient):
  83. """Verify access code validation for length."""
  84. response = await async_client.put("/api/v1/settings/virtual-printer?access_code=123")
  85. # Should fail validation
  86. assert response.status_code == 400
  87. @pytest.mark.asyncio
  88. @pytest.mark.integration
  89. async def test_enable_without_access_code(self, async_client: AsyncClient):
  90. """Verify enabling fails without access code set."""
  91. # First ensure no access code is set by checking current state
  92. # Then try to enable
  93. response = await async_client.put("/api/v1/settings/virtual-printer?enabled=true")
  94. # If access code wasn't set, this should fail
  95. # If it was already set, it will succeed
  96. # Both are valid test outcomes
  97. assert response.status_code in [200, 400]
  98. @pytest.mark.asyncio
  99. @pytest.mark.integration
  100. async def test_enable_with_access_code(self, async_client: AsyncClient):
  101. """Verify enabling succeeds when access code is set."""
  102. # First set access code
  103. await async_client.put("/api/v1/settings/virtual-printer?access_code=12345678")
  104. # Then enable (this will start the servers which may fail in test env)
  105. # We mock the manager to avoid actually starting servers
  106. with patch("backend.app.services.virtual_printer.virtual_printer_manager") as mock_manager:
  107. mock_manager.configure = AsyncMock()
  108. mock_manager.get_status = MagicMock(
  109. return_value={
  110. "enabled": True,
  111. "running": True,
  112. "mode": "immediate",
  113. "name": "Bambuddy",
  114. "serial": "00M09A391800001",
  115. "pending_files": 0,
  116. }
  117. )
  118. response = await async_client.put("/api/v1/settings/virtual-printer?enabled=true")
  119. assert response.status_code == 200
  120. @pytest.mark.asyncio
  121. @pytest.mark.integration
  122. async def test_disable_virtual_printer(self, async_client: AsyncClient):
  123. """Verify virtual printer can be disabled."""
  124. with patch("backend.app.services.virtual_printer.virtual_printer_manager") as mock_manager:
  125. mock_manager.configure = AsyncMock()
  126. mock_manager.get_status = MagicMock(
  127. return_value={
  128. "enabled": False,
  129. "running": False,
  130. "mode": "immediate",
  131. "name": "Bambuddy",
  132. "serial": "00M09A391800001",
  133. "pending_files": 0,
  134. }
  135. )
  136. response = await async_client.put("/api/v1/settings/virtual-printer?enabled=false")
  137. assert response.status_code == 200
  138. result = response.json()
  139. assert result["enabled"] is False
  140. class TestPendingUploadsAPI:
  141. """Integration tests for /api/v1/pending-uploads/ endpoints."""
  142. @pytest.fixture
  143. def mock_pending_uploads(self, db_session):
  144. """Create mock pending uploads in database."""
  145. async def _create_pending(filename: str = "test.3mf"):
  146. from datetime import datetime
  147. from backend.app.models.pending_upload import PendingUpload
  148. upload = PendingUpload(
  149. filename=filename,
  150. file_path=f"/tmp/{filename}",
  151. file_size=1024,
  152. source_ip="192.168.1.100",
  153. status="pending",
  154. )
  155. db_session.add(upload)
  156. await db_session.commit()
  157. await db_session.refresh(upload)
  158. return upload
  159. return _create_pending
  160. # ========================================================================
  161. # List pending uploads
  162. # ========================================================================
  163. @pytest.mark.asyncio
  164. @pytest.mark.integration
  165. async def test_list_pending_uploads_empty(self, async_client: AsyncClient):
  166. """Verify empty list is returned when no pending uploads."""
  167. response = await async_client.get("/api/v1/pending-uploads/")
  168. assert response.status_code == 200
  169. result = response.json()
  170. assert isinstance(result, list)
  171. @pytest.mark.asyncio
  172. @pytest.mark.integration
  173. async def test_get_pending_uploads_count(self, async_client: AsyncClient):
  174. """Verify count endpoint returns correct count."""
  175. response = await async_client.get("/api/v1/pending-uploads/count")
  176. assert response.status_code == 200
  177. result = response.json()
  178. assert "count" in result
  179. assert isinstance(result["count"], int)
  180. # ========================================================================
  181. # Archive pending upload
  182. # ========================================================================
  183. @pytest.mark.asyncio
  184. @pytest.mark.integration
  185. async def test_archive_nonexistent_upload(self, async_client: AsyncClient):
  186. """Verify archiving non-existent upload returns 404."""
  187. response = await async_client.post("/api/v1/pending-uploads/99999/archive")
  188. assert response.status_code == 404
  189. # ========================================================================
  190. # Discard pending upload
  191. # ========================================================================
  192. @pytest.mark.asyncio
  193. @pytest.mark.integration
  194. async def test_discard_nonexistent_upload(self, async_client: AsyncClient):
  195. """Verify discarding non-existent upload returns 404."""
  196. response = await async_client.delete("/api/v1/pending-uploads/99999")
  197. assert response.status_code == 404
  198. # ========================================================================
  199. # Bulk operations
  200. # ========================================================================
  201. @pytest.mark.asyncio
  202. @pytest.mark.integration
  203. async def test_archive_all_empty(self, async_client: AsyncClient):
  204. """Verify archive all with no pending uploads."""
  205. response = await async_client.post("/api/v1/pending-uploads/archive-all")
  206. assert response.status_code == 200
  207. result = response.json()
  208. assert "archived" in result
  209. assert "failed" in result
  210. @pytest.mark.asyncio
  211. @pytest.mark.integration
  212. async def test_discard_all_empty(self, async_client: AsyncClient):
  213. """Verify discard all with no pending uploads."""
  214. response = await async_client.delete("/api/v1/pending-uploads/discard-all")
  215. assert response.status_code == 200
  216. result = response.json()
  217. assert "discarded" in result