import { test, after, mock } from 'node:test' import assert from 'node:assert/strict' import { existsSync, readFileSync } from 'node:fs' import { startAppwriteMock } from './support/appwriteMock.js' import { mailSink } from './support/mailSink.js' import { schemaKeys } from './support/schema.js' import { useTestEnv } from './support/testEnv.js' import { TERMINE_COLLECTIONS, TERMINE_DATETIMES } from '../server/services/termine/schema.js' // Nachbau von Appwrite fuer den Test avv_keine_ip_in_buchung (eine echte Buchung ueber halte); config erst danach importieren const aw = await startAppwriteMock({ schema: { portalFeatures: schemaKeys('portalFeatures'), termineConfig: schemaKeys('termineConfig'), termineBookings: schemaKeys('termineBookings'), termineClaims: schemaKeys('termineClaims'), termineOutbox: schemaKeys('termineOutbox'), }, datetimes: TERMINE_DATETIMES, defaultLimit: 25, }) after(() => aw.close()) useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' }) const { config } = await import('../server/config.js') const { featureConfig } = await import('../server/services/featureRegistry.js') const { LOESCHFRIST_TAGE } = await import('../server/services/termine/frist.js') const { ENTWURF_TAGE } = await import('../server/services/termine/entwuerfe.js') const { normalisiereEinstellung } = await import('../server/services/termine/einstellungen.js') const { datenschutzAbsatz } = await import('../server/services/termine/rechtstexte.js') const { setMailTransportForTests } = await import('../server/services/mailer.js') const { resetOutboxForTests, warteAufVersand } = await import('../server/services/termine/outbox.js') const { speichereEinstellung, resetKontextForTests } = await import('../server/services/termine/kontext.js') const { halte } = await import('../server/services/termine/ablauf.js') const { loeschBezugsfeld } = await import('../server/services/termine/buchungen.js') after(() => setMailTransportForTests(null)) // Vertragsunterlagen des Terminplaners als Markdown: Leistungsbeschreibung (Task F4), AVV (Task F5). // Die Markdown-Dateien sind die Quelle der ausgelieferten Seiten (scripts/build-legal-html.mjs, Task F6). const lies = (rel) => readFileSync(new URL(`../${rel}`, import.meta.url), 'utf8').replace(/\r/g, '') const norm = (text) => text.replace(/\s+/g, ' ').trim() const V = config.termine.avvVersion // ----------------------------------------------------------------------------------------- Aufbau eines Dokuments /** Zerlegt eine Vertragsunterlage: h1, Kopfzeile und Abschnitte (## N. Titel) mit ihren Absätzen "(n) …". */ function lese(md) { const zeilen = md.split('\n') const h1 = zeilen.filter((z) => /^# /.test(z)) const abschnitte = [] let aktuell = null for (const zeile of zeilen) { const h2 = /^## (.+)$/.exec(zeile) if (h2) { aktuell = { titel: h2[1], zeilen: [] } abschnitte.push(aktuell) } else if (aktuell) { aktuell.zeilen.push(zeile) } } for (const a of abschnitte) { a.nummer = /^(\d+)\. /.exec(a.titel)?.[1] ?? null a.text = a.zeilen.join('\n') a.absaetze = [...a.text.matchAll(/^\((\d+)\) /gm)].map((m) => Number(m[1])) } return { h1, kopf: zeilen[2], abschnitte, md } } const LB_MD = lies('docs/rechtliches/leistungsbeschreibung-terminplaner.md') const LB = lese(LB_MD) const abschnitt = (doc, nummer) => doc.abschnitte.find((a) => a.nummer === String(nummer)) // ----------------------------------------------------------------------------------------- Leistungsbeschreibung test('lb_aufbau: Titel, Kopf mit Fassung und die elf Abschnitte in fester Nummerierung', () => { assert.deepEqual(LB.h1, ['# Leistungsbeschreibung Terminplaner']) assert.equal(LB_MD.split('\n')[0], '# Leistungsbeschreibung Terminplaner') assert.equal(LB.kopf, `**Fassung ${V}** · Zusatzbedingungen zum Wartungs-Abo`) assert.deepEqual(LB.abschnitte.map((a) => a.titel), [ '1. Geltung', '2. Leistung', '3. Nicht enthalten', '4. Voraussetzungen und Pflichten des Kunden', '5. Einrichtung und Preis', '6. Verfügbarkeit und Störungen', '7. Haftung für verpasste Termine', '8. Ende der Leistung', '9. Änderungen und Einstellung', '10. Widerruf', '11. Hinweise', ]) // Absätze sind lückenlos nummeriert: (1), (2), … for (const a of LB.abschnitte) { assert.ok(a.absaetze.length >= 1, `${a.titel}: keine Absätze`) assert.deepEqual(a.absaetze, a.absaetze.map((_, i) => i + 1), `${a.titel}: Absätze nicht lückenlos`) } }) test('lb_sauber: keine Platzhalter, Entwurfsmarken, Kommentare oder Kanzlei-Marker', () => { assert.doesNotMatch(LB_MD, /ENTWURF|Entwurf|Prüfpunkt|TODO|\{\{|\}\}|