Files
Webklar-Kundenbereich/test/auftraegeAbFolge.test.js
T

122 lines
6.8 KiB
JavaScript
Raw 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, mock } from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import {
starteUmgebung, KUNDE_ID, PROJEKT_ID, TICKET_ID, ZWEITES_TICKET, FREMD, VON, MELDUNG, festpreisEingabe, kundeDoc, projektDoc,
baueDeps, sha256, abgelehnt, dateienIn, ergebnis, tor, warte, wartebis,
} from './helpers/auftraegeAb.js'
const umg = await starteUmgebung()
const { aw, config, uploadDir } = umg
after(() => umg.schliesse())
beforeEach(umg.zuruecksetzen)
const { getAuftrag, updateAuftrag, findAuftragByTokenHash } = await import('../server/services/auftraege/auftragStore.js')
const { createProjektRechnung, getProjektRechnung } = await import('../server/services/auftraege/projektRechnungenStore.js')
const { lesePdf } = await import('../server/services/auftraege/ablage.js')
const { sperreAuftrag } = await import('../server/services/auftraege/konstanten.js')
const { withLocks } = await import('../server/services/keyedLock.js')
const { legeEntwurfAn, aendereEntwurf, loescheEntwurf, sendeAuftrag, erinnereAuftrag, neueFassung, storniereAuftrag, ordneProjektZu } = await import('../server/services/auftraege/auftraege.js')
const JETZT = new Date('2026-10-06T10:00:00Z')
/** Eine versendete AB (Entwurf anlegen und senden); liefert den Auftrag. */
async function gesendet(deps, patch) {
const entwurf = await legeEntwurfAn(festpreisEingabe(patch), { von: VON })
return (await sendeAuftrag(entwurf.id, { von: VON, deps, jetzt: JETZT })).auftrag
}
/**
* Hält die Sperre der AB wie L5 beim Bestätigen (sperreAuftrag(id), V2), startet die Aktion, wartet, bis sie an der Sperre
* steht, führt waehrend() aus (dort schreibt „L5“) und liefert das Ergebnis der Aktion ({ wert } oder { fehler }) nach dem Freigeben.
*/
async function unterFremderSperre(id, { aktion, bereit = () => true, waehrend }) {
const sperre = tor()
const haelt = withLocks(sperreAuftrag(id), () => sperre.promise)
const lauf = ergebnis(aktion())
await wartebis(bereit)
await warte(30) // die Aktion steht jetzt an der Sperre und darf noch nichts geschrieben haben
await waehrend()
sperre.freigeben()
await haelt
return lauf
}
test('erinnereAuftrag: neuer Token (der alte gilt nicht mehr), gleiche PDF-Datei, Mail mit erinnerung: true, Versanddatum bleibt', async () => {
const { deps, calls } = baueDeps()
const erste = await gesendet(deps)
const spaeter = new Date('2026-10-20T08:00:00Z')
const { auftrag, mailFehler } = await erinnereAuftrag(erste.id, { deps, jetzt: spaeter })
assert.equal(mailFehler, false)
assert.equal(calls.token.length, 2)
assert.notEqual(auftrag.tokenHash, erste.tokenHash)
assert.equal(auftrag.tokenHash, sha256(calls.token[1]))
assert.equal(new Date(auftrag.tokenGueltigBis).getTime(), spaeter.getTime() + 60 * 86_400_000)
assert.equal(await findAuftragByTokenHash(erste.tokenHash), null)
assert.equal((await findAuftragByTokenHash(auftrag.tokenHash)).id, erste.id)
assert.deepEqual(
[auftrag.status, auftrag.nummer, auftrag.gesendetAm, auftrag.pdfDatei, auftrag.pdfSha256, auftrag.mailStatus],
['versendet', erste.nummer, erste.gesendetAm, erste.pdfDatei, erste.pdfSha256, 'gesendet'],
)
assert.equal(calls.pdf.length, 1) // kein neues PDF
assert.equal(calls.mail.length, 2)
assert.deepEqual(calls.mail[1].pdf, await lesePdf(erste.pdfDatei))
assert.deepEqual([calls.mail[1].token, calls.mail[1].erinnerung], [calls.token[1], true])
assert.deepEqual(await getAuftrag(erste.id), auftrag)
})
test('erinnereAuftrag nach einem Mail-Fehler: gewöhnliche Mail ohne Erinnerungs-Hinweis an die aktuelle Adresse des Kunden', async () => {
mock.method(console, 'error', () => {})
const erste = await gesendet(baueDeps({ mailFehler: new Error('Verbindung abgebrochen') }).deps)
assert.deepEqual([erste.mailStatus, erste.gesendetAn], ['fehler', 'anna@example.invalid'])
aw.seed(config.collections.customers, kundeDoc({ email: 'neu@example.invalid' })) // der Admin hat die Adresse korrigiert
const { deps, calls } = baueDeps()
const { auftrag, mailFehler } = await erinnereAuftrag(erste.id, { deps })
assert.equal(mailFehler, false)
assert.deepEqual([auftrag.mailStatus, auftrag.mailFehler, auftrag.gesendetAn], ['gesendet', '', 'neu@example.invalid'])
assert.deepEqual([calls.mail[0].erinnerung, calls.mail[0].auftrag.gesendetAn], [false, 'neu@example.invalid'])
})
test('erinnereAuftrag: scheitert die Mail, gilt trotzdem der neue Token; das Ergebnis meldet mailFehler', async () => {
mock.method(console, 'error', () => {})
const erste = await gesendet(baueDeps().deps)
const kaputt = baueDeps({ mailFehler: Object.assign(new Error('x'), { code: 'ECONNREFUSED' }) })
const { auftrag, mailFehler } = await erinnereAuftrag(erste.id, { deps: kaputt.deps })
assert.equal(mailFehler, true)
assert.deepEqual([auftrag.status, auftrag.mailStatus, auftrag.mailFehler], ['versendet', 'fehler', 'E-Mail konnte nicht gesendet werden (ECONNREFUSED).'])
assert.equal(auftrag.tokenHash, sha256(kaputt.calls.token[0]))
assert.equal(await findAuftragByTokenHash(erste.tokenHash), null)
})
test('erinnereAuftrag: nur versendete (409), unbekannt 404, ohne E-Mail 400, beschädigte PDF-Datei 500 – jeweils ohne neuen Token', async () => {
mock.method(console, 'error', () => {})
const { deps } = baueDeps()
const a = await gesendet(deps)
for (const status of ['entwurf', 'bestaetigt', 'abgerechnet', 'storniert', 'ersetzt', 'widerrufen']) {
await updateAuftrag(a.id, { status })
await abgelehnt(erinnereAuftrag(a.id, { deps }), 409, MELDUNG.keineErinnerung, status)
}
await abgelehnt(erinnereAuftrag('a_gibtsnicht', { deps }), 404, MELDUNG.auftragFehlt)
await updateAuftrag(a.id, { status: 'versendet' })
aw.seed(config.collections.customers, kundeDoc({ email: ' ' }))
await abgelehnt(erinnereAuftrag(a.id, { deps }), 400, MELDUNG.keineMail)
aw.seed(config.collections.customers, kundeDoc())
fs.writeFileSync(path.join(uploadDir, a.pdfDatei), 'manipuliert')
await abgelehnt(erinnereAuftrag(a.id, { deps }), 500, MELDUNG.pruefsumme)
assert.equal((await getAuftrag(a.id)).tokenHash, a.tokenHash)
})
test('erinnereAuftrag wartet auf die Sperre der AB und prüft den Stand danach (L5 bestätigt gerade: keine Erinnerung mehr)', async () => {
const { deps, calls } = baueDeps()
const a = await gesendet(deps)
const lauf = await unterFremderSperre(a.id, {
aktion: () => erinnereAuftrag(a.id, { deps }),
waehrend: async () => {
assert.equal((await getAuftrag(a.id)).tokenHash, a.tokenHash) // noch kein neuer Token
await updateAuftrag(a.id, { status: 'bestaetigt', bestaetigtAm: new Date().toISOString(), bestaetigtUeber: 'link' })
},
})
assert.deepEqual([lauf.fehler?.status, lauf.fehler?.message], [409, MELDUNG.keineErinnerung])
assert.equal(calls.mail.length, 1) // nur die Mail des ersten Sendens
assert.equal((await getAuftrag(a.id)).tokenHash, a.tokenHash)
})