SettingsPageSecrets.test.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. /**
  2. * Saved settings secrets come back as a placeholder. Kept, they are not sent
  3. * anywhere new: changing the MQTT broker holds the auto-save until the
  4. * password is entered again, and the field says so.
  5. */
  6. import { describe, it, expect, beforeEach, vi } from 'vitest';
  7. import { screen, waitFor } from '@testing-library/react';
  8. import userEvent from '@testing-library/user-event';
  9. import { http, HttpResponse } from 'msw';
  10. import { render } from '../utils';
  11. import { server } from '../mocks/server';
  12. import { SettingsPage } from '../../pages/SettingsPage';
  13. import { setAuthToken } from '../../api/client';
  14. const M = '********';
  15. const settings = {
  16. auto_archive: true,
  17. save_thumbnails: true,
  18. capture_finish_photo: true,
  19. default_filament_cost: 25.0,
  20. currency: 'USD',
  21. time_format: 'system',
  22. date_format: 'system',
  23. mqtt_enabled: true,
  24. mqtt_broker: 'broker.example',
  25. mqtt_port: 1883,
  26. mqtt_username: 'u',
  27. mqtt_password: M,
  28. mqtt_topic_prefix: 'bambuddy',
  29. mqtt_use_tls: false,
  30. ha_enabled: false,
  31. ha_url: '',
  32. ha_token: '',
  33. check_updates: false,
  34. check_printer_firmware: false,
  35. };
  36. describe('SettingsPage saved secrets', () => {
  37. let saves: Record<string, unknown>[];
  38. beforeEach(() => {
  39. window.history.replaceState({}, '', '/');
  40. vi.mocked(localStorage.getItem).mockReset();
  41. localStorage.clear();
  42. setAuthToken(null);
  43. saves = [];
  44. server.use(
  45. http.get('/api/v1/settings/', () => HttpResponse.json(settings)),
  46. http.put('/api/v1/settings/', async ({ request }) => {
  47. const body = (await request.json()) as Record<string, unknown>;
  48. saves.push(body);
  49. return HttpResponse.json({ ...settings, ...body });
  50. }),
  51. http.get('/api/v1/printers/', () => HttpResponse.json([])),
  52. http.get('/api/v1/smart-plugs/', () => HttpResponse.json([])),
  53. http.get('/api/v1/notifications/', () => HttpResponse.json([])),
  54. http.get('/api/v1/api-keys/', () => HttpResponse.json([])),
  55. http.get('/api/v1/mqtt/status', () => HttpResponse.json({ enabled: true })),
  56. http.get('/api/v1/virtual-printer/status', () => HttpResponse.json({ running: false })),
  57. http.get('/api/v1/auth/status', () => HttpResponse.json({ auth_enabled: false, requires_setup: false })),
  58. http.get('/api/v1/external-links/', () => HttpResponse.json([])),
  59. );
  60. });
  61. async function openNetwork() {
  62. const user = userEvent.setup();
  63. render(<SettingsPage />);
  64. await screen.findByText('Date Format');
  65. await user.click(screen.getByText('Network'));
  66. await screen.findByText('MQTT Publishing');
  67. // The page suppresses auto-save right after the settings load.
  68. await new Promise((resolve) => setTimeout(resolve, 200));
  69. return user;
  70. }
  71. it('saves a new Home Assistant URL when the token comes from the environment', async () => {
  72. server.use(
  73. http.get('/api/v1/settings/', () =>
  74. HttpResponse.json({ ...settings, ha_enabled: true, ha_url: 'http://ha.example:8123', ha_token: M, ha_token_from_env: true }),
  75. ),
  76. );
  77. const user = userEvent.setup();
  78. render(<SettingsPage />);
  79. await screen.findByText('Date Format');
  80. await user.click(screen.getByText('Network'));
  81. await new Promise((resolve) => setTimeout(resolve, 200));
  82. const url = await screen.findByDisplayValue('http://ha.example:8123');
  83. await user.clear(url);
  84. await user.type(url, 'http://ha.new.example:8123');
  85. await waitFor(() => expect(saves.length).toBeGreaterThan(0), { timeout: 3000 });
  86. expect(saves.at(-1)).toMatchObject({ ha_url: 'http://ha.new.example:8123' });
  87. });
  88. it('says the saved password is kept', async () => {
  89. await openNetwork();
  90. expect(await screen.findByText(/leave it as is to keep it/i)).toBeInTheDocument();
  91. });
  92. it('holds the save and asks for the password again when the broker changes', async () => {
  93. const user = await openNetwork();
  94. const broker = screen.getByPlaceholderText('mqtt.example.com or 192.168.1.100');
  95. await user.clear(broker);
  96. await user.type(broker, 'broker.elsewhere.example');
  97. expect(await screen.findByText(/the address changed: enter it again/i)).toBeInTheDocument();
  98. await new Promise((resolve) => setTimeout(resolve, 1200));
  99. expect(saves).toEqual([]);
  100. const password = screen
  101. .getAllByPlaceholderText('Leave empty for anonymous')
  102. .find((el) => el.getAttribute('type') === 'password')!;
  103. await user.click(password);
  104. await user.keyboard('new-pw');
  105. await waitFor(() => expect(saves.length).toBeGreaterThan(0), { timeout: 3000 });
  106. expect(saves.at(-1)).toMatchObject({ mqtt_broker: 'broker.elsewhere.example', mqtt_password: 'new-pw' });
  107. });
  108. });