FailureDetectionSettings.test.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. /**
  2. * Tests for the Failure Detection settings component (#172).
  3. */
  4. import { describe, it, expect, vi, beforeEach } from 'vitest';
  5. import { screen, waitFor } from '@testing-library/react';
  6. import userEvent from '@testing-library/user-event';
  7. import { render } from '../utils';
  8. import { FailureDetectionSettings } from '../../components/FailureDetectionSettings';
  9. import { http, HttpResponse } from 'msw';
  10. import { server } from '../mocks/server';
  11. const baseSettings = {
  12. auto_archive: true,
  13. save_thumbnails: true,
  14. capture_finish_photo: true,
  15. default_filament_cost: 25,
  16. currency: 'USD',
  17. energy_cost_per_kwh: 0.15,
  18. energy_tracking_mode: 'total',
  19. check_updates: true,
  20. check_printer_firmware: true,
  21. include_beta_updates: false,
  22. obico_enabled: false,
  23. obico_ml_url: '',
  24. obico_sensitivity: 'medium',
  25. obico_action: 'notify',
  26. obico_poll_interval: 10,
  27. obico_enabled_printers: '',
  28. };
  29. const baseStatus = {
  30. is_running: true,
  31. last_error: null,
  32. per_printer: {},
  33. thresholds: { low: 0.38, high: 0.78 },
  34. history: [],
  35. enabled: false,
  36. ml_url: '',
  37. sensitivity: 'medium',
  38. action: 'notify',
  39. poll_interval: 10,
  40. };
  41. describe('FailureDetectionSettings', () => {
  42. beforeEach(() => {
  43. vi.clearAllMocks();
  44. server.use(
  45. http.get('/api/v1/settings/', () => HttpResponse.json(baseSettings)),
  46. http.get('/api/v1/obico/status', () => HttpResponse.json(baseStatus)),
  47. http.get('/api/v1/printers', () => HttpResponse.json([])),
  48. );
  49. });
  50. it('renders headings and fields', async () => {
  51. render(<FailureDetectionSettings />);
  52. await waitFor(() => {
  53. expect(screen.getByText(/AI Failure Detection|Failure Detection/i)).toBeInTheDocument();
  54. });
  55. expect(screen.getByText(/Obico ML API URL/i)).toBeInTheDocument();
  56. expect(screen.getByText(/Sensitivity/i)).toBeInTheDocument();
  57. });
  58. it('test button calls the test-connection endpoint and shows success', async () => {
  59. let called = false;
  60. server.use(
  61. http.get('/api/v1/settings/', () =>
  62. HttpResponse.json({ ...baseSettings, obico_enabled: true, obico_ml_url: 'http://obico:3333' }),
  63. ),
  64. http.post('/api/v1/obico/test-connection', async ({ request }) => {
  65. called = true;
  66. const body = (await request.json()) as { url: string };
  67. expect(body.url).toBe('http://obico:3333');
  68. return HttpResponse.json({ ok: true, status_code: 200, body: 'ok', error: null });
  69. }),
  70. );
  71. render(<FailureDetectionSettings />);
  72. const testBtn = await screen.findByRole('button', { name: /test/i });
  73. await userEvent.click(testBtn);
  74. await waitFor(() => {
  75. expect(called).toBe(true);
  76. });
  77. expect(await screen.findByText(/ML API reachable/i)).toBeInTheDocument();
  78. });
  79. it('shows failure class history entries with red styling', async () => {
  80. server.use(
  81. http.get('/api/v1/obico/status', () =>
  82. HttpResponse.json({
  83. ...baseStatus,
  84. history: [
  85. {
  86. printer_id: 1,
  87. task_name: 'test.3mf',
  88. timestamp: '2026-04-13T10:00:00Z',
  89. current_p: 0.9,
  90. score: 0.85,
  91. class: 'failure',
  92. detections: 1,
  93. },
  94. ],
  95. }),
  96. ),
  97. );
  98. render(<FailureDetectionSettings />);
  99. // Match the history row's score-and-class text, which looks like "failure 0.850"
  100. expect(await screen.findByText(/failure\s+0\.850/)).toBeInTheDocument();
  101. });
  102. });