Files
Webklar-Kundenbereich/test/auftraegeKundenportalSeite.test.js
T

161 lines
9.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/)
})