import { test, after } from 'node:test' import assert from 'node:assert/strict' import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { startAppwriteMock } from './support/appwriteMock.js' import { startFakeStripe } from './support/fakeStripe.js' import { useTestEnv } from './support/testEnv.js' import { schemaKeys } from './support/schema.js' // Buchungskalender: Aktivierung durch das Team (ohne Zahlung) nur mit Zustimmung des Kunden in Textform (AVV Buchungskalender, Vorbemerkung Abs. 2; // wie der Webshop, siehe funktionenShopTextform.test.js; anders: auch im Pilot). Schalter: unterkunftTextformNoetig (services/unterkunft/schalter.js). const aw = await startAppwriteMock({ schema: { portalFeatures: schemaKeys('portalFeatures'), subscriptions: schemaKeys('subscriptions'), featureConsents: schemaKeys('featureConsents') }, }) const st = await startFakeStripe() useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { config } = await import('../server/config.js') const { startTestApp, get, post } = await import('./support/testApp.js') const { customerCookie, seedFixtures } = await import('./support/fixtures.js') const { default: teamRoutes } = await import('../server/routes/team/index.js') const sf = await import('../server/services/siteFeatures.js') const { leseZustimmung } = await import('../server/services/featureConsents.js') const { setLegalDirForTests, legalPdfFiles } = await import('../server/services/legalDocs.js') const { setMailTransportForTests } = await import('../server/services/mailer.js') const { mailSink } = await import('./support/mailSink.js') const { unterkunftTextformNoetig } = await import('../server/services/unterkunft/schalter.js') const app = await startTestApp({ '/api/team': teamRoutes }) const mails = mailSink() setMailTransportForTests(mails) const MODUS = { mode: config.unterkunft.mode, pilotEmails: [...config.unterkunft.pilotEmails] } after(() => { config.unterkunft.mode = MODUS.mode config.unterkunft.pilotEmails = MODUS.pilotEmails setLegalDirForTests(null) setMailTransportForTests(null) return Promise.all([app.close(), aw.close(), st.close()]) }) seedFixtures(aw) aw.seed('employees', { $id: 'e_staff2', userId: 'u_staff2', displayName: 'Andrej', email: 'andrej@webklar.com' }) const STAFF = { headers: { Authorization: 'Bearer jwt-staff' } } const ukMit = (mode, pilotEmails = []) => { config.unterkunft.mode = mode config.unterkunft.pilotEmails = pilotEmails } const team = (customerId, key, body, opts = STAFF) => post(`/api/team/funktionen/${customerId}/${key}`, body, opts) const featuresOf = (res, projectId) => res.body.projects.find((p) => p.projectId === projectId).features const berlinTag = (ms = Date.now()) => new Intl.DateTimeFormat('sv-SE', { timeZone: 'Europe/Berlin' }).format(ms) const tagMitVersatz = (tag, tage) => new Date(Date.parse(`${tag}T12:00:00Z`) + tage * 86400000).toISOString().slice(0, 10) const ttmmjjjj = (tag) => tag.split('-').reverse().join('.') const consents = (projectId) => [...aw.docs('featureConsents').values()].filter((c) => c.projectId === projectId) const k16An = (email) => mails.sent.filter((m) => /ist bereit$/.test(m.subject) && m.to.some((t) => t.address === email)) let nr = 0 function legeKunde({ email } = {}) { nr += 1 const customerId = `c_uktf_${nr}` const projectId = `p_uktf_${nr}` aw.seed('customers', { $id: customerId, email: email ?? `uktf-${nr}@example.invalid`, name: `Kunde UK ${nr}`, status: 'active', portalAccessEnabled: true }) aw.seed('websiteProjects', { $id: projectId, customerId, projectName: `UK-Projekt ${nr}` }) return { customerId, projectId, email: aw.docs('customers').get(customerId).email } } const aktivieren = (k, body = {}, opts = STAFF) => team(k.customerId, 'unterkunft', { projectId: k.projectId, action: 'activate', ...body }, opts) test('unterkunftTextformNoetig: nur bei off nicht (auch im Pilot und für Pilotadressen)', (t) => { t.after(() => ukMit(MODUS.mode, MODUS.pilotEmails)) ukMit('off') assert.equal(unterkunftTextformNoetig('a@example.com'), false) ukMit('pilot', ['mimi@example.com']) assert.equal(unterkunftTextformNoetig('mimi@example.com'), true) ukMit('on', []) assert.equal(unterkunftTextformNoetig('a@example.com'), true) }) test('TEXTFORM_KEYS und Fassung: unterkunft ist aufgenommen, Fassung aus config.unterkunft', () => { assert.ok(sf.TEXTFORM_KEYS.includes('unterkunft')) assert.equal(sf.textformFassung('unterkunft'), config.unterkunft.fassung) }) test('GET /funktionen/:customerId liefert unterkunft { textformNoetig, avvVersion, links } wie der Webshop', async (t) => { t.after(() => ukMit(MODUS.mode, MODUS.pilotEmails)) const k = legeKunde() ukMit('pilot', ['x@example.com']) const res = { body: await get(`/api/team/funktionen/${k.customerId}`, STAFF) } const v = config.unterkunft.fassung assert.equal(res.body.unterkunft.textformNoetig, true) assert.equal(res.body.unterkunft.avvVersion, v) assert.deepEqual(res.body.unterkunft.links, { lb: `${config.publicBaseUrl}/rechtliches/leistungsbeschreibung-unterkunft-${v}.html`, avv: `${config.publicBaseUrl}/rechtliches/avv-unterkunft-${v}.html`, }) assert.ok(res.body.shop && res.body.termine, 'die übrigen Felder bleiben') ukMit('off') assert.equal((await get(`/api/team/funktionen/${k.customerId}`, STAFF)).unterkunft.textformNoetig, false) }) test('unterkunft textform: ohne Angaben 400 ohne Nachweis, mit Angaben 200; Nachweis mit Fassung, Datum, Weg, Mitarbeiter; K16 mit LB und AVV als PDF', async (t) => { t.after(() => ukMit(MODUS.mode, MODUS.pilotEmails)) ukMit('pilot', ['mimi@example.invalid']) const k = legeKunde({ email: 'mimi@example.invalid' }) const v = config.unterkunft.fassung const ohne = await aktivieren(k) assert.equal(ohne.status, 400) assert.match(ohne.body.error, /Datum der Zustimmung/) assert.equal(await sf.getFeatureDoc(k.customerId, k.projectId, 'unterkunft'), null, 'nichts angelegt') assert.equal(consents(k.projectId).length, 0) assert.equal((await aktivieren(k, { textformDate: berlinTag() })).status, 400) const tag = berlinTag() const ok = await aktivieren(k, { textformDate: tag, textformChannel: 'email' }) assert.equal(ok.status, 200, JSON.stringify(ok.body)) assert.equal(featuresOf(ok, k.projectId).unterkunft.state, 'active') const doc = await sf.getFeatureDoc(k.customerId, k.projectId, 'unterkunft') assert.deepEqual([doc.state, doc.enabled, doc.unlockedByPurchase], ['active', true, false]) const [nachweis] = consents(k.projectId) assert.deepEqual([nachweis.featureKey, nachweis.stripeSessionId, nachweis.ip, nachweis.userAgent, nachweis.konto], ['unterkunft', '', '', '', 'Mitarbeiter: Andrej']) const gelesen = leseZustimmung(nachweis) assert.deepEqual(gelesen.fassungen, { leistungsbeschreibung: v, avv: v }) assert.deepEqual(gelesen.texte.map((x) => x.id), ['textform']) assert.equal( gelesen.texte[0].text, `Der Kunde hat am ${ttmmjjjj(tag)} per E-Mail in Textform erklärt, dass er der Leistungsbeschreibung Buchungskalender und dem Auftragsverarbeitungsvertrag Buchungskalender (Fassung ${v}) zustimmt.` ) const k16 = k16An(k.email) assert.equal(k16.length, 1, mails.sent.map((m) => m.subject).join(' / ')) assert.deepEqual(k16[0].attachments.map((a) => a.filename), [`Leistungsbeschreibung-Buchungskalender-${v}.pdf`, `AVV-Buchungskalender-${v}.pdf`]) for (const a of k16[0].attachments) assert.equal(Buffer.from(a.content, 'base64').subarray(0, 4).toString(), '%PDF') }) test('unterkunft textform: Weg WhatsApp wird angenommen und im Nachweis genannt; unbekannter Weg 400', async (t) => { t.after(() => ukMit(MODUS.mode, MODUS.pilotEmails)) ukMit('pilot', ['whatsapp@example.invalid']) const k = legeKunde({ email: 'whatsapp@example.invalid' }) const v = config.unterkunft.fassung const telefon = await aktivieren(k, { textformDate: berlinTag(), textformChannel: 'telefon' }) assert.equal(telefon.status, 400) assert.match(telefon.body.error, /Chat, E-Mail oder WhatsApp/) assert.equal(consents(k.projectId).length, 0) const tag = berlinTag() const ok = await aktivieren(k, { textformDate: tag, textformChannel: 'whatsapp' }) assert.equal(ok.status, 200, JSON.stringify(ok.body)) assert.equal( leseZustimmung(consents(k.projectId)[0]).texte[0].text, `Der Kunde hat am ${ttmmjjjj(tag)} per WhatsApp in Textform erklärt, dass er der Leistungsbeschreibung Buchungskalender und dem Auftragsverarbeitungsvertrag Buchungskalender (Fassung ${v}) zustimmt.` ) }) test('unterkunft textform entfällt: Modus off, schon aktiv, Abschalten; ungültige Datumsangaben 400', async (t) => { t.after(() => ukMit(MODUS.mode, MODUS.pilotEmails)) ukMit('off') const k = legeKunde() assert.equal((await aktivieren(k)).status, 200) assert.equal(consents(k.projectId).length, 0) ukMit('on', []) assert.equal((await aktivieren(k)).status, 200, 'schon aktiv: keine neue Zustimmung') assert.equal((await team(k.customerId, 'unterkunft', { projectId: k.projectId, action: 'disable' })).status, 200) const k2 = legeKunde() const v = config.unterkunft.fassung for (const body of [{ textformDate: tagMitVersatz(v, -1), textformChannel: 'chat' }, { textformDate: tagMitVersatz(berlinTag(), 1), textformChannel: 'chat' }]) { assert.equal((await aktivieren(k2, body)).status, 400) } assert.equal(consents(k2.projectId).length, 0) })