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>
155 lines
6 KiB
JavaScript
155 lines
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'
|
||
|
||
// Eindeutige Namen je Lauf: die Tests arbeiten auf einer bestehenden Instanz
|
||
// und dürfen sich nicht an Resten eines früheren Laufs stoßen.
|
||
const MARKE = Date.now().toString().slice(-6)
|
||
|
||
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(/\/$/)
|
||
}
|
||
|
||
test.describe('Bankverbindungen', () => {
|
||
test.beforeEach(async ({ page }) => {
|
||
await anmelden(page)
|
||
})
|
||
|
||
test('„Bankverbindung anlegen“ führt zur Wahl zwischen HBCI/FinTS und PayPal', async ({ page }) => {
|
||
await page.goto('/banking')
|
||
await page.click('a[href="/banking/neu"]')
|
||
|
||
await expect(page.locator('h2')).toContainText('Wie wird abgerufen?')
|
||
|
||
const karten = page.locator('.formatwahl .formatkarte')
|
||
await expect(karten).toHaveCount(2)
|
||
await expect(karten.nth(0)).toContainText('HBCI/FinTS')
|
||
await expect(karten.nth(1)).toContainText('PayPal')
|
||
})
|
||
|
||
test('Die erste Wahl führt in die bisherige Bankenmaske', async ({ page }) => {
|
||
await page.goto('/banking/neu')
|
||
await page.click('.formatkarte:has-text("HBCI/FinTS")')
|
||
|
||
await expect(page).toHaveURL(/\/banking\/neu\/fints$/)
|
||
await expect(page.locator('.schrittmarke')).toContainText('Schritt 1 von 3')
|
||
await expect(page.locator('#fints_url')).toBeVisible()
|
||
await expect(page.locator('#pin')).toBeVisible()
|
||
})
|
||
|
||
test('Die zweite Wahl führt in die PayPal-Maske mit allen Angaben für die Abfrage', async ({ page }) => {
|
||
await page.goto('/banking/neu')
|
||
await page.click('.formatkarte:has-text("PayPal")')
|
||
|
||
await expect(page).toHaveURL(/\/banking\/neu\/paypal$/)
|
||
|
||
// Die drei Angaben der NVP-Schnittstelle und der Endpunkt-Schalter.
|
||
await expect(page.locator('#api_benutzer')).toBeVisible()
|
||
await expect(page.locator('#api_kennwort')).toBeVisible()
|
||
await expect(page.locator('#api_signatur')).toBeVisible()
|
||
await expect(page.locator('input[name="sandbox"]')).toBeAttached()
|
||
|
||
// Die Kontenzuordnung steht hier im Formular, nicht auf einer eigenen Seite.
|
||
await expect(page.locator('#konto_id')).toBeVisible()
|
||
await expect(page.locator('#konto_id option[value="neu"]')).toHaveCount(1)
|
||
})
|
||
|
||
test('PayPal-Verbindung anlegen, dabei ein neues Konto anlegen und wiederfinden', async ({ page }) => {
|
||
const name = `E2E PayPal ${MARKE}`
|
||
const konto = `E2E PayPal-Guthaben ${MARKE}`
|
||
|
||
await page.goto('/banking/neu/paypal')
|
||
await page.fill('#name', name)
|
||
await page.fill('#api_benutzer', `e2e_api1.example.org`)
|
||
await page.fill('#api_kennwort', 'e2e-kennwort')
|
||
await page.fill('#api_signatur', 'e2e-signatur')
|
||
await page.check('input[name="sandbox"]')
|
||
|
||
// Die Felder des neuen Kontos sind erst nach dieser Wahl sichtbar.
|
||
await expect(page.locator('#neuer_name')).toBeHidden()
|
||
await page.selectOption('#konto_id', 'neu')
|
||
await expect(page.locator('#neuer_name')).toBeVisible()
|
||
|
||
await page.fill('#neuer_name', konto)
|
||
await page.fill('#anfangssaldo', '25,00')
|
||
await page.click('.formular button[type="submit"]')
|
||
|
||
await expect(page.locator('.meldung-erfolg')).toBeVisible()
|
||
await expect(page).toHaveURL(/\/banking$/)
|
||
|
||
// In der Übersicht steht die Verbindung mit ihrer Art.
|
||
const zeile = page.locator('table.liste tr', { hasText: name })
|
||
await expect(zeile).toContainText('PayPal')
|
||
|
||
// Und beim Öffnen steht das eben angelegte Konto zugeordnet da; die
|
||
// Geheimnisse werden nicht wieder angezeigt.
|
||
await zeile.locator('a').first().click()
|
||
await expect(page.locator('#api_benutzer')).toHaveValue('e2e_api1.example.org')
|
||
await expect(page.locator('#api_kennwort')).toHaveValue('')
|
||
await expect(page.locator('#konto_id option:checked')).toHaveText(konto)
|
||
await expect(page.locator('input[name="sandbox"]')).toBeChecked()
|
||
})
|
||
|
||
test('„nicht zugeordnet“ löst eine bestehende Zuordnung wieder', async ({ page }) => {
|
||
const name = `E2E PayPal Zuordnung ${MARKE}`
|
||
const konto = `E2E PayPal-Konto ${MARKE}`
|
||
|
||
await page.goto('/banking/neu/paypal')
|
||
await page.fill('#name', name)
|
||
await page.fill('#api_benutzer', 'e2e_api1.example.org')
|
||
await page.fill('#api_kennwort', 'e2e-kennwort')
|
||
await page.fill('#api_signatur', 'e2e-signatur')
|
||
await page.selectOption('#konto_id', 'neu')
|
||
await page.fill('#neuer_name', konto)
|
||
await page.click('.formular button[type="submit"]')
|
||
await expect(page).toHaveURL(/\/banking$/)
|
||
|
||
await page.locator('table.liste tr', { hasText: name }).locator('a').first().click()
|
||
await expect(page.locator('#konto_id option:checked')).toHaveText(konto)
|
||
|
||
// Die leere Wahl heißt hier ausdrücklich „Zuordnung lösen“ – sie darf
|
||
// nicht als „nichts geändert“ durchrutschen.
|
||
await page.selectOption('#konto_id', '')
|
||
await page.click('.formular button[type="submit"]')
|
||
|
||
await expect(page.locator('.meldung-erfolg')).toBeVisible()
|
||
await expect(page.locator('#konto_id option:checked')).toHaveText('– nicht zugeordnet –')
|
||
})
|
||
|
||
test('Ein PayPal-Zugang kennt kein TAN-Verfahren', async ({ page }) => {
|
||
const name = `E2E PayPal ohne TAN ${MARKE}`
|
||
|
||
await page.goto('/banking/neu/paypal')
|
||
await page.fill('#name', name)
|
||
await page.fill('#api_benutzer', 'e2e_api1.example.org')
|
||
await page.fill('#api_kennwort', 'e2e-kennwort')
|
||
await page.fill('#api_signatur', 'e2e-signatur')
|
||
await page.click('.formular button[type="submit"]')
|
||
await expect(page).toHaveURL(/\/banking$/)
|
||
|
||
const ziel = await page
|
||
.locator('table.liste tr', { hasText: name })
|
||
.locator('a')
|
||
.first()
|
||
.getAttribute('href')
|
||
|
||
await page.goto(`${ziel}/verfahren`)
|
||
|
||
await expect(page.locator('.meldung-warnung')).toContainText('kein TAN-Verfahren')
|
||
await expect(page).toHaveURL(new RegExp(`${ziel}$`))
|
||
})
|
||
})
|