SystemInfoPage.test.tsx 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /**
  2. * Tests for the SystemInfoPage component.
  3. */
  4. import { describe, it, expect, vi, beforeEach } from 'vitest';
  5. import { screen, waitFor } from '@testing-library/react';
  6. import { render } from '../utils';
  7. import { SystemInfoPage } from '../../pages/SystemInfoPage';
  8. import { api } from '../../api/client';
  9. // Mock the API client
  10. vi.mock('../../api/client', () => ({
  11. api: {
  12. getSystemInfo: vi.fn(),
  13. getSettings: vi.fn().mockResolvedValue({}),
  14. updateSettings: vi.fn().mockResolvedValue({}),
  15. },
  16. supportApi: {
  17. getDebugLoggingState: vi.fn().mockResolvedValue({ enabled: false, enabled_at: null, duration_seconds: null }),
  18. setDebugLogging: vi.fn().mockResolvedValue({ enabled: true, enabled_at: new Date().toISOString(), duration_seconds: 0 }),
  19. downloadSupportBundle: vi.fn().mockResolvedValue(undefined),
  20. },
  21. }));
  22. // Mock system info response
  23. const mockSystemInfo = {
  24. app: {
  25. version: '0.1.5b',
  26. base_dir: '/opt/bambuddy',
  27. archive_dir: '/opt/bambuddy/archives',
  28. },
  29. database: {
  30. engine: 'SQLite',
  31. version: 'SQLite 3.45.1',
  32. archives: 150,
  33. archives_completed: 140,
  34. archives_failed: 8,
  35. archives_printing: 2,
  36. printers: 3,
  37. filaments: 25,
  38. projects: 5,
  39. smart_plugs: 2,
  40. total_print_time_seconds: 360000,
  41. total_print_time_formatted: '100h',
  42. total_filament_grams: 5000,
  43. total_filament_kg: 5.0,
  44. },
  45. printers: {
  46. total: 3,
  47. connected: 2,
  48. connected_list: [
  49. { id: 1, name: 'X1C-01', state: 'IDLE', model: 'X1C' },
  50. { id: 2, name: 'P1S-01', state: 'RUNNING', model: 'P1S' },
  51. ],
  52. },
  53. storage: {
  54. archive_size_bytes: 1073741824,
  55. archive_size_formatted: '1.0 GB',
  56. database_size_bytes: 10485760,
  57. database_size_formatted: '10.0 MB',
  58. disk_total_bytes: 107374182400,
  59. disk_total_formatted: '100.0 GB',
  60. disk_used_bytes: 53687091200,
  61. disk_used_formatted: '50.0 GB',
  62. disk_free_bytes: 53687091200,
  63. disk_free_formatted: '50.0 GB',
  64. disk_percent_used: 50.0,
  65. },
  66. system: {
  67. platform: 'Linux',
  68. platform_release: '5.15.0',
  69. platform_version: '#1 SMP',
  70. architecture: 'x86_64',
  71. hostname: 'bambuddy-server',
  72. python_version: '3.11.0',
  73. uptime_seconds: 86400,
  74. uptime_formatted: '1d',
  75. boot_time: '2024-12-11T00:00:00',
  76. },
  77. memory: {
  78. total_bytes: 17179869184,
  79. total_formatted: '16.0 GB',
  80. available_bytes: 8589934592,
  81. available_formatted: '8.0 GB',
  82. used_bytes: 8589934592,
  83. used_formatted: '8.0 GB',
  84. percent_used: 50.0,
  85. },
  86. cpu: {
  87. count: 4,
  88. count_logical: 8,
  89. percent: 25.0,
  90. },
  91. };
  92. describe('SystemInfoPage', () => {
  93. beforeEach(() => {
  94. vi.clearAllMocks();
  95. });
  96. it('renders loading state initially', async () => {
  97. // Make the API call never resolve to test loading state
  98. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockImplementation(
  99. () => new Promise(() => {})
  100. );
  101. render(<SystemInfoPage />);
  102. // Should show loading spinner
  103. expect(document.querySelector('.animate-spin')).toBeInTheDocument();
  104. });
  105. it('renders system info when data loads', async () => {
  106. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  107. render(<SystemInfoPage />);
  108. await waitFor(() => {
  109. expect(screen.getByText('System Information')).toBeInTheDocument();
  110. });
  111. // Check for version
  112. expect(screen.getByText('v0.1.5b')).toBeInTheDocument();
  113. });
  114. it('displays application section', async () => {
  115. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  116. render(<SystemInfoPage />);
  117. await waitFor(() => {
  118. expect(screen.getByText('Application')).toBeInTheDocument();
  119. });
  120. expect(screen.getByText('v0.1.5b')).toBeInTheDocument();
  121. expect(screen.getByText('bambuddy-server')).toBeInTheDocument();
  122. expect(screen.getByText('1d')).toBeInTheDocument();
  123. });
  124. it('displays database statistics', async () => {
  125. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  126. render(<SystemInfoPage />);
  127. await waitFor(() => {
  128. expect(screen.getByText('Database')).toBeInTheDocument();
  129. });
  130. // Check archive counts
  131. expect(screen.getByText('150')).toBeInTheDocument(); // Total archives
  132. expect(screen.getByText('140')).toBeInTheDocument(); // Completed
  133. expect(screen.getByText('8')).toBeInTheDocument(); // Failed
  134. });
  135. it('displays connected printers', async () => {
  136. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  137. render(<SystemInfoPage />);
  138. await waitFor(() => {
  139. expect(screen.getByText('Connected Printers')).toBeInTheDocument();
  140. });
  141. // Check connected printer names
  142. expect(screen.getByText('X1C-01')).toBeInTheDocument();
  143. expect(screen.getByText('P1S-01')).toBeInTheDocument();
  144. // Check printer states
  145. expect(screen.getByText('IDLE')).toBeInTheDocument();
  146. expect(screen.getByText('RUNNING')).toBeInTheDocument();
  147. });
  148. it('displays storage information', async () => {
  149. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  150. render(<SystemInfoPage />);
  151. await waitFor(() => {
  152. expect(screen.getByText('Storage')).toBeInTheDocument();
  153. });
  154. expect(screen.getByText('1.0 GB')).toBeInTheDocument(); // Archive size
  155. expect(screen.getByText('10.0 MB')).toBeInTheDocument(); // Database size
  156. });
  157. it('displays memory usage', async () => {
  158. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  159. render(<SystemInfoPage />);
  160. await waitFor(() => {
  161. expect(screen.getByText('Memory')).toBeInTheDocument();
  162. });
  163. expect(screen.getByText('8.0 GB available')).toBeInTheDocument();
  164. });
  165. it('displays CPU information', async () => {
  166. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  167. render(<SystemInfoPage />);
  168. await waitFor(() => {
  169. expect(screen.getByText('CPU')).toBeInTheDocument();
  170. });
  171. expect(screen.getByText('4')).toBeInTheDocument(); // CPU cores
  172. expect(screen.getByText('25%')).toBeInTheDocument(); // CPU usage
  173. });
  174. it('displays system details', async () => {
  175. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  176. render(<SystemInfoPage />);
  177. await waitFor(() => {
  178. expect(screen.getByText('System Details')).toBeInTheDocument();
  179. });
  180. expect(screen.getByText('Linux')).toBeInTheDocument();
  181. expect(screen.getByText('x86_64')).toBeInTheDocument();
  182. expect(screen.getByText('3.11.0')).toBeInTheDocument(); // Python version
  183. });
  184. it('shows error state when data fails to load', async () => {
  185. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(null);
  186. render(<SystemInfoPage />);
  187. await waitFor(() => {
  188. expect(screen.getByText(/failed to load/i)).toBeInTheDocument();
  189. });
  190. });
  191. it('shows no printers message when none connected', async () => {
  192. const noConnectedPrinters = {
  193. ...mockSystemInfo,
  194. printers: {
  195. total: 3,
  196. connected: 0,
  197. connected_list: [],
  198. },
  199. };
  200. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(noConnectedPrinters);
  201. render(<SystemInfoPage />);
  202. await waitFor(() => {
  203. expect(screen.getByText(/no printers connected/i)).toBeInTheDocument();
  204. });
  205. });
  206. it('has refresh button', async () => {
  207. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  208. render(<SystemInfoPage />);
  209. await waitFor(() => {
  210. expect(screen.getByText('Refresh')).toBeInTheDocument();
  211. });
  212. });
  213. it('applies warning color for high disk usage', async () => {
  214. const highDiskUsage = {
  215. ...mockSystemInfo,
  216. storage: {
  217. ...mockSystemInfo.storage,
  218. disk_percent_used: 80,
  219. },
  220. };
  221. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(highDiskUsage);
  222. render(<SystemInfoPage />);
  223. await waitFor(() => {
  224. expect(screen.getByText('Storage')).toBeInTheDocument();
  225. });
  226. // The progress bar should have yellow color for 75-90% usage
  227. const progressBars = document.querySelectorAll('[class*="bg-yellow"]');
  228. expect(progressBars.length).toBeGreaterThan(0);
  229. });
  230. it('displays extended privacy disclosure items', async () => {
  231. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(mockSystemInfo);
  232. render(<SystemInfoPage />);
  233. await waitFor(() => {
  234. expect(screen.getByText("What's in the support bundle?")).toBeInTheDocument();
  235. });
  236. // Original items
  237. expect(screen.getByText(/App version and debug mode/)).toBeInTheDocument();
  238. expect(screen.getByText(/Debug logs \(sanitized\)/)).toBeInTheDocument();
  239. // New diagnostic items
  240. expect(screen.getByText(/Printer connectivity and firmware versions/)).toBeInTheDocument();
  241. expect(screen.getByText(/Integration status \(Spoolman, MQTT, HA\)/)).toBeInTheDocument();
  242. expect(screen.getByText(/Network interfaces \(subnets only\)/)).toBeInTheDocument();
  243. expect(screen.getByText(/Python package versions/)).toBeInTheDocument();
  244. expect(screen.getByText(/Database health checks/)).toBeInTheDocument();
  245. expect(screen.getByText(/Docker environment details/)).toBeInTheDocument();
  246. });
  247. it('applies danger color for critical disk usage', async () => {
  248. const criticalDiskUsage = {
  249. ...mockSystemInfo,
  250. storage: {
  251. ...mockSystemInfo.storage,
  252. disk_percent_used: 95,
  253. },
  254. };
  255. (api.getSystemInfo as ReturnType<typeof vi.fn>).mockResolvedValue(criticalDiskUsage);
  256. render(<SystemInfoPage />);
  257. await waitFor(() => {
  258. expect(screen.getByText('Storage')).toBeInTheDocument();
  259. });
  260. // The progress bar should have red color for >90% usage
  261. const progressBars = document.querySelectorAll('[class*="bg-red"]');
  262. expect(progressBars.length).toBeGreaterThan(0);
  263. });
  264. });