test_system_api.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. """Integration tests for System API endpoints.
  2. Tests the full request/response cycle for /api/v1/system/ endpoints.
  3. """
  4. from unittest.mock import MagicMock, patch
  5. import pytest
  6. from httpx import AsyncClient
  7. class TestSystemAPI:
  8. """Integration tests for /api/v1/system/ endpoints."""
  9. # ========================================================================
  10. # System Info Endpoint
  11. # ========================================================================
  12. @pytest.mark.asyncio
  13. @pytest.mark.integration
  14. async def test_get_system_info(self, async_client: AsyncClient):
  15. """Verify system info endpoint returns expected structure."""
  16. # Mock psutil to avoid system-specific values
  17. with patch("backend.app.api.routes.system.psutil") as mock_psutil:
  18. mock_psutil.disk_usage.return_value = MagicMock(
  19. total=500000000000, used=250000000000, free=250000000000, percent=50.0
  20. )
  21. mock_psutil.virtual_memory.return_value = MagicMock(
  22. total=16000000000, available=8000000000, used=8000000000, percent=50.0
  23. )
  24. mock_psutil.boot_time.return_value = 1700000000.0
  25. mock_psutil.cpu_count.return_value = 4
  26. mock_psutil.cpu_percent.return_value = 25.0
  27. response = await async_client.get("/api/v1/system/info")
  28. assert response.status_code == 200
  29. result = response.json()
  30. # Verify top-level structure
  31. assert "app" in result
  32. assert "database" in result
  33. assert "printers" in result
  34. assert "storage" in result
  35. assert "system" in result
  36. assert "memory" in result
  37. assert "cpu" in result
  38. @pytest.mark.asyncio
  39. @pytest.mark.integration
  40. async def test_system_info_app_section(self, async_client: AsyncClient):
  41. """Verify app section contains version and directory info."""
  42. with patch("backend.app.api.routes.system.psutil") as mock_psutil:
  43. mock_psutil.disk_usage.return_value = MagicMock(
  44. total=500000000000, used=250000000000, free=250000000000, percent=50.0
  45. )
  46. mock_psutil.virtual_memory.return_value = MagicMock(
  47. total=16000000000, available=8000000000, used=8000000000, percent=50.0
  48. )
  49. mock_psutil.boot_time.return_value = 1700000000.0
  50. mock_psutil.cpu_count.return_value = 4
  51. mock_psutil.cpu_percent.return_value = 25.0
  52. response = await async_client.get("/api/v1/system/info")
  53. result = response.json()
  54. app_info = result["app"]
  55. assert "version" in app_info
  56. assert "base_dir" in app_info
  57. assert "archive_dir" in app_info
  58. @pytest.mark.asyncio
  59. @pytest.mark.integration
  60. async def test_system_info_database_section(self, async_client: AsyncClient):
  61. """Verify database section contains counts and statistics."""
  62. with patch("backend.app.api.routes.system.psutil") as mock_psutil:
  63. mock_psutil.disk_usage.return_value = MagicMock(
  64. total=500000000000, used=250000000000, free=250000000000, percent=50.0
  65. )
  66. mock_psutil.virtual_memory.return_value = MagicMock(
  67. total=16000000000, available=8000000000, used=8000000000, percent=50.0
  68. )
  69. mock_psutil.boot_time.return_value = 1700000000.0
  70. mock_psutil.cpu_count.return_value = 4
  71. mock_psutil.cpu_percent.return_value = 25.0
  72. response = await async_client.get("/api/v1/system/info")
  73. result = response.json()
  74. db_info = result["database"]
  75. assert "archives" in db_info
  76. assert "archives_completed" in db_info
  77. assert "archives_failed" in db_info
  78. assert "printers" in db_info
  79. assert "filaments" in db_info
  80. assert "projects" in db_info
  81. assert "smart_plugs" in db_info
  82. assert "total_print_time_seconds" in db_info
  83. assert "total_print_time_formatted" in db_info
  84. assert "total_filament_grams" in db_info
  85. assert "total_filament_kg" in db_info
  86. @pytest.mark.asyncio
  87. @pytest.mark.integration
  88. async def test_system_info_storage_section(self, async_client: AsyncClient):
  89. """Verify storage section contains disk usage info."""
  90. with patch("backend.app.api.routes.system.psutil") as mock_psutil:
  91. mock_psutil.disk_usage.return_value = MagicMock(
  92. total=500000000000, used=250000000000, free=250000000000, percent=50.0
  93. )
  94. mock_psutil.virtual_memory.return_value = MagicMock(
  95. total=16000000000, available=8000000000, used=8000000000, percent=50.0
  96. )
  97. mock_psutil.boot_time.return_value = 1700000000.0
  98. mock_psutil.cpu_count.return_value = 4
  99. mock_psutil.cpu_percent.return_value = 25.0
  100. response = await async_client.get("/api/v1/system/info")
  101. result = response.json()
  102. storage_info = result["storage"]
  103. assert "archive_size_bytes" in storage_info
  104. assert "archive_size_formatted" in storage_info
  105. assert "database_size_bytes" in storage_info
  106. assert "database_size_formatted" in storage_info
  107. assert "disk_total_bytes" in storage_info
  108. assert "disk_total_formatted" in storage_info
  109. assert "disk_used_bytes" in storage_info
  110. assert "disk_free_bytes" in storage_info
  111. assert "disk_percent_used" in storage_info
  112. @pytest.mark.asyncio
  113. @pytest.mark.integration
  114. async def test_system_info_memory_section(self, async_client: AsyncClient):
  115. """Verify memory section contains RAM usage info."""
  116. with patch("backend.app.api.routes.system.psutil") as mock_psutil:
  117. mock_psutil.disk_usage.return_value = MagicMock(
  118. total=500000000000, used=250000000000, free=250000000000, percent=50.0
  119. )
  120. mock_psutil.virtual_memory.return_value = MagicMock(
  121. total=16000000000, available=8000000000, used=8000000000, percent=50.0
  122. )
  123. mock_psutil.boot_time.return_value = 1700000000.0
  124. mock_psutil.cpu_count.return_value = 4
  125. mock_psutil.cpu_percent.return_value = 25.0
  126. response = await async_client.get("/api/v1/system/info")
  127. result = response.json()
  128. memory_info = result["memory"]
  129. assert "total_bytes" in memory_info
  130. assert "total_formatted" in memory_info
  131. assert "available_bytes" in memory_info
  132. assert "used_bytes" in memory_info
  133. assert "percent_used" in memory_info
  134. @pytest.mark.asyncio
  135. @pytest.mark.integration
  136. async def test_system_info_cpu_section(self, async_client: AsyncClient):
  137. """Verify CPU section contains processor info."""
  138. with patch("backend.app.api.routes.system.psutil") as mock_psutil:
  139. mock_psutil.disk_usage.return_value = MagicMock(
  140. total=500000000000, used=250000000000, free=250000000000, percent=50.0
  141. )
  142. mock_psutil.virtual_memory.return_value = MagicMock(
  143. total=16000000000, available=8000000000, used=8000000000, percent=50.0
  144. )
  145. mock_psutil.boot_time.return_value = 1700000000.0
  146. mock_psutil.cpu_count.return_value = 4
  147. mock_psutil.cpu_percent.return_value = 25.0
  148. response = await async_client.get("/api/v1/system/info")
  149. result = response.json()
  150. cpu_info = result["cpu"]
  151. assert "count" in cpu_info
  152. assert "count_logical" in cpu_info
  153. assert "percent" in cpu_info
  154. @pytest.mark.asyncio
  155. @pytest.mark.integration
  156. async def test_system_info_printers_section(self, async_client: AsyncClient, printer_factory):
  157. """Verify printers section contains connected printer info."""
  158. # Create a test printer
  159. _printer = await printer_factory(name="Test Printer", model="X1C")
  160. with (
  161. patch("backend.app.api.routes.system.psutil") as mock_psutil,
  162. patch("backend.app.api.routes.system.printer_manager") as mock_pm,
  163. ):
  164. mock_psutil.disk_usage.return_value = MagicMock(
  165. total=500000000000, used=250000000000, free=250000000000, percent=50.0
  166. )
  167. mock_psutil.virtual_memory.return_value = MagicMock(
  168. total=16000000000, available=8000000000, used=8000000000, percent=50.0
  169. )
  170. mock_psutil.boot_time.return_value = 1700000000.0
  171. mock_psutil.cpu_count.return_value = 4
  172. mock_psutil.cpu_percent.return_value = 25.0
  173. # Mock no connected printers for simplicity
  174. mock_pm._clients = {}
  175. response = await async_client.get("/api/v1/system/info")
  176. result = response.json()
  177. printers_info = result["printers"]
  178. assert "total" in printers_info
  179. assert "connected" in printers_info
  180. assert "connected_list" in printers_info
  181. assert printers_info["total"] >= 1 # At least our test printer
  182. @pytest.mark.asyncio
  183. @pytest.mark.integration
  184. async def test_system_info_with_archives(self, async_client: AsyncClient, printer_factory, archive_factory):
  185. """Verify database stats include archive counts."""
  186. printer = await printer_factory()
  187. await archive_factory(printer.id, status="completed", print_time_seconds=3600)
  188. await archive_factory(printer.id, status="failed", print_time_seconds=1800)
  189. with (
  190. patch("backend.app.api.routes.system.psutil") as mock_psutil,
  191. patch("backend.app.api.routes.system.printer_manager") as mock_pm,
  192. ):
  193. mock_psutil.disk_usage.return_value = MagicMock(
  194. total=500000000000, used=250000000000, free=250000000000, percent=50.0
  195. )
  196. mock_psutil.virtual_memory.return_value = MagicMock(
  197. total=16000000000, available=8000000000, used=8000000000, percent=50.0
  198. )
  199. mock_psutil.boot_time.return_value = 1700000000.0
  200. mock_psutil.cpu_count.return_value = 4
  201. mock_psutil.cpu_percent.return_value = 25.0
  202. mock_pm._clients = {}
  203. response = await async_client.get("/api/v1/system/info")
  204. result = response.json()
  205. db_info = result["database"]
  206. assert db_info["archives"] >= 2
  207. assert db_info["archives_completed"] >= 1
  208. assert db_info["archives_failed"] >= 1
  209. assert db_info["total_print_time_seconds"] >= 5400
  210. class TestSystemHelperFunctions:
  211. """Tests for system info helper functions."""
  212. def test_format_bytes_bytes(self):
  213. """Verify format_bytes handles bytes correctly."""
  214. from backend.app.api.routes.system import format_bytes
  215. assert format_bytes(500) == "500.0 B"
  216. def test_format_bytes_kilobytes(self):
  217. """Verify format_bytes handles kilobytes correctly."""
  218. from backend.app.api.routes.system import format_bytes
  219. result = format_bytes(1536)
  220. assert "KB" in result
  221. def test_format_bytes_megabytes(self):
  222. """Verify format_bytes handles megabytes correctly."""
  223. from backend.app.api.routes.system import format_bytes
  224. result = format_bytes(1536 * 1024)
  225. assert "MB" in result
  226. def test_format_bytes_gigabytes(self):
  227. """Verify format_bytes handles gigabytes correctly."""
  228. from backend.app.api.routes.system import format_bytes
  229. result = format_bytes(1536 * 1024 * 1024)
  230. assert "GB" in result
  231. def test_format_uptime_minutes(self):
  232. """Verify format_uptime handles minutes correctly."""
  233. from backend.app.api.routes.system import format_uptime
  234. result = format_uptime(300) # 5 minutes
  235. assert "5m" in result
  236. def test_format_uptime_hours(self):
  237. """Verify format_uptime handles hours correctly."""
  238. from backend.app.api.routes.system import format_uptime
  239. result = format_uptime(7200) # 2 hours
  240. assert "2h" in result
  241. def test_format_uptime_days(self):
  242. """Verify format_uptime handles days correctly."""
  243. from backend.app.api.routes.system import format_uptime
  244. result = format_uptime(86400 * 2 + 3600 * 5) # 2 days 5 hours
  245. assert "2d" in result
  246. assert "5h" in result
  247. def test_format_uptime_less_than_minute(self):
  248. """Verify format_uptime handles < 1 minute correctly."""
  249. from backend.app.api.routes.system import format_uptime
  250. result = format_uptime(30) # 30 seconds
  251. assert result == "< 1m"