import { test } from 'node:test' import assert from 'node:assert/strict' import { installFakeDom } from './support/fakeDom.js' import { h } from '../public/auftrag-dom.js' import { initAuftragsseite } from '../public/auftrag.js' import { TOKEN, KLEINUNTERNEHMER, ab, privat, bestaetigt, pruefeQuelltext } from './auftraegeKundenportalDaten.js' const ok = (body, status = 200) => ({ status, body }) const mit = (antworten, fn) => async () => { const fx = installFakeDom({ antworten }) const root = h('div', { id: 'ab-wurzel' }) const ansage = h('div', { id: 'ab-ansage' }) fx.document.body.append(root, ansage) try { await fn(fx, root, ansage) } finally { fx.restore() } } const start = (root, ansage, adresse = {}) => initAuftragsseite({ root, ansage, suche: '', hash: `#t=${TOKEN}`, ...adresse }) // K31 verlinkt #t= const bestaetige = async (fx, root, { frueh = false } = {}) => { await fx.klick(fx.finde(root, '[data-feld="bestaetigung"]')) if (frueh) await fx.klick(fx.finde(root, '[data-feld="fruehbeginn"]')) await fx.klick(fx.finde(root, '[data-aktion="bestaetigen"]')) } test('Ohne gültigen Token: „ungültig“ und Kontaktweg, ohne Anfrage', mit([], async (fx, root, ansage) => { for (const adresse of [{ hash: '' }, { hash: '#t=' }, { hash: '#t=zu-kurz' }, { hash: `#t=${TOKEN}x` }, { hash: '', suche: '?t=zu-kurz' }]) { await start(root, ansage, adresse) assert.equal(root.dataset.zustand, 'ungueltig', JSON.stringify(adresse)) assert.equal(fx.finde(root, 'h1').textContent, 'Dieser Link ist ungültig oder abgelaufen.') assert.equal(fx.finde(root, 'a[href="mailto:support@webklar.com"]').textContent, 'support@webklar.com') assert.equal(fx.finde(root, 'a[href="tel:017623726355"]').textContent, '0176 23726355') } assert.equal(fx.fetchLog.length, 0) })) test('Laden und Anzeigen: Kopf, Zusammenfassung, Unterlagen, Formular; der Token steht nur in Links', mit([ok({ auftrag: ab() })], async (fx, root, ansage) => { await start(root, ansage) assert.deepEqual(fx.fetchLog, [{ method: 'GET', url: `/api/auftrag/${TOKEN}`, body: undefined }]) assert.equal(root.dataset.zustand, 'offen') assert.equal(fx.finde(root, 'h1').textContent, 'Auftragsbestätigung AB-2026-0002') assert.equal(fx.finde(root, '.ab-pille').textContent, 'Bitte bestätigen') assert.equal(fx.alle(root, '[data-position]').length, 2) const pdf = fx.finde(root, '[data-link="pdf"]') const agb = fx.finde(root, '[data-link="agb"]') assert.equal(pdf.getAttribute('href'), `/api/auftrag/${TOKEN}/pdf`) assert.equal(agb.getAttribute('href'), '/rechtliches/agb-2026-09-30.pdf') assert.ok(fx.finde(root, 'form [data-feld="bestaetigung"]')) assert.equal(fx.finde(root, '[data-feld="fruehbeginn"]'), null) assert.equal(fx.finde(root, '[data-hinweis]').textContent, KLEINUNTERNEHMER, 'der § 19-Satz kommt aus der API') assert.equal(fx.finde(root, 'legend').textContent, 'Ihre Bestätigung', 'öffentliche Seite: Sie-Form') assert.equal(fx.text(ansage), 'Ihre Auftragsbestätigung wurde geladen.') assert.ok(!fx.text(root).includes(TOKEN) && !fx.text(ansage).includes(TOKEN)) })) test('Der Token steht im Fragment (#t=, so verlinkt K31); als Rückfall genügt ?t=', mit([ok({ auftrag: ab() }), ok({ auftrag: ab() })], async (fx, root, ansage) => { await start(root, ansage) assert.equal(root.dataset.zustand, 'offen') await start(root, ansage, { hash: '', suche: `?t=${TOKEN}` }) assert.equal(root.dataset.zustand, 'offen') assert.deepEqual(fx.fetchLog.map((a) => a.url), [`/api/auftrag/${TOKEN}`, `/api/auftrag/${TOKEN}`]) })) test('Aufwand: Aufwandstext statt Tabelle', mit([ok({ auftrag: ab({ art: 'aufwand', positionen: [], summeCents: null, aufwandText: '100,00 € pro Stunde.' }) })], async (fx, root, ansage) => { await start(root, ansage) assert.equal(fx.finde(root, '[data-aufwand]').textContent, '100,00 € pro Stunde.') assert.equal(fx.finde(root, 'table'), null) })) test('Bestätigen (Unternehmen): POST mit Häkchen, danach „bestätigt“ mit Zeitpunkt, Fokus und Ansage', mit([ok({ auftrag: ab() }), ok({ auftrag: bestaetigt(ab()) })], async (fx, root, ansage) => { await start(root, ansage) await bestaetige(fx, root) assert.deepEqual(fx.fetchLog[1], { method: 'POST', url: `/api/auftrag/${TOKEN}/bestaetigen`, body: { bestaetigt: true, fruehbeginn: false } }) assert.equal(root.dataset.zustand, 'bestaetigt') assert.equal(fx.finde(root, 'h1').textContent, 'Ihr Auftrag ist bestätigt.') assert.equal(fx.finde(root, '.ab-pille').textContent, 'Bestätigt') const block = fx.finde(root, '[data-bestaetigt]') assert.deepEqual(fx.alle(block, 'p').map((p) => p.textContent), ['Bestätigt am 07.10.2026, 10:15 Uhr']) assert.equal(fx.finde(root, 'form'), null) assert.equal(fx.document.activeElement, block) assert.equal(fx.text(ansage), 'Ihr Auftrag ist bestätigt.') assert.ok(fx.finde(root, '[data-link="pdf"]'), 'Unterlagen bleiben erreichbar') })) test('Privatperson: zweites Häkchen; angezeigt wird, was der Server meldet (Frühbeginn „nein“ trotz Häkchen)', mit([ok({ auftrag: privat() }), ok({ auftrag: bestaetigt(privat({ fruehbeginn: false })) })], async (fx, root, ansage) => { await start(root, ansage) assert.ok(fx.finde(root, '[data-feld="fruehbeginn"]')) await bestaetige(fx, root, { frueh: true }) assert.deepEqual(fx.fetchLog[1].body, { bestaetigt: true, fruehbeginn: true }) assert.deepEqual(fx.alle(fx.finde(root, '[data-bestaetigt]'), 'p').map((p) => p.textContent), [ 'Bestätigt am 07.10.2026, 10:15 Uhr', 'Wir beginnen frühestens am 21.10.2026.', ]) })) test('Schon bestätigt beim Laden: „bestätigt“ ohne Formular, mit der Zeile zum Beginn', mit([ok({ auftrag: bestaetigt(privat({ fruehbeginn: true, fruehesterBeginn: null })) })], async (fx, root, ansage) => { await start(root, ansage) assert.equal(root.dataset.zustand, 'bestaetigt') assert.equal(fx.finde(root, 'form'), null) assert.deepEqual(fx.alle(fx.finde(root, '[data-bestaetigt]'), 'p').map((p) => p.textContent), [ 'Bestätigt am 07.10.2026, 10:15 Uhr', 'Wir beginnen auf Ihren Wunsch schon vor Ablauf der Widerrufsfrist.', ]) })) test('Zustände: ungültig, ersetzt, storniert, widerrufen, zu viele Anfragen, Serverfehler – feste Texte, nie der Token', mit([], async (fx, root, ansage) => { const faelle = [ [ok({ error: 'Dieser Link ist ungültig oder abgelaufen.' }, 404), 'ungueltig', 'Dieser Link ist ungültig oder abgelaufen.'], [ok({ error: 'Diese Auftragsbestätigung wurde ersetzt.' }, 410), 'beendet', 'Diese Auftragsbestätigung wurde ersetzt.'], [ok({ error: 'Diese Auftragsbestätigung wurde storniert.' }, 410), 'beendet', 'Diese Auftragsbestätigung wurde storniert.'], [ok({ error: 'Diese Auftragsbestätigung wurde widerrufen.' }, 410), 'beendet', 'Diese Auftragsbestätigung wurde widerrufen.'], [ok({ error: 'Zu viele Anfragen. Bitte später erneut versuchen.' }, 429), 'limit', 'Zu viele Anfragen'], [ok({ error: 'Interner Fehler.' }, 500), 'fehler', 'Das hat leider nicht geklappt'], [ok({}, 502), 'fehler', 'Das hat leider nicht geklappt'], [ok({}, 200), 'fehler', 'Das hat leider nicht geklappt'], // Antwort ohne auftrag ] for (const [antwort, zustand, titel] of faelle) { fx.antworten.push(antwort) await start(root, ansage) assert.equal(root.dataset.zustand, zustand, titel) assert.equal(fx.finde(root, 'h1').textContent, titel) assert.equal(fx.text(ansage), titel) assert.equal(fx.finde(root, 'form'), null) assert.ok(!fx.text(root).includes(TOKEN) && !fx.text(ansage).includes(TOKEN)) assert.equal(Boolean(fx.finde(root, '[data-aktion="erneut"]')), ['limit', 'fehler'].includes(zustand), `Erneut-Knopf bei ${zustand}`) } assert.equal(fx.finde(root, 'a[href="mailto:support@webklar.com"]') !== null, true, 'auch beim Fehler steht der Kontaktweg da') })) test('Netzfehler beim Laden: Fehlerzustand ohne Fremdtext, „Erneut versuchen“ lädt neu', mit([() => { throw new TypeError('fetch failed') }, ok({ auftrag: ab() })], async (fx, root, ansage) => { await start(root, ansage) assert.equal(root.dataset.zustand, 'fehler') assert.ok(!fx.text(root).includes('fetch failed')) await fx.klick(fx.finde(root, '[data-aktion="erneut"]')) assert.equal(root.dataset.zustand, 'offen') assert.equal(fx.fetchLog.length, 2) })) test('Status wechselt zwischen Laden und Bestätigen (410): „storniert“, kein Formular mehr', mit([ok({ auftrag: ab() }), ok({ error: 'Diese Auftragsbestätigung wurde storniert.' }, 410)], async (fx, root, ansage) => { await start(root, ansage) await bestaetige(fx, root) assert.equal(root.dataset.zustand, 'beendet') assert.equal(fx.finde(root, 'h1').textContent, 'Diese Auftragsbestätigung wurde storniert.') assert.equal(fx.finde(root, 'form'), null) })) test('Fehler beim Bestätigen (429): Meldung in der Fehlerzeile, Seite bleibt offen, Häkchen bleiben', mit([ok({ auftrag: ab() }), ok({ error: 'Zu viele Anfragen. Bitte später erneut versuchen.' }, 429)], async (fx, root, ansage) => { await start(root, ansage) await bestaetige(fx, root) assert.equal(root.dataset.zustand, 'offen') assert.equal(fx.finde(root, '[data-fehler]').textContent, 'Zu viele Anfragen. Bitte versuchen Sie es in einigen Minuten noch einmal.') assert.equal(fx.finde(root, '[data-feld="bestaetigung"]').checked, true) assert.equal(fx.finde(root, '[data-aktion="bestaetigen"]').disabled, false) assert.ok(!fx.text(root).includes(TOKEN)) })) test('Quelltext: genau ein fetch ohne Cookies, keine Protokollausgabe, nur relative Importe', () => { const quelle = pruefeQuelltext('auftrag.js', { fetchAnzahl: 1, importe: ['./auftrag-ansicht.js', './auftrag-dom.js'] }) assert.match(quelle, /credentials: 'omit'/) assert.doesNotMatch(quelle, /location\.href|history\.|document\.referrer/) })