Finanzen: Admin-Routen fuer Bankstatus, Abruf, Neuverbindung und Buchungen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7b35ed67a5
commit
6af9834293
3 files changed
+561
No files matched your search
@@ -0,0 +1,435 @@
|
||||
import { test, after } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import crypto from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { startAppwriteMock } from './support/appwriteMock.js'
|
||||
import { startFakeStripe } from './support/fakeStripe.js'
|
||||
import { startFakeEnableBanking } from './support/fakeEnableBanking.js'
|
||||
import { useTestEnv } from './support/testEnv.js'
|
||||
import { schemaKeys } from './support/schema.js'
|
||||
|
||||
// Nachbauten starten und die Umgebung setzen, BEVOR config.js geladen wird (Muster wie financeBankAuth.test.js)
|
||||
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 })
|
||||
const pem = (key, type) => key.export({ type, format: 'pem' })
|
||||
const keyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bank-routes-test-'))
|
||||
const keyPath = path.join(keyDir, 'private.pem')
|
||||
fs.writeFileSync(keyPath, pem(privateKey, 'pkcs8'))
|
||||
|
||||
const SESSION = 'sess-geheim-4711'
|
||||
const ACCOUNT = 'konto-4711'
|
||||
const NEW_SESSION = 'sess-neu-geheim-4712'
|
||||
const NEW_ACCOUNT = 'konto-neu-1'
|
||||
const IBAN = 'DE00000000000000009313'
|
||||
const BANK_GONE = 'Die Bank hat die Freigabe nicht bestätigt. Bitte erneut verbinden.'
|
||||
|
||||
const fake = await startFakeEnableBanking({ publicKeyPem: pem(publicKey, 'spki'), appId: 'app-test' })
|
||||
const aw = await startAppwriteMock({
|
||||
schema: { bankConnections: schemaKeys('bankConnections'), bankTransactions: schemaKeys('bankTransactions') },
|
||||
})
|
||||
const st = await startFakeStripe()
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||
Object.assign(process.env, { ENABLE_BANKING_APP_ID: 'app-test', ENABLE_BANKING_KEY_PATH: keyPath, ENABLE_BANKING_API_BASE: fake.url })
|
||||
|
||||
const { config } = await import('../server/config.js')
|
||||
const { startTestApp, get, post, rawGet } = await import('./support/testApp.js')
|
||||
const { default: financeRoutes } = await import('../server/routes/admin/finanzen.js')
|
||||
const store = await import('../server/services/finance/bankStore.js')
|
||||
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
|
||||
|
||||
const app = await startTestApp({ '/api/admin/finanzen': financeRoutes })
|
||||
after(async () => {
|
||||
await Promise.all([app.close(), fake.close(), aw.close(), st.close()])
|
||||
fs.rmSync(keyDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
const NOW = Date.now()
|
||||
const MINUTE = 60_000
|
||||
const minutesAgo = (minutes) => new Date(NOW - minutes * MINUTE).toISOString()
|
||||
|
||||
const admin = { headers: { Authorization: 'Bearer jwt-admin' } }
|
||||
const base = '/api/admin/finanzen'
|
||||
const connection = () => aw.docs(config.collections.bankConnections).get('finom')
|
||||
const txDocs = () => aw.docs(config.collections.bankTransactions)
|
||||
const callsTo = (suffix) => fake.calls.filter((c) => c.path.endsWith(suffix))
|
||||
|
||||
const FULL_KEYS = [
|
||||
'status',
|
||||
'ibanMasked',
|
||||
'validUntil',
|
||||
'lastSyncAt',
|
||||
'lastSyncOk',
|
||||
'lastError',
|
||||
'balanceBookedCents',
|
||||
'balanceAvailableCents',
|
||||
'balanceDate',
|
||||
'latestBookingDate',
|
||||
]
|
||||
|
||||
/**
|
||||
* Ausgangszustand: aktiver Zugang mit Konto ACCOUNT, zwei Buchungen und Salden beim Nachbau; der Nachbau kennt
|
||||
* ausserdem den Code 'neu' -> Sitzung NEW_SESSION (Konto NEW_ACCOUNT mit derselben IBAN).
|
||||
*/
|
||||
async function reset({ connected = true, stored = {} } = {}) {
|
||||
for (const id of txDocs().keys()) await store.deleteTransaction(id)
|
||||
await deleteDocument(config.collections.bankConnections, 'finom').catch(() => {})
|
||||
fake.calls.length = 0
|
||||
fake.accounts.clear()
|
||||
fake.sessions.clear()
|
||||
fake.codes.clear()
|
||||
fake.transactions.clear()
|
||||
fake.balances.clear()
|
||||
for (const uid of [ACCOUNT, NEW_ACCOUNT]) {
|
||||
fake.accounts.set(uid, { uid, identification_hash: 'h-4711', account_id: { iban: IBAN }, name: 'Webklar', currency: 'EUR' })
|
||||
}
|
||||
fake.sessions.set('s-neu', {
|
||||
session_id: NEW_SESSION,
|
||||
status: 'AUTHORIZED',
|
||||
accounts: [NEW_ACCOUNT],
|
||||
access: { valid_until: '2027-03-29T00:00:00.000Z', balances: true, transactions: true },
|
||||
aspsp: { name: 'Finom', country: 'DE' },
|
||||
psu_type: 'business',
|
||||
})
|
||||
fake.codes.set('neu', 's-neu')
|
||||
fake.transactions.set(ACCOUNT, [
|
||||
{
|
||||
entry_reference: 'r1',
|
||||
status: 'BOOK',
|
||||
booking_date: '2026-09-29',
|
||||
transaction_amount: { amount: '4.49', currency: 'EUR' },
|
||||
credit_debit_indicator: 'DBIT',
|
||||
creditor: { name: 'PORKBUN.COM' },
|
||||
},
|
||||
])
|
||||
fake.balances.set(ACCOUNT, [{ balance_type: 'ITBD', balance_amount: { amount: '108.93', currency: 'EUR' }, reference_date: '2026-10-01' }])
|
||||
if (!connected) return
|
||||
await store.saveConnection({
|
||||
status: 'aktiv',
|
||||
sessionId: SESSION,
|
||||
accountUid: ACCOUNT,
|
||||
accountIdHash: 'h-4711',
|
||||
iban: IBAN,
|
||||
ibanMasked: 'DE00…9313',
|
||||
validUntil: '2027-03-29T00:00:00.000Z',
|
||||
...stored,
|
||||
})
|
||||
}
|
||||
|
||||
/** Freigabe starten und den gespeicherten state lesen (wie die Seite nach "Verbinden"). */
|
||||
async function begin() {
|
||||
const { body } = await post(`${base}/bank/verbinden`, {}, admin)
|
||||
assert.match(body.url, /fake-bank\/auth\?state=/)
|
||||
return connection().authState
|
||||
}
|
||||
|
||||
// ---------- Zugriff ----------
|
||||
|
||||
test('ohne JWT 401, mit Mitarbeiter-JWT ohne Admin-Label 403, bei jeder Route', async () => {
|
||||
await reset()
|
||||
const routes = [
|
||||
['GET', '/bank'],
|
||||
['POST', '/bank/aktualisieren'],
|
||||
['POST', '/bank/verbinden'],
|
||||
['POST', '/bank/rueckkehr'],
|
||||
['GET', '/buchungen'],
|
||||
]
|
||||
for (const [method, route] of routes) {
|
||||
const call = (headers) => (method === 'GET' ? rawGet(base + route, { headers }) : post(base + route, {}, { headers }))
|
||||
assert.equal((await call({})).status, 401, `${method} ${route} ohne JWT`)
|
||||
assert.equal((await call({ Authorization: 'Bearer jwt-unbekannt' })).status, 401, `${method} ${route} mit falschem JWT`)
|
||||
assert.equal((await call({ Authorization: 'Bearer jwt-staff' })).status, 403, `${method} ${route} ohne Admin-Label`)
|
||||
}
|
||||
assert.equal(fake.calls.length, 0)
|
||||
})
|
||||
|
||||
// ---------- GET /bank ----------
|
||||
|
||||
test('GET /bank: ohne Verbindung { verbindung: null }', async () => {
|
||||
await reset({ connected: false })
|
||||
assert.deepEqual(await get(`${base}/bank`, admin), { verbindung: null })
|
||||
})
|
||||
|
||||
test('GET /bank: genau die Anzeigefelder, nie Sitzung, IBAN, state oder Konto-Kennungen', async () => {
|
||||
await reset({
|
||||
stored: {
|
||||
lastSyncAt: '2026-10-01T09:00:00.000Z',
|
||||
lastSyncOk: true,
|
||||
lastError: '',
|
||||
lastManualSyncAt: '2026-10-01T08:00:00.000Z',
|
||||
balanceBookedCents: 10893,
|
||||
balanceAvailableCents: 10893,
|
||||
balanceDate: '2026-10-01',
|
||||
latestBookingDate: '2026-09-29',
|
||||
authState: 'state-geheim-abc',
|
||||
authStartedAt: '2026-10-01T08:30:00.000Z',
|
||||
warnedThresholds: '30',
|
||||
},
|
||||
})
|
||||
const res = await rawGet(`${base}/bank`, admin)
|
||||
assert.equal(res.status, 200)
|
||||
assert.deepEqual(Object.keys(res.body), ['verbindung'])
|
||||
assert.deepEqual(res.body.verbindung, {
|
||||
status: 'aktiv',
|
||||
ibanMasked: 'DE00…9313',
|
||||
validUntil: '2027-03-29T00:00:00.000Z',
|
||||
lastSyncAt: '2026-10-01T09:00:00.000Z',
|
||||
lastSyncOk: true,
|
||||
lastError: '',
|
||||
balanceBookedCents: 10893,
|
||||
balanceAvailableCents: 10893,
|
||||
balanceDate: '2026-10-01',
|
||||
latestBookingDate: '2026-09-29',
|
||||
})
|
||||
const text = JSON.stringify(res.body)
|
||||
for (const secret of [SESSION, ACCOUNT, IBAN, 'h-4711', 'state-geheim-abc', 'sessionId', 'iban"', 'authState', 'accountUid', 'accountIdHash', 'authStartedAt']) {
|
||||
assert.ok(!text.includes(secret), `Antwort enthält ${secret}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('GET /bank: noch nie abgerufen -> alle Anzeigefelder vorhanden, fehlende null', async () => {
|
||||
await reset()
|
||||
const { verbindung } = await get(`${base}/bank`, admin)
|
||||
assert.deepEqual(Object.keys(verbindung).sort(), [...FULL_KEYS].sort())
|
||||
assert.equal(verbindung.status, 'aktiv')
|
||||
assert.equal(verbindung.lastSyncAt, null)
|
||||
assert.equal(verbindung.balanceBookedCents, null)
|
||||
})
|
||||
|
||||
// ---------- POST /bank/aktualisieren ----------
|
||||
|
||||
test('aktualisieren: zweimal hintereinander -> 200, dann 429 ohne zweiten Bankabruf', async () => {
|
||||
await reset()
|
||||
const first = await post(`${base}/bank/aktualisieren`, {}, admin)
|
||||
assert.equal(first.status, 200)
|
||||
assert.deepEqual(first.body, { ok: true, neu: 1, aktualisiert: 0, geloescht: 0 })
|
||||
assert.equal(txDocs().size, 1)
|
||||
assert.ok(Date.now() - Date.parse(connection().lastManualSyncAt) < 10_000)
|
||||
const bankCalls = fake.calls.length
|
||||
|
||||
const second = await post(`${base}/bank/aktualisieren`, {}, admin)
|
||||
assert.equal(second.status, 429)
|
||||
assert.equal(second.body.error, 'Bitte in 10 Minuten erneut versuchen.')
|
||||
assert.equal(fake.calls.length, bankCalls)
|
||||
})
|
||||
|
||||
test('aktualisieren: die Bank bekommt IP und Browserkennung des Nutzers (Abruf durch einen Menschen)', async () => {
|
||||
await reset()
|
||||
const res = await post(`${base}/bank/aktualisieren`, {}, { headers: { ...admin.headers, 'User-Agent': 'WOMS-Test/1.0', 'X-Forwarded-For': '203.0.113.7' } })
|
||||
assert.equal(res.status, 200)
|
||||
const [balances] = callsTo('/balances')
|
||||
assert.equal(balances.headers['psu-user-agent'], 'WOMS-Test/1.0')
|
||||
// trust proxy 1: die Adresse aus X-Forwarded-For
|
||||
assert.equal(balances.headers['psu-ip-address'], '203.0.113.7')
|
||||
})
|
||||
|
||||
test('aktualisieren: Wartezeit zählt Minuten nach oben; nach 10 Minuten geht es wieder', async () => {
|
||||
await reset({ stored: { lastManualSyncAt: minutesAgo(3) } })
|
||||
const blocked = await post(`${base}/bank/aktualisieren`, {}, admin)
|
||||
assert.equal(blocked.status, 429)
|
||||
assert.equal(blocked.body.error, 'Bitte in 7 Minuten erneut versuchen.')
|
||||
assert.equal(connection().lastManualSyncAt, minutesAgo(3), 'ein abgelehnter Aufruf verlängert die Sperre nicht')
|
||||
|
||||
await store.saveConnection({ lastManualSyncAt: new Date(NOW - 9.5 * MINUTE).toISOString() })
|
||||
const almost = await post(`${base}/bank/aktualisieren`, {}, admin)
|
||||
assert.equal(almost.status, 429)
|
||||
assert.equal(almost.body.error, 'Bitte in 1 Minute erneut versuchen.')
|
||||
|
||||
await store.saveConnection({ lastManualSyncAt: minutesAgo(10.5) })
|
||||
assert.equal((await post(`${base}/bank/aktualisieren`, {}, admin)).status, 200)
|
||||
})
|
||||
|
||||
test('aktualisieren: zwei gleichzeitige Klicks -> ein Abruf, der andere 429', async () => {
|
||||
await reset()
|
||||
const [a, b] = await Promise.all([post(`${base}/bank/aktualisieren`, {}, admin), post(`${base}/bank/aktualisieren`, {}, admin)])
|
||||
assert.deepEqual([a.status, b.status].sort(), [200, 429])
|
||||
assert.equal(callsTo('/balances').length, 1)
|
||||
})
|
||||
|
||||
test('aktualisieren: Fehler der Bank kommen als Ergebnis (200, ok false), ohne Sitzungs-ID', async () => {
|
||||
await reset()
|
||||
fake.failNext({ path: /\/balances$/, status: 500, body: { code: 500, error: 'FAKE_ERROR' } })
|
||||
const res = await post(`${base}/bank/aktualisieren`, {}, admin)
|
||||
assert.equal(res.status, 200)
|
||||
assert.equal(res.body.ok, false)
|
||||
assert.equal(typeof res.body.error, 'string')
|
||||
assert.ok(!JSON.stringify(res.body).includes(SESSION))
|
||||
})
|
||||
|
||||
test('aktualisieren: ohne Bankzugang -> ok false, es wird keine Verbindung angelegt', async () => {
|
||||
await reset({ connected: false })
|
||||
const res = await post(`${base}/bank/aktualisieren`, {}, admin)
|
||||
assert.equal(res.status, 200)
|
||||
assert.equal(res.body.ok, false)
|
||||
assert.equal(res.body.error, 'Kein aktiver Bankzugang')
|
||||
assert.equal(connection(), undefined)
|
||||
})
|
||||
|
||||
// ---------- POST /bank/verbinden ----------
|
||||
|
||||
test('verbinden: liefert { url } der Bank und merkt sich state', async () => {
|
||||
await reset()
|
||||
const res = await post(`${base}/bank/verbinden`, {}, admin)
|
||||
assert.equal(res.status, 200)
|
||||
assert.deepEqual(Object.keys(res.body), ['url'])
|
||||
assert.match(res.body.url, /^http:\/\/127\.0\.0\.1:\d+\/fake-bank\/auth\?state=[a-f0-9]{24}$/)
|
||||
assert.ok(res.body.url.endsWith(connection().authState))
|
||||
// der laufende Zugang bleibt unberührt
|
||||
assert.equal(connection().sessionId, SESSION)
|
||||
assert.equal(connection().status, 'aktiv')
|
||||
})
|
||||
|
||||
test('verbinden: scheitert die Bank -> 502 mit fester Meldung, ohne Einzelheiten', async () => {
|
||||
await reset()
|
||||
fake.failNext({ path: '/auth', status: 500 })
|
||||
const res = await post(`${base}/bank/verbinden`, {}, admin)
|
||||
assert.equal(res.status, 502)
|
||||
assert.deepEqual(res.body, { error: BANK_GONE })
|
||||
assert.equal(connection().authState, undefined)
|
||||
})
|
||||
|
||||
test('verbinden: antwortet die Bank mit 401, kommt trotzdem 502 (nie 401 durchreichen)', async () => {
|
||||
await reset()
|
||||
fake.failNext({ path: '/auth', status: 401, body: { code: 401, error: 'UNAUTHORIZED' } })
|
||||
const res = await post(`${base}/bank/verbinden`, {}, admin)
|
||||
assert.equal(res.status, 502)
|
||||
assert.deepEqual(res.body, { error: BANK_GONE })
|
||||
})
|
||||
|
||||
// ---------- POST /bank/rueckkehr ----------
|
||||
|
||||
test('rueckkehr: falscher state -> 400 mit Meldung, Zugang bleibt', async () => {
|
||||
await reset()
|
||||
await begin()
|
||||
const res = await post(`${base}/bank/rueckkehr`, { code: 'neu', state: 'falsch' }, admin)
|
||||
assert.equal(res.status, 400)
|
||||
assert.deepEqual(res.body, { error: 'Diese Rückkehr passt zu keiner gestarteten Freigabe.' })
|
||||
assert.equal(connection().sessionId, SESSION)
|
||||
assert.equal(callsTo('/sessions').length, 0)
|
||||
})
|
||||
|
||||
test('rueckkehr: ohne Body / ohne state -> 400', async () => {
|
||||
await reset()
|
||||
await begin()
|
||||
assert.equal((await post(`${base}/bank/rueckkehr`, {}, admin)).status, 400)
|
||||
assert.equal((await post(`${base}/bank/rueckkehr`, { code: 'neu' }, admin)).status, 400)
|
||||
})
|
||||
|
||||
test('rueckkehr: Finom meldet einen Fehler -> 400 mit Meldung', async () => {
|
||||
await reset()
|
||||
const state = await begin()
|
||||
const res = await post(`${base}/bank/rueckkehr`, { state, error: 'access_denied' }, admin)
|
||||
assert.equal(res.status, 400)
|
||||
assert.equal(res.body.error, 'Finom hat die Freigabe nicht erteilt (access_denied).')
|
||||
})
|
||||
|
||||
test('rueckkehr: abgelaufene Freigabe -> 400', async () => {
|
||||
await reset()
|
||||
const state = await begin()
|
||||
await store.saveConnection({ authStartedAt: minutesAgo(45) })
|
||||
const res = await post(`${base}/bank/rueckkehr`, { code: 'neu', state }, admin)
|
||||
assert.equal(res.status, 400)
|
||||
assert.equal(res.body.error, 'Die Freigabe ist abgelaufen. Bitte neu starten.')
|
||||
})
|
||||
|
||||
test('rueckkehr: richtiger state -> { ok: true, validUntil }, Sitzung gespeichert und nie in der Antwort', async () => {
|
||||
await reset()
|
||||
const state = await begin()
|
||||
const res = await post(`${base}/bank/rueckkehr`, { code: 'neu', state }, admin)
|
||||
assert.equal(res.status, 200)
|
||||
assert.deepEqual(res.body, { ok: true, validUntil: '2027-03-29T00:00:00.000Z' })
|
||||
const text = JSON.stringify(res.body)
|
||||
assert.ok(!text.includes(NEW_SESSION) && !text.includes(SESSION))
|
||||
|
||||
assert.equal(connection().sessionId, NEW_SESSION)
|
||||
assert.equal(connection().accountUid, NEW_ACCOUNT)
|
||||
assert.equal(connection().status, 'aktiv')
|
||||
assert.equal(connection().authState, '')
|
||||
// die Rückkehr hat einmal abgerufen
|
||||
assert.equal(callsTo('/balances').length, 1)
|
||||
assert.equal(txDocs().size, 0, 'Nachbau kennt für das neue Konto keine Buchungen')
|
||||
})
|
||||
|
||||
test('rueckkehr: scheitert der Codetausch bei der Bank (401) -> 502 mit fester Meldung, nie 401, keine Sitzungs-ID', async () => {
|
||||
await reset()
|
||||
const state = await begin()
|
||||
fake.failNext({ path: '/sessions', status: 401, body: { code: 401, error: 'UNAUTHORIZED' } })
|
||||
const res = await post(`${base}/bank/rueckkehr`, { code: 'neu', state }, admin)
|
||||
assert.equal(res.status, 502)
|
||||
assert.deepEqual(res.body, { error: BANK_GONE })
|
||||
assert.ok(!JSON.stringify(res.body).includes(SESSION))
|
||||
// der bisherige Zugang bleibt, der state gilt weiter (neuer Versuch möglich)
|
||||
assert.equal(connection().sessionId, SESSION)
|
||||
assert.equal(connection().authState, state)
|
||||
})
|
||||
|
||||
test('rueckkehr: lehnt die Bank den Code mit 400 ab, ist das für die Seite ebenfalls 502 (nur die eigene Prüfung gibt 400)', async () => {
|
||||
await reset()
|
||||
const state = await begin()
|
||||
fake.failNext({ path: '/sessions', status: 400, body: { code: 400, error: 'INVALID_CODE' } })
|
||||
const res = await post(`${base}/bank/rueckkehr`, { code: 'neu', state }, admin)
|
||||
assert.equal(res.status, 502)
|
||||
assert.deepEqual(res.body, { error: BANK_GONE })
|
||||
})
|
||||
|
||||
test('rueckkehr: jeder andere Fehler (Bank 500, Appwrite gestört) -> 502 mit fester Meldung', async () => {
|
||||
await reset()
|
||||
let state = await begin()
|
||||
fake.failNext({ path: '/sessions', status: 500 })
|
||||
const bank = await post(`${base}/bank/rueckkehr`, { code: 'neu', state }, admin)
|
||||
assert.equal(bank.status, 502)
|
||||
assert.deepEqual(bank.body, { error: BANK_GONE })
|
||||
|
||||
state = connection().authState
|
||||
aw.failNext({ collection: config.collections.bankConnections, method: 'GET', status: 500, message: 'Appwrite-Geheimnis' })
|
||||
const appwrite = await post(`${base}/bank/rueckkehr`, { code: 'neu', state }, admin)
|
||||
assert.equal(appwrite.status, 502)
|
||||
assert.deepEqual(appwrite.body, { error: BANK_GONE })
|
||||
assert.ok(!JSON.stringify(appwrite.body).includes('Appwrite-Geheimnis'))
|
||||
})
|
||||
|
||||
// ---------- GET /buchungen ----------
|
||||
|
||||
test('buchungen: neueste zuerst, nur Felder der Buchung und $id', async () => {
|
||||
await reset()
|
||||
const row = (id, bookingDate, amountCents) => ({
|
||||
$id: id,
|
||||
accountUid: ACCOUNT,
|
||||
status: 'BOOK',
|
||||
bookingDate,
|
||||
valueDate: bookingDate,
|
||||
amountCents,
|
||||
currency: 'EUR',
|
||||
counterpartyName: 'PORKBUN.COM',
|
||||
counterpartyIban: '',
|
||||
remittance: '',
|
||||
mcc: '4816',
|
||||
entryReference: id,
|
||||
category: '',
|
||||
matchedInvoiceId: '',
|
||||
matchedInvoiceNumber: '',
|
||||
matchedPayoutId: '',
|
||||
fixedCostId: '',
|
||||
note: '',
|
||||
syncedAt: '2026-10-01T09:00:00.000Z',
|
||||
})
|
||||
aw.seed(config.collections.bankTransactions, row('b-mitte', '2026-09-15', -449))
|
||||
aw.seed(config.collections.bankTransactions, row('b-alt', '2026-08-05', 16879))
|
||||
aw.seed(config.collections.bankTransactions, row('b-neu', '2026-09-29', -50))
|
||||
|
||||
const res = await rawGet(`${base}/buchungen`, admin)
|
||||
assert.equal(res.status, 200)
|
||||
assert.deepEqual(Object.keys(res.body), ['buchungen'])
|
||||
assert.deepEqual(res.body.buchungen.map((b) => b.$id), ['b-neu', 'b-mitte', 'b-alt'])
|
||||
assert.deepEqual(res.body.buchungen.map((b) => b.amountCents), [-50, -449, 16879])
|
||||
for (const buchung of res.body.buchungen) {
|
||||
assert.deepEqual(Object.keys(buchung).filter((key) => key.startsWith('$')), ['$id'])
|
||||
}
|
||||
assert.equal(res.body.buchungen[0].counterpartyName, 'PORKBUN.COM')
|
||||
assert.equal(res.body.buchungen[0].category, '')
|
||||
})
|
||||
|
||||
test('buchungen: leer -> { buchungen: [] }', async () => {
|
||||
await reset()
|
||||
assert.deepEqual(await get(`${base}/buchungen`, admin), { buchungen: [] })
|
||||
})
|
||||
Reference in new issue
Block a user