AB-Kern: Senden unter Sperre, Fehlerpfade, Dateirest und Mail-Fehler

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-07 14:14:29 +00:00
1 parent ed7567397d
commit c74f7eeea7
2 files changed
+181 -21

No files matched your search

+113
View File
@@ -114,3 +114,116 @@ test('Senden: nur Entwürfe (409), unbekannte Aufträge (404) und Kunden (404)',
await deleteDocument(config.collections.customers, KUNDE_ID)
await abgelehnt(sende(zweiter.id, deps), 404, MELDUNG.kundeFehlt)
})
test('Senden: scheitert die PDF-Erzeugung (Fehler, leer oder unvollständiger Entwurf), bleibt alles wie vorher und die Nummer ist nicht verbraucht', async () => {
mock.method(console, 'error', () => {})
const entwurf = await neuerEntwurf()
const faelle = [
[baueDeps({ pdfFehler: new Error('pdfkit kaputt') }), 500, MELDUNG.pdf],
[baueDeps({ pdf: Buffer.alloc(0) }), 500, MELDUNG.pdf],
[baueDeps({ pdfFehler: new AuftragError(409, 'Entwurf unvollständig: Anschrift fehlt.') }), 409, 'Entwurf unvollständig: Anschrift fehlt.'],
]
for (const [fehlerhaft, status, meldung] of faelle) {
await abgelehnt(sende(entwurf.id, fehlerhaft.deps), status, meldung)
assert.deepEqual([fehlerhaft.calls.token.length, fehlerhaft.calls.mail.length], [0, 0])
}
assert.deepEqual(dateienIn(uploadDir), [])
const danach = await getAuftrag(entwurf.id)
assert.deepEqual([danach.status, danach.nummer, danach.jahr], ['entwurf', '', null])
assert.ok(!danach.pdfDatei && !danach.tokenHash)
assert.equal((await sende(entwurf.id, baueDeps().deps)).auftrag.nummer, 'AB-2026-0001')
})
test('Senden: eine schon vorhandene Zieldatei wird nie überschrieben – Fehler, Entwurf und Datei bleiben', async () => {
mock.method(console, 'error', () => {})
const entwurf = await neuerEntwurf()
const ziel = path.join(uploadDir, KUNDE_ID, `${entwurf.id}.pdf`)
fs.mkdirSync(path.dirname(ziel), { recursive: true })
fs.writeFileSync(ziel, 'Rest eines Absturzes')
const { deps, calls } = baueDeps()
await abgelehnt(sende(entwurf.id, deps), 500, MELDUNG.pdf)
assert.equal(fs.readFileSync(ziel, 'utf8'), 'Rest eines Absturzes')
assert.equal(calls.mail.length, 0)
assert.equal((await getAuftrag(entwurf.id)).status, 'entwurf')
})
test('Senden: scheitert das Speichern der Auftragsbestätigung, wird die eben abgelegte Datei entfernt und das Senden lässt sich wiederholen', async () => {
mock.method(console, 'error', () => {})
const entwurf = await neuerEntwurf()
const { deps, calls } = baueDeps()
aw.failNext({ collection: config.collections.auftraege, method: 'PATCH', status: 500 })
await assert.rejects(sende(entwurf.id, deps), (err) => err.status === 500)
assert.equal(calls.mail.length, 0)
assert.deepEqual(dateienIn(uploadDir), [])
assert.equal((await getAuftrag(entwurf.id)).status, 'entwurf')
const { auftrag } = await sende(entwurf.id, deps)
assert.equal(auftrag.nummer, 'AB-2026-0001')
assert.deepEqual(dateienIn(uploadDir), [`${KUNDE_ID}/${entwurf.id}.pdf`])
})
test('Senden: scheitert nur die Mail, bleibt die AB versendet (mailStatus fehler) und der Fehlertext verrät nichts Internes', async () => {
mock.method(console, 'error', () => {})
const [e1, e2] = [await neuerEntwurf(), await neuerEntwurf()]
const mitCode = baueDeps({ mailFehler: Object.assign(new Error('535 Login smtp.example.invalid'), { code: 'EAUTH' }) })
const r1 = await sende(e1.id, mitCode.deps)
assert.equal(r1.mailFehler, true)
assert.deepEqual(
[r1.auftrag.status, r1.auftrag.nummer, r1.auftrag.mailStatus, r1.auftrag.mailFehler],
['versendet', 'AB-2026-0001', 'fehler', 'E-Mail konnte nicht gesendet werden (EAUTH).'],
)
assert.ok(r1.auftrag.pdfDatei && r1.auftrag.tokenHash)
assert.deepEqual(await getAuftrag(e1.id), r1.auftrag)
const ohneCode = baueDeps({ mailFehler: new Error('Verbindung zu mail.example.invalid abgebrochen') })
const r2 = await sende(e2.id, ohneCode.deps)
assert.deepEqual([r2.auftrag.nummer, r2.auftrag.mailStatus, r2.auftrag.mailFehler], ['AB-2026-0002', 'fehler', 'E-Mail konnte nicht gesendet werden.'])
})
test('Senden: zwei Entwürfe gleichzeitig bekommen verschiedene Nummern', async () => {
const [a, b] = [await neuerEntwurf(), await neuerEntwurf()]
const { deps } = baueDeps({ pdfWarten: warte(30) })
const ergebnisse = await Promise.all([sende(a.id, deps), sende(b.id, deps)])
assert.deepEqual(ergebnisse.map((e) => e.auftrag.nummer).sort(), ['AB-2026-0001', 'AB-2026-0002'])
assert.equal(new Set(ergebnisse.map((e) => e.auftrag.pdfDatei)).size, 2)
})
test('Senden: dieselbe AB zweimal gleichzeitig (Doppelklick) – einmal gesendet, das zweite Mal 409, genau eine Nummer und eine Mail', async () => {
const entwurf = await neuerEntwurf()
const { deps, calls } = baueDeps({ pdfWarten: warte(30) })
const ergebnisse = await Promise.allSettled([sende(entwurf.id, deps), sende(entwurf.id, deps)])
assert.deepEqual(ergebnisse.map((r) => r.status).sort(), ['fulfilled', 'rejected'])
const abgewiesen = ergebnisse.find((r) => r.status === 'rejected').reason
assert.deepEqual([abgewiesen.status, abgewiesen.message], [409, MELDUNG.nurEntwurfSenden])
assert.deepEqual([calls.pdf.length, calls.mail.length], [1, 1])
assert.deepEqual((await listAuftraege({ jahr: 2026 })).map((a) => a.nummer), ['AB-2026-0001'])
})
test('Senden hält die Sperre: Ändern und Löschen währenddessen warten und scheitern danach mit 409', async () => {
const entwurf = await neuerEntwurf()
const pdfTor = tor()
const { deps, calls } = baueDeps({ pdfWarten: pdfTor.promise })
const senden = ergebnis(sende(entwurf.id, deps))
await wartebis(() => calls.pdf.length === 1) // das Senden hält die Sperre und wartet auf das PDF
const aendern = ergebnis(aendereEntwurf(entwurf.id, { titel: 'Heimlich geändert' }))
const loeschen = ergebnis(loescheEntwurf(entwurf.id))
await warte(30) // beide sind unterwegs, dürfen aber noch nichts geschrieben haben
assert.equal((await getAuftrag(entwurf.id)).titel, 'Website Beispiel')
pdfTor.freigeben()
const [s, a, l] = await Promise.all([senden, aendern, loeschen])
assert.equal(s.wert.auftrag.status, 'versendet')
assert.deepEqual([a.fehler?.status, a.fehler?.message], [409, MELDUNG.nurEntwurfAendern])
assert.deepEqual([l.fehler?.status, l.fehler?.message], [409, MELDUNG.nurEntwurfLoeschen])
const danach = await getAuftrag(entwurf.id)
assert.deepEqual([danach.titel, danach.status], ['Website Beispiel', 'versendet'])
})
test('Senden: während die Mail unterwegs ist, ist die Sperre schon frei (die nächste AB bekommt ihre Nummer)', async () => {
const [a, b] = [await neuerEntwurf(), await neuerEntwurf()]
const mailTor = tor()
const langsam = baueDeps({ mailWarten: mailTor.promise })
const ersteMail = ergebnis(sende(a.id, langsam.deps))
await wartebis(() => langsam.calls.mail.length === 1)
const zweite = await sende(b.id, baueDeps().deps) // läuft durch, obwohl die Mail der ersten noch hängt
assert.equal(zweite.auftrag.nummer, 'AB-2026-0002')
mailTor.freigeben()
assert.equal((await ersteMail).wert.auftrag.nummer, 'AB-2026-0001')
})