TP2 Abschlussprüfung: Rechtstexte (AVV, 6c, News-LB-Archiv, Livegang-Checkliste)

- AVV Vorbemerkung (2): K16 der Textform-Aktivierung trägt nur Leistungsbeschreibung und AVV als PDF
- AVV Nr. 9 Abs. 6, Anlage (Löschung), LB Nr. 8 Abs. 3 und Datenschutz 6c: das Einrichtungs-Ticket bleibt mit einer Kurzfassung der Einstellungen als Arbeitsnachweis (keine Daten von Gästen)
- AVV Nr. 8 Abs. 2: "Gastdaten löschen" löscht nur wartende E-Mails; fehlgeschlagene behalten Adresse und Inhalt bis Nr. 9 Abs. 4
- Datenschutz § 7: Frist für Bestellungen ohne Abo (drei Jahre nach Ablauf des Jahres der Bestellung)
- News-Leistungsbeschreibung vom 30.09.2026 unverändert archiviert (SHA-256-Pin), die geltende Datei verweist darauf
- Kanzlei-Paket: Zitate, Q4/Q9/Q23, Livegang-Checkliste (Abschnitt 7.6/7.7), K23-Hinweis
- build-legal-html: Rückwärtsschrägstrich in Navigationsadressen abgelehnt; TP1-Spezifikation E2 (ohne USt)
- HTML, PDFs und Prüfsummen neu erzeugt (Terminplaner-Unterlagen und Datenschutzerklärung)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-05 06:54:55 +00:00
1 parent 5f99f08a39
commit bea07e73b1
22 files changed
+299 -33

No files matched your search

+38
View File
@@ -2,6 +2,8 @@ import { test } from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { spawnSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
const { config } = await import('../server/config.js')
@@ -446,6 +448,39 @@ test('Fassung 4 archiviert: HTML bis auf den PDF-Link bytegleich, PDF bytegleich
assert.ok(archived.includes('Version 4 · Stand 30.09.2026'))
})
// = `git show 3a0b008:public/vertragsunterlagen/funktionen-leistungsbeschreibung.html | sha256sum` (News-Leistungsbeschreibung, Stand 30.09.2026).
// Bestellungen seit dem 30.09.2026 haben diesem Text zugestimmt; die geltende Datei (Stand 05.10.2026) ersetzt ihn nur für künftige Bestellungen.
const NEWS_LB = 'public/vertragsunterlagen/funktionen-leistungsbeschreibung.html'
const NEWS_LB_ARCHIV = 'public/vertragsunterlagen/funktionen-leistungsbeschreibung-2026-09-30.html'
const NEWS_LB_ARCHIV_SHA256 = '88a34b43362f0e403c5d0d64c2503d34de47860cacdab498f3cd72cee6df5bbc'
test('News-Leistungsbeschreibung vom 30.09.2026 unverändert archiviert; die geltende Datei verweist darauf', () => {
assert.equal(sha256(NEWS_LB_ARCHIV), NEWS_LB_ARCHIV_SHA256, 'das Archiv darf nie verändert werden (Bestellungen seit 2026-09-30 beruhen darauf)')
const alt = read(NEWS_LB_ARCHIV)
assert.ok(alt.includes('<p class="meta">Stand 30.09.2026</p>'))
assert.ok(alt.includes('§ 356 Abs. 4 BGB') && alt.includes('30,00 € einmalig, inklusive Umsatzsteuer'), 'der alte Wortlaut, nicht der berichtigte')
assert.ok(!alt.includes('Frühere Fassung'), 'das Archiv trägt keinen Verweis (byte-identisch mit dem Stand 3a0b008)')
// die geltende Datei bleibt die berichtigte Fassung und nennt die frühere mit Link auf das Archiv
const neu = read(NEWS_LB)
assert.notEqual(sha256(NEWS_LB), NEWS_LB_ARCHIV_SHA256)
assert.ok(neu.includes('<p class="meta">Stand 05.10.2026</p>'))
assert.ok(neu.includes('<p class="meta">Frühere Fassung vom 30.09.2026: <a href="/vertragsunterlagen/funktionen-leistungsbeschreibung-2026-09-30.html">Leistungsbeschreibung (Stand 30.09.2026)</a></p>'))
assert.equal(neu.split('funktionen-leistungsbeschreibung-2026-09-30.html').length - 1, 1, 'genau ein Verweis')
// Rest der geltenden Datei unverändert gegenüber der Fassung ohne die neue Zeile: nur diese eine Zeile kam hinzu
const ohneZeile = neu.split('\n').filter((z) => !z.includes('Frühere Fassung vom 30.09.2026')).join('\n')
assert.ok(ohneZeile.includes('Der Einbau kostet 30,00 € einmalig (ohne Umsatzsteuer gemäß § 19 UStG).') && !ohneZeile.includes('§ 356 Abs. 4'))
})
test('News-Leistungsbeschreibung vom 30.09.2026: das Archiv ist bytegleich mit dem Stand 3a0b008 (nur mit Git-Verlauf)', (t) => {
const r = spawnSync('git', ['show', '3a0b008:public/vertragsunterlagen/funktionen-leistungsbeschreibung.html'], {
cwd: fileURLToPath(new URL('..', import.meta.url)),
timeout: 20000,
maxBuffer: 1 << 20,
})
if (r.status !== 0) return t.skip('Git-Verlauf mit 3a0b008 nicht vorhanden')
assert.ok(Buffer.compare(r.stdout, readFileSync(file(NEWS_LB_ARCHIV))) === 0, 'das Archiv weicht vom Stand 3a0b008 ab')
})
test('Fassung 5: Unterstützung am Ende von § 4 Abs. 4, Abs. 7 verweist auf Satz 1, 2 und 4', () => {
const text = strip(read(CUR))
// "Dazu zählen" bleibt direkt hinter Satz 1 und bezieht sich weiter auf die aufgewendete Zeit
@@ -544,6 +579,8 @@ test('legal_datenschutz_termine: Abschnitt 6c steht zwischen 6b und 7 und nennt
assert.ok(text.includes('schon beim Laden der Seite') && text.includes('Konfiguration mit den Texten und Leistungen'))
// Ende des Abos (Wortlaut der Vorgabe)
assert.ok(text.includes('Endet das Wartungs-Abo oder schalten wir die Funktion ab, schalten wir den Terminplaner spätestens am folgenden Tag ab und löschen alle Termindaten und Einstellungen 30 Tage danach; beginnt innerhalb dieser Frist wieder ein Wartungs-Abo, schalten wir ihn mit den vorhandenen Daten wieder ein.'))
// Ausnahme: das Einrichtungs-Ticket (Kurzfassung der Einstellungen, Arbeitsnachweis) bleibt, ohne Daten von Gästen (AVV Nr. 9 Abs. 6)
assert.ok(text.includes('wieder ein. Eine Ausnahme ist das Einrichtungs-Ticket, in dem wir die Einrichtung als Arbeitsnachweis festhalten (Abschnitt 6): Es bleibt mit einer Kurzfassung der Einstellungen bestehen und enthält keine Daten von Gästen. In unseren Datensicherungen'))
assert.ok(text.includes('bleiben gelöschte Daten noch bis zu sieben Tage erhalten'))
// die Verbote der Nachbartests gelten auch hier
for (const verboten of ['Abschnitt 6 und 7', 'spätestens nach 24 Stunden', 'Endet die Zusammenarbeit, schalten wir', 'Unterauftragsverarbeiter', 'im Rahmen des Wartungs-Abos']) assert.ok(!text.includes(verboten), `noch da: ${verboten}`)
@@ -573,6 +610,7 @@ test('legal_datenschutz_funktionsbestellung: Abschnitt 7 nennt den Nachweis der
const absatz = 'Bestellen Sie eine Funktion (News-Pinnwand, Terminplaner), speichern wir als Nachweis die Texte und Fassungen, denen Sie zugestimmt haben, den Zeitpunkt Ihres Klicks auf „Zahlungspflichtig bestellen“, die Stripe-Sitzung, IP-Adresse und Browserkennung sowie Ihr Konto und die angemeldete Person. '
+ 'Rechtsgrundlagen sind Art. 6 Abs. 1 lit. b, c und f DSGVO; unser berechtigtes Interesse ist der Nachweis des Vertragsschlusses und der Zustimmungen, insbesondere für den Widerruf. '
+ 'Diese Angaben löschen wir drei Jahre nach Ablauf des Jahres, in dem Ihr letztes Abo endet. '
+ 'Bestellen Sie ohne Abo (zum Beispiel nur die News-Pinnwand), löschen wir diese Angaben spätestens drei Jahre nach Ablauf des Jahres der Bestellung. '
+ 'Den Entwurf Ihres Fragebogens speichern wir bis zu 90 Tage nach Ihrer letzten Änderung und löschen ihn danach, wenn Sie nichts bestellt haben.'
assert.ok(abos.includes(absatz), 'Absatz zur Funktionsbestellung (wörtlich) fehlt')
assert.equal(abos.split('Bestellen Sie eine Funktion').length - 1, 1)