diff --git a/apps/web/src/app/(portal)/settings/general/smtp/smtp-settings.test.tsx b/apps/web/src/app/(portal)/settings/general/smtp/smtp-settings.test.tsx new file mode 100644 index 0000000..7309f82 --- /dev/null +++ b/apps/web/src/app/(portal)/settings/general/smtp/smtp-settings.test.tsx @@ -0,0 +1,192 @@ +import { cleanup, render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +// Mock next-intl — returns a short readable string for each key +vi.mock('next-intl', () => ({ + useTranslations: (ns: string) => (key: string) => { + const map: Record> = { + settings: { + 'smtp.title': 'SMTP-Konfiguration', + 'smtp.host': 'Host', + 'smtp.port': 'Port', + 'smtp.encryption': 'Verschluesselung', + 'smtp.username': 'Benutzername', + 'smtp.password': 'Passwort', + 'smtp.fromAddress': 'Absenderadresse (Von)', + 'smtp.fromAddressHelp': 'Die E-Mail-Adresse, die als Absender angezeigt wird.', + 'smtp.save': 'Einstellungen speichern', + 'smtp.test': 'Verbindung testen', + 'smtp.showPassword': 'Passwort anzeigen', + 'smtp.hidePassword': 'Passwort verbergen', + 'smtp.testTesting': 'Verbindung wird getestet...', + 'smtp.testSuccess': 'Verbindung erfolgreich', + 'smtp.testFailed': 'Verbindung fehlgeschlagen', + }, + }; + return map[ns]?.[key] ?? key; + }, +})); + +// Mock @/lib/settings-api +const mockFetchSmtp = vi.fn(); +const mockSaveSmtp = vi.fn(); +const mockTestSmtp = vi.fn(); + +vi.mock('@/lib/settings-api', () => ({ + fetchSmtp: (...args: unknown[]) => mockFetchSmtp(...args), + saveSmtp: (...args: unknown[]) => mockSaveSmtp(...args), + testSmtp: (...args: unknown[]) => mockTestSmtp(...args), +})); + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +describe('SmtpSettingsForm', () => { + it('loads existing config and populates fields (password stays blank)', async () => { + mockFetchSmtp.mockResolvedValue({ + host: 'smtp.example.com', + port: 587, + encryption: 'starttls', + username: 'user@example.com', + fromAddress: 'tessera@example.com', + hasPassword: true, + }); + + const { SmtpSettingsForm } = await import( + '@/components/settings/smtp-settings-form' + ); + render(); + + await waitFor(() => { + expect( + (screen.getByLabelText('Host') as HTMLInputElement).value, + ).toBe('smtp.example.com'); + }); + + expect((screen.getByLabelText('Port') as HTMLInputElement).value).toBe( + '587', + ); + expect( + (screen.getByLabelText('Benutzername') as HTMLInputElement).value, + ).toBe('user@example.com'); + expect( + (screen.getByLabelText('Absenderadresse (Von)') as HTMLInputElement) + .value, + ).toBe('tessera@example.com'); + // T-07-17: password must never be pre-filled from server data + expect( + (screen.getByLabelText('Passwort') as HTMLInputElement).value, + ).toBe(''); + }); + + it('calls saveSmtp with form values on submit', async () => { + mockFetchSmtp.mockResolvedValue({ + host: 'smtp.example.com', + port: 587, + encryption: 'starttls', + username: 'user@example.com', + fromAddress: 'tessera@example.com', + hasPassword: false, + }); + mockSaveSmtp.mockResolvedValue({ + host: 'smtp.example.com', + port: 587, + encryption: 'starttls', + username: 'user@example.com', + fromAddress: 'tessera@example.com', + hasPassword: false, + }); + + const { SmtpSettingsForm } = await import( + '@/components/settings/smtp-settings-form' + ); + render(); + + await waitFor(() => { + expect( + (screen.getByLabelText('Host') as HTMLInputElement).value, + ).toBe('smtp.example.com'); + }); + + fireEvent.click(screen.getByText('Einstellungen speichern')); + + await waitFor(() => { + expect(mockSaveSmtp).toHaveBeenCalledWith( + expect.objectContaining({ + host: 'smtp.example.com', + port: 587, + encryption: 'starttls', + }), + ); + }); + }); + + it('shows success message when testSmtp resolves { success: true }', async () => { + mockFetchSmtp.mockResolvedValue(null); + mockTestSmtp.mockResolvedValue({ success: true }); + + const { SmtpSettingsForm } = await import( + '@/components/settings/smtp-settings-form' + ); + render(); + + await waitFor(() => + expect(screen.getByText('Verbindung testen')).toBeInTheDocument(), + ); + + fireEvent.click(screen.getByText('Verbindung testen')); + + await waitFor(() => { + expect( + screen.getByText('Verbindung erfolgreich'), + ).toBeInTheDocument(); + }); + }); + + it('shows error message when testSmtp resolves { success: false }', async () => { + mockFetchSmtp.mockResolvedValue(null); + mockTestSmtp.mockResolvedValue({ success: false }); + + const { SmtpSettingsForm } = await import( + '@/components/settings/smtp-settings-form' + ); + render(); + + await waitFor(() => + expect(screen.getByText('Verbindung testen')).toBeInTheDocument(), + ); + + fireEvent.click(screen.getByText('Verbindung testen')); + + await waitFor(() => { + expect( + screen.getByText('Verbindung fehlgeschlagen'), + ).toBeInTheDocument(); + }); + }); + + it('toggles password input type between password and text', async () => { + mockFetchSmtp.mockResolvedValue(null); + + const { SmtpSettingsForm } = await import( + '@/components/settings/smtp-settings-form' + ); + render(); + + await waitFor(() => + expect(screen.getByLabelText('Passwort')).toBeInTheDocument(), + ); + + const passwordInput = screen.getByLabelText('Passwort') as HTMLInputElement; + expect(passwordInput.type).toBe('password'); + + const toggleButton = screen.getByRole('button', { + name: 'Passwort anzeigen', + }); + fireEvent.click(toggleButton); + + expect(passwordInput.type).toBe('text'); + }); +});