144 lines
8.1 KiB
JavaScript
144 lines
8.1 KiB
JavaScript
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)
|
||
})
|