116 lines
7.1 KiB
JavaScript
116 lines
7.1 KiB
JavaScript
// test/auftraegeProjektRechnungenStore.test.js
|
|
import { test, after, beforeEach } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { startAppwriteMock } from './support/appwriteMock.js'
|
|
import { schemaKeys } from './support/schema.js'
|
|
import { useTestEnv } from './support/testEnv.js'
|
|
import { AUFTRAEGE_DATETIMES } from '../server/services/auftraege/schema.js'
|
|
|
|
// Speicher der Projektrechnungen (Dokument-ID = Stripe-Rechnungs-ID). Nachbau von Appwrite mit Schema und Standard-Limit 25.
|
|
const aw = await startAppwriteMock({ schema: { projektRechnungen: schemaKeys('projektRechnungen') }, datetimes: AUFTRAEGE_DATETIMES, defaultLimit: 25 })
|
|
after(() => aw.close())
|
|
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
|
|
|
|
const { config } = await import('../server/config.js')
|
|
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
|
|
const { getProjektRechnung, listProjektRechnungen, createProjektRechnung, updateProjektRechnung, umhaengen } =
|
|
await import('../server/services/auftraege/projektRechnungenStore.js')
|
|
const { AuftragError } = await import('../server/services/auftraege/fehler.js')
|
|
|
|
const COL = config.collections.projektRechnungen
|
|
beforeEach(async () => {
|
|
for (const id of [...aw.docs(COL).keys()]) await deleteDocument(COL, id)
|
|
})
|
|
|
|
const DATEN = { kundenId: 'c1', projektId: 'p1', auftragId: 'ab1', art: 'aufwand', betragCents: 22500, arbeitsblattIds: ['w1', 'w2'], erstelltVon: 'kenso@webklar.com' }
|
|
const seed = (id, felder = {}) =>
|
|
aw.seed(COL, {
|
|
$id: id, invoiceId: id, kundenId: 'c1', projektId: 'p1', auftragId: '', art: 'festbetrag', betragCents: 100,
|
|
arbeitsblattIdsJson: '[]', rechnungsnummer: '', aktiv: true, erstelltAm: '2026-10-01T10:00:00.000Z', ...felder,
|
|
})
|
|
|
|
test('createProjektRechnung: Dokument-ID ist die Rechnungs-ID, Standardwerte, Rückgabe in der Form aus V3', async () => {
|
|
const rechnung = await createProjektRechnung('in_1SD6vPRxAbCdEf', DATEN)
|
|
assert.deepEqual(
|
|
{ ...rechnung, erstelltAm: undefined },
|
|
{
|
|
invoiceId: 'in_1SD6vPRxAbCdEf', kundenId: 'c1', projektId: 'p1', auftragId: 'ab1', art: 'aufwand', betragCents: 22500,
|
|
arbeitsblattIds: ['w1', 'w2'], rechnungsnummer: '', aktiv: true, erstelltVon: 'kenso@webklar.com', erstelltAm: undefined,
|
|
},
|
|
)
|
|
assert.match(rechnung.erstelltAm, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)
|
|
const doc = aw.docs(COL).get('in_1SD6vPRxAbCdEf')
|
|
assert.equal(doc.invoiceId, 'in_1SD6vPRxAbCdEf')
|
|
assert.equal(doc.arbeitsblattIdsJson, '["w1","w2"]')
|
|
assert.equal(doc.aktiv, true)
|
|
assert.deepEqual(await getProjektRechnung('in_1SD6vPRxAbCdEf'), rechnung)
|
|
const minimal = await createProjektRechnung('in_test_1', { kundenId: 'c1', art: 'festbetrag', betragCents: 5000 })
|
|
assert.deepEqual([minimal.projektId, minimal.auftragId, minimal.arbeitsblattIds, minimal.rechnungsnummer, minimal.aktiv], ['', '', [], '', true])
|
|
})
|
|
|
|
test('Rechnungs-IDs wie Stripe sie vergibt (auch in_test_1 des Nachbaus); alles andere ist ein Programmfehler und legt nichts an', async () => {
|
|
for (const id of ['in_test_1', 'in_1SD6vPRxAbCdEf', `in_${'a'.repeat(33)}`]) assert.equal((await createProjektRechnung(id, DATEN)).invoiceId, id)
|
|
await Promise.all([...aw.docs(COL).keys()].map((id) => deleteDocument(COL, id)))
|
|
for (const id of ['', 'x', 'in_', 'in_a/b', 'in_../x', 'in_a-b', 'in_a.b', 'IN_abc', `in_${'a'.repeat(34)}`, undefined, 7]) {
|
|
await assert.rejects(() => createProjektRechnung(id, DATEN), TypeError, String(id))
|
|
assert.equal(await getProjektRechnung(id), null, String(id))
|
|
}
|
|
const falsch = [{ kundenId: '' }, { art: 'frei' }, { art: 'xyz' }, { betragCents: 1.5 }, { betragCents: -1 }, { arbeitsblattIds: 'w1' }, { arbeitsblattIds: [''] }, { aktiv: 'ja' }]
|
|
for (const abweichung of falsch) {
|
|
await assert.rejects(() => createProjektRechnung('in_abc', { ...DATEN, ...abweichung }), TypeError, JSON.stringify(abweichung))
|
|
}
|
|
assert.equal(aw.docs(COL).size, 0)
|
|
})
|
|
|
|
test('createProjektRechnung: eine doppelte Rechnungs-ID ist 409', async () => {
|
|
await createProjektRechnung('in_abc', DATEN)
|
|
await assert.rejects(() => createProjektRechnung('in_abc', DATEN), (err) => err instanceof AuftragError && err.status === 409)
|
|
})
|
|
|
|
test('updateProjektRechnung ändert genannte Felder; eine unbekannte Rechnung ist 404', async () => {
|
|
await createProjektRechnung('in_abc', DATEN)
|
|
const gesendet = await updateProjektRechnung('in_abc', { rechnungsnummer: 'WEBKLAR-0009' })
|
|
assert.deepEqual([gesendet.rechnungsnummer, gesendet.aktiv, gesendet.betragCents], ['WEBKLAR-0009', true, 22500])
|
|
const storniert = await updateProjektRechnung('in_abc', { aktiv: false, arbeitsblattIds: ['w1'] })
|
|
assert.deepEqual([storniert.aktiv, storniert.arbeitsblattIds, storniert.rechnungsnummer], [false, ['w1'], 'WEBKLAR-0009'])
|
|
assert.equal(aw.docs(COL).get('in_abc').aktiv, false)
|
|
for (const id of ['in_gibtsnicht', '../x', '']) {
|
|
await assert.rejects(() => updateProjektRechnung(id, { aktiv: false }), (err) => err instanceof AuftragError && err.status === 404, id)
|
|
}
|
|
})
|
|
|
|
test('listProjektRechnungen: nur aktive, Filter, nach erstelltAm aufsteigend, mehr als 25 Treffer', async () => {
|
|
seed('in_a', { erstelltAm: '2026-10-03T10:00:00.000Z', auftragId: 'ab1' })
|
|
seed('in_b', { erstelltAm: '2026-10-01T10:00:00.000Z', auftragId: 'ab1' })
|
|
seed('in_c', { aktiv: false, erstelltAm: '2026-10-02T10:00:00.000Z', auftragId: 'ab1' })
|
|
seed('in_d', { kundenId: 'c2', projektId: 'p2', erstelltAm: '2026-10-04T10:00:00.000Z' })
|
|
const ids = async (filter) => (await listProjektRechnungen(filter)).map((r) => r.invoiceId)
|
|
assert.deepEqual(await ids({ kundenId: 'c1' }), ['in_b', 'in_a'])
|
|
assert.deepEqual(await ids({ kundenId: 'c1', nurAktiv: false }), ['in_b', 'in_c', 'in_a'])
|
|
assert.deepEqual(await ids({ auftragId: 'ab1' }), ['in_b', 'in_a'])
|
|
assert.deepEqual(await ids({ projektId: 'p2' }), ['in_d'])
|
|
assert.deepEqual(await ids(), ['in_b', 'in_a', 'in_d'])
|
|
for (let i = 0; i < 30; i += 1) seed(`in_m${i}`, { kundenId: 'c9' })
|
|
assert.equal((await listProjektRechnungen({ kundenId: 'c9' })).length, 30)
|
|
})
|
|
|
|
test('listProjektRechnungen: ein angegebener, aber leerer oder ungültiger Filter ist ein Fehler, nie „alle“', async () => {
|
|
seed('in_a')
|
|
for (const filter of [{ kundenId: '' }, { projektId: null }, { auftragId: 7 }, { nurAktiv: 'ja' }]) {
|
|
await assert.rejects(() => listProjektRechnungen(filter), TypeError, JSON.stringify(filter))
|
|
}
|
|
})
|
|
|
|
test('umhaengen hängt alle Dokumente einer AB (auch stornierte) an die neue AB, ist wiederholbar und lässt andere unberührt', async () => {
|
|
seed('in_a', { auftragId: 'ab1' })
|
|
seed('in_b', { auftragId: 'ab1', aktiv: false })
|
|
seed('in_c', { auftragId: 'ab2' })
|
|
assert.equal(await umhaengen('ab1', 'ab3'), 2)
|
|
assert.deepEqual([...aw.docs(COL).values()].map((d) => [d.invoiceId, d.auftragId]).sort(), [['in_a', 'ab3'], ['in_b', 'ab3'], ['in_c', 'ab2']])
|
|
assert.equal(await umhaengen('ab1', 'ab3'), 0)
|
|
assert.equal(await umhaengen('ab3', 'ab3'), 0)
|
|
for (const [alt, neu] of [['', 'ab3'], ['ab1', ''], [undefined, 'ab3'], ['ab1', null]]) {
|
|
await assert.rejects(() => umhaengen(alt, neu), TypeError, JSON.stringify([alt, neu]))
|
|
}
|
|
})
|