210 lines
13 KiB
JavaScript
210 lines
13 KiB
JavaScript
import { test, after, beforeEach, mock } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { starteUmgebung, KUNDE_ID, PROJEKT_ID, TICKET_ID, FREMD, VON, MELDUNG, festpreisEingabe, aufwandEingabe, kundeDoc, baueDeps } from './helpers/auftraegeAb.js'
|
||
|
||
const umg = await starteUmgebung()
|
||
const { aw, config } = umg
|
||
const { startTestApp, send } = await import('./support/testApp.js')
|
||
const { default: router } = await import('../server/routes/admin/auftraege.js')
|
||
const { getAuftrag } = await import('../server/services/auftraege/auftragStore.js')
|
||
const { SCHALTER_AUS_MELDUNG } = await import('../server/services/auftraege/konstanten.js')
|
||
const { legeEntwurfAn, sendeAuftrag, setzeDepsFuerTests } = await import('../server/services/auftraege/auftraege.js')
|
||
const app = await startTestApp({ '/api/admin/finanzen/auftraege': router })
|
||
after(async () => {
|
||
await app.close()
|
||
await umg.schliesse()
|
||
})
|
||
|
||
let kontext // Attrappen für PDF, Token und Mail des aktuellen Tests
|
||
beforeEach(async () => {
|
||
await umg.zuruecksetzen()
|
||
kontext = baueDeps()
|
||
setzeDepsFuerTests(kontext.deps)
|
||
})
|
||
const BASIS = '/api/admin/finanzen/auftraege'
|
||
const api = (methode, pfad, body, jwt = 'jwt-admin') => send(methode, BASIS + pfad, { headers: { Authorization: `Bearer ${jwt}` }, body })
|
||
const entwurfAnlegen = (patch) => legeEntwurfAn(festpreisEingabe(patch), { von: VON })
|
||
|
||
const ZUGRIFF = [
|
||
['GET', `/?kunde=${KUNDE_ID}`], ['POST', '/'], ['GET', '/a_1'], ['PATCH', '/a_1'], ['DELETE', '/a_1'], ['GET', '/a_1/pdf'],
|
||
['POST', '/a_1/senden'], ['POST', '/a_1/erinnern'], ['POST', '/a_1/neue-fassung'], ['POST', '/a_1/stornieren'], ['POST', '/a_1/projekt'],
|
||
]
|
||
|
||
test('Zugriff: ohne JWT 401, ohne Admin-Label 403 – bei jeder Route', async () => {
|
||
for (const [methode, pfad] of ZUGRIFF) {
|
||
const body = ['GET', 'DELETE'].includes(methode) ? undefined : {}
|
||
assert.equal((await send(methode, BASIS + pfad, { body })).status, 401, `${methode} ${pfad} ohne JWT`)
|
||
for (const jwt of ['jwt-staff', 'jwt-kunde']) assert.equal((await api(methode, pfad, body, jwt)).status, 403, `${methode} ${pfad} mit ${jwt}`)
|
||
}
|
||
})
|
||
|
||
test('Schalter: ist die Funktion für den Kunden aus, antworten Liste, Anlegen, Lesen, Ändern, Löschen und PDF mit 404 und der festen Meldung', async () => {
|
||
const a = await entwurfAnlegen() // angelegt, solange der Schalter an ist
|
||
config.auftraege.mode = 'off'
|
||
const routen = [['GET', `/?kunde=${KUNDE_ID}`], ['POST', '/', festpreisEingabe()], ['GET', `/${a.id}`], ['PATCH', `/${a.id}`, { titel: 'x' }], ['DELETE', `/${a.id}`], ['GET', `/${a.id}/pdf`]]
|
||
for (const [methode, pfad, body] of routen) {
|
||
const r = await api(methode, pfad, body)
|
||
assert.deepEqual([r.status, r.body], [404, { error: SCHALTER_AUS_MELDUNG }], `${methode} ${pfad}`)
|
||
}
|
||
assert.equal((await getAuftrag(a.id)).titel, 'Website Beispiel') // nichts wurde geändert oder gelöscht
|
||
config.auftraege.mode = 'pilot'
|
||
config.auftraege.pilotEmails = ['anna@example.invalid'] // E-Mail des Kunden
|
||
assert.equal((await api('GET', `/${a.id}`)).status, 200)
|
||
config.auftraege.pilotEmails = ['jemand@example.invalid']
|
||
assert.equal((await api('GET', `/${a.id}`)).status, 404)
|
||
})
|
||
|
||
test('GET Liste: Aufträge des Kunden, neueste zuerst, ohne interne Felder; 400 ohne Kunde, 404 bei unbekanntem Kunden', async () => {
|
||
const a = await entwurfAnlegen()
|
||
const b = await legeEntwurfAn(aufwandEingabe(), { von: VON })
|
||
const r = await api('GET', `/?kunde=${KUNDE_ID}`)
|
||
assert.equal(r.status, 200)
|
||
assert.deepEqual(r.body.auftraege.map((x) => x.id), [b.id, a.id])
|
||
for (const x of r.body.auftraege) {
|
||
for (const feld of ['tokenHash', 'pdfDatei', 'bestaetigtPdfSha256']) assert.equal(feld in x, false, feld)
|
||
assert.equal(typeof x.pdfVorhanden, 'boolean')
|
||
}
|
||
const ohneKunde = await api('GET', '/')
|
||
assert.deepEqual([ohneKunde.status, ohneKunde.body], [400, { error: 'Kunde fehlt.' }])
|
||
assert.equal((await api('GET', '/?kunde=c_gibtsnicht')).status, 404)
|
||
assert.equal((await api('GET', '/?kunde=../x')).status, 404)
|
||
})
|
||
|
||
test('POST: legt einen Entwurf an (200, AuftragTeam mit Texten); Fehler als { error } mit festen Meldungen', async () => {
|
||
const r = await api('POST', '/', festpreisEingabe())
|
||
assert.equal(r.status, 200)
|
||
const a = r.body.auftrag
|
||
assert.deepEqual([a.status, a.titel, a.summeCents, a.erstelltVon], ['entwurf', 'Website Beispiel', 80000, 'kenso@webklar.com'])
|
||
assert.deepEqual([typeof a.zahlungsplanText, a.pdfVorhanden, 'tokenHash' in a], ['string', false, false])
|
||
const ohnePositionen = await api('POST', '/', festpreisEingabe({ positionen: [] }))
|
||
assert.deepEqual([ohnePositionen.status, ohnePositionen.body], [400, { error: MELDUNG.positionen }])
|
||
const ohneKunde = await api('POST', '/', festpreisEingabe({ kundenId: undefined }))
|
||
assert.deepEqual([ohneKunde.status, ohneKunde.body], [400, { error: 'Kunde: Bitte einen Kunden angeben.' }])
|
||
assert.equal((await api('POST', '/', festpreisEingabe({ kundenId: 'c_gibtsnicht' }))).status, 404)
|
||
})
|
||
|
||
test('GET, PATCH, DELETE: Entwurf lesen, ändern, löschen; gesendete AB ist unveränderlich (409); ungültige Kennungen 404', async () => {
|
||
const a = await entwurfAnlegen()
|
||
const gelesen = await api('GET', `/${a.id}`)
|
||
assert.deepEqual([gelesen.status, gelesen.body.auftrag.id], [200, a.id])
|
||
const geaendert = await api('PATCH', `/${a.id}`, { titel: 'Neu' })
|
||
assert.deepEqual([geaendert.status, geaendert.body.auftrag.titel], [200, 'Neu'])
|
||
const falsch = await api('PATCH', `/${a.id}`, { zahlungszielTage: 99 })
|
||
assert.deepEqual([falsch.status, falsch.body], [400, { error: 'Zahlungsziel: Das Zahlungsziel muss eine ganze Zahl von 0 bis 60 Tagen sein.' }])
|
||
const geloescht = await api('DELETE', `/${a.id}`)
|
||
assert.deepEqual([geloescht.status, geloescht.body], [200, { ok: true }])
|
||
assert.deepEqual((await api('GET', `/${a.id}`)).body, { error: MELDUNG.auftragFehlt })
|
||
for (const kennung of ['a%20b', 'x'.repeat(40), '..%2F..']) assert.equal((await api('GET', `/${kennung}`)).status, 404, kennung)
|
||
const b = await entwurfAnlegen()
|
||
await sendeAuftrag(b.id, { von: VON })
|
||
const patch = await api('PATCH', `/${b.id}`, { titel: 'Nachträglich' })
|
||
assert.deepEqual([patch.status, patch.body], [409, { error: MELDUNG.nurEntwurfAendern }])
|
||
const loeschen = await api('DELETE', `/${b.id}`)
|
||
assert.deepEqual([loeschen.status, loeschen.body], [409, { error: MELDUNG.nurEntwurfLoeschen }])
|
||
})
|
||
|
||
test('GET pdf: Entwurf als Vorschau, gesendete AB aus der gespeicherten Datei, mit Kopfzeilen', async () => {
|
||
const entwurf = await entwurfAnlegen()
|
||
const vorschau = await api('GET', `/${entwurf.id}/pdf`)
|
||
assert.equal(vorschau.status, 200)
|
||
assert.equal(vorschau.body, '%PDF-1.4 Entwurf')
|
||
assert.match(vorschau.headers.get('content-type'), /^application\/pdf/)
|
||
assert.equal(vorschau.headers.get('content-disposition'), 'inline; filename="Auftragsbestaetigung-Entwurf.pdf"')
|
||
assert.equal(vorschau.headers.get('cache-control'), 'no-store')
|
||
const { auftrag } = await sendeAuftrag(entwurf.id, { von: VON }) // Attrappen aus setzeDepsFuerTests
|
||
const gespeichert = await api('GET', `/${entwurf.id}/pdf`)
|
||
assert.equal(gespeichert.body, `%PDF-1.4 ${auftrag.nummer}`)
|
||
assert.equal(gespeichert.headers.get('content-disposition'), `inline; filename="${auftrag.nummer}.pdf"`)
|
||
assert.equal((await api('GET', '/a_gibtsnicht/pdf')).status, 404)
|
||
})
|
||
|
||
test('Unerwartete Fehler: 500 mit fester Meldung, nichts von Appwrite gelangt an die Seite', async () => {
|
||
mock.method(console, 'error', () => {})
|
||
const a = await entwurfAnlegen()
|
||
aw.failNext({ collection: config.collections.auftraege, method: 'GET', status: 500, message: 'GEHEIM-Appwrite-Detail' })
|
||
const r = await api('GET', `/${a.id}`)
|
||
assert.deepEqual([r.status, r.body], [500, { error: 'Interner Fehler.' }])
|
||
})
|
||
|
||
test('POST senden: 200 ohne Token und Dateipfad; Kundendaten-, PDF- und Statusfehler mit festen Meldungen; Mail-Fehler als mailFehler: true', async () => {
|
||
mock.method(console, 'error', () => {})
|
||
const a = await entwurfAnlegen()
|
||
aw.seed(config.collections.customers, kundeDoc({ email: '' }))
|
||
let r = await api('POST', `/${a.id}/senden`)
|
||
assert.deepEqual([r.status, r.body], [400, { error: MELDUNG.keineMail }])
|
||
aw.seed(config.collections.customers, kundeDoc({ street: '' }))
|
||
r = await api('POST', `/${a.id}/senden`)
|
||
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.adresse }])
|
||
aw.seed(config.collections.customers, kundeDoc())
|
||
kontext = baueDeps({ pdfFehler: new Error('pdfkit') })
|
||
setzeDepsFuerTests(kontext.deps)
|
||
r = await api('POST', `/${a.id}/senden`)
|
||
assert.deepEqual([r.status, r.body], [500, { error: MELDUNG.pdf }])
|
||
kontext = baueDeps({ mailFehler: new Error('smtp') })
|
||
setzeDepsFuerTests(kontext.deps)
|
||
r = await api('POST', `/${a.id}/senden`)
|
||
assert.equal(r.status, 200)
|
||
assert.equal(r.body.mailFehler, true)
|
||
assert.deepEqual([r.body.auftrag.status, r.body.auftrag.mailStatus, r.body.auftrag.pdfVorhanden], ['versendet', 'fehler', true])
|
||
assert.match(r.body.auftrag.nummer, /^AB-\d{4}-0001$/)
|
||
for (const feld of ['tokenHash', 'pdfDatei', 'bestaetigtPdfSha256']) assert.equal(feld in r.body.auftrag, false, feld)
|
||
r = await api('POST', `/${a.id}/senden`)
|
||
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.nurEntwurfSenden }])
|
||
})
|
||
|
||
test('POST erinnern, neue-fassung, stornieren: Antworten, Folgezustände und 409 bei falschem Status', async () => {
|
||
const a = await entwurfAnlegen()
|
||
let r = await api('POST', `/${a.id}/erinnern`)
|
||
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.keineErinnerung }])
|
||
r = await api('POST', `/${a.id}/senden`)
|
||
assert.deepEqual([r.status, r.body.mailFehler, r.body.auftrag.status], [200, false, 'versendet'])
|
||
r = await api('POST', `/${a.id}/erinnern`)
|
||
assert.deepEqual([r.status, r.body.mailFehler, r.body.auftrag.status], [200, false, 'versendet'])
|
||
assert.equal(kontext.calls.mail.at(-1).erinnerung, true)
|
||
r = await api('POST', `/${a.id}/neue-fassung`)
|
||
assert.deepEqual([r.status, r.body.auftrag.status, r.body.auftrag.ersetztId], [200, 'entwurf', a.id])
|
||
r = await api('POST', `/${a.id}/neue-fassung`)
|
||
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.schonEntwurf }])
|
||
r = await api('POST', `/${a.id}/stornieren`)
|
||
assert.deepEqual([r.status, r.body.auftrag.status], [200, 'storniert'])
|
||
r = await api('POST', `/${a.id}/stornieren`)
|
||
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.keinStorno }])
|
||
})
|
||
|
||
test('POST projekt: ordnet einer AB ohne Projekt das Projekt zu (200, AuftragTeam); 400 ohne oder mit fremdem Projekt, 404 bei unbekannter AB, 409 bei vorhandenem Projekt oder falschem Status', async () => {
|
||
const a = await entwurfAnlegen({ projektId: '', titel: 'Beratung ohne Projekt' })
|
||
await api('POST', `/${a.id}/senden`)
|
||
let r = await api('POST', `/${a.id}/projekt`, {})
|
||
assert.deepEqual([r.status, r.body], [400, { error: MELDUNG.projektFehlt }])
|
||
r = await api('POST', `/${a.id}/projekt`)
|
||
assert.deepEqual([r.status, r.body], [400, { error: MELDUNG.projektFehlt }])
|
||
r = await api('POST', `/${a.id}/projekt`, { projektId: FREMD.projekt })
|
||
assert.deepEqual([r.status, r.body], [400, { error: MELDUNG.projektFremd }])
|
||
r = await api('POST', '/a_gibtsnicht/projekt', { projektId: PROJEKT_ID })
|
||
assert.deepEqual([r.status, r.body], [404, { error: MELDUNG.auftragFehlt }])
|
||
assert.equal((await getAuftrag(a.id)).projektId, '') // die abgelehnten Aufrufe haben nichts geändert
|
||
r = await api('POST', `/${a.id}/projekt`, { projektId: PROJEKT_ID })
|
||
assert.equal(r.status, 200)
|
||
assert.deepEqual([r.body.auftrag.projektId, r.body.auftrag.zeitquelle, r.body.auftrag.status], [PROJEKT_ID, [TICKET_ID], 'versendet'])
|
||
for (const feld of ['tokenHash', 'pdfDatei', 'bestaetigtPdfSha256']) assert.equal(feld in r.body.auftrag, false, feld)
|
||
r = await api('POST', `/${a.id}/projekt`, { projektId: PROJEKT_ID })
|
||
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.schonProjekt }])
|
||
const b = await entwurfAnlegen({ projektId: '', titel: 'Zweite Beratung ohne Projekt' })
|
||
await api('POST', `/${b.id}/senden`)
|
||
await api('POST', `/${b.id}/stornieren`)
|
||
r = await api('POST', `/${b.id}/projekt`, { projektId: PROJEKT_ID })
|
||
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.keinProjekt }])
|
||
})
|
||
|
||
test('Schalter aus: auch senden, erinnern, neue-fassung, stornieren und projekt antworten 404 mit der festen Meldung', async () => {
|
||
const a = await entwurfAnlegen({ projektId: '', titel: 'Beratung ohne Projekt' })
|
||
config.auftraege.mode = 'off'
|
||
for (const aktion of ['senden', 'erinnern', 'neue-fassung', 'stornieren', 'projekt']) {
|
||
const r = await api('POST', `/${a.id}/${aktion}`, aktion === 'projekt' ? { projektId: PROJEKT_ID } : undefined)
|
||
assert.deepEqual([r.status, r.body], [404, { error: SCHALTER_AUS_MELDUNG }], aktion)
|
||
}
|
||
const danach = await getAuftrag(a.id)
|
||
assert.deepEqual([danach.status, danach.projektId], ['entwurf', ''])
|
||
assert.equal(kontext.calls.pdf.length + kontext.calls.mail.length, 0)
|
||
})
|