diff --git a/apps/web/src/components/settings/smtp-settings-form.test.tsx b/apps/web/src/components/settings/smtp-settings-form.test.tsx
new file mode 100644
index 0000000..b703c39
--- /dev/null
+++ b/apps/web/src/components/settings/smtp-settings-form.test.tsx
@@ -0,0 +1,76 @@
+import { cleanup, render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import de from '@/messages/de.json';
+import { fetchSmtp, saveSmtp } from '@/lib/settings-api';
+import { SmtpSettingsForm } from './smtp-settings-form';
+
+/**
+ * smtp-settings-form.test — NEU (quick-260914-m97). Das SMTP-Formular
+ * hatte bisher keine Testdatei. Zwei Tests fuer das neue Feld
+ * „Fehlermeldungen an“: Vorbelegung aus GET /settings/smtp und der
+ * PUT-Vertrag mit `saveSmtpConfig` (Wert bzw. `null` zum Loeschen).
+ */
+vi.mock('@/lib/settings-api', () => ({
+ fetchSmtp: vi.fn(),
+ saveSmtp: vi.fn(),
+ testSmtp: vi.fn(),
+}));
+
+vi.mock('next-intl', async () => {
+ const messages = (await import('@/messages/de.json')).default as Record;
+ const lookup = (path: string): string | undefined =>
+ path.split('.').reduce((o, k) => (o && typeof o === 'object' ? (o as any)[k] : undefined), messages) as
+ | string
+ | undefined;
+ return {
+ useTranslations: (ns?: string) => (key: string) => lookup(ns ? `${ns}.${key}` : key) ?? key,
+ };
+});
+
+const LABEL = de.settings.smtp.bugReportRecipient;
+
+beforeEach(() => {
+ vi.mocked(fetchSmtp).mockResolvedValue({
+ host: 'h',
+ port: 587,
+ encryption: 'starttls',
+ fromAddress: 'a@b.invalid',
+ hasPassword: false,
+ bugReportRecipient: 'fehler@b.invalid',
+ });
+ vi.mocked(saveSmtp).mockResolvedValue({} as any);
+});
+
+afterEach(() => {
+ cleanup();
+ vi.clearAllMocks();
+});
+
+describe('SmtpSettingsForm — Feld Fehlermeldungen an (quick-260914-m97)', () => {
+ it('Test 1: das Feld ist aus GET /settings/smtp vorbelegt', async () => {
+ render();
+
+ const input = (await screen.findByLabelText(LABEL)) as HTMLInputElement;
+ await waitFor(() => expect(input.value).toBe('fehler@b.invalid'));
+ expect(input.type).toBe('email');
+ });
+
+ it('Test 2: PUT-Payload traegt den Wert; leeres Feld -> null', async () => {
+ const user = userEvent.setup();
+ render();
+ const input = (await screen.findByLabelText(LABEL)) as HTMLInputElement;
+ await waitFor(() => expect(input.value).toBe('fehler@b.invalid'));
+
+ await user.clear(input);
+ await user.type(input, 'neu@b.invalid');
+ await user.click(screen.getByRole('button', { name: de.settings.smtp.save }));
+ await waitFor(() => expect(saveSmtp).toHaveBeenCalledTimes(1));
+ expect(vi.mocked(saveSmtp).mock.calls[0][0]).toMatchObject({ bugReportRecipient: 'neu@b.invalid' });
+
+ await user.clear(input);
+ await user.click(screen.getByRole('button', { name: de.settings.smtp.save }));
+ await waitFor(() => expect(saveSmtp).toHaveBeenCalledTimes(2));
+ expect(vi.mocked(saveSmtp).mock.calls[1][0]).toMatchObject({ bugReportRecipient: null });
+ });
+});
diff --git a/apps/web/src/components/settings/smtp-settings-form.tsx b/apps/web/src/components/settings/smtp-settings-form.tsx
index 909221a..57c65d2 100644
--- a/apps/web/src/components/settings/smtp-settings-form.tsx
+++ b/apps/web/src/components/settings/smtp-settings-form.tsx
@@ -23,6 +23,7 @@ interface FormState {
username: string;
password: string; // T-07-17: always starts blank; never pre-filled from server
fromAddress: string;
+ bugReportRecipient: string; // quick-260914-m97: Postfach fuer den Fehler-melden-Knopf
}
const DEFAULT_FORM: FormState = {
@@ -32,6 +33,7 @@ const DEFAULT_FORM: FormState = {
username: '',
password: '',
fromAddress: '',
+ bugReportRecipient: '',
};
type TestFeedback =
@@ -70,6 +72,7 @@ export function SmtpSettingsForm() {
username: config.username ?? '',
password: '', // T-07-17: never pre-filled
fromAddress: config.fromAddress,
+ bugReportRecipient: config.bugReportRecipient ?? '',
});
}
})
@@ -95,6 +98,8 @@ export function SmtpSettingsForm() {
if (form.username.trim()) payload.username = form.username.trim();
// T-07-17: only include password when user has typed a new one
if (form.password) payload.password = form.password;
+ // quick-260914-m97: das Formular ist der einzige Klient — leer bedeutet loeschen (null)
+ payload.bugReportRecipient = form.bugReportRecipient.trim() || null;
return payload;
};
@@ -300,6 +305,26 @@ export function SmtpSettingsForm() {
+ {/* Fehlermeldungen an (quick-260914-m97) */}
+
+
+
+ setForm((prev) => ({ ...prev, bugReportRecipient: e.target.value }))
+ }
+ />
+
+ {t('smtp.bugReportRecipientHelp')}
+
+
+
{/* Test-E-Mail Empfänger */}