From 3565503da4715099e845be8d3464381cd9330729 Mon Sep 17 00:00:00 2001 From: Sascha Date: Thu, 30 Jul 2026 10:42:44 +0200 Subject: [PATCH] fix: allow bare domains in URL validation and update related error messages --- backend/routes/audit.js | 4 ++- .../audit/__tests__/AuditForm.test.tsx | 25 ++++++++----------- src/config/locales/de/audit.ts | 1 - src/config/locales/en/TextConfig.ts | 1 - src/config/locales/en/audit.ts | 1 - 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/backend/routes/audit.js b/backend/routes/audit.js index 1a13497..8c62264 100644 --- a/backend/routes/audit.js +++ b/backend/routes/audit.js @@ -22,9 +22,11 @@ const validateAuditRequest = [ body('name').trim().isLength({ min: 1, max: 100 }).escape(), body('company').trim().isLength({ min: 1, max: 150 }).escape(), body('email').trim().isEmail().normalizeEmail().isLength({ max: 254 }), + // No require_protocol: people paste bare domains ("example.com") too. + // protocols still pins an explicit scheme, if given, to http/https. body('url') .trim() - .isURL({ protocols: ['http', 'https'], require_protocol: true }) + .isURL({ protocols: ['http', 'https'] }) .isLength({ max: 2048 }), body('role').isIn(['agency', 'client']), body('motivation').trim().isLength({ min: 1, max: 2000 }).escape(), diff --git a/src/components/audit/__tests__/AuditForm.test.tsx b/src/components/audit/__tests__/AuditForm.test.tsx index 6cfaecd..d8fe2f5 100644 --- a/src/components/audit/__tests__/AuditForm.test.tsx +++ b/src/components/audit/__tests__/AuditForm.test.tsx @@ -73,28 +73,25 @@ describe('AuditForm', () => { }); }); - it('rejects a URL without a protocol and wires the message to the field', async () => { + it('accepts a bare domain without a protocol', async () => { const user = userEvent.setup(); + const fetchSpy = vi + .spyOn(globalThis, 'fetch') + .mockResolvedValue(new Response(null, { status: 200 })); renderForm(); + await fillValidForm(user); const urlField = screen.getByLabelText('Welche Seite soll ich prüfen?'); + await user.clear(urlField); await user.type(urlField, 'example.de'); await user.click(screen.getByRole('button', { name: 'Audit anfragen' })); - await waitFor(() => expect(urlField).toHaveAttribute('aria-invalid', 'true')); + await waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(1)); + expect(urlField).not.toHaveAttribute('aria-invalid', 'true'); - // The message deliberately appears twice - once in the error summary and - // once at the field. Assert the one a screen reader reads out for the - // field, reached through aria-describedby. - const describedBy = urlField.getAttribute('aria-describedby') ?? ''; - const errorNode = describedBy - .split(' ') - .map((id) => document.getElementById(id)) - .find((node) => node?.className.includes('audit-form__error')); - - expect(errorNode).toHaveTextContent( - 'Bitte gib die vollständige Adresse an, inklusive https://' - ); + const [, init] = fetchSpy.mock.calls[0]; + const payload = JSON.parse(String(init?.body)); + expect(payload.url).toBe('example.de'); }); it('posts to the configured endpoint and reports success', async () => { diff --git a/src/config/locales/de/audit.ts b/src/config/locales/de/audit.ts index 53dc204..d338034 100644 --- a/src/config/locales/de/audit.ts +++ b/src/config/locales/de/audit.ts @@ -96,7 +96,6 @@ export const audit = { urlLabel: 'Welche Seite soll ich prüfen?', urlHint: 'Die vollständige Adresse einer einzelnen Seite, zum Beispiel https://deine-firma.de/kontakt', urlErrorRequired: 'Bitte nenn mir die Seite, die ich prüfen soll.', - urlErrorInvalid: 'Bitte gib die vollständige Adresse an, inklusive https://', roleLegend: 'Wer bist du?', roleAgency: 'Agentur oder Dienstleister', diff --git a/src/config/locales/en/TextConfig.ts b/src/config/locales/en/TextConfig.ts index 1a1fd77..92752f9 100644 --- a/src/config/locales/en/TextConfig.ts +++ b/src/config/locales/en/TextConfig.ts @@ -376,7 +376,6 @@ export interface TextConfig { urlLabel: string; urlHint: string; urlErrorRequired: string; - urlErrorInvalid: string; roleLegend: string; roleAgency: string; roleClient: string; diff --git a/src/config/locales/en/audit.ts b/src/config/locales/en/audit.ts index e5f9c3a..6b0a225 100644 --- a/src/config/locales/en/audit.ts +++ b/src/config/locales/en/audit.ts @@ -96,7 +96,6 @@ export const audit = { urlLabel: 'Which page should I review?', urlHint: 'The full address of a single page, for example https://your-company.com/contact', urlErrorRequired: 'Please tell me which page to review.', - urlErrorInvalid: 'Please give the full address, including https://', roleLegend: 'Who are you?', roleAgency: 'Agency or service provider',