/** * Email settings: the SMTP save request carries the form values (and an * empty password when it was not retyped, so the server keeps the stored * one), the test email goes out with the form's settings, and the * advanced-auth toggle only calls enable/disable when its preconditions hold. */ import { describe, it, expect, beforeEach } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { render } from '../utils'; import { server } from '../mocks/server'; import { EmailSettings } from '../../components/EmailSettings'; type Captured = { method: string; path: string; body: unknown }; let captured: Captured[]; let advancedStatus: { advanced_auth_enabled: boolean; smtp_configured: boolean }; let authEnabled: boolean; const STORED_SMTP = { smtp_host: 'smtp.example', smtp_port: 587, smtp_username: 'mailer', smtp_password: null, smtp_security: 'starttls', smtp_auth_enabled: true, smtp_from_email: 'from@example.com', smtp_from_name: 'BamBuddy', }; async function capture(request: Request) { const text = await request.text(); captured.push({ method: request.method, path: new URL(request.url).pathname, body: text ? JSON.parse(text) : null, }); } beforeEach(() => { captured = []; advancedStatus = { advanced_auth_enabled: false, smtp_configured: true }; authEnabled = true; server.use( http.get('*/api/v1/auth/status', () => HttpResponse.json({ auth_enabled: authEnabled, requires_setup: false })), http.get('*/api/v1/auth/smtp', () => HttpResponse.json(STORED_SMTP)), http.get('*/api/v1/auth/advanced-auth/status', () => HttpResponse.json(advancedStatus)), http.post('*/api/v1/auth/smtp', async ({ request }) => { await capture(request); return HttpResponse.json({ message: 'ok' }); }), http.post('*/api/v1/auth/smtp/test', async ({ request }) => { await capture(request); return HttpResponse.json({ success: true, message: 'sent' }); }), http.post('*/api/v1/auth/advanced-auth/enable', async ({ request }) => { await capture(request); return HttpResponse.json({ message: 'enabled', advanced_auth_enabled: true }); }), http.post('*/api/v1/auth/advanced-auth/disable', async ({ request }) => { await capture(request); return HttpResponse.json({ message: 'disabled', advanced_auth_enabled: false }); }), ); }); describe('EmailSettings wiring', () => { it('save sends host/port/security/from and an empty password when it was not retyped', async () => { const user = userEvent.setup(); render(); await screen.findByDisplayValue('smtp.example'); const from = screen.getByDisplayValue('from@example.com'); await user.clear(from); await user.type(from, 'printer@example.com'); await user.click(screen.getByRole('button', { name: /save settings/i })); await waitFor(() => expect(captured.filter((c) => c.path.endsWith('/auth/smtp'))).toHaveLength(1)); const req = captured[0]; expect(req.method).toBe('POST'); expect(req.body).toEqual({ smtp_host: 'smtp.example', smtp_port: 587, smtp_username: 'mailer', smtp_password: '', smtp_security: 'starttls', smtp_auth_enabled: true, smtp_from_email: 'printer@example.com', smtp_from_name: 'BamBuddy', }); }); it('changing security to SSL sends ssl with port 465, and a retyped password is sent', async () => { const user = userEvent.setup(); render(); await screen.findByDisplayValue('smtp.example'); await user.selectOptions(screen.getByDisplayValue('STARTTLS (Port 587)'), 'ssl'); await user.type(document.querySelector('input[type="password"]') as HTMLInputElement, 'test-password-1'); await user.click(screen.getByRole('button', { name: /save settings/i })); await waitFor(() => expect(captured).toHaveLength(1)); expect(captured[0].body).toMatchObject({ smtp_security: 'ssl', smtp_port: 465, smtp_password: 'test-password-1', }); }); it('save without a host sends nothing', async () => { const user = userEvent.setup(); render(); await user.clear(await screen.findByDisplayValue('smtp.example')); await user.click(screen.getByRole('button', { name: /save settings/i })); expect(await screen.findByText(/please fill in all required fields/i)).toBeInTheDocument(); expect(captured).toHaveLength(0); }); it('send test email posts the recipient and the form as it is, unsaved edits included', async () => { const user = userEvent.setup(); render(); await screen.findByDisplayValue('smtp.example'); const from = screen.getByDisplayValue('from@example.com'); await user.clear(from); await user.type(from, 'typed@example.com'); await user.type(screen.getByPlaceholderText('test@example.com'), 'probe@example.com'); await user.click(screen.getByRole('button', { name: /send test email/i })); await waitFor(() => expect(captured).toHaveLength(1)); expect(captured[0]).toEqual({ method: 'POST', path: '/api/v1/auth/smtp/test', body: { test_recipient: 'probe@example.com', smtp: { ...STORED_SMTP, smtp_password: '', smtp_from_email: 'typed@example.com' }, }, }); }); it('enable advanced auth calls enable once SMTP is configured', async () => { const user = userEvent.setup(); render(); await screen.findByText(/advanced authentication is disabled/i); // Wait for AuthProvider to report auth enabled before clicking await waitFor(async () => { captured = []; await user.click(screen.getByRole('button', { name: /^enable$/i })); expect(captured.map((c) => c.path)).toContain('/api/v1/auth/advanced-auth/enable'); }); expect(captured.map((c) => c.path)).not.toContain('/api/v1/auth/advanced-auth/disable'); }); it('enable advanced auth sends nothing while SMTP is not configured/tested', async () => { advancedStatus = { advanced_auth_enabled: false, smtp_configured: false }; const user = userEvent.setup(); render(); await screen.findByText(/advanced authentication is disabled/i); // Click until auth status has loaded and the SMTP precondition is the one refusing await waitFor(async () => { await user.click(screen.getByRole('button', { name: /^enable$/i })); expect(screen.getAllByText(/configure and test smtp settings first/i).length).toBeGreaterThan(0); }); expect(captured).toHaveLength(0); }); it('enable advanced auth sends nothing while app authentication is off', async () => { authEnabled = false; const user = userEvent.setup(); render(); await screen.findByText(/advanced authentication is disabled/i); await user.click(screen.getByRole('button', { name: /^enable$/i })); expect(await screen.findByText(/enable authentication first/i)).toBeInTheDocument(); expect(captured).toHaveLength(0); }); it('disable advanced auth calls disable when it is on', async () => { advancedStatus = { advanced_auth_enabled: true, smtp_configured: true }; const user = userEvent.setup(); render(); await screen.findByText(/advanced authentication is enabled/i); await waitFor(async () => { captured = []; await user.click(screen.getByRole('button', { name: /^disable$/i })); expect(captured.map((c) => c.path)).toContain('/api/v1/auth/advanced-auth/disable'); }); expect(captured.map((c) => c.path)).not.toContain('/api/v1/auth/advanced-auth/enable'); }); });