Files
Webklar-Kundenbereich/test/auftraegeKundeRoutes.test.js

144 lines
8.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, abDokument, kunde } from './auftraegeBestaetigungHilfe.js'
// Portal-Routen /api/auftraege (V5): nur eigene, gesendete ABs; Schalter; Bestätigen mit Portal-Sitzung
process.env.AUFTRAEGE_UPLOAD_DIR = mkdtempSync(path.join(os.tmpdir(), 'auftraege-kunde-'))
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 { config } = await import('../server/config.js')
const { startTestApp, send } = await import('./support/testApp.js')
const { seedFixtures, customerCookie, adminCookie } = 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 { setMailTransportForTests } = await import('../server/services/mailer.js')
const { default: kundeRoutes } = await import('../server/routes/auftraegeKunde.js')
const app = await startTestApp({ '/api/auftraege': kundeRoutes })
after(() => {
setMailTransportForTests(null)
return Promise.all([app.close(), aw.close()])
})
seedFixtures(aw)
const PDF = '%PDF-1.4 Kunden-AB'
for (const [kundenId, id] of [['c1', 'ab1'], ['c2', 'ab3'], ['c1', 'ab4']]) await speicherePdf(kundenId, id, Buffer.from(PDF))
const NICHT_GEFUNDEN = 'Auftragsbestätigung nicht gefunden.'
const ENTWURF = { id: 'ab2', nummer: '', jahr: null, laufnummer: null, status: 'entwurf', gesendetAm: null, gesendetAn: '', tokenHash: '', tokenGueltigBis: null, pdfDatei: '', pdfSha256: '' }
const AB4 = { id: 'ab4', nummer: 'AB-2026-0003', status: 'bestaetigt', bestaetigtAm: '2026-10-07T08:30:00.000Z', bestaetigtUeber: 'link', pdfDatei: 'c1/ab4.pdf', tokenHash: '' }
const AB3 = { id: 'ab3', nummer: 'AB-2026-0002', kundenId: 'c2', pdfDatei: 'c2/ab3.pdf', kunde: kunde('privat', { email: 'k2@example.invalid' }), tokenHash: '' }
let sink
const seed = (over = {}) => aw.seed('auftraege', { $id: over.id || 'ab1', ...auftragToDoc(abDokument(over)) })
beforeEach(() => {
config.auftraege.mode = 'on'
config.auftraege.pilotEmails = []
sink = mailSink()
setMailTransportForTests(sink)
for (const over of [{}, ENTWURF, AB3, AB4]) seed(over)
})
const cookie = customerCookie('c1')
const get = (pfad, opts = { cookie }) => send('GET', `/api/auftraege${pfad}`, opts)
const post = (pfad, body, opts = { cookie }) => send('POST', `/api/auftraege${pfad}`, { ...opts, body })
test('GET /api/auftraege: nur eigene, nur gesendete ABs, mit id und kannBestaetigen, ohne interne Felder', async () => {
const res = await get('')
assert.equal(res.status, 200)
assert.equal(res.body.aktiv, true)
assert.deepEqual(res.body.auftraege.map((a) => a.id).sort(), ['ab1', 'ab4'])
const ab1 = res.body.auftraege.find((a) => a.id === 'ab1')
const ab4 = res.body.auftraege.find((a) => a.id === 'ab4')
assert.deepEqual([ab1.nummer, ab1.status, ab1.kannBestaetigen, ab4.status, ab4.kannBestaetigen], ['AB-2026-0001', 'versendet', true, 'bestaetigt', false])
for (const intern of ['kundenId', 'kunde', 'tokenHash', 'tokenGueltigBis', 'pdfDatei', 'pdfSha256', 'gesendetAn']) assert.ok(!(intern in ab1), intern)
assert.equal(res.headers.get('cache-control'), 'no-store')
})
test('kannBestaetigen wie in V3: versendet und abgerechnet ohne frühere Bestätigung ja, sonst nein', async () => {
const faelle = [
[{}, true],
[{ status: 'abgerechnet' }, true],
[{ status: 'abgerechnet', bestaetigtAm: '2026-10-07T08:30:00.000Z' }, false],
[{ status: 'bestaetigt', bestaetigtAm: '2026-10-07T08:30:00.000Z' }, false],
[{ status: 'ersetzt' }, false],
[{ status: 'storniert' }, false],
[{ status: 'widerrufen' }, false],
]
for (const [over, erwartet] of faelle) {
seed(over)
const ab1 = (await get('')).body.auftraege.find((a) => a.id === 'ab1')
assert.ok(ab1, `${JSON.stringify(over)}: auch ersetzte, stornierte und widerrufene ABs stehen in der Liste`)
assert.equal(ab1.kannBestaetigen, erwartet, JSON.stringify(over))
}
})
test('ohne Anmeldung und mit alter Mitarbeiter-Sitzung: 401, nichts ändert sich', async () => {
for (const opts of [{}, { cookie: adminCookie() }]) {
assert.equal((await get('', opts)).status, 401)
assert.equal((await get('/ab1/pdf', opts)).status, 401)
assert.equal((await post('/ab1/bestaetigen', { bestaetigt: true }, opts)).status, 401)
}
assert.equal((await getAuftrag('ab1')).bestaetigtAm, null)
})
test('Schalter aus: leere Liste, PDF und Bestätigen wie bei unbekannter AB; im Pilot nur für Adressen der Liste', async () => {
config.auftraege.mode = 'off'
assert.deepEqual((await get('')).body, { aktiv: false, auftraege: [] })
for (const res of [await get('/ab1/pdf'), await post('/ab1/bestaetigen', { bestaetigt: true })]) assert.deepEqual([res.status, res.body], [404, { error: NICHT_GEFUNDEN }])
assert.equal((await getAuftrag('ab1')).bestaetigtAm, null)
config.auftraege.mode = 'pilot'
config.auftraege.pilotEmails = ['k@example.invalid']
assert.equal((await get('')).body.aktiv, true)
assert.deepEqual((await get('', { cookie: customerCookie('c2') })).body, { aktiv: false, auftraege: [] })
})
test('GET /api/auftraege/:id/pdf: eigene AB (auch ersetzt), nie fremde, Entwürfe, unbekannte oder ungültige IDs', async () => {
const eigene = await get('/ab1/pdf')
assert.deepEqual([eigene.status, eigene.headers.get('content-type'), eigene.body], [200, 'application/pdf', PDF])
assert.equal(eigene.headers.get('content-disposition'), 'inline; filename="AB-2026-0001.pdf"')
seed({ status: 'ersetzt' })
assert.equal((await get('/ab1/pdf')).status, 200)
for (const id of ['ab3', 'ab2', 'gibtsnicht', '..%2F..%2Fx', 'a'.repeat(40)]) {
const res = await get(`/${id}/pdf`)
assert.deepEqual([res.status, res.body], [404, { error: NICHT_GEFUNDEN }], id)
}
})
test('POST …/bestaetigen: bestätigt über das Portal (Protokoll, K32, T22); der zweite Aufruf ändert nichts', async () => {
const vorher = Date.now()
const res = await post('/ab1/bestaetigen', { bestaetigt: true, fruehbeginn: true })
assert.equal(res.status, 200)
assert.deepEqual([res.body.auftrag.id, res.body.auftrag.status, res.body.auftrag.kannBestaetigen], ['ab1', 'bestaetigt', false])
const doc = await getAuftrag('ab1')
assert.deepEqual([doc.bestaetigtUeber, doc.fruehbeginn], ['portal', true])
assert.ok(new Date(doc.bestaetigtAm).getTime() >= vorher - 1000 && new Date(doc.bestaetigtAm).getTime() <= Date.now() + 1000)
assert.equal(sink.sent.length, 2)
const zweite = await post('/ab1/bestaetigen', { bestaetigt: true, fruehbeginn: false })
assert.deepEqual([zweite.status, zweite.body], [200, res.body])
assert.equal(sink.sent.length, 2)
})
test('POST …/bestaetigen: ohne bestaetigt: true 400; fremde, Entwurf, unbekannte AB 404; ersetzt, storniert, widerrufen 410 – nie eine Änderung', async () => {
for (const body of [{}, { bestaetigt: 'ja' }, { bestaetigt: false }]) {
const res = await post('/ab1/bestaetigen', body)
assert.deepEqual([res.status, res.body], [400, { error: 'Bitte bestätigen Sie den Auftrag mit dem Häkchen.' }])
}
for (const id of ['ab3', 'ab2', 'gibtsnicht']) {
const res = await post(`/${id}/bestaetigen`, { bestaetigt: true })
assert.deepEqual([res.status, res.body], [404, { error: NICHT_GEFUNDEN }], id)
}
for (const [status, text] of [['ersetzt', 'ersetzt'], ['storniert', 'storniert'], ['widerrufen', 'widerrufen']]) {
seed({ status })
const res = await post('/ab1/bestaetigen', { bestaetigt: true })
assert.deepEqual([res.status, res.body], [410, { error: `Diese Auftragsbestätigung wurde ${text}.` }], status)
assert.equal((await getAuftrag('ab1')).bestaetigtAm, null, status)
}
assert.equal((await getAuftrag('ab3')).bestaetigtAm, null)
assert.equal(sink.sent.length, 0)
})