test_virtual_printer_api.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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=queue")
  45. assert response.status_code == 200
  46. result = response.json()
  47. assert result["mode"] == "queue"
  48. @pytest.mark.asyncio
  49. @pytest.mark.integration
  50. async def test_update_mode_to_immediate(self, async_client: AsyncClient):
  51. """Verify mode can be set to immediate."""
  52. response = await async_client.put("/api/v1/settings/virtual-printer?mode=immediate")
  53. assert response.status_code == 200
  54. result = response.json()
  55. assert result["mode"] == "immediate"
  56. @pytest.mark.asyncio
  57. @pytest.mark.integration
  58. async def test_update_access_code(self, async_client: AsyncClient):
  59. """Verify access code can be set."""
  60. response = await async_client.put("/api/v1/settings/virtual-printer?access_code=12345678")
  61. assert response.status_code == 200
  62. result = response.json()
  63. assert result["access_code_set"] is True
  64. @pytest.mark.asyncio
  65. @pytest.mark.integration
  66. async def test_update_access_code_wrong_length(self, async_client: AsyncClient):
  67. """Verify access code validation for length."""
  68. response = await async_client.put("/api/v1/settings/virtual-printer?access_code=123")
  69. # Should fail validation
  70. assert response.status_code == 400
  71. @pytest.mark.asyncio
  72. @pytest.mark.integration
  73. async def test_enable_without_access_code(self, async_client: AsyncClient):
  74. """Verify enabling fails without access code set."""
  75. # First ensure no access code is set by checking current state
  76. # Then try to enable
  77. response = await async_client.put("/api/v1/settings/virtual-printer?enabled=true")
  78. # If access code wasn't set, this should fail
  79. # If it was already set, it will succeed
  80. # Both are valid test outcomes
  81. assert response.status_code in [200, 400]
  82. @pytest.mark.asyncio
  83. @pytest.mark.integration
  84. async def test_enable_with_access_code(self, async_client: AsyncClient):
  85. """Verify enabling succeeds when access code is set."""
  86. # First set access code
  87. await async_client.put("/api/v1/settings/virtual-printer?access_code=12345678")
  88. # Then enable (this will start the servers which may fail in test env)
  89. # We mock the manager to avoid actually starting servers
  90. with patch("backend.app.services.virtual_printer.virtual_printer_manager") as mock_manager:
  91. mock_manager.configure = AsyncMock()
  92. mock_manager.get_status = MagicMock(
  93. return_value={
  94. "enabled": True,
  95. "running": True,
  96. "mode": "immediate",
  97. "name": "Bambuddy",
  98. "serial": "00M09A391800001",
  99. "pending_files": 0,
  100. }
  101. )
  102. response = await async_client.put("/api/v1/settings/virtual-printer?enabled=true")
  103. assert response.status_code == 200
  104. @pytest.mark.asyncio
  105. @pytest.mark.integration
  106. async def test_disable_virtual_printer(self, async_client: AsyncClient):
  107. """Verify virtual printer can be disabled."""
  108. with patch("backend.app.services.virtual_printer.virtual_printer_manager") as mock_manager:
  109. mock_manager.configure = AsyncMock()
  110. mock_manager.get_status = MagicMock(
  111. return_value={
  112. "enabled": False,
  113. "running": False,
  114. "mode": "immediate",
  115. "name": "Bambuddy",
  116. "serial": "00M09A391800001",
  117. "pending_files": 0,
  118. }
  119. )
  120. response = await async_client.put("/api/v1/settings/virtual-printer?enabled=false")
  121. assert response.status_code == 200
  122. result = response.json()
  123. assert result["enabled"] is False
  124. class TestPendingUploadsAPI:
  125. """Integration tests for /api/v1/pending-uploads/ endpoints."""
  126. @pytest.fixture
  127. def mock_pending_uploads(self, db_session):
  128. """Create mock pending uploads in database."""
  129. async def _create_pending(filename: str = "test.3mf"):
  130. from datetime import datetime
  131. from backend.app.models.pending_upload import PendingUpload
  132. upload = PendingUpload(
  133. filename=filename,
  134. file_path=f"/tmp/{filename}",
  135. file_size=1024,
  136. source_ip="192.168.1.100",
  137. status="pending",
  138. )
  139. db_session.add(upload)
  140. await db_session.commit()
  141. await db_session.refresh(upload)
  142. return upload
  143. return _create_pending
  144. # ========================================================================
  145. # List pending uploads
  146. # ========================================================================
  147. @pytest.mark.asyncio
  148. @pytest.mark.integration
  149. async def test_list_pending_uploads_empty(self, async_client: AsyncClient):
  150. """Verify empty list is returned when no pending uploads."""
  151. response = await async_client.get("/api/v1/pending-uploads/")
  152. assert response.status_code == 200
  153. result = response.json()
  154. assert isinstance(result, list)
  155. @pytest.mark.asyncio
  156. @pytest.mark.integration
  157. async def test_get_pending_uploads_count(self, async_client: AsyncClient):
  158. """Verify count endpoint returns correct count."""
  159. response = await async_client.get("/api/v1/pending-uploads/count")
  160. assert response.status_code == 200
  161. result = response.json()
  162. assert "count" in result
  163. assert isinstance(result["count"], int)
  164. # ========================================================================
  165. # Archive pending upload
  166. # ========================================================================
  167. @pytest.mark.asyncio
  168. @pytest.mark.integration
  169. async def test_archive_nonexistent_upload(self, async_client: AsyncClient):
  170. """Verify archiving non-existent upload returns 404."""
  171. response = await async_client.post("/api/v1/pending-uploads/99999/archive")
  172. assert response.status_code == 404
  173. # ========================================================================
  174. # Discard pending upload
  175. # ========================================================================
  176. @pytest.mark.asyncio
  177. @pytest.mark.integration
  178. async def test_discard_nonexistent_upload(self, async_client: AsyncClient):
  179. """Verify discarding non-existent upload returns 404."""
  180. response = await async_client.delete("/api/v1/pending-uploads/99999")
  181. assert response.status_code == 404
  182. # ========================================================================
  183. # Bulk operations
  184. # ========================================================================
  185. @pytest.mark.asyncio
  186. @pytest.mark.integration
  187. async def test_archive_all_empty(self, async_client: AsyncClient):
  188. """Verify archive all with no pending uploads."""
  189. response = await async_client.post("/api/v1/pending-uploads/archive-all")
  190. assert response.status_code == 200
  191. result = response.json()
  192. assert "archived" in result
  193. assert "failed" in result
  194. @pytest.mark.asyncio
  195. @pytest.mark.integration
  196. async def test_discard_all_empty(self, async_client: AsyncClient):
  197. """Verify discard all with no pending uploads."""
  198. response = await async_client.delete("/api/v1/pending-uploads/discard-all")
  199. assert response.status_code == 200
  200. result = response.json()
  201. assert "discarded" in result