23 KiB
Finanzen Plan 1: Bankanbindung (Enable Banking → Finom) Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Der Kundenportal-Server ruft Kontostand und Umsätze des Finom-Kontos über Enable Banking ab, speichert sie dauerhaft in Appwrite, erneuert den Bankzugang auf Knopfdruck und warnt vor dessen Ablauf.
Architecture: Ein dünner Enable-Banking-Client (node:crypto signiert das RS256-JWT, fetch spricht die API) unter server/services/finance/, ein Sync-Dienst, der Buchungen idempotent in bankTransactions schreibt, und ein Admin-Router /api/admin/finanzen (requireAppwriteStaff). Jobs laufen wie die übrigen Portal-Jobs per setInterval in server/index.js.
Tech Stack: Node 20 (ESM), Express 4, Appwrite REST (services/appwriteAdmin.js), node:test, Nachbauten in test/support/.
Spec: docs/superpowers/specs/2026-10-01-finanzen-design.md
Global Constraints
- Arbeitsverzeichnis: Worktree
/home/webklar/apps/kundenbereich.webklar.com/dev-finanzen, Branchfeature/finanzen(vonmainc37dd49). Nie pushen ohne ausdrückliches OK des Nutzers (der Deploy-Webhook kann jede gepushte Branch live stellen). - Tests:
npm test(=node --test test/*.test.js) muss vor jedem Commit grün sein; einzelne Datei:node --test test/<datei>.test.js. - Keine neuen npm-Abhängigkeiten.
- Enable Banking: API
https://api.enablebanking.com, JWT-Header{ typ: 'JWT', alg: 'RS256', kid: <App-ID> }, Payload{ iss: 'enablebanking.com', aud: 'api.enablebanking.com', iat, exp: iat + 3600 }. - ASPSP
{ name: 'Finom', country: 'DE' },psu_type: 'business', Redirecthttps://ticket.webklar.com/finanzen/bank-callback, Zugangvalid_until= jetzt + 179 Tage. ENABLE_BANKING_API_BASE(nur Tests) wird nur für127.0.0.1/localhostakzeptiert, sonst ignoriert (wieSTRIPE_API_BASE).- Beträge intern immer als ganze Cent (
amountCents, Integer); Eingang positiv, Ausgang negativ. - Daten im Format
YYYY-MM-DD; Berechnungen „heute“ in Europe/Berlin. - Der private Schlüssel und Sitzungs-IDs erscheinen nie in Logs, Fehlermeldungen oder API-Antworten.
- Kommentare und Fehlermeldungen auf Deutsch, Stil wie die umgebenden Dateien (ASCII-Umlaute in Code-Kommentaren wie im Bestand, echte Umlaute in Texten für Menschen).
Review Focus
- Doppelte Abrufe: Dieselbe Buchung kommt bei jedem Abruf erneut → darf nie doppelt gespeichert werden und darf Nutzerfelder (
category,matched*,fixedCostId,note) nie überschreiben. Test in Task 3. - Vorgemerkte Buchungen: Eine
PDNG-Buchung verschwindet beim nächsten Abruf (wird gebucht oder storniert) → die alte Vormerkung wird gelöscht, die gebuchte erscheint einmal. Test in Task 3. - Abgelaufener Zugang: Enable Banking antwortet 401/403 → Status
abgelaufen, alte Daten bleiben, kein Absturz des Jobs, keine Wiederholungsschleife. Test in Task 3. - Fremde Rückkehr:
statepasst nicht oder ist älter als 30 Minuten, oder Finom meldeterror→ 400 mit verständlicher Meldung, bestehende Sitzung bleibt unverändert. Test in Task 4. - Paging: Enable Banking liefert
continuation_key→ alle Seiten werden geholt, aber höchstens 20 Seiten (Schutz vor Endlosschleife). Test in Task 1.
Task 1: Konfiguration und Enable-Banking-Client
Files:
- Modify:
server/config.js(neuer BlockenableBankingnachstripe) - Create:
server/services/finance/enableBanking.js - Create:
test/support/fakeEnableBanking.js - Test:
test/financeEnableBanking.test.js
Interfaces:
-
Produces:
config.enableBanking = { appId, keyPath, apiBase, redirectUrl, aspsp: { name: 'Finom', country: 'DE' }, enabled }–appIdausENABLE_BANKING_APP_ID,keyPathausENABLE_BANKING_KEY_PATH,apiBase=ENABLE_BANKING_API_BASEnur bei 127.0.0.1/localhost sonst'https://api.enablebanking.com',redirectUrl=${config.womsBaseUrl}/finanzen/bank-callback,enabled= beide gesetzt.createJwt({ now = Date.now() } = {}) → stringstartAuth({ state, validUntil }) → Promise<{ url, authorizationId }>createSession(code) → Promise<{ sessionId, validUntil, accounts: Array<{ uid, identificationHash, iban, name, currency }> }>getSession(sessionId) → Promise<{ status, validUntil, accountUids: string[] }>getBalances(accountUid, { psu } = {}) → Promise<Array<{ type, amountCents, currency, date }>>getTransactions(accountUid, { dateFrom, psu } = {}) → Promise<object[]>(Rohobjekte von Enable Banking, alle Seiten)class EnableBankingError extends Error { status, code }–status= HTTP-Status,code=body.code ?? body.error ?? nullpsu={ ip, userAgent }→ KopfzeilenPsu-Ip-Address,Psu-User-AgentstartFakeEnableBanking({ publicKeyPem })intest/support/fakeEnableBanking.js→{ url, calls, sessions: Map, accounts: Map, transactions: Map<uid, object[]>, pageSize, failNext({ path, status, body }), close() }; prüft jedes JWT mitpublicKeyPem(crypto.verify('sha256', …)) und Claims, sonst 401.
-
Step 1: Write the failing tests in
test/financeEnableBanking.test.js. Schlüsselpaar im Test erzeugen (crypto.generateKeyPairSync('rsa', { modulusLength: 2048 })), PEM in eine Datei imos.tmpdir()schreiben,ENABLE_BANKING_APP_ID='app-test',ENABLE_BANKING_KEY_PATH=<datei>,ENABLE_BANKING_API_BASE=<fake.url>vor dem Import vonconfig.jssetzen.
test('createJwt: RS256, kid = App-ID, Claims', () => {
const [h, p, s] = createJwt({ now: 1_790_000_000_000 }).split('.')
const header = JSON.parse(Buffer.from(h, 'base64url'))
const payload = JSON.parse(Buffer.from(p, 'base64url'))
assert.deepEqual(header, { typ: 'JWT', alg: 'RS256', kid: 'app-test' })
assert.deepEqual(payload, { iss: 'enablebanking.com', aud: 'api.enablebanking.com', iat: 1_790_000_000, exp: 1_790_003_600 })
assert.ok(crypto.verify('sha256', Buffer.from(`${h}.${p}`), publicKey, Buffer.from(s, 'base64url')))
})
test('startAuth schickt Finom, business, Redirect und valid_until', async () => {
const r = await startAuth({ state: 'abc', validUntil: '2027-03-29T00:00:00.000Z' })
assert.match(r.url, /^https?:\/\//)
const call = fake.calls.at(-1)
assert.equal(call.path, '/auth')
assert.deepEqual(call.body, { access: { valid_until: '2027-03-29T00:00:00.000Z' }, aspsp: { name: 'Finom', country: 'DE' }, state: 'abc', redirect_url: 'https://ticket.webklar.com/finanzen/bank-callback', psu_type: 'business' })
})
test('createSession liefert Sitzung und Konten', async () => { /* fake: code 'ok' → session_id 's1', accounts [{uid:'u1', identification_hash:'h1', account_id:{iban:'DE62…9313'}, name:'Webklar', currency:'EUR'}], access.valid_until */
const s = await createSession('ok')
assert.equal(s.sessionId, 's1'); assert.equal(s.accounts[0].uid, 'u1'); assert.equal(s.accounts[0].identificationHash, 'h1'); assert.equal(s.accounts[0].iban, 'DE62…9313')
})
test('getTransactions holt alle Seiten (continuation_key), höchstens 20', async () => { /* 5 Buchungen, pageSize 2 → 3 Aufrufe, 5 Ergebnisse; endlose continuation_key → nach 20 Aufrufen Schluss */ })
test('getBalances rechnet in Cent', async () => { /* "108.93" → 10893, "0.5" → 50 */ })
test('PSU-Kopfzeilen nur wenn psu übergeben', async () => { /* calls[].headers['psu-ip-address'] */ })
test('Fehler → EnableBankingError mit status und code, ohne Sitzungs-ID in der Meldung', async () => { /* failNext({ path: /\/accounts\//, status: 401, body: { code: 'EXPIRED_SESSION' } }) */ })
test('ENABLE_BANKING_API_BASE außerhalb von 127.0.0.1 wird ignoriert', () => { /* reine Funktion resolveApiBase(value) exportieren und prüfen */ })
-
Step 2: Run
node --test test/financeEnableBanking.test.js– Expected: FAIL (Modul fehlt). -
Step 3: Implement
test/support/fakeEnableBanking.js(RoutenPOST /auth,POST /sessions,GET /sessions/:id,GET /accounts/:uid/balances,GET /accounts/:uid/transactionsmitcontinuation_key= Index als String), dannserver/config.jsundserver/services/finance/enableBanking.js. Den Schlüssel bei jedemcreateJwtlesen (readFileSync(config.enableBanking.keyPath)), damit ein Schlüsseltausch ohne Neustart wirkt.toCents(amountString)als exportierte Hilfsfunktion: Ganzzahl- und Nachkommateil getrennt parsen, keine Gleitkommarechnung. -
Step 4: Run
node --test test/financeEnableBanking.test.js– Expected: PASS. Dannnpm test– Expected: alles grün. -
Step 5: Commit
git add server/config.js server/services/finance/enableBanking.js test/support/fakeEnableBanking.js test/financeEnableBanking.test.js
git commit -m "Finanzen: Enable-Banking-Client (JWT, Freigabe, Sitzung, Salden, Umsaetze)"
Task 2: Collections und Übernahme der bestehenden Sitzung
Files:
- Create:
scripts/setup-finanzen.mjs - Modify:
test/support/schema.js(zweite Quelldatei) - Modify:
server/config.js(collections.bankConnections,bankTransactions,financeSettings,financeFixedCosts) - Test:
test/financeSetup.test.js
Interfaces:
-
Produces: Collections nach Spec „Datenmodell“ mit genau diesen Attributen (Größen):
bankConnections:status16,sessionId64,accountUid64,accountIdHash128,ibanMasked40,iban40,validUntil32,lastSyncAt32,lastSyncOkboolean,lastError500,lastManualSyncAt32,balanceBookedCentsinteger,balanceAvailableCentsinteger,balanceDate10,latestBookingDate10,authState64,authStartedAt32,warnedThresholds32bankTransactions:accountUid64,status8,bookingDate10,valueDate10,amountCentsinteger,currency3,counterpartyName255,counterpartyIban40,remittance1000,mcc8,entryReference128,category32,matchedInvoiceId64,matchedInvoiceNumber64,matchedPayoutId64,fixedCostId64,note500,syncedAt32; Indexidx_booking(key,bookingDate)financeSettings:minBufferCentsinteger,taxReservePercentinteger,previousYearRevenueCentsinteger,estPrepaymentCentsinteger,bankDetailsText500financeFixedCosts:name120,amountCentsinteger,interval16,nextDate10,counterpartyMatch120,activeboolean- Aufruf mit Übernahme:
node scripts/setup-finanzen.mjs --session <id> --account <uid> --valid-until <iso>legt/aktualisiertbankConnections/finommitstatus: 'aktiv'und legtfinanceSettings/mainmittaxReservePercent: 30,minBufferCents: 0,estPrepaymentCents: 0an, falls nicht vorhanden. Ohne Argumente nur Collections. schemaKeys(collection)sucht insetup-abo-collections.mjsundsetup-finanzen.mjs.
-
Step 1: Write the failing test
test/financeSetup.test.js:schemaKeys('bankTransactions')enthältamountCents,category,matchedInvoiceId;schemaKeys('bankConnections')enthältstatus,warnedThresholds;schemaKeys('financeFixedCosts')enthältcounterpartyMatch;schemaKeys('subscriptions')funktioniert weiter. -
Step 2: Run
node --test test/financeSetup.test.js– Expected: FAIL („Collection bankTransactions fehlt im Setup-Skript“). -
Step 3: Implement
scripts/setup-finanzen.mjsnach dem Muster vonsetup-abo-collections.mjs(gleiche Helferaw,ensure,waitForAttributes,ensureCollection, Blockformat'<id>',…\n ],damitschemaKeysgreift;permissions: []). Argumente mitnode:utilparseArgs. Sitzungs-ID nie ausgeben (nur „Sitzung übernommen, gültig bis …“). -
Step 4: Run
node --test test/financeSetup.test.jsundnpm test– Expected: PASS. -
Step 5: Commit
git commit -m "Finanzen: Collections und Setup-Skript mit Sitzungsuebernahme"(Dateien einzeln adden).
Task 3: Buchungen abrufen und speichern
Files:
- Create:
server/services/finance/dates.js - Create:
server/services/finance/bankStore.js - Create:
server/services/finance/bankSync.js - Test:
test/financeDates.test.js - Test:
test/financeBankSync.test.js
Interfaces:
- Consumes: Task 1 (
getBalances,getTransactions,EnableBankingError), Task 2 (Collections). - Produces:
dates.js:berlinDate(date = new Date()) → 'YYYY-MM-DD'(Europe/Berlin),addDays(ymd, n) → 'YYYY-MM-DD',daysBetween(fromYmd, toYmd) → number(ganze Tage,toYmd − fromYmd),formatDe(ymd) → 'TT.MM.JJJJ'. Tests:berlinDate(new Date('2026-09-30T22:30:00Z')) === '2026-10-01';addDays('2026-03-29', 1) === '2026-03-30'(Zeitumstellung);daysBetween('2026-10-01', '2027-03-29') === 179;formatDe('2027-03-29') === '29.03.2027'.transactionDocId(raw) → string(36 Zeichen Hex, Regel aus Spec)toTransactionDoc(raw, { accountUid, syncedAt }) → object– Felder aus Spec;CRDT→debtor/debtor_account,DBIT→creditor/creditor_account;remittance=remittance_information.join(' ')(gekürzt auf 1000);ibanausaccount.iban ?? account.other?.identification ?? ''.bankStore.getConnection() → Promise<object|null>,saveConnection(patch) → Promise<object>(legtfinoman oder aktualisiert),upsertTransaction(doc) → Promise<'neu'|'aktualisiert'>(aktualisiert nur Bankfelder:status,bookingDate,valueDate,amountCents,currency,counterpartyName,counterpartyIban,remittance,mcc,entryReference,accountUid,syncedAt),listTransactions({ limit = 1000 } = {}) → Promise<object[]>(neueste zuerst nachbookingDate),getTransaction(id),updateTransaction(id, patch),deleteTransaction(id).syncBank({ now = new Date(), psu = null } = {}) → Promise<{ ok: boolean, neu: number, aktualisiert: number, geloescht: number, error?: string }>
Ablauf syncBank: Verbindung laden (keine oder status: 'abgelaufen' → { ok: false, error: 'Kein aktiver Bankzugang' } ohne API-Aufruf); dateFrom = latestBookingDate − 7 Tage, mindestens heute − 89 Tage, ohne latestBookingDate heute − 89 Tage; Salden holen (ITBD oder CLBD → balanceBookedCents, ITAV → balanceAvailableCents, Datum des gewählten Saldos → balanceDate); Umsätze holen und upserten; PDNG-Dokumente mit bookingDate >= dateFrom, die in diesem Abruf nicht vorkamen, löschen; Verbindung aktualisieren (lastSyncAt, lastSyncOk: true, lastError: '', latestBookingDate = jüngstes bookingDate der BOOK-Buchungen). Fehler: EnableBankingError mit Status 401/403 → status: 'abgelaufen', lastError: 'Bankzugang abgelaufen oder widerrufen'; andere Fehler → status bleibt, lastSyncOk: false, lastError = Meldung. Nie werfen.
- Step 1: Write the failing tests (
startAppwriteMock({ schema: { bankConnections: schemaKeys('bankConnections'), bankTransactions: schemaKeys('bankTransactions') } })+startFakeEnableBanking):toTransactionDoc: Eingang „EWV SOFTWARE GMBH“,168.79,CRDT→amountCents: 16879,counterpartyName: 'EWV SOFTWARE GMBH',remittance: 'Rechnung 0005 vom 28.07.2026'; KartenzahlungDBIT4.49mitmerchant_category_code: '4816'→amountCents: -449,mcc: '4816',counterpartyIban: ''.transactionDocId: gleicheentry_reference→ gleiche ID; ohneentry_referenceundtransaction_id→ stabil aus Datum|Betrag|Gegenpartei|Zweck; Ergebnis passt auf/^[a-f0-9]{36}$/.- Erster Abruf: 9 Buchungen →
neu: 9, SaldobalanceBookedCents: 10893,latestBookingDate: '2026-09-29',dateFromim Aufruf = heute − 89 Tage. - Zweiter Abruf mit denselben Buchungen nach
updateTransaction(id, { category: 'rechnung-extern', note: 'x' })→neu: 0,category/noteunverändert. - Vormerkung
PDNG(entry_referencep1) im ersten Abruf, im zweiten stattdessenBOOK(entry_referenceb1) → Dokumentp1gelöscht (geloescht: 1),b1vorhanden. - Fake antwortet 401 auf
/accounts/…/balances→ Ergebnisok: false, Verbindungstatus: 'abgelaufen', vorhandene Buchungen bleiben; dritter Aufruf macht keinen API-Aufruf. - Fake antwortet 500 →
status: 'aktiv',lastSyncOk: false,lastErrorgesetzt.
- Step 2: Run
node --test test/financeDates.test.js test/financeBankSync.test.js– Expected: FAIL. - Step 3: Implement
bankStore.js(überlistDocuments/getDocument/createDocument/updateDocument/deleteDocumentausappwriteAdmin.js,Query.orderDesc('bookingDate'),Query.limit) undbankSync.js. „Heute“ immer überberlinDate()ausdates.js(Intl.DateTimeFormat('sv-SE', { timeZone: 'Europe/Berlin' }); Tagesrechnung in UTC-Mitternacht, damit die Zeitumstellung nicht stört). - Step 4: Run
node --test test/financeDates.test.js test/financeBankSync.test.jsundnpm test– Expected: PASS. - Step 5: Commit
git commit -m "Finanzen: Bankabruf speichert Buchungen idempotent, Vormerkungen, Ablauf"
Task 4: Bankzugang neu verbinden
Files:
- Modify:
server/services/finance/bankSync.js - Test:
test/financeBankAuth.test.js
Interfaces:
-
Consumes: Task 1 (
startAuth,createSession), Task 3 (bankStore,syncBank). -
Produces:
startBankAuth({ now = new Date() } = {}) → Promise<{ url }>–state=randomBytes(12).toString('hex'), speichertauthState,authStartedAt.completeBankAuth({ code, state, error }, { now = new Date() } = {}) → Promise<{ ok: true, validUntil, sync }>; Fehler alsErrormitstatus = 400und Meldung:stateleer/abweichend → „Diese Rückkehr passt zu keiner gestarteten Freigabe.“authStartedAtälter als 30 Minuten → „Die Freigabe ist abgelaufen. Bitte neu starten.“errorgesetzt → „Finom hat die Freigabe nicht erteilt ().“
- Kontowahl: gleiches
identificationHashwie gespeichert, sonst gleiche IBAN, sonst erstes EUR-Konto; keins → 400 „Kein Euro-Konto in der Freigabe.“ - Erfolg setzt
sessionId,accountUid,accountIdHash,iban,ibanMasked(erste 4 + „…“ + letzte 4),validUntil,status: 'aktiv',authState: '',warnedThresholds: '',lastError: ''und startetsyncBank.
-
Step 1: Write the failing tests: Start speichert
authStateund liefert URL; Rückkehr mit richtigemstate→ neuesessionId,status: 'aktiv',warnedThresholds: '', Sync gelaufen; falscherstate→ 400 und altesessionIdunverändert; 31 Minuten später → 400;error: 'access_denied'→ 400 mit „access_denied“ in der Meldung; zwei Konten, gespeicherter Hash passt aufs zweite → zweites gewählt. -
Step 2: Run
node --test test/financeBankAuth.test.js– Expected: FAIL. -
Step 3: Implement in
bankSync.js. -
Step 4: Run die Testdatei und
npm test– Expected: PASS. -
Step 5: Commit
git commit -m "Finanzen: Bankzugang aus WOMS neu verbinden (Freigabe, Rueckkehr, Kontowahl)"
Task 5: Ablaufwarnung (Vorlage T18) und Jobs
Files:
- Create:
server/mail-templates/T18 Bankzugang erneuern.html - Modify:
server/services/finance/bankSync.js - Modify:
server/index.js(Jobs imapp.listen-Callback) - Test:
test/financeBankConsent.test.js
Interfaces:
-
Produces:
checkBankConsent({ now = new Date() } = {}) → Promise<{ sent: number|null }>–tage= volle Tage bisvalidUntil(aufgerundet); Schwellen[30, 14, 3, 0]; die kleinste erreichte, noch nicht inwarnedThresholdsstehende Schwelle löstsendAdminNotice({ template: 'T18', values, blocks })aus und wird angehängt;sent= Schwelle odernull. -
T18: Betreff
Bankzugang Finom läuft {{ablauf_text}}mitablauf_text= „in {{tage}} Tagen ab“ bzw. „heute ab“/„ist abgelaufen“; Platzhalter{{ablauf_text}},{{gueltig_bis}}(29.03.2027),{{link}}(${config.womsBaseUrl}/finance/einstellungen); optionaler Blockabgelaufen. Kopfkommentar und Aufbau wieT17 Repo zuordnen.html. -
Jobs (nur wenn
config.enableBanking.enabled):syncBank()2 Minuten nach Start und alle 6 Stunden;checkBankConsent()3 Minuten nach Start und alle 24 Stunden; beide.unref(), Fehler nurconsole.warnmit Präfix[finanzen]. -
Step 1: Write the failing tests (mailSink wie in
aboRoutes.test.js):validUntilin 29 Tagen → T18 einmal mitgueltig_bis, zweiter Aufruf am selben Tag → keine Mail; 13 Tage → Schwelle 14; abgelaufen → Blockabgelaufenenthalten;test/mailTemplates.test.jsbleibt grün (T18 hat Kopfkommentar mit Betreff und Platzhaltern). -
Step 2: Run
node --test test/financeBankConsent.test.js– Expected: FAIL. -
Step 3: Implement Vorlage,
checkBankConsent, Jobs inserver/index.js. -
Step 4: Run
node --test test/financeBankConsent.test.js test/mailTemplates.test.jsundnpm test– Expected: PASS. -
Step 5: Commit
git commit -m "Finanzen: Warnung vor Ablauf des Bankzugangs (T18) und Abruf-Jobs"
Task 6: Admin-Router für Bank und Buchungen
Files:
- Create:
server/routes/admin/finanzen.js - Modify:
server/index.js(app.use('/api/admin/finanzen', adminFinanzenRoutes)bei den übrigen/api/admin/*) - Modify:
server/index.jsCORS-Methoden bleibenGET, POST, PATCH, DELETE, OPTIONS(kein PUT verwenden) - Test:
test/financeRoutesBank.test.js
Interfaces:
-
Consumes: Tasks 3–4.
-
Produces (alle hinter
requireAppwriteStaff):GET /api/admin/finanzen/bank→{ verbindung: { status, ibanMasked, validUntil, lastSyncAt, lastSyncOk, lastError, balanceBookedCents, balanceAvailableCents, balanceDate, latestBookingDate } | null }(ohnesessionId,iban,authState)POST /api/admin/finanzen/bank/aktualisieren→ Ergebnis vonsyncBank({ psu: { ip: req.ip, userAgent: req.get('User-Agent') } }); warlastManualSyncAtvor < 10 Minuten → 429{ error: 'Bitte in N Minuten erneut versuchen.' }; setztlastManualSyncAt.POST /api/admin/finanzen/bank/verbinden→{ url }POST /api/admin/finanzen/bank/rueckkehrBody{ code, state, error }→{ ok: true, validUntil }oder 400{ error }GET /api/admin/finanzen/buchungen→{ buchungen: object[] }(Felder ausbankTransactions, ohne$-Metafelder außer$id)- Router exportiert zusätzlich nichts; Plan 2 ergänzt Routen in derselben Datei.
-
Step 1: Write the failing tests mit
startTestApp({ '/api/admin/finanzen': routes })und HeaderAuthorization: Bearer jwt-admin: ohne JWT → 401;jwt-staff→ 403;GET /bankenthält keinsessionId; zweimalaktualisierenhintereinander → 200 dann 429;rueckkehrmit falschemstate→ 400 mit Meldung;GET /buchungensortiert neueste zuerst. -
Step 2: Run
node --test test/financeRoutesBank.test.js– Expected: FAIL. -
Step 3: Implement Router und Mount.
-
Step 4: Run die Testdatei und
npm test– Expected: PASS. -
Step 5: Commit
git commit -m "Finanzen: Admin-Routen fuer Bankstatus, Abruf, Neuverbindung und Buchungen"