Die Fragenliste F1 bis F18 ist ins Paket übernommen und um F19 bis F29 ergänzt; die alte Datei verweist nur noch auf das Paket. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
7.7 KiB
JavaScript
108 lines
7.7 KiB
JavaScript
import { test } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { existsSync, readFileSync } from 'node:fs'
|
||
import { config } from '../server/config.js'
|
||
import { BESTAETIGUNG_TEXT, gewaehrleistungsHinweis } from '../server/services/shop/rechtstexte.js'
|
||
|
||
// Kanzlei-Paket Webshop (Task J2): docs/rechtliches/webshop-kanzlei-paket.md. Das Paket zitiert Texte, die im Programm stehen;
|
||
// dieser Test prueft, dass jedes Zitat wortgleich zur Quelle passt, dass F1 bis F29 vorhanden sind, dass die Fundstellen existieren
|
||
// und dass Paket, Einbauanleitung und Datenschutz-Absatz zur Entscheidung W5 (keine Abo-Kopplung im Programm) passen.
|
||
|
||
const lies = (rel) => readFileSync(new URL(`../${rel}`, import.meta.url), 'utf8').replace(/\r/g, '')
|
||
const norm = (text) => text.replace(/\s+/g, ' ').trim()
|
||
const zitat = (text) => text.split('\n').map((z) => `> ${z}`.trimEnd()).join('\n')
|
||
|
||
const PAKET = lies('docs/rechtliches/webshop-kanzlei-paket.md')
|
||
|
||
test('shop_paket_aufbau: Abschnitte 1 bis 7 und F1 bis F29 in Reihenfolge', () => {
|
||
let pos = -1
|
||
for (const h of ['## 1. Zweck und Umfang', '## 2. Produktablauf in Kurzform', '## 3. Texte in Endfassung', '## 4. Fragen an die Kanzlei', '## 5. Fundstellen im Code', '## 6. Nicht Teil der Kanzleiprüfung', '## 7. Checkliste für den Inhaber vor `SHOP_ENABLED=on`']) {
|
||
const p = PAKET.indexOf(`\n${h}\n`)
|
||
assert.ok(p > pos, `Überschrift fehlt oder steht falsch: ${h}`)
|
||
pos = p
|
||
}
|
||
for (let i = 1; i <= 29; i += 1) assert.equal(PAKET.split(new RegExp(`^### F${i}: `, 'm')).length - 1, 1, `F${i} genau einmal`)
|
||
assert.ok(!PAKET.includes('### F30:'))
|
||
// jede Frage hat Text und Frage
|
||
const bloecke = PAKET.split(/^### F\d+: /m).slice(1)
|
||
assert.equal(bloecke.length, 29)
|
||
for (const [i, b] of bloecke.entries()) assert.ok(b.includes('**Text.**') && b.includes('**Frage.**'), `F${i + 1}: Text und Frage`)
|
||
})
|
||
|
||
test('shop_paket_zitate: Haken, Knopf, Bestätigung und Gewährleistungshinweis stehen wörtlich im Programm', () => {
|
||
const haken = config.features.shop.consent.items
|
||
assert.equal(haken.length, 5)
|
||
for (const h of haken) assert.ok(PAKET.includes(`> ${h.text}`), `Haken ${h.id}`)
|
||
assert.ok(PAKET.includes(`> ${config.features.shop.consent.knopf}`))
|
||
assert.ok(PAKET.includes(`> ${BESTAETIGUNG_TEXT}`))
|
||
assert.ok(PAKET.includes(zitat(gewaehrleistungsHinweis().replace(/\n\n/g, '\n\n'))), 'Gewährleistungshinweis')
|
||
assert.equal(config.shop.fassung, '2026-10-05')
|
||
assert.ok(PAKET.includes('Fassung **2026-10-05**'))
|
||
assert.ok(PAKET.includes('299 €') && config.features.shop.priceLabel === '299 €')
|
||
})
|
||
|
||
test('shop_paket_widget_zitate: die Widget-Texte, die das Paket nennt, stehen im Widget', () => {
|
||
const widget = lies('public/embed/shop.js')
|
||
for (const s of [
|
||
'Zahlungspflichtig bestellen',
|
||
'Vertrag widerrufen',
|
||
'Widerruf bestätigen',
|
||
'Sie haben ein gesetzliches Widerrufsrecht, soweit unten für einzelne Artikel nichts anderes steht. Einzelheiten finden Sie in der Widerrufsbelehrung.',
|
||
'Hier können Sie Ihren Vertrag widerrufen. Bitte geben Sie Ihren Namen, die E-Mail-Adresse Ihrer Bestellung und möglichst die Bestellnummer an. Sie steht in Ihrer Bestätigungs-E-Mail.',
|
||
'Bitte prüfen Sie Ihre Angaben. Mit „Widerruf bestätigen“ erklären Sie den Widerruf Ihres Vertrags.',
|
||
'Name, Lieferadresse und Zahlung geben Sie im nächsten Schritt bei unserem Zahlungsdienstleister Stripe ein.',
|
||
'AGB lesen', 'Widerrufsbelehrung lesen', 'Versand & Zahlung lesen',
|
||
]) {
|
||
assert.ok(widget.includes(s), `Widget: ${s}`)
|
||
assert.ok(PAKET.includes(s), `Paket: ${s}`)
|
||
}
|
||
})
|
||
|
||
test('shop_paket_fundstellen: genannte Dateien existieren, Bezeichner stehen in der Datei', () => {
|
||
const pfade = new Set([...PAKET.matchAll(/`((?:server|public|docs|scripts)\/[A-Za-z0-9_./ -]+?\.(?:js|mjs|md|html|sh|webp|pdf))`/g)].map((m) => m[1]))
|
||
assert.ok(pfade.size > 20)
|
||
for (const p of pfade) assert.ok(existsSync(new URL(`../${p}`, import.meta.url)), `Datei fehlt: ${p}`)
|
||
const tabelle = PAKET.slice(PAKET.indexOf('## 5. Fundstellen im Code'), PAKET.indexOf('## 6. Nicht Teil'))
|
||
for (const zeile of tabelle.split('\n').filter((z) => z.startsWith('| ') && !z.startsWith('| Thema') && !z.startsWith('|---'))) {
|
||
const spalten = zeile.split('|').map((s) => s.trim())
|
||
const dateien = [...spalten[2].matchAll(/`([^`]+)`/g)].map((m) => m[1])
|
||
const namen = [...(spalten[3] || '').matchAll(/`([^`]+)`/g)].map((m) => m[1])
|
||
for (const n of namen) {
|
||
if (n.startsWith('Routen') || !/^[A-Za-z_]+$/.test(n)) continue
|
||
const gefunden = dateien.some((d) => !d.endsWith('/') && lies(d).includes(n))
|
||
assert.ok(gefunden, `Bezeichner ${n} nicht in ${dateien.join(', ')}`)
|
||
}
|
||
}
|
||
})
|
||
|
||
test('shop_paket_w5: Webshop ist im Programm nicht an ein Abo gekoppelt, Dokumente sagen es einheitlich', () => {
|
||
const lb = norm(lies('docs/rechtliches/leistungsbeschreibung-webshop.md'))
|
||
const avv = norm(lies('docs/rechtliches/avv-webshop.md'))
|
||
const einbau = norm(lies('docs/funktionen/shop-einbau.md'))
|
||
assert.ok(lb.includes('Zusatzbedingungen zum Vertrag über den Betrieb der Website'))
|
||
assert.ok(lb.includes('Endet der Vertrag über den Betrieb der Website, endet auch der Webshop; WEBklar schaltet ihn zum Vertragsende ab und informiert den Händler vorher per E-Mail.'))
|
||
assert.ok(lb.includes('Der Preis steht neben den Leistungen eines Abos: Die Einrichtung wird weder auf ein Stundenkontingent angerechnet noch damit bezahlt.'))
|
||
assert.ok(lb.includes('(6) Endet der Vertrag über den Betrieb der Website, bevor WEBklar den Webshop freigeschaltet hat, erstattet WEBklar den Einrichtungspreis vollständig.'))
|
||
assert.ok(!/Hosting-Abo oder Wartungs-Abo/.test(lb.replace('im Hosting-Abo, im Wartungs-Abo oder aufgrund', '')), 'LB bindet nicht an ein bestimmtes Abo')
|
||
assert.ok(avv.includes('spätestens mit dem Ende des Vertrags, aufgrund dessen WEBklar die Website des Kunden betreibt'))
|
||
assert.ok(!avv.includes('Ende des Hosting-Abos oder Wartungs-Abos'))
|
||
assert.ok(einbau.includes('**nicht in der Software**') && einbau.includes('„Abschalten“') && einbau.includes('Leistungsbeschreibung Nr. 10 Abs. 6'))
|
||
assert.ok(norm(PAKET).includes('Das Programm prüft das **nicht**'))
|
||
// der Webshop liest im Programm keine Abo-Daten
|
||
for (const d of ['server/services/shop/schalter.js', 'server/services/shop/jobs.js', 'server/services/shop/bestellungen.js']) assert.doesNotMatch(lies(d), /aboStatus|aboAbgleich|subscriptions/, d)
|
||
})
|
||
|
||
test('shop_einbau_und_absatz: Einbauanleitung nennt Snippet, Widerrufslink, Impressum, Vorschau, Testkauf; Absatz ist Muster in Sie-Form', () => {
|
||
const einbau = lies('docs/funktionen/shop-einbau.md')
|
||
for (const s of ['data-webklar-shop="SITEKEY"', 'https://project.webklar.com/embed/shop.js', '/shop/<siteKey>/widerruf', 'Vertrag widerrufen', 'Impressum', 'docs/funktionen/shop-datenschutz-absatz.md', 'https://project.webklar.com/shop/<siteKey>`', '**1 €**', 'Erstatten', 'Ticket', 'Abschalten', '90 Tage', '299 €']) assert.ok(einbau.includes(s), `Einbauanleitung: ${s}`)
|
||
const absatz = lies('docs/funktionen/shop-datenschutz-absatz.md')
|
||
assert.ok(absatz.includes('**Muster – keine Rechtsberatung.**'))
|
||
for (const s of ['Stripe Payments Europe, Limited', 'EU-US Data Privacy Framework', 'Klarna', 'SEPA-Lastschrift', 'sessionStorage', '§ 25 Abs. 2 Nr. 2 TDDDG', 'drei Jahre nach dem Ende des Jahres der Bestellung', 'Handels- und Steuerrecht', 'Auftragsverarbeitung nach Art. 28 DSGVO', 'WEBklar GbR', 'Ihre Rechte']) assert.ok(absatz.includes(s), `Absatz: ${s}`)
|
||
assert.ok(!/\bdu\b|\bdein/i.test(absatz.slice(absatz.indexOf('## [Nummer]'))), 'Sie-Form')
|
||
})
|
||
|
||
test('shop_kanzlei_fragen_pointer: die Arbeitsliste verweist auf das Paket', () => {
|
||
const f = lies('docs/rechtliches/webshop-kanzlei-fragen.md')
|
||
assert.ok(f.includes('docs/rechtliches/webshop-kanzlei-paket.md') && f.length < 1000)
|
||
})
|