Zwei Features an derselben Stelle, die im Arbeitsverzeichnis nebeneinander lagen und sich dieselben Dateien teilen (Routen, BankingController, Bankzugang, Zugangsformular, Dokumentation) – getrennte Commits ließen sich daraus nur mit einem kaputten Zwischenstand schneiden. PayPal als zweite Art von Bankverbindung ---------------------------------------- „Bankverbindung anlegen“ führt zur Wahl der Art: HBCI/FinTS oder PayPal (bank_zugaenge.typ, Migration 014). Die Abfrage läuft über die Classic-NVP-API (Services\PaypalNvp, TransactionSearch und GetBalance). Kein Dreischritt, da PayPal genau ein Guthabenkonto führt – Zugangsdaten und Kontenzuordnung stehen in einem Formular. Konten, Umsätze, Protokoll, Regeln und Abgleich bleiben dieselben. Bankleitzahlen-Verzeichnis -------------------------- Verwaltung → Bankleitzahlen (/verwaltung/bankinstitute, Migration 015) nimmt die CSV-Institutsliste der Deutschen Kreditwirtschaft auf und beantwortet damit die eine Angabe, die beim Anlegen einer FinTS-Verbindung regelmäßig fehlt: den PIN/TAN-Zugangspunkt. Reine Nachschlagedaten ohne benutzer_id; ein Import ersetzt den gesamten Bestand, statt ihn zu ergänzen. „Nur prüfen“ und „Übernehmen“ sind zwei Knöpfe an derselben Datei – abgelegt wird sie nicht, ihr Inhalt steht ja in der Tabelle. Services\FintsInstituteImport nimmt der Datei ihre Eigenheiten ab: ISO-8859-1 statt UTF-8, ein Institutsname über zwei Zeilen in Anführungszeichen, dieselbe Bankleitzahl vielfach (eine Zeile je Filiale), Hunderte leerer Zeilen am Ende, Text statt Adresse, wo es keinen PIN/TAN-Zugang gibt. Spalten werden über ihre Überschrift angesprochen; fehlen BLZ und Institut, bricht der Import ab. Gesucht wird über Bankleitzahl, BIC, Name und Ort in einem Zug: „1822“ steht bei der 1822direkt im Namen und bei der Frankfurter Sparkasse im BIC, in keiner der beiden Bankleitzahlen. Aus der Trefferliste führt „Verbindung anlegen“ ins gefüllte FinTS-Formular. Die FinTS-Adresse ist damit kein Pflichtfeld mehr – bleibt sie leer, wird sie zur Bankleitzahl nachgeschlagen. Findet auch das nichts, weist Bankzugang den Zugang ab, statt ihn ohne Gegenstelle zu speichern. Tests: 31_PaypalZugangTest, 32_BankinstituteTest, e2e/specs/banking.spec.js, e2e/specs/bankinstitute.spec.js. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
98 lines
3.6 KiB
JavaScript
98 lines
3.6 KiB
JavaScript
// @ts-check
|
||
const { test, expect } = require('@playwright/test')
|
||
|
||
const BENUTZER = process.env.E2E_USER || 'e2e'
|
||
const PASSWORT = process.env.E2E_PASSWORD || 'e2e-testpasswort'
|
||
|
||
async function anmelden(page) {
|
||
await page.goto('/login')
|
||
|
||
if (page.url().includes('/einrichtung')) {
|
||
throw new Error(
|
||
'Instanz ist noch nicht eingerichtet. Zuerst einen Benutzer anlegen: ' +
|
||
'docker compose exec -u www-data php php scripts/e2e-benutzer.php'
|
||
)
|
||
}
|
||
|
||
await page.fill('#benutzername', BENUTZER)
|
||
await page.fill('#passwort', PASSWORT)
|
||
await page.click('button[type="submit"]')
|
||
await expect(page).toHaveURL(/\/$/)
|
||
}
|
||
|
||
// Die Zeile im Format der echten Institutsliste – mit allen Spalten in der
|
||
// Reihenfolge, in der die Deutsche Kreditwirtschaft sie ausgibt.
|
||
const KOPF = 'Nr.;BLZ;BIC;Institut;Ort;RZ;Organisation;HBCI-Zugang DNS;' +
|
||
'HBCI- Zugang IP-Adresse;HBCI-Version;DDV;RDH-1;RDH-2;RDH-3;RDH-4;RDH-5;' +
|
||
'RDH-6;RDH-7;RDH-8;RDH-9;RDH-10;RAH-7;RAH-9;RAH-10;PIN/TAN-Zugang URL;Version;' +
|
||
'Datum letzte Änderung'
|
||
|
||
const ZEILE = '1526;50050222;HELADEF1822;E2E-Institut (PIN/TAN);Frankfurt am Main;' +
|
||
'Finanz Informatik GmbH & Co. KG;DSGV;;;;;;;;;;;;;;;;;;' +
|
||
'https://fints.beispiel.invalid/fints/hbci;FinTS V3.0;09.02.2022'
|
||
|
||
/**
|
||
* Das Bankleitzahlen-Verzeichnis im Browser.
|
||
*
|
||
* Geprüft wird nur der Weg – Upload, Prüfbericht, Suche –, nicht das Einlesen
|
||
* selbst: Das steckt in der PHP-Suite. Und übernommen wird hier nichts, denn
|
||
* ein Import ersetzt den gesamten Bestand der Instanz; der Prüflauf lässt ihn
|
||
* unangetastet.
|
||
*/
|
||
test.describe('Bankleitzahlen-Verzeichnis', () => {
|
||
test.beforeEach(async ({ page }) => {
|
||
await anmelden(page)
|
||
})
|
||
|
||
test('Die Verwaltung führt zum Verzeichnis', async ({ page }) => {
|
||
await page.goto('/verwaltung')
|
||
|
||
const kachel = page.locator('.kachel', { hasText: 'Bankleitzahlen' })
|
||
await kachel.getByRole('link', { name: /Nachschlagen/ }).click()
|
||
|
||
await expect(page).toHaveURL(/\/verwaltung\/bankinstitute$/)
|
||
await expect(page.locator('h1')).toContainText('Bankleitzahlen')
|
||
})
|
||
|
||
test('Der Prüflauf berichtet, ohne den Bestand anzurühren', async ({ page }) => {
|
||
await page.goto('/verwaltung/bankinstitute')
|
||
|
||
await page.setInputFiles('#datei', {
|
||
name: 'e2e-institute.csv',
|
||
mimeType: 'text/csv',
|
||
buffer: Buffer.from([KOPF, ZEILE, ''].join('\r\n'), 'latin1'),
|
||
})
|
||
await page.getByRole('button', { name: 'Nur prüfen' }).click()
|
||
|
||
const bericht = page.locator('.kachel', { hasText: 'Prüfung' })
|
||
await expect(bericht).toContainText('e2e-institute.csv')
|
||
await expect(bericht).toContainText('Es wurde nichts geschrieben')
|
||
await expect(bericht).toContainText('50050222')
|
||
await expect(bericht).toContainText('fints.beispiel.invalid')
|
||
})
|
||
|
||
test('Eine fremde CSV-Datei wird abgewiesen', async ({ page }) => {
|
||
await page.goto('/verwaltung/bankinstitute')
|
||
|
||
await page.setInputFiles('#datei', {
|
||
name: 'kontoauszug.csv',
|
||
mimeType: 'text/csv',
|
||
buffer: Buffer.from('Datum;Betrag;Empfänger\r\n01.02.2026;-12,50;Bäcker\r\n', 'utf8'),
|
||
})
|
||
await page.getByRole('button', { name: 'Nur prüfen' }).click()
|
||
|
||
await expect(page.locator('.meldung-fehler')).toContainText('keine FinTS-Institutsliste')
|
||
})
|
||
|
||
test('Die Suche nimmt Bankleitzahl wie Namen', async ({ page }) => {
|
||
await page.goto('/verwaltung/bankinstitute')
|
||
|
||
await page.fill('#suche', '50050222')
|
||
await page.getByRole('button', { name: 'Suchen' }).click()
|
||
await expect(page).toHaveURL(/suche=50050222/)
|
||
|
||
// Ob es einen Treffer gibt, hängt vom eingelesenen Bestand ab – die
|
||
// Seite muss in beiden Fällen antworten, statt leer zu bleiben.
|
||
await expect(page.locator('table.liste, .platzhalter').first()).toBeVisible()
|
||
})
|
||
})
|