Rechtsrunde Fassung 6 freigegeben (28.10.2026): Datum, Entwurfsmarken, R8b
Der Inhaber hat am 28.10.2026 alle Vorschläge R1 bis R18 angenommen (R10 = Option A) und zusätzlich R8b entschieden: Webshop und Buchungskalender kehren nach einem neuen Wartungs-Abo in der Frist automatisch zurück, wie der Server es schon tut. - Fassungen: Abo 2026-10-28-v6, Funktionsunterlagen 2026-10-28 (Seiten neu gebaut) - Entwurfsmarken und meta robots noindex entfernt, ENTWURF_V6 = false - WORTLAUT_STATUS = 'freigegeben' - Nr. 10 Abs. 5 Webshop/Buchungskalender ohne "auf Wunsch in Textform" (R8b) - Entscheidungsliste: Abschnitt "Freigabe"; Prüfpunkte D1 bis D8 auf die aktuelle Datenschutzerklärung (Fassung 22.10.2026, eine Zeile tiefer) umgestellt - Tests auf das neue Datum und den Status freigegeben gezogen Live ändert sich für Kunden nichts: die Seiten liegen unter /neu/ und gelten nur in der neuen Ansicht (Pilot). Umschalten (P10) folgt erst auf ausdrückliches "umschalten". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2873638b1c
commit
64f1a3b26b
36 files changed
+129
-131
No files matched your search
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const { config } = await import('../server/config.js')
|
||||
const ENTWURF = true
|
||||
const ENTWURF = false
|
||||
const lies = (rel) => readFileSync(new URL(`../${rel}`, import.meta.url), 'utf8')
|
||||
|
||||
const PFLICHT = [ // stelle = Überschrift im Entwurf; von/bis = Anker-Ids der Seite (null = Dokumentanfang bzw. -ende)
|
||||
@@ -29,7 +29,7 @@ const PFLICHT = [ // stelle = Überschrift im Entwurf; von/bis = Anker-Ids der
|
||||
]
|
||||
test('Entwurf Fassung 6: Stellen in Reihenfolge, jeder Pflichtsatz im Wortlaut unter seiner Stelle', () => {
|
||||
const md = lies('docs/rechtliches/abo-bedingungen-v6.md')
|
||||
assert.match(md, /^# Abo-Bedingungen Fassung 6 \(Entwurf\)\n/)
|
||||
assert.match(md, /^# Abo-Bedingungen Fassung 6\n/)
|
||||
assert.deepEqual([...md.matchAll(/^## (.+)$/gm)].map((m) => m[1]), [...PFLICHT.map((p) => p.stelle), 'Was gleich bleibt'])
|
||||
assert.equal(md.includes('Entwurf zur Freigabe, keine Rechtsberatung'), ENTWURF)
|
||||
for (const p of PFLICHT) {
|
||||
|
||||
@@ -50,7 +50,7 @@ test('consentAbo: Linkzeile mit Fassung, Hinweis (termine, shop, unterkunft), be
|
||||
if (key in HINWEIS) assert.equal(c.items[1].text, HINWEIS[key], key)
|
||||
}
|
||||
assert.equal(config.aboFunktionen.texte.beginn, 'Bitte beginnt sofort mit der Einrichtung. Mir ist klar: Mit der vollständigen Einrichtung erlischt mein Widerrufsrecht, und wenn ich vorher widerrufe, zahle ich den bis dahin erbrachten Teil.')
|
||||
assert.equal(V, '2026-10-16')
|
||||
assert.equal(V, '2026-10-28')
|
||||
assert.deepEqual(HINWEIS, { ...WORTLAUT_V6.hinweis }, 'die Hinweise sind die Sätze aus WORTLAUT_V6')
|
||||
assert.equal(V, FUNKTIONEN_FASSUNG_V6)
|
||||
assert.equal(reg.consentAbo('termine').items[0].text, mitFassung(WORTLAUT_V6.linkzeile.einschaltenMitAvv, FUNKTIONEN_FASSUNG_V6))
|
||||
|
||||
@@ -5,11 +5,11 @@ const { config } = await import('../server/config.js')
|
||||
const { WORTLAUT_V6, FUNKTIONEN_FASSUNG_V6, FUNKTIONEN_UNTERLAGEN_V6, mitFassung } = await import('../server/services/products.js')
|
||||
|
||||
test('Linkzeilen: Wortlaut, Platzhalter wird gefüllt', () => {
|
||||
assert.equal(mitFassung(WORTLAUT_V6.linkzeile.bestellen, '2026-10-16-v6'), 'Es gelten die Vertragsbedingungen mit Leistungsbeschreibung, Auftragsverarbeitung und Widerrufsbelehrung (Fassung 2026-10-16-v6). Alles kommt danach auch als PDF per Mail.')
|
||||
assert.equal(mitFassung(WORTLAUT_V6.linkzeile.einschaltenMitAvv, '2026-10-16'), 'Es gelten Leistungsbeschreibung und Auftragsverarbeitung (Fassung 2026-10-16).')
|
||||
assert.equal(mitFassung(WORTLAUT_V6.linkzeile.einschaltenOhneAvv, '2026-10-16'), 'Es gilt die Leistungsbeschreibung (Fassung 2026-10-16).')
|
||||
assert.equal(mitFassung(WORTLAUT_V6.linkzeile.bestellen, '2026-10-28-v6'), 'Es gelten die Vertragsbedingungen mit Leistungsbeschreibung, Auftragsverarbeitung und Widerrufsbelehrung (Fassung 2026-10-28-v6). Alles kommt danach auch als PDF per Mail.')
|
||||
assert.equal(mitFassung(WORTLAUT_V6.linkzeile.einschaltenMitAvv, '2026-10-28'), 'Es gelten Leistungsbeschreibung und Auftragsverarbeitung (Fassung 2026-10-28).')
|
||||
assert.equal(mitFassung(WORTLAUT_V6.linkzeile.einschaltenOhneAvv, '2026-10-28'), 'Es gilt die Leistungsbeschreibung (Fassung 2026-10-28).')
|
||||
assert.throws(() => mitFassung(WORTLAUT_V6.linkzeile.bestellen, ''), /Fassung/)
|
||||
assert.throws(() => mitFassung('ohne Platzhalter', '2026-10-16'), /Platzhalter/)
|
||||
assert.throws(() => mitFassung('ohne Platzhalter', '2026-10-28'), /Platzhalter/)
|
||||
})
|
||||
test('Häkchen beginn: Wortlaut je Fall (nur privat in der Frist)', () => {
|
||||
assert.deepEqual(WORTLAUT_V6.beginn, {
|
||||
@@ -33,13 +33,13 @@ test('alle Wortlaute: du-Form, kein Preis, nur {fassung} als Platzhalter, tief e
|
||||
for (const o of [WORTLAUT_V6, WORTLAUT_V6.linkzeile, WORTLAUT_V6.beginn, WORTLAUT_V6.hinweis]) assert.ok(Object.isFrozen(o))
|
||||
})
|
||||
test('Funktionsunterlagen Fassung 6: Reihenfolge, Vorschaupfad, AVV nur wo Daten im Auftrag verarbeitet werden', () => {
|
||||
assert.equal(FUNKTIONEN_FASSUNG_V6, '2026-10-16')
|
||||
assert.equal(FUNKTIONEN_FASSUNG_V6, '2026-10-28')
|
||||
assert.deepEqual(Object.keys(FUNKTIONEN_UNTERLAGEN_V6), ['news', 'termine', 'shop', 'unterkunft', 'zeiten', 'analyse'])
|
||||
assert.deepEqual(FUNKTIONEN_UNTERLAGEN_V6.termine.lb, { version: '2026-10-16', url: '/neu/rechtliches/leistungsbeschreibung-terminplaner-2026-10-16.html', pdfFile: 'neu/rechtliches/leistungsbeschreibung-terminplaner-2026-10-16.pdf', anhangName: 'Leistungsbeschreibung-Terminplaner-2026-10-16.pdf' })
|
||||
const slugs = Object.values(FUNKTIONEN_UNTERLAGEN_V6).flatMap((u) => [u.lb, u.avv]).filter(Boolean).map((d) => d.url.replace(/^\/neu\/rechtliches\/|-2026-10-16\.html$/g, ''))
|
||||
assert.deepEqual(FUNKTIONEN_UNTERLAGEN_V6.termine.lb, { version: '2026-10-28', url: '/neu/rechtliches/leistungsbeschreibung-terminplaner-2026-10-28.html', pdfFile: 'neu/rechtliches/leistungsbeschreibung-terminplaner-2026-10-28.pdf', anhangName: 'Leistungsbeschreibung-Terminplaner-2026-10-28.pdf' })
|
||||
const slugs = Object.values(FUNKTIONEN_UNTERLAGEN_V6).flatMap((u) => [u.lb, u.avv]).filter(Boolean).map((d) => d.url.replace(/^\/neu\/rechtliches\/|-2026-10-28\.html$/g, ''))
|
||||
assert.deepEqual(slugs, ['leistungsbeschreibung-news', 'leistungsbeschreibung-terminplaner', 'avv-terminplaner', 'leistungsbeschreibung-webshop', 'avv-webshop', 'leistungsbeschreibung-unterkunft', 'avv-unterkunft', 'leistungsbeschreibung-zeiten', 'leistungsbeschreibung-website-analyse', 'avv-website-analyse'])
|
||||
assert.deepEqual([FUNKTIONEN_UNTERLAGEN_V6.news.avv, FUNKTIONEN_UNTERLAGEN_V6.zeiten.avv], [null, null])
|
||||
assert.deepEqual(Object.values(FUNKTIONEN_UNTERLAGEN_V6).flatMap((u) => [u.lb, u.avv]).filter(Boolean).map((d) => d.anhangName.replace('-2026-10-16.pdf', '')), ['Leistungsbeschreibung-News-Pinnwand', 'Leistungsbeschreibung-Terminplaner', 'AVV-Terminplaner', 'Leistungsbeschreibung-Webshop', 'AVV-Webshop', 'Leistungsbeschreibung-Buchungskalender', 'AVV-Buchungskalender', 'Leistungsbeschreibung-Oeffnungszeiten-live', 'Leistungsbeschreibung-Website-Analyse', 'AVV-Website-Analyse'])
|
||||
assert.deepEqual(Object.values(FUNKTIONEN_UNTERLAGEN_V6).flatMap((u) => [u.lb, u.avv]).filter(Boolean).map((d) => d.anhangName.replace('-2026-10-28.pdf', '')), ['Leistungsbeschreibung-News-Pinnwand', 'Leistungsbeschreibung-Terminplaner', 'AVV-Terminplaner', 'Leistungsbeschreibung-Webshop', 'AVV-Webshop', 'Leistungsbeschreibung-Buchungskalender', 'AVV-Buchungskalender', 'Leistungsbeschreibung-Oeffnungszeiten-live', 'Leistungsbeschreibung-Website-Analyse', 'AVV-Website-Analyse'])
|
||||
})
|
||||
test('Verdrahtung mit P5 (läuft erst nach dem Merge von kn/server; rot, bis der Controller verdrahtet hat)', { skip: config.aboFunktionen === undefined && 'config.aboFunktionen fehlt: P5 noch nicht gemerged' }, async () => {
|
||||
const reg = await import('../server/services/featureRegistry.js')
|
||||
|
||||
@@ -13,7 +13,7 @@ const { render, funktionen6Dokumente } = await import('../scripts/build-legal-ht
|
||||
|
||||
const ROOT = fileURLToPath(new URL('..', import.meta.url))
|
||||
const lies = (rel) => readFileSync(join(ROOT, rel), 'utf8')
|
||||
const ENTWURF = true
|
||||
const ENTWURF = false
|
||||
|
||||
const B = { // Bausteine; {N} = Nominativ, {G} = Genitiv der Partei (Kunde/Kunden, Händler/Händlers, Vermieter/Vermieters)
|
||||
geltung: 'Diese Leistungsbeschreibung gilt, wenn der {N} die Funktion im Kundenportal einschaltet und die dort angezeigte Zeile mit dem Klick auf „Einschalten“ bestätigt. Sie gilt für Funktionen, die ab ihrer Veröffentlichung eingeschaltet werden; hat der {N} die Funktion zuvor einzeln gekauft, gilt für sie weiter die Leistungsbeschreibung, unter der er sie gekauft hat.',
|
||||
@@ -49,7 +49,7 @@ const abschnitte = (md) => md.split(/^## /m).slice(1).map((s) => ({ kopf: s.spli
|
||||
test('Leistungsbeschreibungen v6: Fassungszeile, Bausteine, Pflichtsatz, Frist, keine Preise', () => {
|
||||
for (const key of LISTE) {
|
||||
const e = LB[key], md = lies(`docs/rechtliches/${e.slug}-v6.md`)
|
||||
assert.match(md.split('\n')[2], new RegExp(`^\\*\\*Fassung 2026-10-16\\*\\*${ENTWURF ? ' · Entwurf – Prüfung durch Kanzlei offen' : ''} · Zusatzbedingungen zum Wartungs-Abo$`), key)
|
||||
assert.match(md.split('\n')[2], new RegExp(`^\\*\\*Fassung 2026-10-28\\*\\*${ENTWURF ? ' · Entwurf – Prüfung durch Kanzlei offen' : ''} · Zusatzbedingungen zum Wartungs-Abo$`), key)
|
||||
assert.doesNotMatch(md, VERBOTEN, key)
|
||||
for (const t of ['geltung', 'voraussetzung', 'einrichtung', 'projekt', 'mehraufwand', key === 'zeiten' ? 'endeZeiten' : 'ende']) assert.ok(md.includes(bau(B[t], e.partei)), `${key}: Baustein ${t}`)
|
||||
if (key === 'zeiten') assert.doesNotMatch(md, /Karenz|Behalten|spätestens am folgenden Tag/, 'Zeiten: 30 Tage Weiterlauf wie das bestehende Modul, kein Behalten, nicht die Abschaltung am Folgetag')
|
||||
@@ -67,13 +67,13 @@ test('Terminplaner v6: Überschriften wie Fassung 5 (Nr. 5 heißt „Einrichtung
|
||||
})
|
||||
test('Seiten der Gruppe sind genau render(Markdown)', () => {
|
||||
for (const key of LISTE) {
|
||||
const d = funktionen6Dokumente().find((x) => x.html.includes(`/${LB[key].slug}-2026-10-16.html`))
|
||||
const d = funktionen6Dokumente().find((x) => x.html.includes(`/${LB[key].slug}-2026-10-28.html`))
|
||||
assert.equal(lies(d.html), render(lies(d.md), d.meta), `${d.html}: node scripts/build-legal-html.mjs funktionen6 --nur=${d.doc}`)
|
||||
}
|
||||
})
|
||||
test('Entwurfsseiten der Gruppe: Entwurfsmarke in der Quelle und meta robots noindex in der Seite (N45)', () => {
|
||||
for (const key of LISTE) {
|
||||
const d = funktionen6Dokumente().find((x) => x.html.includes(`/${LB[key].slug}-2026-10-16.html`))
|
||||
const d = funktionen6Dokumente().find((x) => x.html.includes(`/${LB[key].slug}-2026-10-28.html`))
|
||||
assert.equal(lies(d.md).includes('Entwurf – Prüfung durch Kanzlei offen'), ENTWURF, d.md)
|
||||
assert.equal(lies(d.html).includes('<meta name="robots" content="noindex">'), ENTWURF, d.html)
|
||||
}
|
||||
@@ -98,7 +98,7 @@ const BA = {
|
||||
}
|
||||
test('Website-Analyse v6 (Leistungsbeschreibung): in beiden Abos, Einrichtung kostenlos, Ende mit dem Abo, Zielgruppenregel bleibt', () => {
|
||||
const md = lies('docs/rechtliches/leistungsbeschreibung-website-analyse-v6.md')
|
||||
assert.match(md.split('\n')[2], /^\*\*Fassung 2026-10-16\*\* · (Entwurf – Prüfung durch Kanzlei offen · )?Zusatzbedingungen zum Hosting-Abo und zum Wartungs-Abo$/)
|
||||
assert.match(md.split('\n')[2], /^\*\*Fassung 2026-10-28\*\* · (Entwurf – Prüfung durch Kanzlei offen · )?Zusatzbedingungen zum Hosting-Abo und zum Wartungs-Abo$/)
|
||||
assert.doesNotMatch(md, /€|per Haken|[a-zäöüß,;:] Sie\b|Im Hosting-Abo ist die Website-Analyse nicht enthalten|Sie ist im Wartungs-Abo ohne Aufpreis|im Kundenportal bestellt/)
|
||||
assert.ok(md.includes(B.geltung.replaceAll('{N}', 'Kunde')))
|
||||
for (const t of Object.values(BA)) assert.ok(md.includes(t), t.slice(0, 40))
|
||||
@@ -114,16 +114,16 @@ const AVV = { // [alt, neu] je Datei; der Rest ab "## 1." ist sonst wortgleich
|
||||
['(Ausschalten oder Ende des Wartungs-Abos)', '(Ausschalten oder Ende des Abos, das sie enthält)'],
|
||||
['Das Wartungs-Abo bleibt davon unberührt.', 'Das Abo bleibt davon unberührt.']],
|
||||
}
|
||||
const KOPF_AVV = ENTWURF ? '**Fassung 2026-10-16** · Entwurf – Prüfung durch Kanzlei offen' : '**Fassung 2026-10-16**'
|
||||
const KOPF_AVV = ENTWURF ? '**Fassung 2026-10-28** · Entwurf – Prüfung durch Kanzlei offen' : '**Fassung 2026-10-28**'
|
||||
const AVV_FUNKTION = { termine: 'Terminplaner', shop: 'Webshop', unterkunft: 'Buchungskalender', analyse: 'Website-Analyse' }
|
||||
const avvVorbemerkung = (funktion, fassung) => `Der Kunde schließt diesen Vertrag, wenn er im Kundenportal die Funktion „${funktion}“ einschaltet und die dort angezeigte Zeile „Es gelten Leistungsbeschreibung und Auftragsverarbeitung (Fassung ${fassung})“ mit dem Klick auf „Einschalten“ bestätigt; diesem Vertrag und der Leistungsbeschreibung „${funktion}“ („Leistungsbeschreibung“) in der angezeigten Fassung stimmt er damit zu. Das Portal speichert den Wortlaut der angezeigten Zeile, die Fassungen, den Zeitpunkt, das Kundenkonto und die angemeldete Person. Anschließend sendet WEBklar dem Kunden eine E-Mail, die beide Texte in dieser Fassung als PDF enthält und auf sie verlinkt.`
|
||||
function pruefeAvv(key, slug) {
|
||||
const v5 = lies(`docs/rechtliches/${slug}.md`), v6 = lies(`docs/rechtliches/${slug}-v6.md`)
|
||||
assert.equal(v6.split('\n')[2], KOPF_AVV)
|
||||
assert.ok(v6.includes(`(1) ${avvVorbemerkung(AVV_FUNKTION[key], '2026-10-16')}`))
|
||||
assert.ok(v6.includes(`(1) ${avvVorbemerkung(AVV_FUNKTION[key], '2026-10-28')}`))
|
||||
assert.doesNotMatch(v6, /per Haken|Mit der Bestellung weist|Vertrag über den Betrieb der Website|keine Bestellung geworden/)
|
||||
let erwartet = v5.replace(/^\*\*Fassung \d{4}-\d{2}-\d{2}\*\*.*$/m, KOPF_AVV)
|
||||
erwartet = erwartet.replace(/^\(1\) Der Kunde schließt diesen Vertrag[^\n]*$/m, `(1) ${avvVorbemerkung(AVV_FUNKTION[key], '2026-10-16')}`)
|
||||
erwartet = erwartet.replace(/^\(1\) Der Kunde schließt diesen Vertrag[^\n]*$/m, `(1) ${avvVorbemerkung(AVV_FUNKTION[key], '2026-10-28')}`)
|
||||
for (const [alt, neu] of AVV[key]) { assert.ok(erwartet.includes(alt), `Bestand enthält: ${alt.slice(0, 40)}`); erwartet = erwartet.replaceAll(alt, neu) }
|
||||
assert.equal(v6, erwartet, `${slug}-v6.md = Bestandsfassung mit genau diesen Ersetzungen`)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const BESTELLEN = mitFassung(WORTLAUT_V6.linkzeile.bestellen, FASSUNG) // der Sa
|
||||
const T = bestelltexteFuerClient({ fassung: FASSUNG, termsUrl: URL_V6 })
|
||||
|
||||
test('Status, Knopf und feste Fehlertexte', () => {
|
||||
assert.deepEqual([WORTLAUT_STATUS, KNOPF], ['entwurf', 'Zahlungspflichtig bestellen'])
|
||||
assert.deepEqual([WORTLAUT_STATUS, KNOPF], ['freigegeben', 'Zahlungspflichtig bestellen'])
|
||||
assert.equal(WECHSEL_GESPERRT, 'Der Wechsel gilt sofort. Ohne dein Häkchen können wir ihn nicht ausführen.')
|
||||
assert.equal(FEHLER_NACHWEIS, 'Das hat gerade nicht geklappt. Bitte versuche es gleich noch einmal.')
|
||||
assert.equal(FEHLER_BEDINGUNGEN, 'Die Bedingungen wurden aktualisiert. Bitte lade die Seite neu.')
|
||||
@@ -32,7 +32,7 @@ test('klartext und Fassungsangabe', () => {
|
||||
})
|
||||
|
||||
test('Nutzlast: Linkzeile und Häkchen sind der Wortlaut aus P8 (WORTLAUT_V6), dazu Sperrzeile, Warnzeile, Satz, Kundenart-Zeile (Entwurf)', () => {
|
||||
assert.deepEqual([T.status, T.fassung, T.knopf], ['entwurf', FASSUNG, 'Zahlungspflichtig bestellen'])
|
||||
assert.deepEqual([T.status, T.fassung, T.knopf], ['freigegeben', FASSUNG, 'Zahlungspflichtig bestellen'])
|
||||
assert.equal(klartext(T.abo.linkzeile.text), `${BESTELLEN} Datenschutz`)
|
||||
assert.deepEqual(T.abo.linkzeile.text.match(/\[[^\]]+\]/g), ['[Vertragsbedingungen]', '[Datenschutz]'])
|
||||
assert.deepEqual(T.abo.linkzeile.urls, [URL_V6, '/datenschutz.html'])
|
||||
@@ -98,9 +98,9 @@ test('nachweisTexte: Reihenfolge, Häkchen nur wenn gezeigt und gesetzt, Klartex
|
||||
assert.equal(nachweisTexte('abo', { kundenart: 'privat', fassung }).some((t) => t.id === 'bestellung'), false, 'ohne Zeile keine Zeile')
|
||||
})
|
||||
|
||||
test('Quelle: keine Imports, du-Form, als Entwurf gekennzeichnet', () => {
|
||||
test('Quelle: keine Imports, du-Form, als freigegeben gekennzeichnet', () => {
|
||||
const q = readFileSync(new URL('../server/services/bestelltexte.js', import.meta.url), 'utf8')
|
||||
assert.doesNotMatch(q, /^import /m)
|
||||
assert.doesNotMatch(q, /\b(Sie|Ihr|Ihre|Ihnen)\b/)
|
||||
assert.match(q, /ENTWURF/)
|
||||
assert.match(q, /Freigegeben vom Inhaber am 28\.10\.2026/)
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs'
|
||||
|
||||
// Entscheidungsliste, Kanzlei-Fragen, Ablauf nach der Freigabe und Datenschutz-Prüfpunkte der Rechtsrunde Fassung 6 (Task 11).
|
||||
const lies = (rel) => readFileSync(new URL(`../${rel}`, import.meta.url), 'utf8')
|
||||
const ENTWURF = true
|
||||
const ENTWURF = false
|
||||
const md = lies('docs/rechtliches/kundenansicht-entscheidungen.md'), dsg = lies('docs/rechtliches/datenschutz-kundenansicht-pruefpunkte.md')
|
||||
test('Entscheidungsliste: R1 bis R18, je Punkt Vorschlag, Alternative und Kanzlei-Frage', () => {
|
||||
const z = md.split('\n').filter((l) => /^\| R\d+ \|/.test(l)).map((l) => l.split('|').slice(1, -1).map((s) => s.trim()))
|
||||
@@ -39,5 +39,5 @@ test('Datenschutz-Prüfpunkte D1 bis D8 mit gültiger Fundstelle in public/daten
|
||||
const zeilen = lies('public/datenschutz.html').split('\n')
|
||||
const refs = [...dsg.matchAll(/public\/datenschutz\.html:(\d+)/g)].map((m) => Number(m[1]))
|
||||
assert.ok(refs.length >= 8 && refs.every((n) => n >= 1 && n <= zeilen.length))
|
||||
assert.ok(zeilen[126].includes('Zahlungspflichtig bestellen') && zeilen[125].includes('Leistungsbeginn') && zeilen[101].includes('Wartungs-Abo'), 'D1, D2, D5 zitieren die richtigen Zeilen')
|
||||
assert.ok(zeilen[127].includes('Zahlungspflichtig bestellen') && zeilen[126].includes('Leistungsbeginn') && zeilen[102].includes('Wartungs-Abo'), 'D1, D2, D5 zitieren die richtigen Zeilen')
|
||||
})
|
||||
@@ -14,7 +14,7 @@ useTestEnv({ appwriteUrl: 'http://127.0.0.1:1', stripeUrl: 'http://127.0.0.1:1'
|
||||
const { render, lauf, funktionen6Dokumente } = await import('../scripts/build-legal-html.mjs')
|
||||
|
||||
const ROOT = fileURLToPath(new URL('..', import.meta.url))
|
||||
const F = '2026-10-16'
|
||||
const F = '2026-10-28'
|
||||
const SLUGS = ['leistungsbeschreibung-news', 'leistungsbeschreibung-terminplaner', 'avv-terminplaner', 'leistungsbeschreibung-webshop', 'avv-webshop', 'leistungsbeschreibung-unterkunft', 'avv-unterkunft', 'leistungsbeschreibung-zeiten', 'leistungsbeschreibung-website-analyse', 'avv-website-analyse']
|
||||
test('funktionen6Dokumente: zehn Seiten unter public/neu/rechtliches, Quellen *-v6.md, Navigation im Vorschaupfad', () => {
|
||||
const d = funktionen6Dokumente()
|
||||
@@ -68,7 +68,7 @@ test('build-legal-pdfs.sh: Modi abo6 und funktionen6, all und erste Usage-Zeile
|
||||
assert.equal(spawnSync('bash', ['-n', join(ROOT, 'scripts/build-legal-pdfs.sh')], { encoding: 'utf8' }).status, 0)
|
||||
})
|
||||
test('PDFs der Fassung 6: erst nach der Freigabe vorhanden; wenn vorhanden, PDF, Prüfsumme der Seite daneben', { skip: !existsSync(join(ROOT, 'public/neu/rechtliches/WEBklar-Abo-Vertragsunterlagen.pdf')) && 'PDFs entstehen erst nach der Freigabe (Task 11, Ablauf)' }, () => {
|
||||
for (const p of ['abo-bedingungen.html', ...SLUGS.map((s) => `${s}-2026-10-16.html`)]) {
|
||||
for (const p of ['abo-bedingungen.html', ...SLUGS.map((s) => `${s}-2026-10-28.html`)]) {
|
||||
const pdf = join(ROOT, 'public/neu/rechtliches', p === 'abo-bedingungen.html' ? 'WEBklar-Abo-Vertragsunterlagen.pdf' : p.replace('.html', '.pdf'))
|
||||
assert.equal(readFileSync(pdf).subarray(0, 5).toString(), '%PDF-', pdf)
|
||||
assert.equal(readFileSync(`${pdf}.sha256`, 'utf8').trim(), createHash('sha256').update(readFileSync(join(ROOT, 'public/neu/rechtliches', p))).digest('hex'), p)
|
||||
|
||||
+10
-10
@@ -141,14 +141,14 @@ test('Vertragsunterlagen je Version', () => {
|
||||
|
||||
test('Fassungstabelle: Reihenfolge, Muster nur am Ende, v6 im Vorschaupfad', () => {
|
||||
assert.deepEqual(FASSUNGEN.map((f) => f.muster?.source ?? 'sonst'), ['-v6$', '-v5$', '-v4$', 'sonst'])
|
||||
assert.deepEqual(termsDocFor('2026-10-16-v6'), {
|
||||
version: '2026-10-16-v6', url: '/neu/rechtliches/abo-bedingungen.html',
|
||||
assert.deepEqual(termsDocFor('2026-10-28-v6'), {
|
||||
version: '2026-10-28-v6', url: '/neu/rechtliches/abo-bedingungen.html',
|
||||
pdfFile: 'neu/rechtliches/WEBklar-Abo-Vertragsunterlagen.pdf', endParagraph: '§ 11',
|
||||
})
|
||||
for (const v of ['2026-10-16-v60', '2026-10-16-v6x', 'v6-2026-10-16', '2026-10-16-v66']) {
|
||||
for (const v of ['2026-10-28-v60', '2026-10-28-v6x', 'v6-2026-10-28', '2026-10-28-v66']) {
|
||||
assert.equal(termsDocFor(v).url, '/vertragsunterlagen/v3/abo-bedingungen.html', v)
|
||||
}
|
||||
const a = contractAttachmentFor('2026-10-16-v6')
|
||||
const a = contractAttachmentFor('2026-10-28-v6')
|
||||
assert.equal(a.filename, 'WEBklar-Abo-Vertragsunterlagen.pdf')
|
||||
assert.ok(path.isAbsolute(a.path) && a.path.endsWith(path.join('public', 'neu', 'rechtliches', 'WEBklar-Abo-Vertragsunterlagen.pdf')))
|
||||
})
|
||||
@@ -159,8 +159,8 @@ test('Fassung 5 bleibt bis P10 an den heutigen Adressen, das Archiv ist vorberei
|
||||
assert.deepEqual(v5.archiv, { url: '/vertragsunterlagen/v5/abo-bedingungen.html', pdfFile: 'vertragsunterlagen/v5/WEBklar-Abo-Vertragsunterlagen.pdf' })
|
||||
})
|
||||
test('isTermsV4OrNewer: v4, v5, v6 ja; v3, fehlende und fremde Endungen nein', () => {
|
||||
for (const v of ['2026-09-30-v4', '2026-09-30-v5', '2026-10-16-v6']) assert.equal(isTermsV4OrNewer(v), true, v)
|
||||
for (const v of ['2026-09-25-v3', '2026-09-25', null, undefined, '', '2026-10-16-v60', 7]) assert.equal(isTermsV4OrNewer(v), false, String(v))
|
||||
for (const v of ['2026-09-30-v4', '2026-09-30-v5', '2026-10-28-v6']) assert.equal(isTermsV4OrNewer(v), true, v)
|
||||
for (const v of ['2026-09-25-v3', '2026-09-25', null, undefined, '', '2026-10-28-v60', 7]) assert.equal(isTermsV4OrNewer(v), false, String(v))
|
||||
})
|
||||
test('aktuelleAboFassung: v6 nur mit neuer Ansicht', () => {
|
||||
// appwriteUserId gehört zur gültigen Kunden-Sitzung (N1, schalter.js istKundenSitzung)
|
||||
@@ -172,13 +172,13 @@ test('aktuelleAboFassung: v6 nur mit neuer Ansicht', () => {
|
||||
Object.assign(config.kundenansichtNeu, { mode: 'pilot', pilotEmails: [] })
|
||||
assert.equal(aktuelleAboFassung(k('a@example.invalid')), '2026-09-30-v5', 'leere Liste lässt niemanden durch')
|
||||
config.kundenansichtNeu.pilotEmails = ['a@example.invalid']
|
||||
assert.equal(aktuelleAboFassung(k(' A@Example.INVALID ')), '2026-10-16-v6', 'Großschreibung und Leerraum')
|
||||
assert.equal(aktuelleAboFassung(k(' A@Example.INVALID ')), '2026-10-28-v6', 'Großschreibung und Leerraum')
|
||||
assert.equal(aktuelleAboFassung(k('b@example.invalid')), '2026-09-30-v5')
|
||||
assert.equal(aktuelleAboFassung(k('b@example.invalid', { viewAs: true, viewId: 'v1' })), '2026-10-16-v6', 'Mitarbeiter-Kundenansicht')
|
||||
assert.equal(aktuelleAboFassung(k('b@example.invalid', { viewAs: true, viewId: 'v1' })), '2026-10-28-v6', 'Mitarbeiter-Kundenansicht')
|
||||
Object.assign(config.kundenansichtNeu, { mode: 'on', pilotEmails: [] })
|
||||
assert.equal(aktuelleAboFassung(k('b@example.invalid')), '2026-10-16-v6')
|
||||
assert.equal(aktuelleAboFassung(k('b@example.invalid')), '2026-10-28-v6')
|
||||
} finally { Object.assign(config.kundenansichtNeu, vorher) }
|
||||
assert.deepEqual([config.abo.termsVersion, config.abo.termsVersionNeu], ['2026-09-30-v5', '2026-10-16-v6'])
|
||||
assert.deepEqual([config.abo.termsVersion, config.abo.termsVersionNeu], ['2026-09-30-v5', '2026-10-28-v6'])
|
||||
})
|
||||
|
||||
test('Produktansicht enthält Leistungen ohne 1-Postfach-Regel bei Wartung', () => {
|
||||
|
||||
Reference in new issue
Block a user