import { test, after } from 'node:test' import assert from 'node:assert/strict' import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Router } from 'express' import { useTestEnv } from './support/testEnv.js' useTestEnv({ appwriteUrl: 'http://127.0.0.1:1', stripeUrl: 'http://127.0.0.1:1' }) const { config } = await import('../server/config.js') const { startTestApp } = await import('./support/testApp.js') const { legalPaths, legalPdfFiles, legalPdfAttachments, featureLegalAttachments, featureLegalReady, legalRouter } = await import( '../server/services/legalDocs.js' ) const analyse = await import('../server/services/analyseLegal.js') const { featureConfig } = await import('../server/services/featureRegistry.js') const V = config.termine.avvVersion const ALT = '2026-10-02' // eingefrorene Altfassung der Gaestetexte und der Analyse-Rechtstexte const root = (rel) => new URL(`../${rel}`, import.meta.url) // Router mit eigenem Verzeichnis: die Adressen ohne Fassung liefern die Datei der Fassung aus config const dir = mkdtempSync(join(tmpdir(), 'legaldocs-')) const inhalt = (name, version) => `
${name} ${version}
` const DATEIEN = ['leistungsbeschreibung-terminplaner', 'avv-terminplaner', 'widerrufsbelehrung-funktionen'] for (const slug of DATEIEN) writeFileSync(join(dir, `${slug}-${V}.html`), inhalt(slug, V)) writeFileSync(join(dir, `avv-website-analyse-${ALT}.html`), inhalt('avv-website-analyse', ALT)) const rechtliches = Router() rechtliches.use(legalRouter({ dir })) const app = await startTestApp({ '/rechtliches': rechtliches }) after(() => { app.close() rmSync(dir, { recursive: true, force: true }) }) const get = async (path) => { const r = await fetch(app.url + path) return { status: r.status, text: await r.text() } } /** Konstante und Register fuer einen Test setzen; stellt sie danach wieder her. */ async function mitFassung(fassung, fn) { const alt = config.termine.avvVersion config.termine.avvVersion = fassung try { return await fn() } finally { config.termine.avvVersion = alt } } test('legalPaths("termine", V) nennt drei versionierte Adressen', () => { assert.deepEqual(legalPaths('termine', V), { lb: `/rechtliches/leistungsbeschreibung-terminplaner-${V}.html`, avv: `/rechtliches/avv-terminplaner-${V}.html`, widerruf: `/rechtliches/widerrufsbelehrung-funktionen-${V}.html`, }) assert.deepEqual(Object.keys(legalPaths('termine', V)), ['lb', 'avv', 'widerruf']) assert.deepEqual(legalPaths('analyse', ALT), { avv: `/rechtliches/avv-website-analyse-${ALT}.html`, lb: `/rechtliches/leistungsbeschreibung-website-analyse-${ALT}.html`, }) }) test('legalPaths: ungültige Fassung und unbekannter Satz werfen', () => { for (const satz of ['termine', 'analyse']) { for (const v of ['../x', '2026-10-5', '2026-10-05x', 'ENTWURF', '', undefined, null, '2026-10-05/../x']) { assert.throws(() => legalPaths(satz, v), /Fassung/, `${satz}: ${String(v)}`) assert.throws(() => legalPdfFiles(satz, v), /Fassung/) } } for (const satz of ['news', 'unbekannt', '__proto__', 'toString', '', undefined]) { assert.throws(() => legalPaths(satz, V), /Satz/, String(satz)) assert.throws(() => legalPdfFiles(satz, V), /Satz/) } }) test('legalPdfFiles: Dateinamen und Pfade je Satz und Fassung', () => { const t = legalPdfFiles('termine', V) assert.deepEqual(Object.keys(t), ['lb', 'avv', 'widerruf']) assert.equal(t.lb.filename, `Leistungsbeschreibung-Terminplaner-${V}.pdf`) assert.equal(t.avv.filename, `AVV-Terminplaner-${V}.pdf`) assert.equal(t.widerruf.filename, `Widerrufsbelehrung-Muster-Widerrufsformular-${V}.pdf`) assert.equal(t.lb.path, new URL(`../public/rechtliches/leistungsbeschreibung-terminplaner-${V}.pdf`, import.meta.url).pathname) assert.equal(t.avv.path, new URL(`../public/rechtliches/avv-terminplaner-${V}.pdf`, import.meta.url).pathname) assert.equal(t.widerruf.path, new URL(`../public/rechtliches/widerrufsbelehrung-funktionen-${V}.pdf`, import.meta.url).pathname) const a = legalPdfFiles('analyse', ALT) assert.deepEqual(Object.keys(a), ['avv', 'lb']) assert.equal(a.avv.filename, `AVV-Website-Analyse-${ALT}.pdf`) assert.equal(a.lb.filename, `Leistungsbeschreibung-Website-Analyse-${ALT}.pdf`) }) test('legalPdfAttachments: fehlende Datei wirft, Analyse liefert avv und lb, ohne lässt Dokumente weg', async () => { await assert.rejects(legalPdfAttachments('termine', '1999-01-01')) await assert.rejects(legalPdfAttachments('analyse', '1999-01-01')) await assert.rejects(legalPdfAttachments('unbekannt', V), /Satz/) const alle = await legalPdfAttachments('analyse', ALT) assert.deepEqual(alle.map((x) => x.filename), [`AVV-Website-Analyse-${ALT}.pdf`, `Leistungsbeschreibung-Website-Analyse-${ALT}.pdf`]) for (const x of alle) { assert.equal(x.contentType, 'application/pdf') assert.equal(x.content.subarray(0, 4).toString(), '%PDF') } const ohneAvv = await legalPdfAttachments('analyse', ALT, { ohne: ['avv'] }) assert.deepEqual(ohneAvv.map((x) => x.filename), [`Leistungsbeschreibung-Website-Analyse-${ALT}.pdf`]) // eine ausgelassene Datei muss nicht vorhanden sein assert.deepEqual(await legalPdfAttachments('termine', '1999-01-01', { ohne: ['lb', 'avv', 'widerruf'] }), []) await assert.rejects(legalPdfAttachments('analyse', ALT, { ohne: ['gibtsnicht'] }), /Dokument/) }) test('featureLegalAttachments / featureLegalReady: news ohne Dokumente, unbekannte Funktion nie bereit', async () => { assert.deepEqual(await featureLegalAttachments('news'), []) assert.deepEqual(await featureLegalAttachments('news', { ohne: ['lb'] }), []) assert.equal(await featureLegalReady('news'), true) await assert.rejects(featureLegalAttachments('gibtsnicht'), /Funktion/) await assert.rejects(featureLegalAttachments('analyse'), /Funktion/) await assert.rejects(featureLegalAttachments('__proto__'), /Funktion/) assert.equal(await featureLegalReady('gibtsnicht'), false) assert.equal(await featureLegalReady('analyse'), false) }) test('featureLegalAttachments("termine") liest die Fassung aus config.termine.avvVersion; fehlt eine Datei, ist die Funktion nicht bereit', async () => { await mitFassung('1999-01-01', async () => { await assert.rejects(featureLegalAttachments('termine')) assert.equal(await featureLegalReady('termine'), false) }) }) test('config.termine.avvVersion: Datumsmuster, nach der Altfassung der Gästetexte, echter Tag', () => { assert.match(config.termine.avvVersion, /^\d{4}-\d{2}-\d{2}$/) assert.ok(config.termine.avvVersion > ALT, `${config.termine.avvVersion} liegt nicht nach ${ALT}`) assert.ok(config.termine.avvVersion >= '2026-10-03') assert.ok(!Number.isNaN(Date.parse(`${config.termine.avvVersion}T00:00:00Z`))) assert.equal(new Date(`${config.termine.avvVersion}T00:00:00Z`).toISOString().slice(0, 10), config.termine.avvVersion) }) test('config.ustHinweis bleibt wörtlich', () => { assert.equal(config.ustHinweis, 'ohne Umsatzsteuer gemäß § 19 UStG') }) test('Register: leistungsbeschreibung und avv des Terminplaners folgen der Konstante (Getter, Drift-Wächter)', async () => { const t = featureConfig('termine') assert.deepEqual(t.leistungsbeschreibung, { version: V, url: legalPaths('termine', V).lb }) assert.deepEqual(t.avv, { version: V, url: legalPaths('termine', V).avv }) assert.doesNotMatch(JSON.stringify([t.leistungsbeschreibung, t.avv]), /ENTWURF/) // zur Laufzeit geaendert: der Getter folgt await mitFassung('2099-01-02', async () => { assert.deepEqual(t.leistungsbeschreibung, { version: '2099-01-02', url: legalPaths('termine', '2099-01-02').lb }) assert.deepEqual(t.avv, { version: '2099-01-02', url: legalPaths('termine', '2099-01-02').avv }) }) assert.deepEqual(t.leistungsbeschreibung, { version: V, url: legalPaths('termine', V).lb }) // ein Test darf den Eintrag ueberschreiben (siehe termineFragebogen.test.js) const merke = t.leistungsbeschreibung t.leistungsbeschreibung = { version: 'T1', url: '/x' } assert.deepEqual(t.leistungsbeschreibung, { version: 'T1', url: '/x' }) t.leistungsbeschreibung = merke assert.deepEqual(t.leistungsbeschreibung, merke) }) test('News: Leistungsbeschreibung folgt der Fassung