Files
Webklar-Kundenbereich/test/knEinschaltenLogik.test.js
T
ClaudeandClaude Sonnet 5.5 bfe3895a50 Kundenansicht neu P7: Einschalten zeigt Linkzeile, Häkchen und Unterlagen der Fassung 6 aus P8
Verdrahtet P5 Task 15 und P7 Task 0 (N33): consentAbo liest Linkzeile und Hinweis aus WORTLAUT_V6, unterlagenFuer die fünf Funktionen aus FUNKTIONEN_UNTERLAGEN_V6 (Analyse bleibt bei den Alt-Unterlagen bis zur Freigabe), config.aboFunktionen.texte.hinweis entfällt, pdfAnhaenge liest PDFs der Fassung 6, sichereAdresse lässt /neu/rechtliches/ zu. Der Analyse-Kundenweg setzt wie der Team-Weg den Abo-Marker (unlockedBy und einrichtungAbrechnung abo).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 19:56:40 +00:00

129 lines
10 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { ETAGE_FUER, consentVon, anfrageStatusVon, einschaltWeg, tagNachFrist, datumKurz, beginnLage, einschaltKoerper, linkzeile, sichereAdresse, fehlerAktion } from '../public/neu/funktion-einschalten.js'
import { daten, katalogEintrag, projekt, feature } from './support/knFunktionenDaten.js'
const abo = (produkt, extra = {}) => ({ produkt, laeuft: produkt !== null, gekuendigt: false, widerrufsfristBis: null, ...extra })
const angenommen = (key) => projekt('p1', 'B', { [key]: feature('off', { anfrageStatus: 'angenommen' }) })
const GEKUENDIGT = 'Dein Abo ist gekündigt. Solange die Kündigung läuft, kannst du nichts Neues einschalten.'
const NICHT_AKTIV = 'Dein Abo ist noch nicht aktiv. Sobald die erste Zahlung da ist, kannst du einschalten.'
const NUR_BETRIEB = 'Webshop gibt es für Betriebe. Stimmt das nicht? Schreib uns im Chat.'
test('Etagen-Tabelle und Zugriffsfunktionen', () => {
assert.deepEqual(ETAGE_FUER, { news: 'news', termine: 'termine', shop: 'webshop', unterkunft: 'buchungen', zeiten: 'zeiten', analyse: 'analyse' })
assert.equal(consentVon(daten(), 'news').version, '2026-10-20')
assert.equal(consentVon(daten({ katalogEdit: { analyse: { consent: null } } }), 'analyse'), null)
assert.equal(consentVon(daten(), 'gibtsnicht'), null)
assert.equal(anfrageStatusVon(angenommen('shop'), 'shop'), 'angenommen')
assert.equal(anfrageStatusVon(projekt('p1', 'B'), 'shop'), '')
})
test('einschaltWeg: Vorprüfung in der Reihenfolge des Servers (V5)', () => {
const faelle = [ // [erwartet, key, abo, kundenart, projekt, Katalogänderung]
[{ art: 'meldung', text: 'Diese Funktion ist für dich gerade nicht verfügbar.' }, 'news', abo('wartung'), 'unternehmen', undefined, { verfuegbar: false }],
[{ art: 'meldung', text: GEKUENDIGT }, 'news', abo('wartung', { gekuendigt: true })],
[{ art: 'bestellen', produkt: 'wartung' }, 'news', abo(null)],
[{ art: 'bestellen', produkt: 'hosting' }, 'analyse', abo(null)],
[{ art: 'meldung', text: NICHT_AKTIV }, 'news', abo('wartung', { laeuft: false })],
[{ art: 'wechsel' }, 'news', abo('hosting')],
[{ art: 'etage', etage: 'analyse' }, 'analyse', abo('hosting')],
[{ art: 'meldung', text: NUR_BETRIEB }, 'shop', abo('wartung'), 'privat'],
[{ art: 'meldung', text: NUR_BETRIEB }, 'shop', abo('wartung'), ''], // leere Kundenart zählt als privat (E4)
[{ art: 'seite' }, 'news', abo('wartung')],
[{ art: 'seite' }, 'zeiten', abo('wartung')],
[{ art: 'etage', etage: 'termine' }, 'termine', abo('wartung')],
[{ art: 'etage', etage: 'webshop' }, 'shop', abo('wartung')],
[{ art: 'etage', etage: 'buchungen' }, 'unterkunft', abo('wartung')],
[{ art: 'seite' }, 'shop', abo('wartung'), 'unternehmen', angenommen('shop')],
[{ art: 'seite' }, 'unterkunft', abo('wartung'), 'unternehmen', angenommen('unterkunft')],
]
for (const [erwartet, key, a, kundenart = 'unternehmen', p = projekt('p1', 'B'), edit = {}] of faelle) {
assert.deepEqual(einschaltWeg({ eintrag: katalogEintrag(key, edit), projekt: p, abo: a, kundenart }), erwartet, `${key} ${JSON.stringify(a)} ${kundenart}`)
}
})
test('tagNachFrist und datumKurz: Berliner Folgetag, Sommer- und Winterzeit', () => {
assert.equal(tagNachFrist('2026-10-30T22:59:59.999Z'), '2026-10-31') // 23:59:59 MEZ
assert.equal(tagNachFrist('2026-08-14T21:59:59.999Z'), '2026-08-15') // 23:59:59 MESZ
assert.equal(tagNachFrist('2026-12-31T22:59:59.999Z'), '2027-01-01')
assert.deepEqual([tagNachFrist(null), tagNachFrist('kein Datum'), datumKurz('2026-10-31'), datumKurz('')], ['', '', '31.10.', ''])
})
test('beginnLage: Häkchen nur für privat bis zum Fristende; die Antwort des Servers gewinnt', () => {
const jetzt = new Date('2026-10-20T10:00:00Z')
const frist = '2026-10-30T22:59:59.999Z'
const lage = (kundenart, widerrufsfristBis, extra = {}) => beginnLage({ kundenart, abo: { widerrufsfristBis }, jetzt, ...extra })
assert.deepEqual(lage('privat', frist), { noetig: true, ab: '2026-10-31' })
assert.deepEqual(lage('', frist), { noetig: true, ab: '2026-10-31' })
assert.deepEqual(lage('unternehmen', frist), { noetig: false, ab: '' })
assert.deepEqual(lage('privat', null), { noetig: false, ab: '' })
assert.equal(lage('privat', frist, { jetzt: new Date(frist) }).noetig, true) // genau am Ende noch Frist
assert.equal(lage('privat', frist, { jetzt: new Date('2026-10-30T23:00:00.000Z') }).noetig, false)
assert.deepEqual(lage('unternehmen', null, { serverAb: '2026-11-02T23:00:00.000Z' }), { noetig: true, ab: '2026-11-03' }) // Form von P5: Berliner Mitternacht
assert.equal(lage('privat', null, { serverAb: '2026-11-03' }).ab, '2026-11-03')
})
test('einschaltKoerper: Fassung aus dem Register, Haken nur "beginn", nie die Kundenart', () => {
const c = consentVon(daten(), 'news')
assert.deepEqual(einschaltKoerper({ projectId: 'p1', consent: c, beginnGesetzt: false }), { projectId: 'p1', zustimmung: { version: '2026-10-20', haken: [] } })
const mit = einschaltKoerper({ projectId: 'p1', consent: c, beginnGesetzt: true })
assert.deepEqual(mit.zustimmung.haken, ['beginn'])
assert.equal('kundenart' in mit, false)
})
test('sichereAdresse: nur https und Pfade unter /rechtliches/, /vertragsunterlagen/ oder /neu/rechtliches/', () => {
for (const gut of ['/rechtliches/avv-2026-10-05.html', '/vertragsunterlagen/funktionen-leistungsbeschreibung.html', '/neu/rechtliches/leistungsbeschreibung-terminplaner-2026-10-16.html', 'https://example.org/a.pdf']) assert.equal(sichereAdresse(gut), gut)
for (const schlecht of ['javascript:alert(1)', 'http://example.org/', '//example.org/x', '/rechtliches/../geheim', '/rechtliches/%2e%2e/x', 'https://user:pw@example.org/', '/api/abo', '/neu/rechtliches/../geheim', '/neu/api/abo', '/rechtliches/a b', '', null, 42]) assert.equal(sichereAdresse(schlecht), '', String(schlecht))
})
test('linkzeile: die zwei Wörter werden Links; unsichere oder fehlende Adressen nicht', () => {
const text = 'Es gelten Leistungsbeschreibung und Auftragsverarbeitung (Fassung 2026-10-05).'
const u = katalogEintrag('termine').unterlagen
assert.deepEqual(linkzeile(text, u), [
{ text: 'Es gelten ' }, { text: 'Leistungsbeschreibung', href: u.leistungsbeschreibung.url }, { text: ' und ' },
{ text: 'Auftragsverarbeitung', href: u.avv.url }, { text: ' (Fassung 2026-10-05).' },
])
assert.deepEqual(linkzeile('Es gilt die Leistungsbeschreibung (Fassung 2026-10-04).', { leistungsbeschreibung: u.leistungsbeschreibung, avv: null }), [
{ text: 'Es gilt die ' }, { text: 'Leistungsbeschreibung', href: u.leistungsbeschreibung.url }, { text: ' (Fassung 2026-10-04).' },
])
assert.deepEqual(linkzeile(text, { leistungsbeschreibung: { url: 'javascript:alert(1)' }, avv: null }), [{ text }])
assert.deepEqual(linkzeile('Es gelten Leistungsbeschreibung (Fassung 2026-10-05).', u), [
{ text: 'Es gelten ' }, { text: 'Leistungsbeschreibung', href: u.leistungsbeschreibung.url }, { text: ' (Fassung 2026-10-05). ' }, { text: 'Auftragsverarbeitung', href: u.avv.url },
])
// beide Wörter fehlen im Text: beide Links hängen hinten an, mit " · " dazwischen
assert.deepEqual(linkzeile('Es gelten die Unterlagen.', u), [
{ text: 'Es gelten die Unterlagen. ' }, { text: 'Leistungsbeschreibung', href: u.leistungsbeschreibung.url }, { text: ' · ' }, { text: 'Auftragsverarbeitung', href: u.avv.url },
])
assert.deepEqual(linkzeile(text, null), [{ text }])
})
test('fehlerAktion: alle 409-Codes aus V5 und die Randfälle', () => {
const f = (status, code, body = {}, message = 'Servertext') => ({ status, code, message, body: { code, ...body } })
const AB = '2026-11-02T23:00:00.000Z'
const faelle = [ // [Fehler, Funktion, erwartete Aktion]
[f(409, 'nicht_verfuegbar'), 'termine', { art: 'meldung', text: 'Diese Funktion ist für dich gerade nicht verfügbar.' }],
[f(409, 'abo_fehlt', { grund: 'falsches_abo', wechsel: true }), 'termine', { art: 'wechsel' }],
[f(409, 'abo_fehlt', { grund: 'kein_abo' }), 'termine', { art: 'bestellen', produkt: 'wartung' }],
[f(409, 'abo_fehlt', { grund: 'kein_abo' }), 'analyse', { art: 'bestellen', produkt: 'hosting' }],
[f(409, 'abo_fehlt', { grund: 'gekuendigt' }), 'termine', { art: 'meldung', text: GEKUENDIGT }],
[f(409, 'abo_fehlt', { grund: 'nicht_aktiv' }), 'termine', { art: 'meldung', text: NICHT_AKTIV }],
[f(409, 'abo_fehlt', { grund: 'falsches_abo' }), 'termine', { art: 'meldung', text: 'Dein Abo enthält diese Funktion nicht.' }],
[f(409, 'abo_fehlt', { grund: 'beginnt_spaeter', ab: AB }), 'termine', { art: 'spaeter', ab: AB, text: 'Dein Abo beginnt am 03.11.2026. Ab dann kannst du einschalten.' }], // N20
[f(409, 'abo_fehlt', { grund: 'beginnt_spaeter' }), 'termine', { art: 'meldung', text: 'Servertext' }], // ohne Datum: Text des Servers
[f(409, 'abo_fehlt', { grund: 'unbekannt' }), 'termine', { art: 'meldung', text: 'Servertext' }],
[f(409, 'nur_betrieb'), 'shop', { art: 'meldung', text: NUR_BETRIEB }],
[f(409, 'bestellung_offen', { offen: [{ schritt: 'zeiten', text: 'x' }] }), 'termine', { art: 'etage', etage: 'termine' }],
[f(409, 'bestellung_offen', { offen: ['Anfrage'] }), 'shop', { art: 'etage', etage: 'webshop' }],
[f(409, 'beginn_noetig', { ab: AB }), 'termine', { art: 'beginn', ab: AB }],
[f(409, 'bereits_eingeschaltet'), 'termine', { art: 'schon' }],
[f(409, 'fassung_veraltet'), 'termine', { art: 'neu-laden', text: 'Die Bedingungen wurden aktualisiert. Bitte lies die Zeile noch einmal.' }],
[f(409, 'bestellung_laeuft'), 'termine', { art: 'meldung', text: 'Servertext' }], // weitere Codes aus P5: Text des Servers
[f(409, 'abgeschaltet'), 'termine', { art: 'meldung', text: 'Servertext' }],
[f(503, 'unterlagen_fehlen'), 'termine', { art: 'meldung', text: 'Servertext' }],
[f(403, 'view_blocked'), 'termine', { art: 'meldung', text: 'Servertext' }],
[{ status: 0, message: 'Keine Verbindung. Bitte versuch es gleich noch einmal.', body: {} }, 'termine', { art: 'meldung', text: 'Keine Verbindung. Bitte versuch es gleich noch einmal.' }],
[{ status: 500, message: '', body: {} }, 'termine', { art: 'meldung', text: 'Das Einschalten hat gerade nicht geklappt. Versuch es gleich noch einmal oder schreib uns im Chat.' }],
]
for (const [fehler, key, erwartet] of faelle) assert.deepEqual(fehlerAktion(fehler, { eintrag: katalogEintrag(key) }), erwartet, `${fehler.status} ${fehler.code}`)
})