import { test } from 'node:test' import assert from 'node:assert/strict' // Hochladen der Fassung 6 nach WOMS "Rechtliches" (scripts/rechtliches-hochladen.mjs): die Tests rufen main nur mit Nachbau-deps auf, // nie mit dem echten Schlüssel oder dem Netz. const { FUNKTIONEN_UNTERLAGEN_V6, FUNKTIONEN_FASSUNG_V6 } = await import('../server/services/products.js') const m = await import('../scripts/rechtliches-hochladen.mjs') // der Import selbst darf weder Schlüssel lesen noch das Netz nutzen test('parseArgs: ein Dokument je Aufruf, Hochladen nur mit --freigabe=Datum, --probe und --pruefen lesen nur, --karten-anlegen legt nur Karten an', () => { assert.deepEqual(m.parseArgs(['--probe']), { modus: 'probe', documentKey: null, freigabe: null }) assert.deepEqual(m.parseArgs(['--karten-anlegen']), { modus: 'karten', documentKey: null, freigabe: null }) assert.deepEqual(m.parseArgs(['--pruefen']), { modus: 'pruefen', documentKey: null, freigabe: null }) assert.deepEqual(m.parseArgs(['avv-webshop', '--freigabe=2026-10-20']), { modus: 'hochladen', documentKey: 'avv-webshop', freigabe: '2026-10-20' }) for (const bad of [[], ['avv-webshop'], ['--freigabe=2026-10-20'], ['avv-webshop', 'avv-terminplaner', '--freigabe=2026-10-20'], ['avv-webshop', '--freigabe=20.10.2026'], ['avv-webshop', '--freigabe=2026-13-40'], ['datenschutz', '--freigabe=2026-10-20'], ['--probe', 'avv-webshop'], ['--probe', '--freigabe=2026-10-20'], ['--karten-anlegen', 'avv-webshop'], ['--karten-anlegen', '--freigabe=2026-10-20'], ['--pruefen', '--probe'], ['--pruefen', 'avv-webshop']]) { assert.throws(() => m.parseArgs(bad), /Aufruf|Freigabe|Dokument/, JSON.stringify(bad)) } assert.equal(typeof m.main, 'function') }) test('Tabelle: elf Dokumente, Dateien im Vorschaupfad, passend zu FUNKTIONEN_UNTERLAGEN_V6', () => { assert.deepEqual(m.DOKUMENTE_V6.map((d) => d.documentKey), ['abo-bedingungen', 'leistungsbeschreibung-news', 'leistungsbeschreibung-terminplaner', 'avv-terminplaner', 'leistungsbeschreibung-webshop', 'avv-webshop', 'leistungsbeschreibung-unterkunft', 'avv-unterkunft', 'leistungsbeschreibung-zeiten', 'leistungsbeschreibung-website-analyse', 'avv-website-analyse']) assert.deepEqual(m.dokumentFuer('abo-bedingungen').dateien, ['public/neu/rechtliches/WEBklar-Abo-Vertragsunterlagen.pdf', 'public/neu/rechtliches/abo-bedingungen.html']) const unterlagen = Object.values(FUNKTIONEN_UNTERLAGEN_V6).flatMap((u) => [u.lb, u.avv]).filter(Boolean) assert.deepEqual(m.DOKUMENTE_V6.slice(1).map((d) => d.dateien), unterlagen.map((u) => [`public/${u.pdfFile}`, `public${u.url}`])) assert.deepEqual(m.DOKUMENTE_V6.map((d) => d.quelle), m.DOKUMENTE_V6.map((d) => d.dateien[1]), 'quelle = HTML-Pfad, der in source steht') assert.throws(() => m.dokumentFuer('datenschutz'), /Unbekanntes Dokument/) }) test('planVersion: neue Fassung als vN, bisherige gültige wird abgelöst, doppelter Upload wird erkannt', () => { const alle = [{ $id: 'a', version: 'v1', validFrom: '2026-09-30', status: 'current', source: 'x' }, { $id: 'b', version: 'v0', validFrom: '2026-09-25', status: 'superseded', source: 'y' }] assert.deepEqual(m.planVersion({ alle, source: 's', validFrom: '2026-10-20' }), { schon: false, nummer: 'v3', aktuellId: 'a', validToAltes: '2026-10-19' }) assert.equal(m.planVersion({ alle, source: 'x', validFrom: '2026-10-20' }).schon, true) assert.deepEqual(m.planVersion({ alle: [], source: 's', validFrom: '2026-10-20' }), { schon: false, nummer: 'v1', aktuellId: null, validToAltes: null }) assert.equal(m.planVersion({ alle, source: 's', validFrom: '2026-09-30' }).validToAltes, '2026-09-30', 'gleicher Tag: validTo = validFrom wie in der Vorlage') assert.equal(m.tagVorher('2026-03-01'), '2026-02-28') }) // Nachbau des WOMS-Backends für main(): Karten als Dokumente { $id, key, sort }, Versionen und Dateien leer; zählt alle Aufrufe function backend(karten) { const aufrufe = [], log = [], 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}`) 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: 0, documents: [] }) if (methode === 'GET' && pfad.startsWith('/storage/buckets/legal-files/files')) return antwort({ total: 0, files: [] }) if (methode === 'POST' && pfad.endsWith('/collections/legalDocuments/documents')) { const b = JSON.parse(opt.body); karten.push({ $id: b.documentId, ...b.data }); return antwort({ $id: b.documentId }) } throw new Error(`unerwartet: ${methode} ${pfad}`) } return { karten, aufrufe, log, deps: { fetch, schluessel: () => 'test-schluessel', log: (z) => log.push(z) } } } const NUR_LESEN = (b) => b.aufrufe.every((a) => a.startsWith('GET ')) const FEHLEN = ['leistungsbeschreibung-news', 'leistungsbeschreibung-website-analyse', 'avv-website-analyse'] // „leistungsbeschreibung-website-analyse“ hat 37 Zeichen, eine Dokument-ID darf höchstens 36 haben const kartenDoc = (key, i) => ({ $id: key.slice(0, 36), key, sort: i + 1 }) const VORHANDEN = m.DOKUMENTE_V6.map((d) => d.documentKey).filter((k) => !FEHLEN.includes(k)) // die Karten laut WOMS scripts/setup-legal.mjs test('kartenPlan: genau die drei fehlenden Karten in Tabellenreihenfolge, mit zulässiger Dokument-ID', () => { const plan = m.kartenPlan({ vorhandeneKeys: VORHANDEN, hoechsteSort: 14 }) assert.deepEqual(plan.map((k) => [k.daten.key, k.daten.sort]), [[FEHLEN[0], 15], [FEHLEN[1], 16], [FEHLEN[2], 17]]) assert.deepEqual(plan.map((k) => k.daten.title), ['Leistungsbeschreibung News-Pinnwand', 'Leistungsbeschreibung Website-Analyse', 'AVV Website-Analyse']) assert.ok(plan.every((k) => k.daten.liveUrl.startsWith('https://project.webklar.com/neu/rechtliches/') && k.daten.liveUrl.endsWith(`-${FUNKTIONEN_FASSUNG_V6}.html`))) assert.ok(plan.every((k) => /^[A-Za-z0-9][A-Za-z0-9._-]{0,35}$/.test(k.id)), 'Dokument-ID höchstens 36 Zeichen') assert.equal(plan[0].id, FEHLEN[0], 'kurze Schlüssel bleiben die ID, wie in setup-legal.mjs') assert.notEqual(plan[1].id, FEHLEN[1]); assert.equal(m.kartenId(FEHLEN[1]), plan[1].id, 'lange Schlüssel: abgeleitete ID, immer dieselbe') assert.deepEqual(m.kartenPlan({ vorhandeneKeys: m.DOKUMENTE_V6.map((d) => d.documentKey), hoechsteSort: 20 }), []) }) test('--probe: eine fehlende Karte ist ein Stopp (Exit 1), mit allen Karten Exit 0; schreibt nie', async () => { const b = backend(VORHANDEN.map(kartenDoc)) assert.equal(await m.main(['--probe'], b.deps), 1) for (const k of FEHLEN) assert.ok(b.log.includes(`STOPP: Karte fehlt: ${k}`), k) assert.ok(NUR_LESEN(b), b.aufrufe.join(', ')) assert.equal(await m.main(['--probe'], backend(m.DOKUMENTE_V6.map((d, i) => kartenDoc(d.documentKey, i))).deps), 0) }) test('--karten-anlegen: legt nur die drei fehlenden Karten an, ein zweiter Lauf tut nichts, danach ist --probe grün', async () => { const b = backend(VORHANDEN.map(kartenDoc)) assert.equal(await m.main(['--karten-anlegen'], b.deps), 0) assert.deepEqual(b.aufrufe.filter((a) => !a.startsWith('GET ')), FEHLEN.map(() => 'POST /databases/woms-database/collections/legalDocuments/documents')) assert.deepEqual(b.karten.slice(VORHANDEN.length).map((k) => k.key), FEHLEN) b.aufrufe.length = 0 assert.equal(await m.main(['--karten-anlegen'], b.deps), 0); assert.ok(NUR_LESEN(b)) assert.equal(await m.main(['--probe'], b.deps), 0) }) test('Hochladen ohne Karte bricht ab, bevor etwas geschrieben wird', async () => { const b = backend(VORHANDEN.map(kartenDoc)) assert.equal(await m.main([FEHLEN[0], '--freigabe=2026-10-20'], b.deps), 1) assert.ok(b.log.some((z) => z.includes(`Karte fehlt: ${FEHLEN[0]}`)) && NUR_LESEN(b)) }) test('pruefeHochgeladen und --pruefen: alle elf Dokumente brauchen eine aktuelle Version mit passender Quelle', async () => { const ok = (d) => [{ $id: 'v', version: 'v2', status: 'current', source: `kundenbereich@abc1234:${d.quelle}` }] const alle = Object.fromEntries(m.DOKUMENTE_V6.map((d) => [d.documentKey, ok(d)])) assert.deepEqual(m.pruefeHochgeladen(alle), { fehlt: [] }) assert.deepEqual(m.pruefeHochgeladen({ ...alle, 'avv-website-analyse': [] }).fehlt, ['avv-website-analyse']) assert.deepEqual(m.pruefeHochgeladen({ ...alle, 'abo-bedingungen': [{ ...alle['abo-bedingungen'][0], source: 'kundenbereich@7ae0ffd:public/abo-bedingungen.html' }] }).fehlt, ['abo-bedingungen'], 'Fassung 5 zählt nicht') assert.deepEqual(m.pruefeHochgeladen({ ...alle, 'leistungsbeschreibung-news': [{ ...alle['leistungsbeschreibung-news'][0], status: 'superseded' }] }).fehlt, ['leistungsbeschreibung-news']) assert.equal(m.pruefeHochgeladen({}).fehlt.length, 11) const b = backend([]) // Versionen leer: alle elf fehlen assert.equal(await m.main(['--pruefen'], b.deps), 1) assert.equal(b.log.filter((z) => z.startsWith('FEHLT ')).length, 11); assert.ok(NUR_LESEN(b)) })