EmailSettingsWiring.test.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /**
  2. * Email settings: the SMTP save request carries the form values (and an
  3. * empty password when it was not retyped, so the server keeps the stored
  4. * one), the test email goes out with the form's settings, and the
  5. * advanced-auth toggle only calls enable/disable when its preconditions hold.
  6. */
  7. import { describe, it, expect, beforeEach } from 'vitest';
  8. import { screen, waitFor } from '@testing-library/react';
  9. import userEvent from '@testing-library/user-event';
  10. import { http, HttpResponse } from 'msw';
  11. import { render } from '../utils';
  12. import { server } from '../mocks/server';
  13. import { EmailSettings } from '../../components/EmailSettings';
  14. type Captured = { method: string; path: string; body: unknown };
  15. let captured: Captured[];
  16. let advancedStatus: { advanced_auth_enabled: boolean; smtp_configured: boolean };
  17. let authEnabled: boolean;
  18. const STORED_SMTP = {
  19. smtp_host: 'smtp.example',
  20. smtp_port: 587,
  21. smtp_username: 'mailer',
  22. smtp_password: null,
  23. smtp_security: 'starttls',
  24. smtp_auth_enabled: true,
  25. smtp_from_email: 'from@example.com',
  26. smtp_from_name: 'BamBuddy',
  27. };
  28. async function capture(request: Request) {
  29. const text = await request.text();
  30. captured.push({
  31. method: request.method,
  32. path: new URL(request.url).pathname,
  33. body: text ? JSON.parse(text) : null,
  34. });
  35. }
  36. beforeEach(() => {
  37. captured = [];
  38. advancedStatus = { advanced_auth_enabled: false, smtp_configured: true };
  39. authEnabled = true;
  40. server.use(
  41. http.get('*/api/v1/auth/status', () => HttpResponse.json({ auth_enabled: authEnabled, requires_setup: false })),
  42. http.get('*/api/v1/auth/smtp', () => HttpResponse.json(STORED_SMTP)),
  43. http.get('*/api/v1/auth/advanced-auth/status', () => HttpResponse.json(advancedStatus)),
  44. http.post('*/api/v1/auth/smtp', async ({ request }) => {
  45. await capture(request);
  46. return HttpResponse.json({ message: 'ok' });
  47. }),
  48. http.post('*/api/v1/auth/smtp/test', async ({ request }) => {
  49. await capture(request);
  50. return HttpResponse.json({ success: true, message: 'sent' });
  51. }),
  52. http.post('*/api/v1/auth/advanced-auth/enable', async ({ request }) => {
  53. await capture(request);
  54. return HttpResponse.json({ message: 'enabled', advanced_auth_enabled: true });
  55. }),
  56. http.post('*/api/v1/auth/advanced-auth/disable', async ({ request }) => {
  57. await capture(request);
  58. return HttpResponse.json({ message: 'disabled', advanced_auth_enabled: false });
  59. }),
  60. );
  61. });
  62. describe('EmailSettings wiring', () => {
  63. it('save sends host/port/security/from and an empty password when it was not retyped', async () => {
  64. const user = userEvent.setup();
  65. render(<EmailSettings />);
  66. await screen.findByDisplayValue('smtp.example');
  67. const from = screen.getByDisplayValue('from@example.com');
  68. await user.clear(from);
  69. await user.type(from, 'printer@example.com');
  70. await user.click(screen.getByRole('button', { name: /save settings/i }));
  71. await waitFor(() => expect(captured.filter((c) => c.path.endsWith('/auth/smtp'))).toHaveLength(1));
  72. const req = captured[0];
  73. expect(req.method).toBe('POST');
  74. expect(req.body).toEqual({
  75. smtp_host: 'smtp.example',
  76. smtp_port: 587,
  77. smtp_username: 'mailer',
  78. smtp_password: '',
  79. smtp_security: 'starttls',
  80. smtp_auth_enabled: true,
  81. smtp_from_email: 'printer@example.com',
  82. smtp_from_name: 'BamBuddy',
  83. });
  84. });
  85. it('changing security to SSL sends ssl with port 465, and a retyped password is sent', async () => {
  86. const user = userEvent.setup();
  87. render(<EmailSettings />);
  88. await screen.findByDisplayValue('smtp.example');
  89. await user.selectOptions(screen.getByDisplayValue('STARTTLS (Port 587)'), 'ssl');
  90. await user.type(document.querySelector('input[type="password"]') as HTMLInputElement, 'test-password-1');
  91. await user.click(screen.getByRole('button', { name: /save settings/i }));
  92. await waitFor(() => expect(captured).toHaveLength(1));
  93. expect(captured[0].body).toMatchObject({
  94. smtp_security: 'ssl',
  95. smtp_port: 465,
  96. smtp_password: 'test-password-1',
  97. });
  98. });
  99. it('save without a host sends nothing', async () => {
  100. const user = userEvent.setup();
  101. render(<EmailSettings />);
  102. await user.clear(await screen.findByDisplayValue('smtp.example'));
  103. await user.click(screen.getByRole('button', { name: /save settings/i }));
  104. expect(await screen.findByText(/please fill in all required fields/i)).toBeInTheDocument();
  105. expect(captured).toHaveLength(0);
  106. });
  107. it('send test email posts the recipient and the form as it is, unsaved edits included', async () => {
  108. const user = userEvent.setup();
  109. render(<EmailSettings />);
  110. await screen.findByDisplayValue('smtp.example');
  111. const from = screen.getByDisplayValue('from@example.com');
  112. await user.clear(from);
  113. await user.type(from, 'typed@example.com');
  114. await user.type(screen.getByPlaceholderText('test@example.com'), 'probe@example.com');
  115. await user.click(screen.getByRole('button', { name: /send test email/i }));
  116. await waitFor(() => expect(captured).toHaveLength(1));
  117. expect(captured[0]).toEqual({
  118. method: 'POST',
  119. path: '/api/v1/auth/smtp/test',
  120. body: {
  121. test_recipient: 'probe@example.com',
  122. smtp: { ...STORED_SMTP, smtp_password: '', smtp_from_email: 'typed@example.com' },
  123. },
  124. });
  125. });
  126. it('enable advanced auth calls enable once SMTP is configured', async () => {
  127. const user = userEvent.setup();
  128. render(<EmailSettings />);
  129. await screen.findByText(/advanced authentication is disabled/i);
  130. // Wait for AuthProvider to report auth enabled before clicking
  131. await waitFor(async () => {
  132. captured = [];
  133. await user.click(screen.getByRole('button', { name: /^enable$/i }));
  134. expect(captured.map((c) => c.path)).toContain('/api/v1/auth/advanced-auth/enable');
  135. });
  136. expect(captured.map((c) => c.path)).not.toContain('/api/v1/auth/advanced-auth/disable');
  137. });
  138. it('enable advanced auth sends nothing while SMTP is not configured/tested', async () => {
  139. advancedStatus = { advanced_auth_enabled: false, smtp_configured: false };
  140. const user = userEvent.setup();
  141. render(<EmailSettings />);
  142. await screen.findByText(/advanced authentication is disabled/i);
  143. // Click until auth status has loaded and the SMTP precondition is the one refusing
  144. await waitFor(async () => {
  145. await user.click(screen.getByRole('button', { name: /^enable$/i }));
  146. expect(screen.getAllByText(/configure and test smtp settings first/i).length).toBeGreaterThan(0);
  147. });
  148. expect(captured).toHaveLength(0);
  149. });
  150. it('enable advanced auth sends nothing while app authentication is off', async () => {
  151. authEnabled = false;
  152. const user = userEvent.setup();
  153. render(<EmailSettings />);
  154. await screen.findByText(/advanced authentication is disabled/i);
  155. await user.click(screen.getByRole('button', { name: /^enable$/i }));
  156. expect(await screen.findByText(/enable authentication first/i)).toBeInTheDocument();
  157. expect(captured).toHaveLength(0);
  158. });
  159. it('disable advanced auth calls disable when it is on', async () => {
  160. advancedStatus = { advanced_auth_enabled: true, smtp_configured: true };
  161. const user = userEvent.setup();
  162. render(<EmailSettings />);
  163. await screen.findByText(/advanced authentication is enabled/i);
  164. await waitFor(async () => {
  165. captured = [];
  166. await user.click(screen.getByRole('button', { name: /^disable$/i }));
  167. expect(captured.map((c) => c.path)).toContain('/api/v1/auth/advanced-auth/disable');
  168. });
  169. expect(captured.map((c) => c.path)).not.toContain('/api/v1/auth/advanced-auth/enable');
  170. });
  171. });