Rechtstexte: Widerrufsbelehrung der Funktionen aus dem Gesetzesmuster
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3073fffcf5
commit
6b4cbb3204
3 files changed
+268
-7
No files matched your search
@@ -1,10 +1,14 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { config } from '../server/config.js'
|
||||
import {
|
||||
RECHTSTEXTE_VERSION,
|
||||
RECHTSTEXTE_ENTWURF,
|
||||
WEBKLAR_KONTAKT,
|
||||
widerrufsbelehrung,
|
||||
widerrufsbelehrungFunktion,
|
||||
formularHinweis,
|
||||
datenschutzAbsatz,
|
||||
ZUSTIMMUNG_VORZEITIG,
|
||||
@@ -481,3 +485,178 @@ test('6c-Entwurf: nur als Entwurfsdatei, public/datenschutz.html bleibt unverän
|
||||
const html = lies('public/datenschutz.html')
|
||||
assert.ok(!/6c\./.test(html) && !html.includes('id="termine"'), '6c darf vor TP2 nicht in public/datenschutz.html stehen')
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------- Widerrufsbelehrung der Funktionen (WEBklar als Unternehmer, Task F3)
|
||||
|
||||
const KONTAKT_WEBKLAR = 'WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler, Telefon: 0176 23726355, E-Mail: support@webklar.com'
|
||||
const AN_ZEILE_WEBKLAR = 'An WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler, E-Mail: support@webklar.com:'
|
||||
const WIDERRUFEN_URL = 'https://project.webklar.com/widerrufen.html'
|
||||
const funktion = () => widerrufsbelehrungFunktion()
|
||||
|
||||
test('Gäste-Belehrung bleibt byte-gleich (Hash der Ausgabe vor Task F3)', () => {
|
||||
const text = widerrufsbelehrung(BETRIEB)
|
||||
assert.equal(createHash('sha256').update(text).digest('hex'), 'a4b3549b7b4ba0bdeeca438bc823d07eede5e4aa35e21e335fb5304ff36a1ff6')
|
||||
assert.equal(text.length, 2983)
|
||||
})
|
||||
|
||||
test('WEBKLAR_KONTAKT: WEBklar GbR als Unternehmer', () => {
|
||||
assert.deepEqual(WEBKLAR_KONTAKT, {
|
||||
name: 'WEBklar GbR',
|
||||
adresse: 'Am Schwimmbad 10, 67722 Winnweiler',
|
||||
telefon: '0176 23726355',
|
||||
email: 'support@webklar.com',
|
||||
})
|
||||
})
|
||||
|
||||
test('belehrung_funktion_muster_woertlich: jeder Absatz der Anlage 1 für Dienstleistungen steht wörtlich darin, nur die Lücken sind gefüllt', () => {
|
||||
const text = funktion()
|
||||
const muster = musterZeilen()
|
||||
assert.equal(muster.length, 8)
|
||||
assert.equal(fristBaustein(), 'des Vertragsabschlusses.')
|
||||
for (const zeile of muster) {
|
||||
const erwartet = zeile
|
||||
.replace('ab dem Tag .', `ab dem Tag ${fristBaustein()}`)
|
||||
.replace('uns ( )', `uns (${KONTAKT_WEBKLAR})`)
|
||||
assert.ok(text.includes(erwartet), `Absatz fehlt oder ist verändert: ${erwartet}`)
|
||||
}
|
||||
// Fristbeginn und Kontakt stehen genau einmal
|
||||
assert.equal(text.split('ab dem Tag des Vertragsabschlusses.').length - 1, 1)
|
||||
assert.equal(text.split(KONTAKT_WEBKLAR).length - 1, 1)
|
||||
})
|
||||
|
||||
test('belehrung_funktion_widerrufsfunktion: Satzrahmen der Anlage 1 wörtlich, die Lücke ist die Adresse der Widerrufsfunktion', () => {
|
||||
const text = funktion()
|
||||
const m = /„(Sie können Ihr Widerrufsrecht auch online unter) … \[[^\]]+\] (ausüben\.) (Wenn Sie diese Online-Funktion nutzen, [^“]+?)\s*“/.exec(ANLAGE_1)
|
||||
assert.ok(m, 'Gestaltungshinweis zur Widerrufsfunktion fehlt oder hat sich geändert in anlage-1.txt')
|
||||
const absatz = `${m[1]} ${WIDERRUFEN_URL} ${m[2]} ${m[3]}`
|
||||
assert.ok(text.includes(absatz), 'Absatz zur Widerrufsfunktion fehlt oder weicht vom Satzrahmen der Anlage ab')
|
||||
assert.equal(text.split(WIDERRUFEN_URL).length - 1, 1)
|
||||
// kein Hinweis auf eine Schaltfläche der Gäste-Fassung
|
||||
assert.ok(!text.includes('Termin verwalten') && !text.includes('Schaltfläche'))
|
||||
// der Wertersatz für Dienstleistungen steht wie in der Gäste-Fassung
|
||||
const w = /Haben Sie verlangt, dass die Dienstleistungen oder Lieferung von Wasser\/Gas\/Strom\/Fernwärme \[Unzutreffendes streichen\] (während[^“]+?)\s*“/.exec(ANLAGE_1)
|
||||
assert.ok(w)
|
||||
assert.ok(text.includes(`Haben Sie verlangt, dass die Dienstleistungen ${w[1]}`))
|
||||
})
|
||||
|
||||
test('belehrung_funktion_formular: Muster-Widerrufsformular der Anlage 2 wörtlich, „An“-Zeile mit WEBklar', () => {
|
||||
const text = funktion()
|
||||
const z = zeilen(ANLAGE_2)
|
||||
const start = z.indexOf('Muster-Widerrufsformular')
|
||||
assert.ok(start >= 0)
|
||||
const inhalt = z.slice(start).filter((s) => s && s !== '–')
|
||||
let anZeile = 0
|
||||
for (const zeile of inhalt) {
|
||||
if (zeile.startsWith('An [hier ist')) {
|
||||
anZeile += 1
|
||||
assert.ok(text.includes(`– ${AN_ZEILE_WEBKLAR}`), '„An“-Zeile mit WEBklar fehlt')
|
||||
} else {
|
||||
assert.ok(text.includes(zeile), `Formularzeile fehlt oder ist verändert: ${zeile}`)
|
||||
}
|
||||
}
|
||||
assert.equal(anZeile, 1)
|
||||
assert.equal(text.split(AN_ZEILE_WEBKLAR).length - 1, 1)
|
||||
})
|
||||
|
||||
test('Belehrung der Funktionen: reiner Text ohne Platzhalter, deterministisch, Sie-Form, nichts zu Waren', () => {
|
||||
const text = funktion()
|
||||
assert.equal(typeof text, 'string')
|
||||
assert.ok(text.startsWith('Widerrufsbelehrung\n\nWiderrufsrecht\n'))
|
||||
assert.ok(text.includes('\n') && !text.includes('\r'))
|
||||
assert.ok(!/\{\{|\}\}|\[|\]|…|\.\.\.|\(\s*\)/.test(text), 'offener Platzhalter')
|
||||
assert.ok(!/undefined|null|NaN/.test(text))
|
||||
assert.ok(!/Rücksendung|zurückzusenden|Besitz genommen|Wir holen die Waren|Sie haben die Waren|elektronisch ausfüllen|Wir können die Rückzahlung verweigern/.test(text))
|
||||
assert.ok(!DU_WOERTER.test(text))
|
||||
assert.ok(!/&|<[a-z/]/i.test(text))
|
||||
assert.equal(funktion(), funktion())
|
||||
// Gäste-Fassung und Funktionen-Fassung unterscheiden sich nur in Kontakt, Widerrufsfunktion und „An“-Zeile
|
||||
const gast = widerrufsbelehrung(WEBKLAR_KONTAKT)
|
||||
assert.notEqual(gast, text)
|
||||
const ohneUnterschied = (t) => t.replace(/uns \([^)]*\)/, 'uns (K)').replace(/Sie können Ihr Widerrufsrecht auch online unter .*? ausüben\./, 'WF').replace(/– An [^\n]*/, '– An A')
|
||||
assert.equal(ohneUnterschied(gast), ohneUnterschied(text))
|
||||
})
|
||||
|
||||
/** Text eines HTML-Ausschnitts: Tags weg, Entitäten der Seite aufgelöst, Leerraum zu einem Leerzeichen. */
|
||||
const klartext = (html) =>
|
||||
html
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
|
||||
test('belehrung_gleich_abo: Belehrung der Funktionen = Belehrung in den Abo-Bedingungen (Drift-Wächter), Muster-Formular ebenso', () => {
|
||||
const html = lies('public/abo-bedingungen.html')
|
||||
const von = html.indexOf('<h3>Widerrufsrecht</h3>')
|
||||
const bis = html.indexOf('Ende der Widerrufsbelehrung')
|
||||
assert.ok(von > 0 && bis > von, 'Aufbau der Abo-Bedingungen hat sich geändert')
|
||||
// Die Kontaktklammer wird vorher in beiden durch einen Platzhalter ersetzt: die Gäste-Bausteine setzen „Telefon: …, E-Mail: …“ mit
|
||||
// Doppelpunkt, die Abo-Bedingungen ohne
|
||||
const klammer = (t) => t.replace(/uns \([^)]*\)/, 'uns (KONTAKT)')
|
||||
const abo = klammer(klartext(html.slice(von, bis)))
|
||||
const text = funktion()
|
||||
const formularKopf = text.indexOf('\n\nMuster-Widerrufsformular\n\n')
|
||||
assert.ok(formularKopf > 0)
|
||||
const textBelehrung = text.slice(text.indexOf('Widerrufsrecht'), formularKopf)
|
||||
assert.equal(klammer(klartext(textBelehrung)), abo)
|
||||
|
||||
// Muster-Widerrufsformular: Kasten der Abo-Bedingungen gegen die Formularzeilen, „An“-Zeile vorher ersetzt
|
||||
const formVon = html.indexOf('<h3 id="formular">Muster-Widerrufsformular</h3>')
|
||||
const formBis = html.indexOf('<h2 id="widerruf-hinweise">')
|
||||
assert.ok(formVon > bis && formBis > formVon)
|
||||
const an = (t) => t.replace(/– An .*?:(?= – Hiermit)/, '– An ANSCHRIFT:')
|
||||
const aboForm = an(klartext(html.slice(formVon, formBis)).replace(/^Muster-Widerrufsformular /, ''))
|
||||
const textForm = an(klartext(text.slice(formularKopf + 2).replace(/^Muster-Widerrufsformular\n+/, '')))
|
||||
assert.equal(textForm, aboForm)
|
||||
assert.ok(aboForm.includes('– An ANSCHRIFT:') && textForm.includes('– An ANSCHRIFT:'))
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------- Dokument widerrufsbelehrung-funktionen.md
|
||||
|
||||
const DOKU = 'docs/rechtliches/widerrufsbelehrung-funktionen.md'
|
||||
|
||||
/** Absätze eines Textes: durch Leerzeilen getrennt, jede Zeile eines Absatzes für sich (Formularzeilen), Überschriften-Zeichen weg. */
|
||||
const absaetze = (text) =>
|
||||
text
|
||||
.replace(/\r/g, '')
|
||||
.split(/\n{2,}/)
|
||||
.flatMap((block) => block.split('\n'))
|
||||
.map((z) => z.replace(/^#+\s+/, '').trim())
|
||||
.filter(Boolean)
|
||||
|
||||
test('belehrung_dokument_gepinnt: Textblock der Datei = Ausgabe von widerrufsbelehrungFunktion(), Kopf mit Fassung, keine Platzhalter', () => {
|
||||
const md = lies(DOKU).replace(/\r/g, '')
|
||||
const zeilenMd = md.split('\n')
|
||||
assert.equal(zeilenMd[0], '# Widerrufsbelehrung und Muster-Widerrufsformular (Einrichtung von Funktionen)')
|
||||
assert.equal(zeilenMd[2], `**Fassung ${config.termine.avvVersion}**`)
|
||||
assert.ok(!/\[|\]|…|\{\{|\}\}/.test(md), 'offener Platzhalter')
|
||||
assert.ok(!/ENTWURF|Entwurf|Prüfpunkt|TODO|<!--/.test(md))
|
||||
assert.ok(md.includes('Das Widerrufsrecht gilt nur für Verbraucher.'))
|
||||
|
||||
// Belehrung und Formular stehen als Überschriften und Absätze (Markdown ohne Codeblock), wörtlich und in Reihenfolge
|
||||
const von = md.indexOf('## Widerrufsbelehrung')
|
||||
const bis = md.indexOf('## Hinweise zum Widerruf')
|
||||
assert.ok(von > 0 && bis > von, 'Aufbau der Datei: Abschnitte „Widerrufsbelehrung“ … „Hinweise zum Widerruf“')
|
||||
assert.deepEqual(absaetze(md.slice(von, bis)), absaetze(widerrufsbelehrungFunktion()))
|
||||
// keine Codeblöcke, Zitate oder Tabellen im Belehrungsteil
|
||||
assert.ok(!/```|^>|^\|/m.test(md.slice(von, bis)))
|
||||
})
|
||||
|
||||
test('belehrung_dokument_hinweise: vier Hinweise zum Widerruf, nicht Teil der gesetzlichen Belehrung', () => {
|
||||
const md = lies(DOKU).replace(/\r/g, '')
|
||||
const hinweise = md.slice(md.indexOf('## Hinweise zum Widerruf'))
|
||||
assert.match(hinweise, /nicht zur gesetzlichen Widerrufsbelehrung/)
|
||||
const punkte = hinweise.split('\n').filter((z) => z.startsWith('- '))
|
||||
assert.equal(punkte.length, 4)
|
||||
assert.match(punkte[0], /jede Bestellung einer Funktion gesondert/)
|
||||
assert.match(punkte[0], /Website-Projekt/)
|
||||
assert.match(punkte[1], /endet diese Funktion/)
|
||||
assert.match(punkte[1], /Wartungs-Abo bleibt davon unberührt/)
|
||||
assert.match(punkte[2], /ausdrücklichen Wunsch/)
|
||||
assert.match(punkte[2], /angemessenen Betrag für die bis dahin erbrachte Leistung/)
|
||||
assert.match(punkte[2], /Anteil am Gesamtpreis/)
|
||||
assert.match(punkte[2], /Arbeitsblatt erfasste Aufwand/)
|
||||
assert.match(punkte[3], /ursprüngliche Zahlungsmittel/)
|
||||
assert.match(punkte[3], /Stripe/)
|
||||
assert.ok(!DU_WOERTER.test(md))
|
||||
})
|
||||
Reference in new issue
Block a user