Kundenportal Aufträge: Seitenlogik der öffentlichen Bestätigungsseite
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bd87bb115c
commit
38734e99aa
2 files changed
+267
No files matched your search
@@ -0,0 +1,160 @@
|
||||
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/)
|
||||
})
|
||||
Reference in new issue
Block a user