test_archives_api.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. """Integration tests for Archives API endpoints.
  2. Tests the full request/response cycle for /api/v1/archives/ endpoints.
  3. """
  4. import pytest
  5. from httpx import AsyncClient
  6. class TestArchivesAPI:
  7. """Integration tests for /api/v1/archives/ endpoints."""
  8. # ========================================================================
  9. # List endpoints
  10. # ========================================================================
  11. @pytest.mark.asyncio
  12. @pytest.mark.integration
  13. async def test_list_archives_empty(self, async_client: AsyncClient):
  14. """Verify empty list is returned when no archives exist."""
  15. response = await async_client.get("/api/v1/archives/")
  16. assert response.status_code == 200
  17. data = response.json()
  18. assert isinstance(data, list)
  19. assert len(data) == 0
  20. @pytest.mark.asyncio
  21. @pytest.mark.integration
  22. async def test_list_archives_with_data(
  23. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  24. ):
  25. """Verify list returns existing archives."""
  26. printer = await printer_factory()
  27. await archive_factory(printer.id, print_name="Test Archive")
  28. response = await async_client.get("/api/v1/archives/")
  29. assert response.status_code == 200
  30. data = response.json()
  31. assert isinstance(data, list)
  32. assert len(data) >= 1
  33. assert any(a["print_name"] == "Test Archive" for a in data)
  34. @pytest.mark.asyncio
  35. @pytest.mark.integration
  36. async def test_list_archives_pagination(
  37. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  38. ):
  39. """Verify pagination works correctly."""
  40. printer = await printer_factory()
  41. # Create 5 archives
  42. for i in range(5):
  43. await archive_factory(printer.id, print_name=f"Archive {i}")
  44. # Get first page with limit 2
  45. response = await async_client.get("/api/v1/archives/?limit=2&offset=0")
  46. assert response.status_code == 200
  47. data = response.json()
  48. assert isinstance(data, list)
  49. assert len(data) == 2
  50. @pytest.mark.asyncio
  51. @pytest.mark.integration
  52. async def test_list_archives_filter_by_printer(
  53. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  54. ):
  55. """Verify filtering by printer_id works."""
  56. printer1 = await printer_factory(name="Printer 1", serial_number="00M09A000000001")
  57. printer2 = await printer_factory(name="Printer 2", serial_number="00M09A000000002")
  58. await archive_factory(printer1.id, print_name="Printer 1 Archive")
  59. await archive_factory(printer2.id, print_name="Printer 2 Archive")
  60. response = await async_client.get(
  61. f"/api/v1/archives/?printer_id={printer1.id}"
  62. )
  63. assert response.status_code == 200
  64. data = response.json()
  65. assert all(a["printer_id"] == printer1.id for a in data)
  66. # ========================================================================
  67. # Get single endpoint
  68. # ========================================================================
  69. @pytest.mark.asyncio
  70. @pytest.mark.integration
  71. async def test_get_archive(
  72. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  73. ):
  74. """Verify single archive can be retrieved."""
  75. printer = await printer_factory()
  76. archive = await archive_factory(printer.id, print_name="Get Test Archive")
  77. response = await async_client.get(f"/api/v1/archives/{archive.id}")
  78. assert response.status_code == 200
  79. result = response.json()
  80. assert result["id"] == archive.id
  81. assert result["print_name"] == "Get Test Archive"
  82. @pytest.mark.asyncio
  83. @pytest.mark.integration
  84. async def test_get_archive_not_found(self, async_client: AsyncClient):
  85. """Verify 404 for non-existent archive."""
  86. response = await async_client.get("/api/v1/archives/9999")
  87. assert response.status_code == 404
  88. # ========================================================================
  89. # Update endpoints
  90. # ========================================================================
  91. @pytest.mark.asyncio
  92. @pytest.mark.integration
  93. async def test_update_archive_name(
  94. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  95. ):
  96. """Verify archive name can be updated."""
  97. printer = await printer_factory()
  98. archive = await archive_factory(printer.id, print_name="Original Name")
  99. response = await async_client.patch(
  100. f"/api/v1/archives/{archive.id}",
  101. json={"print_name": "Updated Name"}
  102. )
  103. assert response.status_code == 200
  104. assert response.json()["print_name"] == "Updated Name"
  105. @pytest.mark.asyncio
  106. @pytest.mark.integration
  107. async def test_update_archive_notes(
  108. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  109. ):
  110. """Verify archive notes can be updated."""
  111. printer = await printer_factory()
  112. archive = await archive_factory(printer.id)
  113. response = await async_client.patch(
  114. f"/api/v1/archives/{archive.id}",
  115. json={"notes": "Great print!"}
  116. )
  117. assert response.status_code == 200
  118. assert response.json()["notes"] == "Great print!"
  119. @pytest.mark.asyncio
  120. @pytest.mark.integration
  121. async def test_update_archive_favorite(
  122. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  123. ):
  124. """Verify archive favorite status can be updated."""
  125. printer = await printer_factory()
  126. archive = await archive_factory(printer.id)
  127. response = await async_client.patch(
  128. f"/api/v1/archives/{archive.id}",
  129. json={"is_favorite": True}
  130. )
  131. assert response.status_code == 200
  132. assert response.json()["is_favorite"] is True
  133. # ========================================================================
  134. # Delete endpoints
  135. # ========================================================================
  136. @pytest.mark.asyncio
  137. @pytest.mark.integration
  138. async def test_delete_archive(
  139. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  140. ):
  141. """Verify archive can be deleted."""
  142. printer = await printer_factory()
  143. archive = await archive_factory(printer.id)
  144. archive_id = archive.id
  145. response = await async_client.delete(f"/api/v1/archives/{archive_id}")
  146. assert response.status_code == 200
  147. # Verify deleted
  148. response = await async_client.get(f"/api/v1/archives/{archive_id}")
  149. assert response.status_code == 404
  150. @pytest.mark.asyncio
  151. @pytest.mark.integration
  152. async def test_delete_nonexistent_archive(self, async_client: AsyncClient):
  153. """Verify deleting non-existent archive returns 404."""
  154. response = await async_client.delete("/api/v1/archives/9999")
  155. assert response.status_code == 404
  156. # ========================================================================
  157. # Statistics endpoints
  158. # ========================================================================
  159. @pytest.mark.asyncio
  160. @pytest.mark.integration
  161. async def test_get_archive_stats(
  162. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  163. ):
  164. """Verify archive statistics can be retrieved."""
  165. printer = await printer_factory()
  166. await archive_factory(
  167. printer.id,
  168. status="completed",
  169. print_time_seconds=3600,
  170. filament_used_grams=50.0,
  171. )
  172. await archive_factory(
  173. printer.id,
  174. status="completed",
  175. print_time_seconds=7200,
  176. filament_used_grams=100.0,
  177. )
  178. response = await async_client.get("/api/v1/archives/stats")
  179. assert response.status_code == 200
  180. result = response.json()
  181. # Check for actual stats fields
  182. assert "total_prints" in result
  183. assert "successful_prints" in result
  184. class TestArchiveDataIntegrity:
  185. """Tests for archive data integrity."""
  186. @pytest.mark.asyncio
  187. @pytest.mark.integration
  188. async def test_archive_linked_to_printer(
  189. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  190. ):
  191. """Verify archive is properly linked to printer."""
  192. printer = await printer_factory(name="My Printer")
  193. archive = await archive_factory(printer.id)
  194. response = await async_client.get(f"/api/v1/archives/{archive.id}")
  195. assert response.status_code == 200
  196. result = response.json()
  197. assert result["printer_id"] == printer.id
  198. @pytest.mark.asyncio
  199. @pytest.mark.integration
  200. async def test_archive_stores_print_data(
  201. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  202. ):
  203. """Verify archive stores all print data correctly."""
  204. printer = await printer_factory()
  205. archive = await archive_factory(
  206. printer.id,
  207. print_name="Test Print",
  208. filename="test.3mf",
  209. status="completed",
  210. filament_type="PLA",
  211. filament_used_grams=75.5,
  212. print_time_seconds=5400,
  213. )
  214. response = await async_client.get(f"/api/v1/archives/{archive.id}")
  215. assert response.status_code == 200
  216. result = response.json()
  217. assert result["print_name"] == "Test Print"
  218. assert result["filename"] == "test.3mf"
  219. assert result["status"] == "completed"
  220. assert result["filament_type"] == "PLA"
  221. assert result["filament_used_grams"] == 75.5
  222. assert result["print_time_seconds"] == 5400
  223. @pytest.mark.asyncio
  224. @pytest.mark.integration
  225. async def test_archive_update_persists(
  226. self, async_client: AsyncClient, archive_factory, printer_factory, db_session
  227. ):
  228. """CRITICAL: Verify archive updates persist."""
  229. printer = await printer_factory()
  230. archive = await archive_factory(printer.id, notes="Original notes")
  231. # Update
  232. await async_client.patch(
  233. f"/api/v1/archives/{archive.id}",
  234. json={"notes": "Updated notes", "is_favorite": True}
  235. )
  236. # Verify persistence
  237. response = await async_client.get(f"/api/v1/archives/{archive.id}")
  238. result = response.json()
  239. assert result["notes"] == "Updated notes"
  240. assert result["is_favorite"] is True