test_maintenance_api.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. """Integration tests for Maintenance API endpoints."""
  2. import pytest
  3. from httpx import AsyncClient
  4. class TestMaintenanceTypesAPI:
  5. """Integration tests for /api/v1/maintenance/types endpoints."""
  6. @pytest.mark.asyncio
  7. @pytest.mark.integration
  8. async def test_list_maintenance_types(self, async_client: AsyncClient):
  9. """Verify maintenance types list returns data with defaults."""
  10. response = await async_client.get("/api/v1/maintenance/types")
  11. assert response.status_code == 200
  12. data = response.json()
  13. assert isinstance(data, list)
  14. # Should have default system types
  15. assert len(data) >= 1
  16. @pytest.mark.asyncio
  17. @pytest.mark.integration
  18. async def test_list_includes_system_types(self, async_client: AsyncClient):
  19. """Verify default system types are created."""
  20. response = await async_client.get("/api/v1/maintenance/types")
  21. assert response.status_code == 200
  22. data = response.json()
  23. names = [t["name"] for t in data]
  24. # Check for some default types
  25. assert "Lubricate Linear Rails" in names or len(data) > 0
  26. @pytest.mark.asyncio
  27. @pytest.mark.integration
  28. async def test_create_custom_maintenance_type(self, async_client: AsyncClient):
  29. """Verify custom maintenance type can be created."""
  30. data = {
  31. "name": "Custom Test Task",
  32. "description": "Test description",
  33. "default_interval_hours": 200.0,
  34. "interval_type": "hours",
  35. "icon": "Wrench",
  36. }
  37. response = await async_client.post("/api/v1/maintenance/types", json=data)
  38. assert response.status_code == 200
  39. result = response.json()
  40. assert result["name"] == "Custom Test Task"
  41. assert result["is_system"] is False
  42. @pytest.mark.asyncio
  43. @pytest.mark.integration
  44. async def test_create_custom_type_persists_wiki_url(self, async_client: AsyncClient):
  45. """#1596: pre-fix, the POST handler hard-coded every constructor field
  46. by name and silently dropped `wiki_url`. The schema accepted the value,
  47. the response echoed `null`, and the row landed without it. Pin the
  48. contract so the constructor doesn't drift again."""
  49. data = {
  50. "name": "Wiki URL Persistence Test",
  51. "default_interval_hours": 50.0,
  52. "interval_type": "hours",
  53. "wiki_url": "https://wiki.example.com/lubrication",
  54. }
  55. response = await async_client.post("/api/v1/maintenance/types", json=data)
  56. assert response.status_code == 200
  57. assert response.json()["wiki_url"] == "https://wiki.example.com/lubrication"
  58. # Verify it persists through a separate GET round-trip — the POST
  59. # response could have echoed the request body without committing.
  60. list_response = await async_client.get("/api/v1/maintenance/types")
  61. assert list_response.status_code == 200
  62. matching = [t for t in list_response.json() if t["name"] == data["name"]]
  63. assert len(matching) == 1
  64. assert matching[0]["wiki_url"] == "https://wiki.example.com/lubrication"
  65. @pytest.mark.asyncio
  66. @pytest.mark.integration
  67. async def test_update_maintenance_type(self, async_client: AsyncClient):
  68. """Verify maintenance type can be updated."""
  69. # First create a custom type
  70. create_data = {
  71. "name": "Update Test",
  72. "description": "Original",
  73. "default_interval_hours": 100.0,
  74. }
  75. create_response = await async_client.post("/api/v1/maintenance/types", json=create_data)
  76. assert create_response.status_code == 200
  77. type_id = create_response.json()["id"]
  78. # Update it
  79. update_data = {"description": "Updated description"}
  80. response = await async_client.patch(f"/api/v1/maintenance/types/{type_id}", json=update_data)
  81. assert response.status_code == 200
  82. assert response.json()["description"] == "Updated description"
  83. @pytest.mark.asyncio
  84. @pytest.mark.integration
  85. async def test_delete_custom_maintenance_type(self, async_client: AsyncClient):
  86. """Verify custom maintenance type can be deleted."""
  87. # Create a custom type
  88. create_data = {
  89. "name": "Delete Test",
  90. "description": "To be deleted",
  91. "default_interval_hours": 50.0,
  92. }
  93. create_response = await async_client.post("/api/v1/maintenance/types", json=create_data)
  94. type_id = create_response.json()["id"]
  95. # Delete it
  96. response = await async_client.delete(f"/api/v1/maintenance/types/{type_id}")
  97. assert response.status_code == 200
  98. class TestPrinterMaintenanceAPI:
  99. """Integration tests for /api/v1/maintenance/printers endpoints."""
  100. @pytest.mark.asyncio
  101. @pytest.mark.integration
  102. async def test_get_printer_maintenance_not_found(self, async_client: AsyncClient):
  103. """Verify 404 for non-existent printer."""
  104. response = await async_client.get("/api/v1/maintenance/printers/9999")
  105. assert response.status_code == 404
  106. @pytest.mark.asyncio
  107. @pytest.mark.integration
  108. async def test_get_printer_maintenance(self, async_client: AsyncClient, printer_factory, db_session):
  109. """Verify maintenance overview for a printer."""
  110. printer = await printer_factory(name="Maintenance Test Printer")
  111. response = await async_client.get(f"/api/v1/maintenance/printers/{printer.id}")
  112. assert response.status_code == 200
  113. data = response.json()
  114. assert data["printer_id"] == printer.id
  115. assert data["printer_name"] == "Maintenance Test Printer"
  116. assert "maintenance_items" in data
  117. assert "total_print_hours" in data
  118. @pytest.mark.asyncio
  119. @pytest.mark.integration
  120. async def test_get_all_maintenance_overview(self, async_client: AsyncClient, printer_factory, db_session):
  121. """Verify overview endpoint returns all printers."""
  122. await printer_factory(name="Overview Printer 1")
  123. await printer_factory(name="Overview Printer 2")
  124. response = await async_client.get("/api/v1/maintenance/overview")
  125. assert response.status_code == 200
  126. data = response.json()
  127. assert isinstance(data, list)
  128. @pytest.mark.asyncio
  129. @pytest.mark.integration
  130. async def test_get_maintenance_summary(self, async_client: AsyncClient):
  131. """Verify summary endpoint returns counts."""
  132. response = await async_client.get("/api/v1/maintenance/summary")
  133. assert response.status_code == 200
  134. data = response.json()
  135. assert "total_due" in data
  136. assert "total_warning" in data
  137. assert "printers_with_issues" in data
  138. class TestMaintenanceItemsAPI:
  139. """Integration tests for /api/v1/maintenance/items endpoints."""
  140. @pytest.fixture
  141. async def maintenance_item(self, async_client: AsyncClient, printer_factory, db_session):
  142. """Create a maintenance item for testing."""
  143. printer = await printer_factory(name="Item Test Printer")
  144. # Get the printer's maintenance overview to create items
  145. response = await async_client.get(f"/api/v1/maintenance/printers/{printer.id}")
  146. assert response.status_code == 200
  147. data = response.json()
  148. # Return the first maintenance item
  149. if data["maintenance_items"]:
  150. return data["maintenance_items"][0]
  151. return None
  152. @pytest.mark.asyncio
  153. @pytest.mark.integration
  154. async def test_update_maintenance_item(self, async_client: AsyncClient, maintenance_item):
  155. """Verify maintenance item can be updated."""
  156. if not maintenance_item:
  157. pytest.skip("No maintenance items available")
  158. item_id = maintenance_item["id"]
  159. response = await async_client.patch(
  160. f"/api/v1/maintenance/items/{item_id}", json={"custom_interval_hours": 150.0}
  161. )
  162. assert response.status_code == 200
  163. @pytest.mark.asyncio
  164. @pytest.mark.integration
  165. async def test_disable_maintenance_item(self, async_client: AsyncClient, maintenance_item):
  166. """Verify maintenance item can be disabled."""
  167. if not maintenance_item:
  168. pytest.skip("No maintenance items available")
  169. item_id = maintenance_item["id"]
  170. response = await async_client.patch(f"/api/v1/maintenance/items/{item_id}", json={"enabled": False})
  171. assert response.status_code == 200
  172. assert response.json()["enabled"] is False
  173. @pytest.mark.asyncio
  174. @pytest.mark.integration
  175. async def test_perform_maintenance(self, async_client: AsyncClient, maintenance_item):
  176. """Verify maintenance can be marked as performed."""
  177. if not maintenance_item:
  178. pytest.skip("No maintenance items available")
  179. item_id = maintenance_item["id"]
  180. response = await async_client.post(
  181. f"/api/v1/maintenance/items/{item_id}/perform", json={"notes": "Test maintenance performed"}
  182. )
  183. assert response.status_code == 200
  184. data = response.json()
  185. assert data["last_performed_at"] is not None
  186. @pytest.mark.asyncio
  187. @pytest.mark.integration
  188. async def test_get_maintenance_history(self, async_client: AsyncClient, maintenance_item):
  189. """Verify maintenance history can be retrieved."""
  190. if not maintenance_item:
  191. pytest.skip("No maintenance items available")
  192. item_id = maintenance_item["id"]
  193. # First perform maintenance to create history
  194. await async_client.post(f"/api/v1/maintenance/items/{item_id}/perform", json={"notes": "History test"})
  195. response = await async_client.get(f"/api/v1/maintenance/items/{item_id}/history")
  196. assert response.status_code == 200
  197. history = response.json()
  198. assert isinstance(history, list)
  199. @pytest.mark.asyncio
  200. @pytest.mark.integration
  201. async def test_update_maintenance_item_not_found(self, async_client: AsyncClient):
  202. """Verify 404 for non-existent maintenance item."""
  203. response = await async_client.patch("/api/v1/maintenance/items/9999", json={"enabled": False})
  204. assert response.status_code == 404
  205. class TestPrinterHoursAPI:
  206. """Integration tests for /api/v1/maintenance/printers/{id}/hours endpoint."""
  207. @pytest.mark.asyncio
  208. @pytest.mark.integration
  209. async def test_set_printer_hours(self, async_client: AsyncClient, printer_factory, db_session):
  210. """Verify printer hours can be set."""
  211. printer = await printer_factory(name="Hours Test Printer")
  212. response = await async_client.patch(
  213. f"/api/v1/maintenance/printers/{printer.id}/hours", params={"total_hours": 500.0}
  214. )
  215. assert response.status_code == 200
  216. data = response.json()
  217. assert data["total_hours"] == 500.0
  218. @pytest.mark.asyncio
  219. @pytest.mark.integration
  220. async def test_set_printer_hours_not_found(self, async_client: AsyncClient):
  221. """Verify 404 for non-existent printer."""
  222. response = await async_client.patch("/api/v1/maintenance/printers/9999/hours", params={"total_hours": 100.0})
  223. assert response.status_code == 404