From c7061837fb1d39737d0e2935faf3039eadbba647 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 23:10:23 +0000 Subject: [PATCH] =?UTF-8?q?Workers=20P4=20Endpr=C3=BCfung=20W5:=20?= =?UTF-8?q?=E2=80=9EFreigeben=20&=20senden=E2=80=9C=20ist=20ohne=20g=C3=BC?= =?UTF-8?q?ltige=20Vorschau-Adresse=20gesperrt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bei einer http- oder fremden Adresse zeigte die Zeile „Keine gültige Vorschau-Adresse hinterlegt.“ statt des Links, ließ aber das Freigeben zu: der Mensch gab eine Vorschau frei, die er nicht ansehen kann. Der Knopf ist jetzt gesperrt (auch approve() prüft previewHref), der Hinweis nennt Grund und Ausweg („Entwurf prüfen oder verwerfen“), Verwerfen bleibt frei. Das Zeilenmodell (canApprovePreview) bleibt unverändert. Das Sperren schließt fail-closed: weichen baseUrl des Dienstes und PREVIEW_HOST in WOMS einmal ab, ist Freigeben gesperrt, bis WOMS angepasst ist. Prüfstand prüft den gesperrten Knopf. Co-Authored-By: Claude Sonnet 5.5 --- scripts/workers-harness/shot.cjs | 4 ++++ src/components/workers/PultList.jsx | 8 ++++--- tests/unit/workersPultUi.test.mjs | 35 +++++++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/scripts/workers-harness/shot.cjs b/scripts/workers-harness/shot.cjs index 97136d0..42f9e8b 100644 --- a/scripts/workers-harness/shot.cjs +++ b/scripts/workers-harness/shot.cjs @@ -1229,6 +1229,10 @@ async function vorschau(browser, base) { check('vorschau: Vorschau mit http-Adresse wird kein Link (Hinweis „Keine gültige Vorschau-Adresse hinterlegt.“)', !fakeLinks.some((href) => /entwurf\.project\.webklar\.com/.test(href || '')) && (await textOf(falsch.locator('.wk-pult-actions'))).includes(NO_PREVIEW_URL), fakeLinks) const falschApprove = falsch.getByRole('button', { name: 'Freigeben & senden', exact: true }) metrics.freigabeOhneVorschauLink = { knopfDa: (await falschApprove.count()) === 1, knopfFrei: (await falschApprove.count()) === 1 && (await falschApprove.isEnabled()) } + // Endprüfung W5: eine Vorschau ohne gültige Adresse lässt sich nicht freigeben (der Mensch könnte sie nicht ansehen); der Hinweis nennt den Ausweg, Verwerfen bleibt frei + check('vorschau: ohne gültige Vorschau-Adresse ist „Freigeben & senden“ gesperrt, der Hinweis nennt den Grund, „Verwerfen“ bleibt frei', + metrics.freigabeOhneVorschauLink.knopfDa && !metrics.freigabeOhneVorschauLink.knopfFrei && (await textOf(falsch.locator('.wk-pult-actions'))).includes('Freigabe nicht möglich') + && (await falsch.getByRole('button', { name: 'Verwerfen', exact: true }).isEnabled()), metrics.freigabeOhneVorschauLink) await falsch.scrollIntoViewIfNeeded() await shoot(page, 'vorschau-falsche-adresse.png') diff --git a/src/components/workers/PultList.jsx b/src/components/workers/PultList.jsx index 5512b74..3d0ce6b 100644 --- a/src/components/workers/PultList.jsx +++ b/src/components/workers/PultList.jsx @@ -147,8 +147,10 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin run(() => api.pultAction(row.id, 'discard', {}), 'verworfen.', true) } - // Vorschau freigeben: der erste Klick fragt nach, der zweite binnen 4 s reiht den Link an den Interessenten ein (Max sendet im Sendefenster) + // Vorschau freigeben: der erste Klick fragt nach, der zweite binnen 4 s reiht den Link an den Interessenten ein (Max sendet im Sendefenster). + // Nur mit gültiger Vorschau-Adresse (https, Vorschau-Server): was der Mensch nicht ansehen kann, gibt er nicht frei (Endprüfung W5). const approve = () => { + if (!row.previewHref) return // ohne gültige Adresse sieht der Mensch die Vorschau nicht: der Knopf ist gesperrt, das hier fängt jeden anderen Weg ab if (confirming !== 'approve') { setConfirming('approve') return @@ -282,8 +284,8 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin <> {row.previewHref ? Vorschau ansehen - : Keine gültige Vorschau-Adresse hinterlegt.} - diff --git a/tests/unit/workersPultUi.test.mjs b/tests/unit/workersPultUi.test.mjs index e4abb03..d8b0f24 100644 --- a/tests/unit/workersPultUi.test.mjs +++ b/tests/unit/workersPultUi.test.mjs @@ -578,11 +578,16 @@ test('PultList (gerendert, P4-T32): Aktionen je Art – Antworten, Freigeben, Ba assert.ok(html.includes('Freigeben & senden')); assert.ok(buttonOf(html, 'Verwerfen')); assert.ok(html.includes('sendet die Vorschau nicht')) for (const t of ['Anzeige öffnen', 'Als Mensch antworten', 'Vorschau bauen', 'Wirklich freigeben?']) assert.ok(!html.includes(t), t) assert.ok(!buttonOf(html, 'Erledigt')); assert.ok(!html.includes('prüfen')) - // fremde, unsichere oder fehlende Adresse: kein Link, Hinweis statt Link, der Knopf zum Freigeben bleibt + // gültige Adresse: der Knopf zum Freigeben ist bedienbar + assert.doesNotMatch(html, /]*disabled[^>]*>Freigeben & senden<\/button>/) + // fremde, unsichere oder fehlende Adresse: kein Link, Hinweis statt Link, und der Knopf zum Freigeben ist gesperrt (Endprüfung W5: der Mensch soll keine + // Vorschau freigeben, die er nicht ansehen kann); der Hinweis nennt den Grund und den Ausweg for (const sourceUrl of ['https://fremd.example/AAA/', 'javascript:alert(1)', 'http://entwurf.project.webklar.com/AAA/', '']) { html = show([{ ...freigabe, sourceUrl }]) assert.ok(!html.includes('Vorschau ansehen'), sourceUrl); assert.ok(!html.includes('href="javascript:'), sourceUrl); assert.ok(!html.includes('fremd.example'), sourceUrl) - assert.ok(html.includes('Keine gültige Vorschau-Adresse hinterlegt.'), sourceUrl); assert.ok(html.includes('Freigeben & senden'), sourceUrl) + assert.ok(html.includes('Keine gültige Vorschau-Adresse hinterlegt. Freigabe nicht möglich – bitte den Entwurf prüfen oder verwerfen.'), sourceUrl) + assert.match(html, /]*disabled[^>]*>Freigeben & senden<\/button>/, sourceUrl) + assert.ok(buttonOf(html, 'Verwerfen')); assert.doesNotMatch(html, /]*disabled[^>]*>Verwerfen<\/button>/, sourceUrl) // Verwerfen bleibt frei } // freigegeben (erledigt) oder von einem Kollegen übernommen: nichts mehr freizugeben assert.ok(!show([{ ...freigabe, status: 'erledigt' }]).includes('Freigeben')) @@ -841,6 +846,32 @@ test('PultList (bedient, P4-T32): Vorschau freigeben – erster Klick fragt nach } finally { ui.end() } }) +// Endprüfung P4 (W5): ohne gültige Vorschau-Adresse lässt sich nicht freigeben (auch nicht über die Rückfrage), mit gültiger Adresse wie bisher +test('PultList (bedient, W5): „Freigeben & senden“ ist ohne gültige Vorschau-Adresse gesperrt – kein Klick, keine Rückfrage, keine Aktion', async () => { + const freigabe = { ...unsicher, $id: 'f1', kind: 'vorschau_freigabe', title: 'Vorschau prüfen', draft: 'Hier ist Ihre Vorschau.' } + for (const sourceUrl of ['http://entwurf.project.webklar.com/AAA/', 'https://fremd.example/AAA/', '']) { + const api = scriptedApi() + const ui = await startList([{ ...freigabe, sourceUrl }], api) + try { + assert.deepEqual(ui.disabled('Freigeben & senden'), [true], sourceUrl) + assert.ok(ui.text().includes('Keine gültige Vorschau-Adresse hinterlegt. Freigabe nicht möglich')); assert.ok(!ui.has('Vorschau ansehen')) + await ui.click('Freigeben & senden') + assert.ok(ui.has('Freigeben & senden')); assert.ok(!ui.has('Wirklich freigeben?')); assert.equal(api.calls.length, 0) + assert.deepEqual([...ui.timers.values()], []) + // Verwerfen bleibt möglich und sendet nichts: der Mensch kann den Eintrag schließen + assert.deepEqual(ui.disabled('Verwerfen'), [false]) + } finally { ui.end() } + } + // gültige Adresse: Freigeben ist frei und fragt wie bisher nach + const api = scriptedApi() + const ui = await startList([{ ...freigabe, sourceUrl: 'https://entwurf.project.webklar.com/AAA/' }], api) + try { + assert.deepEqual(ui.disabled('Freigeben & senden'), [false]); assert.ok(!ui.text().includes('Keine gültige Vorschau-Adresse')) + await ui.click('Freigeben & senden'); assert.ok(ui.has('Wirklich freigeben?')) + await ui.click('Wirklich freigeben?'); assert.deepEqual(api.calls, [['f1', 'approve-preview']]) + } finally { ui.end() } +}) + test('PultList (bedient, P4-T32): „Antwort des Kunden einfügen“ – leeres Feld mit Hinweis, Prüfung, Einfügen, doppelt, Fehler des Dienstes, Text bleibt beim Filterwechsel', async () => { const plattform = { ...unsicher, $id: 'k1', kind: 'plattform_antwort', status: 'in_arbeit', claimedBy: 'mensch:u2', title: 'Suche Webdesigner', draft: 'Claras Entwurf', sourceUrl: 'https://www.kleinanzeigen.de/s-anzeige/x/1-2-3', prospect: { $id: 'p1', name: 'Verein' } } const api = scriptedApi(Promise.resolve({ status: 'neu', messageId: 'm1', threadId: 't1' }), Promise.resolve({ status: 'doppelt', messageId: 'm1', threadId: 't1' }), failure('Dieser Kontakt ist gesperrt.', 409))