test_system_api.py 12 KB

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