Gästetexte des Terminplaners: Entwurf-Kennzeichnung entfernt

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-05 04:08:46 +00:00
1 parent c1266fcce7
commit 495d5ad9ca
11 files changed
+79 -56

No files matched your search

+5 -2
View File
@@ -1217,12 +1217,15 @@ test('Anleitung ohne gespeicherte Einstellung: nur der Einbau-Code und die Warnu
})
test('Anleitung, Drift: Ticket auf Version 1, Einstellung jetzt Version 3 => Warnung am Ende von warnungen, ticketVersion und ticketWoid', async () => {
const k = legeAnleitung({ version: 3, ticketDetails: TICKET_MIT_STAND(1) })
// eine andere Warnung muss davor stehen (ohne Entwurfs-Banner sonst keine): fehlende Impressum-URL
const einstellung = d8Einstellung()
einstellung.betrieb.impressumUrl = ''
const k = legeAnleitung({ einstellung, version: 3, ticketDetails: TICKET_MIT_STAND(1) })
const res = await anleitung(k)
assert.equal(res.status, 200, JSON.stringify(res.body))
assert.equal(warnungDrift(1, 3), 'Einstellungen seit der Bestellung geändert (Ticket Version 1, jetzt 3).')
assert.equal(res.body.warnungen.at(-1), warnungDrift(1, 3))
assert.ok(res.body.warnungen.length > 1, 'sie steht hinter den uebrigen Warnungen')
assert.ok(res.body.warnungen.length > 1 && /Impressum/.test(res.body.warnungen[0]), `sie steht hinter den uebrigen Warnungen: ${res.body.warnungen.join(' | ')}`)
assert.equal(res.body.warnungen.filter((w) => w.startsWith('Einstellungen seit der Bestellung')).length, 1)
assert.equal(res.body.ticketVersion, 1)
assert.equal(res.body.ticketWoid, ticketDoc(k.ticketId).woid)
+37 -2
View File
@@ -219,6 +219,11 @@ test('cssVorschlag: der Hinweis nennt den Kontrast gegen Weiss, wenn der Akzent
const NOW = new Date('2026-10-05T10:00:00Z') // Montag, 12:00 Berliner Zeit
const SPAETER = new Date('2026-10-06T10:00:00Z') // einen Tag spaeter: nur die "Erzeugt am"-Zeile aendert sich
const GEHEIM = 'XYZ-GEHEIM'
// Endregel (Leistungsbeschreibung Nr. 4 Abs. 9): wer den Datenschutz-Absatz einfügt; Checkliste und Schritt 4 der Kurzfassung nennen sie
const DATENSCHUTZ_ENDREGEL =
'liegt die Datenschutzerklärung in der von WEBklar betreuten Website, fügen wir den Absatz beim Einbau ein, das gehört zur Einrichtung und wird nicht auf das Stundenkontingent angerechnet; '
+ 'sonst den Absatz als Textbaustein per Chat an den Kunden senden, der ihn selbst einfügt'
const BETRIEB = {
name: 'Salon Muster',
email: 'inhaber-intern@salon-muster.example',
@@ -880,6 +885,36 @@ test('D3b Banner: ENTWURF_BANNER genau dann, wenn entwurf, und zwar als erste Wa
assert.equal(erzeuge({ entwurf: true }).warnungen.filter((w) => w === ENTWURF_BANNER).length, 1)
})
test('einbau_ohne_banner: im Standardaufruf steht kein ENTWURF-Banner in Warnungen, Volltext und Kurzfassung; mit entwurf: true bleibt er', () => {
assert.equal(RECHTSTEXTE_ENTWURF, false)
const r = erzeuge()
assert.deepEqual(r.warnungen, [])
assert.ok(!r.warnungen.includes(ENTWURF_BANNER))
assert.ok(!r.text.includes('ENTWURF'), 'Volltext')
assert.ok(!r.kurz.includes('ENTWURF'), 'Kurzfassung (Ticket)')
for (const a of r.abschnitte) assert.ok(!a.text.includes('ENTWURF'), `Abschnitt ${a.id}`)
// die D3b-Tests mit ausdrücklichem entwurf: true bleiben gültig: der Banner ist weiter eine eigene Warnung
const mit = erzeuge({ entwurf: true })
assert.deepEqual(mit.warnungen, [ENTWURF_BANNER])
assert.ok(mit.kurz.includes(ENTWURF_BANNER))
})
test('checkliste_datenschutz_endregel: die Checkliste nennt die Endregel der Leistungsbeschreibung, die Zwischenregel ist weg', () => {
const text = abschnitt(erzeuge(), 'checkliste').text
const punkt = zeilen(text).find((z) => z.includes('Datenschutz-Absatz „Online-Terminbuchung“ steht in der Datenschutzerklärung'))
assert.ok(punkt, text)
assert.ok(punkt.includes(`(Fassung Sie; ${DATENSCHUTZ_ENDREGEL}).`), punkt)
for (const teil of ['von WEBklar betreuten Website', 'fügen wir den Absatz beim Einbau ein', 'nicht auf das Stundenkontingent angerechnet', 'Textbaustein per Chat an den Kunden']) {
assert.ok(punkt.includes(teil), `Endregel: ${teil}`)
}
assert.ok(!text.includes('liegt die Datenschutzerklärung nicht in unserem Repo'), 'Zwischenregel aus D3b ist weg')
assert.ok(!erzeuge().kurz.includes('nicht in unserem Repo'), 'auch nicht in der Kurzfassung')
assert.ok(!erzeuge().text.includes('nicht in unserem Repo'), 'und nicht im Volltext')
const du = fixtureEinstellung()
du.anrede = 'du'
assert.ok(abschnitt(erzeuge({ einstellung: du }), 'checkliste').text.includes(`(Fassung Du; ${DATENSCHUTZ_ENDREGEL}).`))
})
test('D3b Warnungen: fehlende Telefonnummer, und Anschrift nur, wenn Gaeste ein Widerrufsrecht haben', () => {
const ohneTelefon = fixtureEinstellung()
ohneTelefon.betrieb.telefon = ''
@@ -996,7 +1031,7 @@ test('D3b Checkliste: immer-Punkte (Freischalten, Probebuchung, stornieren, Kont
'Datenschutz-Absatz', 'Impressum', 'Kontrast', '390 px', 'Tastatur',
]) assert.ok(text.includes(erwartet), `${erwartet}\n---\n${text}`)
assert.ok(text.includes(BETRIEB.impressumUrl) && text.includes(BETRIEB.datenschutzUrl), 'die URLs stehen zum Pruefen im Punkt')
assert.ok(text.includes('liegt die Datenschutzerklärung nicht in unserem Repo: Absatz per Chat an den Kunden senden'), 'Zwischenregel bis Block F')
assert.ok(text.includes(DATENSCHUTZ_ENDREGEL), 'Endregel der Leistungsbeschreibung (Nr. 4 Abs. 9)')
assert.ok(text.includes(`${config.womsBaseUrl}/customers/c1?tab=funktionen`), 'Link ins WOMS-Kundenpanel')
// die Reihenfolge: erst Einbau, dann Freischalten, dann Probebuchung, dann die Pruefungen am sichtbaren Widget
const stelle = (teil) => z.findIndex((x) => x.includes(teil))
@@ -1246,7 +1281,7 @@ test('D4 kurz Schritt 4 und 6: Datenschutz-Fassung nach Anrede, Preis nur bei be
du.anrede = 'du'
assert.match(schritt({}, 4), /Sie-Form/)
assert.match(schritt({ einstellung: du }, 4), /Du-Form/)
assert.ok(schritt({}, 4).includes('Absatz per Chat an den Kunden senden'), 'Zwischenregel aus der Checkliste (F10b2 ersetzt sie an einer Stelle)')
assert.ok(schritt({}, 4).includes(DATENSCHUTZ_ENDREGEL), 'dieselbe Endregel wie in der Checkliste')
assert.match(schritt({ feature: PAKET_FEATURE }, 6), /Nicht auf das Stundenkontingent anrechnen \(Einrichtung bezahlt, 149 €\)/)
assert.ok(schritt({}, 6).includes('Nicht auf das Stundenkontingent anrechnen'))
assert.ok(!schritt({}, 6).includes('bezahlt'), 'ohne Kauf keine Behauptung, die Einrichtung sei bezahlt')
+1 -1
View File
@@ -235,7 +235,7 @@ test('Einbauanleitung: Snippet, Variablen, drei Stile, Datenschutz-Absatz, Abnah
'data-modus', 'data-api', 'data-knopf', 'data-leistung',
'--wkt-akzent', '--wkt-akzent-text', '--wkt-hintergrund', '--wkt-text', '--wkt-rand', '--wkt-radius', '--wkt-schrift', '--wkt-schrift-titel',
'@layer wkt', 'wkt-style',
'docs/rechtliches/termine-datenschutz-absatz.md', 'ENTWURF',
'docs/rechtliches/termine-datenschutz-absatz.md', 'Fassung',
'Abnahme', '/api/public/termine/v1', '/config', '/tage', '/zeiten', '/buchungen', '/widerruf', '/kalender/',
'termine-demo.html', 'termine-widget.ui.mjs',
]) {
+23 -8
View File
@@ -1,6 +1,6 @@
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { existsSync, readFileSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { config } from '../server/config.js'
import {
@@ -17,7 +17,6 @@ import {
GESUNDHEIT_HINWEIS,
} from '../server/services/termine/rechtstexte.js'
// ENTWURF – nur Pilot, vor Einsatz bei fremden Betrieben prüfen.
// Die Belehrung wird gegen den Wortlaut der Anlagen 1 und 2 zu Art. 246a EGBGB geprueft (docs/rechtliches/quellen/,
// abgerufen 2026-10-02): jeder Absatz des Musters muss unveraendert im Ergebnis stehen, nur die Luecken sind gefuellt.
@@ -246,8 +245,8 @@ test('Formular-Hinweis: Sie ist die Vorgabe, Aufbewahrung 90 Tage ohne gültigen
// ---------------------------------------------------------------- Datenschutz-Absatz „Online-Terminbuchung“ (Quelle der Einbauanleitung und der Doku)
test('Rechtstexte sind Entwurf, bis Block F die ENTWURF-Köpfe und diese Konstante zusammen entfernt', () => {
assert.equal(RECHTSTEXTE_ENTWURF, true)
test('D1 (f): die Rechtstexte gelten nicht mehr als Entwurf (RECHTSTEXTE_ENTWURF === false)', () => {
assert.equal(RECHTSTEXTE_ENTWURF, false)
})
const DS_SCHALTER = ['telefonFeld', 'freitext', 'erinnerung', 'widerruf', 'telefon']
@@ -508,13 +507,29 @@ const DOKUMENTE = [
'docs/rechtliches/termine-widerrufsbelehrung-muster.md',
'docs/rechtliches/termine-datenschutz-absatz.md',
'docs/rechtliches/termine-hinweise.md',
'docs/rechtliches/termine-datenschutz-6c-entwurf.md',
'docs/rechtliches/termine-datenschutz-6c.md',
]
test('Jedes Rechtstext-Dokument beginnt mit dem Entwurfs-Kopf', () => {
// Das Wort ENTWURF als Kennzeichnung; ENTWURF_BANNER und RECHTSTEXTE_ENTWURF (Bezeichner im Code) zählen nicht
const ENTWURF_WORT = /ENTWURF(?![_A-Z])/
test('kein_entwurf_mehr: kein ENTWURF-Kopf in den vier Dokumenten, keiner in den Texten von rechtstexte.js und in der Einbau-Doku; die Konstante steht auf false', () => {
for (const rel of DOKUMENTE) {
assert.equal(lies(rel).split('\n')[0].trim(), KOPF, rel)
const md = lies(rel)
assert.ok(!ENTWURF_WORT.test(md), `${rel}: ENTWURF steht noch im Dokument`)
assert.notEqual(md.split('\n')[0].trim(), KOPF, `${rel}: der Entwurfs-Kopf steht noch in der ersten Zeile`)
assert.ok(md.startsWith('# '), `${rel}: das Dokument beginnt mit der Überschrift`)
}
assert.ok(!existsSync(new URL('../docs/rechtliches/termine-datenschutz-6c-entwurf.md', import.meta.url)), 'die 6c-Datei heißt nicht mehr -entwurf')
const quelle = lies('server/services/termine/rechtstexte.js')
assert.ok(!ENTWURF_WORT.test(quelle.replace(/RECHTSTEXTE_ENTWURF/g, '')), 'rechtstexte.js: ENTWURF in Kopf oder Kommentar (nur der Bezeichner RECHTSTEXTE_ENTWURF ist erlaubt)')
assert.ok(/export const RECHTSTEXTE_ENTWURF = false\b/.test(quelle))
assert.equal(RECHTSTEXTE_ENTWURF, false)
const einbau = lies('docs/funktionen/termine-einbau.md')
assert.ok(!ENTWURF_WORT.test(einbau), 'termine-einbau.md: ENTWURF')
assert.ok(!/nur beim Pilotkunden einsetzen|Solange der Kopf dort steht/.test(einbau), 'termine-einbau.md Abschnitt 6 ohne die Pilot-Einschränkung')
// die Gäste-Dokumente haben keine offene Rechtsprüfungs-Liste mehr (die Fragen stehen im Kanzlei-Paket)
for (const rel of DOKUMENTE.slice(0, 3)) assert.ok(!/^## Für die Rechtsprüfung/m.test(lies(rel)), `${rel}: Liste „Für die Rechtsprüfung offen“ steht noch`)
})
test('Hinweise-Dokument: Formular-Hinweis (Sie/Du) und alle Zustimmungstexte stehen wörtlich darin', () => {
@@ -553,7 +568,7 @@ test('Datenschutz-Absatz: Löschfristen der Vorgaben, Sie- und Du-Fassung, Auftr
})
test('6c-Entwurf: nur als Entwurfsdatei, public/datenschutz.html bleibt unverändert (ohne Abschnitt 6c)', () => {
const md = lies('docs/rechtliches/termine-datenschutz-6c-entwurf.md')
const md = lies('docs/rechtliches/termine-datenschutz-6c.md')
assert.ok(md.includes('6c. Online-Terminbuchung'))
assert.ok(md.includes('Auftragsverarbeiter'))
assert.ok(/nicht (in|auf) .*(einbauen|veröffentlich)|nicht eingebaut|nicht veröffentlicht/i.test(md))