109 lines
5.6 KiB
JavaScript
109 lines
5.6 KiB
JavaScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { createHash } from 'node:crypto'
|
|
|
|
const { config } = await import('../server/config.js')
|
|
|
|
// = `git show 18c3cdf:public/abo-bedingungen.html | sha256sum` (Fassung 3, Stand 25.09.2026).
|
|
// Fest eingetragen, weil die Tests auch ohne git laufen muessen (node:20-alpine).
|
|
const V3_SHA256 = '1c9e4b002dcd5d7896bbda24c573e56544b7123e3a88d154e5db77bd2855bc47'
|
|
|
|
const file = (rel) => new URL(`../${rel}`, import.meta.url)
|
|
const read = (rel) => readFileSync(file(rel), 'utf8')
|
|
const sha256 = (rel) => createHash('sha256').update(readFileSync(file(rel))).digest('hex')
|
|
const strip = (html) => html.replace(/<[^>]+>/g, ' ').replace(/ /g, ' ').replace(/\s+/g, ' ').trim()
|
|
|
|
// HTML-Ausschnitt von einem Anker bis zum naechsten, danach ohne Tags. Die Inhaltsuebersicht wiederholt
|
|
// die Ueberschriften, deshalb wird nicht nach dem ersten Text-Treffer, sondern nach id="..." geschnitten.
|
|
const section = (html, fromId, toId) => {
|
|
const a = html.indexOf(`id="${fromId}"`)
|
|
assert.ok(a >= 0, `Anker #${fromId} fehlt`)
|
|
const b = html.indexOf(`id="${toId}"`, a + 1)
|
|
assert.ok(b > a, `Anker #${toId} fehlt nach #${fromId}`)
|
|
return strip(html.slice(a, b))
|
|
}
|
|
const between = (s, a, b) => {
|
|
const i = s.indexOf(a)
|
|
assert.ok(i >= 0, `Text fehlt: ${a}`)
|
|
const j = s.indexOf(b, i)
|
|
assert.ok(j > i, `Text fehlt: ${b}`)
|
|
return s.slice(i, j)
|
|
}
|
|
|
|
const V3 = 'public/vertragsunterlagen/v3/abo-bedingungen.html'
|
|
const V4 = 'public/abo-bedingungen.html'
|
|
|
|
test('Fassung 3 unverändert archiviert (HTML und PDF)', () => {
|
|
assert.equal(sha256(V3), V3_SHA256)
|
|
// Das bisherige PDF bleibt die Fassung 3 und liegt identisch im Archiv
|
|
assert.equal(
|
|
sha256('public/vertragsunterlagen/v3/WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf'),
|
|
sha256('public/WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf')
|
|
)
|
|
assert.ok(read(V3).includes('Version 3 · Stand 25.09.2026'))
|
|
})
|
|
|
|
test('Widerrufsbelehrung und Muster-Widerrufsformular wortgleich mit Fassung 3', () => {
|
|
const v3 = read(V3), v4 = read(V4)
|
|
const muster = (belehrung) => between(belehrung, 'Widerrufsrecht Sie haben das Recht', 'Ende der Widerrufsbelehrung')
|
|
const muster3 = muster(section(v3, 'widerruf', 'formular'))
|
|
assert.ok(muster3.length > 1500)
|
|
assert.equal(muster(section(v4, 'widerruf', 'formular')), muster3)
|
|
// Muster-Widerrufsformular (Anlage 2 zu Art. 246a EGBGB) ebenfalls unveraendert
|
|
const formular = (s) => between(s, 'Muster-Widerrufsformular', '(*) Unzutreffendes streichen.')
|
|
assert.equal(formular(section(v4, 'formular', 'widerruf-hinweise')), formular(section(v3, 'formular', 'avv')))
|
|
})
|
|
|
|
test('Fassung 4: beide Abos, Postfächer, Zusatzspeicher, Wechsel', () => {
|
|
const v4html = read(V4), v4 = strip(v4html)
|
|
for (const s of ['15,00 €', '50,00 €', 'beliebig viele Postfächer', 'zusammen 5 GB', '1,00 €', 'Zusatzspeicher',
|
|
'12 Monate', 'statische', 'Hetzner', '§ 19 UStG', 'Leistungsbeschreibung A', 'Leistungsbeschreibung B',
|
|
'„Zahlungspflichtig bestellen“', '„Verträge hier kündigen“', '„Nur den E-Mail-Zusatzspeicher“', '„Speichern“',
|
|
'Vertragsbedingungen für die WEBklar-Abos']) assert.ok(v4.includes(s), s)
|
|
assert.ok(!v4.includes('Version 3'))
|
|
const hosting = section(v4html, 'hosting', 'wartung')
|
|
assert.ok(hosting.includes('1 E-Mail-Postfach'))
|
|
assert.ok(hosting.includes('statische'))
|
|
const wartung = section(v4html, 'wartung', 'widerruf')
|
|
assert.ok(wartung.includes('beliebig viele Postfächer'))
|
|
assert.ok(wartung.includes('zusammen 5 GB'))
|
|
assert.ok(!wartung.includes('1 E-Mail-Postfach'))
|
|
// Hinweise zum Widerruf bei Wechsel und Zusatzspeicher stehen ausserhalb der Musterbelehrung
|
|
const hinweise = section(v4html, 'widerruf-hinweise', 'avv')
|
|
assert.ok(hinweise.includes('Wechsel'))
|
|
assert.ok(hinweise.includes('Zusatzspeicher'))
|
|
assert.ok(hinweise.includes('Wertersatz'))
|
|
})
|
|
|
|
test('Anker der Fassung 4', () => {
|
|
const v4 = read(V4)
|
|
for (const id of ['widerruf', 'hosting', 'wartung', 'zusatzspeicher', 'wechsel', 'avv', 'widerruf-hinweise', 'formular', 'tom', 'sub']) {
|
|
assert.equal(v4.split(`id="${id}"`).length, 2, `Anker #${id} genau einmal`)
|
|
}
|
|
// Reihenfolge: allgemeiner Teil, Leistungsbeschreibung A, B, Widerruf, Hinweise, Anlagen 1-3
|
|
const order = ['id="wechsel"', 'id="zusatzspeicher"', 'id="hosting"', 'id="wartung"', 'id="widerruf"', 'id="formular"',
|
|
'id="widerruf-hinweise"', 'id="avv"', 'id="tom"', 'id="sub"'].map((m) => v4.indexOf(m))
|
|
assert.deepEqual([...order].sort((a, b) => a - b), order)
|
|
})
|
|
|
|
test('Version und Stand passen zu termsVersion', () => {
|
|
const m = /^(\d{4})-(\d{2})-(\d{2})-v4$/.exec(config.abo.termsVersion)
|
|
assert.ok(m, `termsVersion ${config.abo.termsVersion}`)
|
|
const [, y, mo, d] = m
|
|
assert.ok(strip(read(V4)).includes(`Version 4 · Stand ${d}.${mo}.${y}`))
|
|
assert.ok(read(V4).includes(`content: "Version 4 · Stand ${d}.${mo}.${y}"`), 'Kopfzeile im Druck')
|
|
})
|
|
|
|
test('PDF der Fassung 4 verlinkt, erzeugt und im Build-Skript', () => {
|
|
const v4 = read(V4)
|
|
assert.ok(v4.includes('href="/WEBklar-Abo-Vertragsunterlagen.pdf"'))
|
|
assert.ok(!v4.includes('WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf'))
|
|
const pdf = readFileSync(file('public/WEBklar-Abo-Vertragsunterlagen.pdf'))
|
|
assert.ok(pdf.length > 50_000 && pdf.subarray(0, 5).toString() === '%PDF-')
|
|
const build = read('scripts/build-legal-pdfs.sh')
|
|
assert.ok(build.includes('pdf public/abo-bedingungen.html public/WEBklar-Abo-Vertragsunterlagen.pdf'))
|
|
const erzeugt = build.split('\n').filter((l) => l.startsWith('pdf '))
|
|
assert.ok(!erzeugt.some((l) => l.includes('WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf')), 'Fassung 3 wird nicht neu erzeugt')
|
|
})
|