Files
Webklar-Kundenbereich/test/shopKanzleiPaket.test.js
T
ClaudeandClaude Opus 5.5 c259e45412 Shop J2: Einbauanleitung, Datenschutz-Absatz für die Händler-Website, Kanzlei-Paket Webshop (F1 bis F29) mit Test
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>
2026-10-05 09:27:01 +00:00

108 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
})