Abschnitt 11: Sperre und Kürzung ab dem Lesen unserer Antwort, nur je Beitrag und E-Mail-Adresse (neues Gesuch = neuer Beitrag); aussortierte Anfragen 30 Tage; feste Ausschlussliste der Websuche samt robots.txt; Kriterium für die Dauer der Reste; Kopie der Standardvertragsklauseln; Hinweis in der ersten Nachricht nennt den Widerspruch; Abschnitt 11 und 14: die KI sortiert aus, ein Mitarbeiter entscheidet über jede Antwort. Alarm-Postfach-Anbieter und Aufbewahrung der Server-Sicherungen stehen als sichtbare [OFFEN]-Markierung im Text (auch in Abschnitt 12). Tests: Pins angepasst, Markierungen nur die zwei erlaubten, neuer Test für Zusagen in Reichweite des Dienstes. Upload-Skript bricht ab, solange eine [OFFEN]-Markierung steht oder die Prüfsumme neben dem PDF nicht zur HTML passt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
142 lines
11 KiB
JavaScript
142 lines
11 KiB
JavaScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { createHash } from 'node:crypto'
|
|
|
|
// Hochladen der Datenschutzerklärung nach WOMS "Rechtliches" (scripts/datenschutz-hochladen.mjs): die Tests rufen main nur mit Nachbau-deps
|
|
// auf, nie mit dem echten Schlüssel oder dem Netz. Das globale fetch wirft und zählt, damit ein Weg am Nachbau vorbei auffällt.
|
|
let netzzugriffe = 0
|
|
globalThis.fetch = () => {
|
|
netzzugriffe += 1
|
|
throw new Error('Netzzugriff im Test')
|
|
}
|
|
const m = await import('../scripts/datenschutz-hochladen.mjs') // der Import liest weder Schlüssel noch Netz
|
|
|
|
const frueher = ['v1', 'v2', 'v3', 'v4', 'v5'].map((v, i) => ({ $id: `s${i}`, version: v, validFrom: `2026-09-0${i + 1}`, status: 'superseded', source: `x${i}` }))
|
|
const AKTUELL = { $id: 'alt', version: 'v6', validFrom: '2026-10-05', status: 'current', source: 'kundenbereich@aaaaaaa:public/datenschutz.html' }
|
|
// stand = das Datum, das im Nachbau der HTML-Datei hinter "Stand" steht (die PDF-Datei ist beliebiger Inhalt)
|
|
// zusatz = zusätzlicher Inhalt der HTML (zum Beispiel eine offene Markierung); summe = Inhalt der .sha256 (null = passend zur HTML, 'fehlt' = nicht lesbar)
|
|
function backend({ versionen = [...frueher, AKTUELL], karten = [{ $id: 'datenschutz', key: 'datenschutz' }], gitStatus = '', stand = '20.10.2026', vorhanden = () => true, zusatz = '', summe = null } = {}) {
|
|
const aufrufe = [], log = []
|
|
const antwort = (obj) => ({ ok: true, status: 200, text: async () => JSON.stringify(obj) })
|
|
const fetch = async (url, opt = {}) => {
|
|
const methode = opt.method ?? 'GET', pfad = new URL(url).pathname.replace(/^\/v1/, '')
|
|
aufrufe.push({ methode, pfad, body: opt.body })
|
|
if (methode === 'GET' && pfad.endsWith('/collections/legalDocuments/documents')) return antwort({ total: karten.length, documents: karten })
|
|
if (methode === 'GET' && pfad.endsWith('/collections/legalVersions/documents')) return antwort({ total: versionen.length, documents: versionen })
|
|
if (methode === 'GET' && pfad.startsWith('/storage/buckets/legal-files/files')) return antwort({ total: 0, files: [] })
|
|
if (methode === 'POST' && pfad === '/storage/buckets/legal-files/files') return antwort({ $id: `f${aufrufe.length}` })
|
|
if (methode === 'POST' && pfad.endsWith('/collections/legalVersions/documents')) return antwort({ $id: 'v-neu' })
|
|
if (methode === 'PATCH' && pfad.includes('/collections/legalVersions/documents/')) return antwort({})
|
|
throw new Error(`unerwartet: ${methode} ${pfad}`)
|
|
}
|
|
const html = () => Buffer.from((stand === null ? '<p class="meta">ohne Datum</p>' : `<p class="meta">Stand ${stand}</p>`) + zusatz)
|
|
const pruefsumme = () => {
|
|
if (summe === 'fehlt') throw new Error('ENOENT')
|
|
return Buffer.from(`${summe ?? createHash('sha256').update(html()).digest('hex')}\n`)
|
|
}
|
|
return { aufrufe, log, deps: { fetch, schluessel: () => 'test-schluessel', log: (z) => log.push(z), git: (args) => (args[0] === 'status' ? gitStatus : 'abc1234\n'), dateien: { existsSync: vorhanden, readFileSync: (rel) => (rel.endsWith('.html') ? html() : rel.endsWith('.sha256') ? pruefsumme() : Buffer.from('x')) } } }
|
|
}
|
|
const schreibend = (b) => b.aufrufe.filter((a) => a.methode !== 'GET')
|
|
|
|
test('parseArgs: --probe allein oder --freigabe=Tag, sonst Aufruf-Fehler', () => {
|
|
assert.deepEqual(m.parseArgs(['--probe']), { modus: 'probe', freigabe: null })
|
|
assert.deepEqual(m.parseArgs(['--freigabe=2026-10-20']), { modus: 'hochladen', freigabe: '2026-10-20' })
|
|
for (const bad of [[], ['--freigabe=20.10.2026'], ['--freigabe=2026-13-40'], ['--probe', '--freigabe=2026-10-20'], ['datenschutz']]) assert.throws(() => m.parseArgs(bad), /Aufruf|Freigabe/)
|
|
for (const bad of [['--freigabe=2026-10-20', '--freigabe=2026-10-21'], ['--probe', '--probe'], ['--freigabe=2026-10-20', '--karten-anlegen'], ['datenschutz', '--freigabe=2026-10-20']]) assert.throws(() => m.parseArgs(bad), /Aufruf|Freigabe/, JSON.stringify(bad))
|
|
})
|
|
test('Hochladen: Dateien zuerst (PDF, dann HTML), dann Version v7, dann die bisherige ablösen', async () => {
|
|
const b = backend()
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], b.deps), 0)
|
|
assert.deepEqual(schreibend(b).map((a) => `${a.methode} ${a.pfad.split('/').slice(-1)[0]}`), ['POST files', 'POST files', 'POST documents', 'PATCH alt'])
|
|
const neu = JSON.parse(schreibend(b)[2].body).data
|
|
assert.deepEqual([neu.documentKey, neu.version, neu.validFrom, neu.status, neu.source, neu.fileIds.length], ['datenschutz', 'v7', '2026-10-20', 'current', 'kundenbereich@abc1234:public/datenschutz.html', 2])
|
|
assert.equal(neu.changeNote, 'Fassung vom 20.10.2026: Abschnitt 11 Interessenten (Antworten auf öffentliche Anfragen)')
|
|
assert.deepEqual(JSON.parse(schreibend(b)[3].body).data, { status: 'superseded', validTo: '2026-10-19' })
|
|
})
|
|
test('Hochladen: die beiden Dateien sind die PDF und die HTML der Datenschutzerklärung, mit Dateiname und Typ', async () => {
|
|
const b = backend()
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], b.deps), 0)
|
|
const dateien = schreibend(b).slice(0, 2).map((a) => a.body.get('file'))
|
|
assert.deepEqual(dateien.map((f) => [f.name, f.type]), [['WEBklar-Datenschutzerklaerung.pdf', 'application/pdf'], ['datenschutz.html', 'text/html']])
|
|
assert.equal(await dateien[1].text(), '<p class="meta">Stand 20.10.2026</p>')
|
|
})
|
|
test('Changenote nennt weder Workers noch Hustler (sie kann für Kunden sichtbar werden)', async () => {
|
|
const b = backend()
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], b.deps), 0)
|
|
assert.doesNotMatch(JSON.parse(schreibend(b)[2].body).data.changeNote, /workers|hustler/i)
|
|
})
|
|
test('schon hochgeladen, fehlende Karte, nicht eingecheckter Stand und --probe schreiben nichts', async () => {
|
|
const schon = backend({ versionen: [{ ...AKTUELL, source: 'kundenbereich@abc1234:public/datenschutz.html' }] })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], schon.deps), 0)
|
|
const ohneKarte = backend({ karten: [] })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], ohneKarte.deps), 1)
|
|
const schmutzig = backend({ gitStatus: ' M public/datenschutz.html\n' })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], schmutzig.deps), 1)
|
|
const probe = backend()
|
|
assert.equal(await m.main(['--probe'], probe.deps), 0)
|
|
for (const b of [schon, ohneKarte, schmutzig, probe]) assert.equal(schreibend(b).length, 0)
|
|
})
|
|
test('Fehlende Karte und fehlende Datei brechen mit Meldung ab, bevor etwas geschrieben wird', async () => {
|
|
const ohneKarte = backend({ karten: [] })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], ohneKarte.deps), 1)
|
|
assert.ok(ohneKarte.log.some((z) => z.includes('Karte fehlt: datenschutz')))
|
|
assert.ok(!ohneKarte.log.some((z) => z.includes('--karten-anlegen')), 'dieses Skript legt keine Karten an')
|
|
const ohnePdf = backend({ vorhanden: (rel) => !rel.endsWith('.pdf') })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], ohnePdf.deps), 1)
|
|
assert.ok(ohnePdf.log.some((z) => z.includes('Datei fehlt: docs/WEBklar-Datenschutzerklaerung.pdf')))
|
|
assert.equal(schreibend(ohneKarte).length + schreibend(ohnePdf).length, 0)
|
|
})
|
|
test('Stand in der HTML muss der Freigabe entsprechen, sonst Abbruch vor jedem Schreiben', async () => {
|
|
const falsch = backend({ stand: '17.10.2026' })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], falsch.deps), 1)
|
|
assert.ok(falsch.log.some((z) => z.includes('Stand 17.10.2026') && z.includes('20.10.2026')), falsch.log.join(' | '))
|
|
const ohneStand = backend({ stand: null })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], ohneStand.deps), 1)
|
|
assert.ok(ohneStand.log.some((z) => z.includes('Stand')), ohneStand.log.join(' | '))
|
|
for (const b of [falsch, ohneStand]) assert.equal(schreibend(b).length, 0)
|
|
const richtig = backend({ stand: '21.10.2026' })
|
|
assert.equal(await m.main(['--freigabe=2026-10-21'], richtig.deps), 0, 'mit passender Freigabe geht derselbe Stand durch')
|
|
})
|
|
test('offene Markierung im Text: Abbruch vor jedem Schreiben, mit Nennung der Markierung; Kommentare zählen nicht', async () => {
|
|
const offen = backend({ zusatz: '<p>bei <span class="offen">[OFFEN: Anbieter Alarm-Postfach]</span></p>' })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], offen.deps), 1)
|
|
assert.ok(offen.log.some((z) => z.includes('offene Markierung') && z.includes('[OFFEN: Anbieter Alarm-Postfach]')), offen.log.join(' | '))
|
|
const zweiMal = backend({ zusatz: '<p>[OFFEN: Aufbewahrung der Server-Sicherungen] und noch einmal [OFFEN: Aufbewahrung der Server-Sicherungen]</p>' })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], zweiMal.deps), 1)
|
|
assert.equal(zweiMal.log.filter((z) => z.includes('offene Markierung'))[0].match(/Aufbewahrung der Server-Sicherungen/g).length, 1, 'jede Markierung nur einmal genannt')
|
|
for (const b of [offen, zweiMal]) assert.equal(schreibend(b).length, 0)
|
|
const kommentar = backend({ zusatz: '<!-- [OFFEN: nur eine Notiz im Quelltext] -->' })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], kommentar.deps), 0, 'eine Markierung im HTML-Kommentar ist nicht sichtbar')
|
|
})
|
|
test('Prüfsumme neben dem PDF muss zur HTML passen, sonst Abbruch vor jedem Schreiben (veraltetes PDF)', async () => {
|
|
const veraltet = backend({ summe: 'a'.repeat(64) })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], veraltet.deps), 1)
|
|
assert.ok(veraltet.log.some((z) => z.includes('das PDF ist nicht aus dieser HTML gedruckt') && z.includes('build-legal-pdfs.sh datenschutz')), veraltet.log.join(' | '))
|
|
const fehlt = backend({ summe: 'fehlt' })
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], fehlt.deps), 1)
|
|
assert.ok(fehlt.log.some((z) => z.includes('WEBklar-Datenschutzerklaerung.pdf.sha256') && z.includes('fehlt')), fehlt.log.join(' | '))
|
|
for (const b of [veraltet, fehlt]) assert.equal(schreibend(b).length, 0)
|
|
// passende Prüfsumme (Standard im Nachbau): der Upload geht durch
|
|
const passt = backend()
|
|
assert.equal(await m.main(['--freigabe=2026-10-20'], passt.deps), 0)
|
|
assert.equal(schreibend(passt).length, 4)
|
|
})
|
|
test('--probe liest nur: Bucket, Karte und Versionen, meldet eine fehlende Karte als Stopp', async () => {
|
|
const b = backend()
|
|
assert.equal(await m.main(['--probe'], b.deps), 0)
|
|
assert.ok(b.log.includes('datenschutz: v1:superseded v2:superseded v3:superseded v4:superseded v5:superseded v6:current'), b.log.join(' | '))
|
|
assert.deepEqual(b.aufrufe.map((a) => a.pfad.replace(/^\/databases\/woms-database/, '')), ['/storage/buckets/legal-files/files', '/collections/legalDocuments/documents', '/collections/legalVersions/documents'])
|
|
const ohneKarte = backend({ karten: [] })
|
|
assert.equal(await m.main(['--probe'], ohneKarte.deps), 1)
|
|
assert.ok(ohneKarte.log.includes('STOPP: Karte fehlt: datenschutz'))
|
|
})
|
|
test('ungültige Aufrufe enden mit Exit 1 und Hinweis, ohne ein einziges Netz-Zeichen', async () => {
|
|
const b = backend()
|
|
assert.equal(await m.main([], b.deps), 1)
|
|
assert.match(b.log[0], /Aufruf/)
|
|
assert.equal(b.aufrufe.length, 0)
|
|
})
|
|
test('kein Test hat das echte fetch erreicht', () => {
|
|
assert.equal(netzzugriffe, 0)
|
|
})
|