import { test, after, beforeEach } from 'node:test' import assert from 'node:assert/strict' import os from 'node:os' import path from 'node:path' import { mkdtempSync } from 'node:fs' import { startAppwriteMock } from './support/appwriteMock.js' import { useTestEnv } from './support/testEnv.js' import { mailSink } from './support/mailSink.js' import { AUFTRAEGE_DATETIMES, AUFTRAEGE_SCHEMA, TOKEN, abDokument } from './auftraegeBestaetigungHilfe.js' // Kundenansicht (Mitarbeiter im Portal eines Kunden): Bestätigen ist eine Erklärung im Namen des Kunden und bleibt gesperrt process.env.AUFTRAEGE_UPLOAD_DIR = mkdtempSync(path.join(os.tmpdir(), 'auftraege-kundenansicht-')) process.env.AUFTRAEGE_ENABLED = 'on' const aw = await startAppwriteMock({ schema: AUFTRAEGE_SCHEMA, datetimes: AUFTRAEGE_DATETIMES, defaultLimit: 25 }) useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' }) const { startTestApp, send } = await import('./support/testApp.js') const { seedFixtures, customerCookie, kundenansichtCookie } = await import('./support/fixtures.js') const { auftragToDoc, getAuftrag } = await import('../server/services/auftraege/auftragStore.js') const { speicherePdf } = await import('../server/services/auftraege/ablage.js') const { kundenansichtGuard, KUNDENANSICHT_ALLOWED } = await import('../server/middleware/kundenansichtGuard.js') const { setMailTransportForTests } = await import('../server/services/mailer.js') const { default: kundeRoutes } = await import('../server/routes/auftraegeKunde.js') const { default: tokenRoutes } = await import('../server/routes/auftrag.js') // Mount-Pfade und Reihenfolge wie in server/index.js: der Wächter steht vor den Routen const app = await startTestApp({ '/': kundenansichtGuard, '/api/auftraege': kundeRoutes, '/api/auftrag': tokenRoutes }) after(() => { setMailTransportForTests(null) return Promise.all([app.close(), aw.close()]) }) seedFixtures(aw) await speicherePdf('c1', 'ab1', Buffer.from('%PDF-1.4 Test')) const VIEW = 'a'.repeat(32) const kv = () => ({ cookie: kundenansichtCookie({ customerId: 'c1', viewId: VIEW }), headers: { 'x-kundenansicht': VIEW } }) let sink beforeEach(() => { sink = mailSink() setMailTransportForTests(sink) aw.seed('auftraege', { $id: 'ab1', ...auftragToDoc(abDokument()) }) }) const unveraendert = async () => { const doc = await getAuftrag('ab1') assert.deepEqual([doc.status, doc.bestaetigtAm, sink.sent.length], ['versendet', null, 0]) } test('kein Eintrag der Erlaubnisliste gilt für Aufträge', () => { for (const pfad of ['/api/auftraege', '/api/auftraege/ab1/bestaetigen', '/api/auftraege/ab1/pdf', `/api/auftrag/${TOKEN}`, `/api/auftrag/${TOKEN}/bestaetigen`]) { assert.ok(!KUNDENANSICHT_ALLOWED.some(([, muster]) => muster.test(pfad)), pfad) } }) test('POST /api/auftraege/:id/bestaetigen in der Kundenansicht: 403 view_blocked, die AB bleibt unverändert', async () => { const res = await send('POST', '/api/auftraege/ab1/bestaetigen', { ...kv(), body: { bestaetigt: true, fruehbeginn: true } }) assert.equal(res.status, 403) assert.equal(res.body.code, 'view_blocked') await unveraendert() }) test('alle Schreib-Routen des Kunden-Routers sind in der Kundenansicht gesperrt (nur Bestätigen ist eine)', async () => { const schreibRouten = kundeRoutes.stack .filter((schicht) => schicht.route) .flatMap((schicht) => Object.keys(schicht.route.methods).filter((m) => !['get', 'head', 'options', '_all'].includes(m)).map((m) => [m.toUpperCase(), `/api/auftraege${String(schicht.route.path).replace(/:[A-Za-z]+/g, 'ab1')}`])) assert.deepEqual(schreibRouten, [['POST', '/api/auftraege/ab1/bestaetigen']]) for (const [methode, pfad] of schreibRouten) { const res = await send(methode, pfad, { ...kv(), body: { bestaetigt: true } }) assert.equal(res.status, 403, `${methode} ${pfad}`) } await unveraendert() }) test('auch der öffentliche Link bestätigt nichts, wenn der Browser eine Kundenansicht-Sitzung trägt', async () => { const mitKennung = await send('POST', `/api/auftrag/${TOKEN}/bestaetigen`, { ...kv(), body: { bestaetigt: true } }) assert.deepEqual([mitKennung.status, mitKennung.body.code], [403, 'view_blocked']) const ohneKennung = await send('POST', `/api/auftrag/${TOKEN}/bestaetigen`, { cookie: kv().cookie, body: { bestaetigt: true } }) assert.deepEqual([ohneKennung.status, ohneKennung.body.code], [409, 'view_replaced']) await unveraendert() }) test('Lesen bleibt erlaubt, kannBestaetigen ist in der Kundenansicht false; der Kunde selbst darf bestätigen', async () => { const liste = await send('GET', '/api/auftraege', kv()) assert.equal(liste.status, 200) assert.deepEqual(liste.body.auftraege.map((a) => [a.id, a.kannBestaetigen]), [['ab1', false]]) assert.equal((await send('GET', '/api/auftraege/ab1/pdf', kv())).status, 200) const selbst = await send('GET', '/api/auftraege', { cookie: customerCookie('c1') }) assert.deepEqual(selbst.body.auftraege.map((a) => [a.id, a.kannBestaetigen]), [['ab1', true]]) const res = await send('POST', '/api/auftraege/ab1/bestaetigen', { cookie: customerCookie('c1'), body: { bestaetigt: true } }) assert.equal(res.status, 200) assert.equal((await getAuftrag('ab1')).status, 'bestaetigt') })