import { test, after } from 'node:test' import assert from 'node:assert/strict' import { startAppwriteMock } from './support/appwriteMock.js' import { startFakeStripe } from './support/fakeStripe.js' import { useTestEnv } from './support/testEnv.js' // Register config.features (News + Terminplaner) und featureRegistry.js. Dienste (siteFeatures, ticketAdmin) // brauchen die Test-Umgebung vor dem ersten Import von server/config.js. const aw = await startAppwriteMock() const st = await startFakeStripe() after(() => Promise.all([aw.close(), st.close()])) useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { config } = await import('../server/config.js') const reg = await import('../server/services/featureRegistry.js') const sf = await import('../server/services/siteFeatures.js') const { createFeatureTicket } = await import('../server/services/ticketAdmin.js') // Wortlaut-Pin des News-Satzes und der Fassung (der einzige Literal-Pin auf Texte, die Block F2 ändert) const NEWS_SATZ = 'Ich verlange ausdrücklich, dass WEBklar vor Ablauf der Widerrufsfrist mit dem Einbau beginnt. Mir ist bekannt, dass mein Widerrufsrecht erlischt, sobald der Einbau vollständig erbracht ist (§ 356 Abs. 4 BGB).' const NEWS_FASSUNG = '2026-09-30' const SITE_KEY = 'abcdefghijklmnop' // ---------------------------------------------------------------------------------- News (Festwerte) --- test('News-Eintrag entspricht den bisherigen Festwerten', async () => { const news = reg.featureConfig('news') assert.equal(news.key, 'news') assert.equal(news.product, 'newsSetup') assert.deepEqual(news.label, { name: 'News-Pinnwand', kurz: 'News', keine: 'keine News-Pinnwand' }) assert.equal(news.priceLabel, '30 €') assert.equal(news.setupEstimate, 'meist innerhalb von 1–2 Wochen') assert.equal(news.consent.version, NEWS_FASSUNG) assert.equal(news.consent.knopf, 'Zahlungspflichtig bestellen · 30 €') assert.deepEqual(news.consent.items, [{ id: 'beginn', haken: true, text: NEWS_SATZ }]) assert.deepEqual(news.leistungsbeschreibung, { version: NEWS_FASSUNG, url: '/vertragsunterlagen/funktionen-leistungsbeschreibung.html' }) assert.equal(news.avv, null) assert.equal(news.widerruf, null) assert.equal(news.returnHash, 'news') assert.equal(news.returnProjekt, false) assert.equal(news.requiresWartungsAbo, false) assert.equal(news.ticketFreischaltung, true) assert.equal( news.disabledMessage, 'Die News-Pinnwand wurde für diese Website abgeschaltet. Schreib uns im Chat, wenn du sie wieder möchtest.' ) assert.equal(news.disabledMessage, sf.DISABLED_MESSAGE) assert.equal(news.orderedConflictMessage, 'Die News-Pinnwand ist für dieses Projekt schon bestellt.') assert.equal(news.unavailableMessage, undefined) assert.equal(news.ticket.topic({ projectName: 'Bäckerei' }), 'News-Pinnwand einbauen – Bäckerei') assert.equal(news.ticket.topic({ subdomain: 'cafe' }), 'News-Pinnwand einbauen – cafe') assert.equal(news.ticket.topic(null), 'News-Pinnwand einbauen –') assert.deepEqual(news.mails, { funktion: 'News-Pinnwand', formen: { dein: 'Deine News-Pinnwand', nom: 'die News-Pinnwand', akk: 'die News-Pinnwand', gen: 'der News-Pinnwand', pron: 'sie' }, knopf: 'Beitrag schreiben', danach: 'Danach kannst Du Beiträge im Portal schreiben.', bereit: 'Du kannst ab sofort Beiträge schreiben, sie erscheinen direkt bei Dir auf der Seite.', preisHinweis: news.mails.preisHinweis, anleitung: false, }) assert.match(news.mails.preisHinweis, /einmalig/) }) test('News-Ticketdetails sind die sechs Zeilen des bisherigen Standards', async () => { const news = reg.featureConfig('news') const project = { $id: 'p1', projectName: 'Bäckerei', liveDomain: 'baeckerei.de', previewUrl: 'https://p.example', subdomain: 'baeckerei' } const ctx = { feature: news, doc: { siteKey: SITE_KEY, featureKey: 'news' }, project, customer: { name: 'Kunde' } } const expected = [ 'Funktion: news', 'Projekt: Bäckerei', 'Domain: baeckerei.de', `siteKey: ${SITE_KEY}`, `Einbau:
+ https://project.webklar.com/embed/news.js`, 'Anleitung: docs/funktionen/news-einbau.md (Repo Webklar-Kundenbereich)', ].join('\n') assert.equal(await news.ticket.details(ctx), expected) // Domain: liveDomain || previewUrl || subdomain; Projektname: projectName || subdomain const ohneLive = await news.ticket.details({ ...ctx, project: { projectName: 'X', previewUrl: 'https://p.example', subdomain: 's' } }) assert.match(ohneLive, /^Domain: https:\/\/p\.example$/m) const nurSub = await news.ticket.details({ ...ctx, project: { subdomain: 's' } }) assert.match(nurSub, /^Projekt: s$/m) assert.match(nurSub, /^Domain: s$/m) // Parität zu createFeatureTicket (Standardtexte): dasselbe Dokument, Wort für Wort aw.seed('customers', { $id: 'c1', name: 'Kunde Eins', location: 'Berlin' }) const { ticketId } = await createFeatureTicket({ customerId: 'c1', project, featureKey: 'news', siteKey: SITE_KEY }) const ticket = aw.docs('workorders').get(ticketId) assert.equal(ticket.details, expected) assert.equal(ticket.topic, news.ticket.topic(project)) }) // ---------------------------------------------------------------------------------- Alias funktionen --- test('Alias bleibt: config.funktionen folgt config.features.news zur Laufzeit', () => { const news = config.features.news assert.equal(config.funktionen.setupEstimate, news.setupEstimate) assert.equal(config.funktionen.consentText, news.consent.items[0].text) assert.equal(config.funktionen.consentText, NEWS_SATZ) const vorherEstimate = news.setupEstimate const vorherText = news.consent.items[0].text try { news.setupEstimate = 'in 3 Tagen' news.consent.items[0].text = 'Neuer Satz' assert.equal(config.funktionen.setupEstimate, 'in 3 Tagen') assert.equal(config.funktionen.consentText, 'Neuer Satz') } finally { news.setupEstimate = vorherEstimate news.consent.items[0].text = vorherText } assert.equal(config.funktionen.setupEstimate, vorherEstimate) assert.equal(config.funktionen.consentText, vorherText) }) // ---------------------------------------------------------------------------------- siteFeatures --- test('FEATURE_LABELS und FEATURE_KEYS unverändert, aus dem Register abgeleitet', () => { assert.deepEqual(sf.FEATURE_LABELS, { news: { name: 'News-Pinnwand', keine: 'keine News-Pinnwand' }, termine: { name: 'Terminplaner', keine: 'keinen Terminplaner' }, zeiten: { name: 'Öffnungszeiten live', keine: 'keine Öffnungszeiten live' }, }) assert.deepEqual(sf.FEATURE_KEYS, ['news', 'termine', 'zeiten']) assert.deepEqual(sf.FEATURE_KEYS, reg.featureKeys()) assert.deepEqual(reg.featureKeys(), ['news', 'termine', 'zeiten']) }) // ---------------------------------------------------------------------------------- Schlüsselschutz --- test('Schlüsselschutz: nur eigene Schlüssel des Registers', () => { for (const key of ['analyse', '__proto__', 'constructor', 'toString', 'hasOwnProperty', undefined, null, '', 'News']) { assert.equal(reg.featureConfig(key), null, `featureConfig(${String(key)})`) assert.equal(reg.oeffentlicheAnsicht(key), null, `oeffentlicheAnsicht(${String(key)})`) assert.deepEqual(reg.featureHaken(key), {}, `featureHaken(${String(key)})`) } assert.equal(reg.featureConfig('news'), config.features.news) assert.equal(reg.featureConfig('termine'), config.features.termine) }) // ---------------------------------------------------------------------------------- Haken --- test('Haken überschreibbar: config.features.