import { test, after } from 'node:test' import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { startAppwriteMock } from './support/appwriteMock.js' import { startFakeStripe } from './support/fakeStripe.js' import { useTestEnv } from './support/testEnv.js' // Sätze der Bestellseiten (Fassung 6, Entwurf): Linkzeile und Häkchen sind Kopien von WORTLAUT_V6 und hier gebunden. const aw = await startAppwriteMock() const st = await startFakeStripe() after(() => Promise.all([aw.close(), st.close()])) useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { WORTLAUT_STATUS, KNOPF, WECHSEL_GESPERRT, FEHLER_NACHWEIS, FEHLER_BEDINGUNGEN, FEHLER_UNVOLLSTAENDIG, klartext, bestelltexteFuerClient, bestellZeile, nachweisTexte } = await import('../server/services/bestelltexte.js') const { config } = await import('../server/config.js') const { WORTLAUT_V6, mitFassung, termsDocFor } = await import('../server/services/products.js') const FASSUNG = config.abo.termsVersionNeu const URL_V6 = termsDocFor(FASSUNG).url const BESTELLEN = mitFassung(WORTLAUT_V6.linkzeile.bestellen, FASSUNG) // der Satz aus P8, Fassungsangabe = Versionszeichenfolge const T = bestelltexteFuerClient({ fassung: FASSUNG, termsUrl: URL_V6 }) test('Status, Knopf und feste Fehlertexte', () => { assert.deepEqual([WORTLAUT_STATUS, KNOPF], ['entwurf', 'Zahlungspflichtig bestellen']) assert.equal(WECHSEL_GESPERRT, 'Der Wechsel gilt sofort. Ohne dein Häkchen können wir ihn nicht ausführen.') assert.equal(FEHLER_NACHWEIS, 'Das hat gerade nicht geklappt. Bitte versuche es gleich noch einmal.') assert.equal(FEHLER_BEDINGUNGEN, 'Die Bedingungen wurden aktualisiert. Bitte lade die Seite neu.') assert.equal(FEHLER_UNVOLLSTAENDIG, 'Die Bestellung ist unvollständig. Bitte lade die Seite neu.') }) test('klartext und Fassungsangabe', () => { assert.equal(klartext('Es gilt [A] und [B].'), 'Es gilt A und B.') for (const leer of ['', undefined, null, 6]) assert.throws(() => bestelltexteFuerClient({ fassung: leer, termsUrl: URL_V6 }), /Fassung/) }) test('Nutzlast: Linkzeile und Häkchen sind der Wortlaut aus P8 (WORTLAUT_V6), dazu Sperrzeile, Warnzeile, Satz, Kundenart-Zeile (Entwurf)', () => { assert.deepEqual([T.status, T.fassung, T.knopf], ['entwurf', FASSUNG, 'Zahlungspflichtig bestellen']) assert.equal(klartext(T.abo.linkzeile.text), `${BESTELLEN} Datenschutz`) assert.deepEqual(T.abo.linkzeile.text.match(/\[[^\]]+\]/g), ['[Vertragsbedingungen]', '[Datenschutz]']) assert.deepEqual(T.abo.linkzeile.urls, [URL_V6, '/datenschutz.html']) assert.equal(T.abo.haken, WORTLAUT_V6.beginn.abo) assert.deepEqual(T.wechsel.linkzeile, T.abo.linkzeile) assert.equal(T.wechsel.haken, WORTLAUT_V6.beginn.wechsel) assert.equal(T.wechsel.gesperrt, WECHSEL_GESPERRT) assert.equal(klartext(T.speicher.linkzeile.text), `${BESTELLEN} Datenschutz`) assert.deepEqual(T.speicher.linkzeile.urls, [`${URL_V6}#zusatzspeicher`, '/datenschutz.html']) assert.equal(T.speicher.warnung.text, 'Wichtig: Passen die gespeicherten E-Mails nach der Verkleinerung nicht mehr in den verbleibenden Speicher, nehmen die Postfächer keine neuen E-Mails mehr an. Bitte räume vorher auf. Mehr dazu in den [Regeln zum Zusatzspeicher].') assert.deepEqual(T.speicher.warnung.urls, [`${URL_V6}#zusatzspeicher`]) assert.equal(T.gutschrift.satz.text, 'Mit dem Absenden bestätigst du, dass deine Angaben stimmen und die Rechnung auf dich bzw. dein Unternehmen ausgestellt ist. Es gelten die [Bedingungen der Übernahmegutschrift]. Angaben und Nachweise verwendet WEBklar zur Prüfung und Abwicklung des Antrags ([Datenschutz]).') assert.deepEqual(T.gutschrift.satz.urls, ['#gutschrift?bedingungen=1', '/datenschutz.html#uebernahmegutschrift']) assert.equal(T.gutschrift.knopf, 'Antrag absenden') assert.deepEqual(T.kundenart, { unternehmen: 'Du bestellst als Betrieb', privat: 'Du bestellst privat', korrektur: { text: 'Stimmt nicht? [Schreib uns im Chat]', urls: ['#chat'] } }) // jede Markierung hat genau eine Adresse for (const s of [T.abo.linkzeile, T.wechsel.linkzeile, T.speicher.linkzeile, T.speicher.warnung, T.gutschrift.satz, T.kundenart.korrektur]) { assert.equal((s.text.match(/\[[^\]]+\]/g) ?? []).length, s.urls.length, s.text) } }) test('Nutzlast ist unabhängig vom Aufrufer: eigene Kopien, nichts geteilt', () => { const a = bestelltexteFuerClient({ fassung: FASSUNG, termsUrl: URL_V6 }) a.abo.linkzeile.urls.push('x') a.wechsel.linkzeile.urls.push('y') assert.deepEqual(bestelltexteFuerClient({ fassung: FASSUNG, termsUrl: URL_V6 }).wechsel.linkzeile.urls, [URL_V6, '/datenschutz.html']) assert.throws(() => bestelltexteFuerClient({ fassung: FASSUNG, termsUrl: '' }), /Adresse/) }) test('bestellZeile: feste Zeilen aus geprüften Werten', () => { assert.equal(bestellZeile('abo', { produkt: 'Wartungs-Abo', monatCents: 5000, mindestMonate: 12, beginn: 'sofort' }), 'Wartungs-Abo: 50,00 € pro Monat (Gesamtpreis, gemäß § 19 UStG ohne Umsatzsteuer), Beginn sofort, Mindestlaufzeit 12 Monate ab Leistungsbeginn') assert.equal(bestellZeile('abo', { produkt: 'Hosting-Abo', monatCents: 2000, mindestMonate: 12, beginn: 'nach_frist', beginnAm: '2026-10-30T05:00:00.000Z', angebot: 'Erste 2 Monate je 10,00 €' }), 'Hosting-Abo: 20,00 € pro Monat (Gesamtpreis, gemäß § 19 UStG ohne Umsatzsteuer), individuelles Angebot: Erste 2 Monate je 10,00 €, Beginn nach Ablauf der Widerrufsfrist am 30.10.2026, Mindestlaufzeit 12 Monate ab Leistungsbeginn') assert.equal(bestellZeile('wechsel', { heuteCents: 4100, monatCents: 5000, mindestMonate: 12 }), 'Wechsel ins Wartungs-Abo, Beginn sofort. Heute fällig: 41,00 € (gemäß § 19 UStG ohne Umsatzsteuer), danach 50,00 € pro Monat zuzüglich gebuchtem Zusatzspeicher, Mindestlaufzeit 12 Monate ab dem Wechsel') assert.equal(bestellZeile('speicher', { units: 2, gb: 10, monatCents: 200 }), 'Zusatzspeicher: 2 Pakete (+10 GB), 2,00 € pro Monat (gemäß § 19 UStG ohne Umsatzsteuer), monatlich kündbar') assert.equal(bestellZeile('speicher', { units: 1, gb: 5, monatCents: 100 }), 'Zusatzspeicher: 1 Paket (+5 GB), 1,00 € pro Monat (gemäß § 19 UStG ohne Umsatzsteuer), monatlich kündbar') assert.throws(() => bestellZeile('gutschrift', {})) // ungeprüfte Werte erzeugen keine Zeile assert.throws(() => bestellZeile('abo', { produkt: 'Wartungs-Abo', monatCents: 50.5, mindestMonate: 12, beginn: 'sofort' }), TypeError) assert.throws(() => bestellZeile('abo', { produkt: 'Wartungs-Abo', monatCents: 5000, mindestMonate: 12, beginn: 'irgendwann' }), TypeError) assert.throws(() => bestellZeile('abo', { produkt: 'Wartungs-Abo', monatCents: 5000, mindestMonate: 12, beginn: 'nach_frist', beginnAm: 'morgen' }), TypeError) assert.throws(() => bestellZeile('speicher', { units: 0, gb: 5, monatCents: 100 }), TypeError) }) test('nachweisTexte: Reihenfolge, Häkchen nur wenn gezeigt und gesetzt, Klartext', () => { const fassung = FASSUNG const betrieb = nachweisTexte('abo', { kundenart: 'unternehmen', fassung, bestellung: 'Zeile' }) assert.deepEqual(betrieb.map((t) => t.id), ['bestellung', 'linkzeile', 'knopf', 'kundenart']) assert.equal(betrieb[1].text, `${BESTELLEN} Datenschutz`) assert.deepEqual(betrieb[3], { id: 'kundenart', text: 'Du bestellst als Betrieb · Stimmt nicht? Schreib uns im Chat', haken: false }) const privat = nachweisTexte('abo', { kundenart: 'privat', fassung, bestellung: 'Zeile', beginnGehakt: true }) assert.deepEqual(privat.map((t) => [t.id, t.haken]), [['bestellung', false], ['linkzeile', false], ['beginn', true], ['knopf', false], ['kundenart', false]]) assert.equal(privat[2].text, WORTLAUT_V6.beginn.abo) assert.ok(!nachweisTexte('abo', { kundenart: 'privat', fassung, bestellung: 'Zeile' }).some((t) => t.id === 'beginn')) assert.ok(!nachweisTexte('wechsel', { kundenart: 'unternehmen', fassung, bestellung: 'Zeile', beginnGehakt: true }).some((t) => t.id === 'beginn')) assert.equal(nachweisTexte('wechsel', { kundenart: 'privat', fassung, bestellung: 'Z', beginnGehakt: true })[2].text, WORTLAUT_V6.beginn.wechsel) assert.deepEqual(nachweisTexte('speicher', { kundenart: 'privat', fassung, bestellung: 'Z', beginnGehakt: true }).map((t) => t.id), ['bestellung', 'linkzeile', 'knopf', 'kundenart']) assert.deepEqual(nachweisTexte('gutschrift', {}).map((t) => [t.id, t.text]), [['satz', klartext(T.gutschrift.satz.text)], ['knopf', 'Antrag absenden']]) assert.throws(() => nachweisTexte('unbekannt', {})) // leere und unbekannte Kundenart gelten als privat (E4); ohne Fassung entsteht kein Nachweis assert.equal(nachweisTexte('abo', { kundenart: '', fassung, bestellung: 'Z' }).at(-1).text, 'Du bestellst privat · Stimmt nicht? Schreib uns im Chat') assert.equal(nachweisTexte('abo', { kundenart: 'kaputt', fassung, bestellung: 'Z', beginnGehakt: true }).some((t) => t.id === 'beginn'), true) assert.throws(() => nachweisTexte('abo', { kundenart: 'privat', fassung: '', bestellung: 'Z' }), /Fassung/) assert.equal(nachweisTexte('abo', { kundenart: 'privat', fassung }).some((t) => t.id === 'bestellung'), false, 'ohne Zeile keine Zeile') }) test('Quelle: keine Imports, du-Form, als Entwurf gekennzeichnet', () => { const q = readFileSync(new URL('../server/services/bestelltexte.js', import.meta.url), 'utf8') assert.doesNotMatch(q, /^import /m) assert.doesNotMatch(q, /\b(Sie|Ihr|Ihre|Ihnen)\b/) assert.match(q, /ENTWURF/) })