Steuerkram/tests/37_BankSourceTest.php
fds2610-bot 995b52fbd3 Feature: FinTS-Direktverbindung und vereinheitlichte Importquellen
- FinTS-Abruf für Comdirect/DKB/Sparkassen via nemiah/php-fints (4.1)
  inkl. photoTAN-Eingabe-Modal und Multi-Konto-Auswahl
- Neue Tabelle bank_sources (CSV/API/FinTS) ersetzt bank_import_formats
  und bank_connections, Backfill in Migration 068
- Symmetrische Verschlüsselung sensibler Felder (PayPal-Signature,
  FinTS-PIN) mit AES-256-GCM, APP_KEY in .env
- Routes /bank/fints* → /bank/direkt*; "+ Neue Bank…"-Dialog erlaubt
  freie API/FinTS-Anbieter
- Scripts/encrypt_bank_secrets.php als idempotenter Migrations-Helfer
- Neue Tests 36 (Crypto) und 37 (BankSource)
2026-05-22 07:17:00 +02:00

129 lines
5.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
use Steuerkram\Config;
use Steuerkram\Database;
use Steuerkram\Models\BankSource;
/**
* Test 37: BankSource – CRUD über alle drei Typen, Verschlüsselungs-Roundtrip,
* Dedup über external_id und FK-Integrität bank_import_batches.source_id.
*/
class BankSourceTest extends TestCase
{
protected string $testName = 'Test 37: BankSource – CSV/API/FinTS Roundtrip & Verschlüsselung';
public function run(): void
{
// Test-Key setzen (falls noch nicht aus .env)
$oldKey = $_ENV['APP_KEY'] ?? null;
if (empty(Config::get('app_key', ''))) {
$_ENV['APP_KEY'] = str_repeat('c3', 32);
Config::load();
}
try {
// 1. CSV-Quelle anlegen und wieder lesen
$csvId = BankSource::create([
'type' => 'csv',
'bezeichnung' => 'TestCSV',
'kuerzel' => 'TC',
'farbe' => '#abcdef',
'skip_rows' => 3,
'has_header_row' => 1,
'delimiter' => ';',
'enclosure' => '"',
'col_buchungstag' => 0,
'col_wertstellung' => 1,
'col_vorgang' => 2,
'col_buchungstext' => 3,
'col_wert' => 4,
'date_format' => 'd.m.Y',
'decimal_separator' => ',',
'thousands_separator' => '.',
]);
$this->assertGreaterThan(0, $csvId, 'CSV-Quelle wurde angelegt');
$csv = BankSource::find($csvId);
$this->assertNotEmpty($csv, 'CSV-Quelle gefunden');
$this->assertEquals('csv', $csv['type'], 'CSV-Quelle hat type=csv');
$this->assertEquals('TestCSV', $csv['name'], 'BankSource liefert Alias name = bezeichnung');
$this->assertEquals('TC', $csv['kuerzel'], 'Kürzel korrekt');
// 2. API-Quelle mit verschlüsselten Credentials
$providerApi = Database::fetchOne("SELECT id FROM bank_providers WHERE verbindungstyp='API' LIMIT 1");
if ($providerApi) {
$apiId = BankSource::create([
'type' => 'api',
'provider_id' => (int)$providerApi['id'],
'bezeichnung' => 'TestAPI',
'app_name' => 'app-user',
'client_id' => 'secret-key-1',
'secret' => 'super-signature',
'sandbox' => 0,
]);
$this->assertGreaterThan(0, $apiId, 'API-Quelle wurde angelegt');
// DB-Spalte muss verschlüsselt sein
$raw = Database::fetchOne('SELECT client_id_enc, secret_enc FROM bank_sources WHERE id = ?', [$apiId]);
$this->assert(str_starts_with($raw['client_id_enc'], 'enc:v1:'), 'client_id_enc ist verschlüsselt');
$this->assert(str_starts_with($raw['secret_enc'], 'enc:v1:'), 'secret_enc ist verschlüsselt');
// BankSource::find liefert entschlüsselt
$api = BankSource::find($apiId);
$this->assertEquals('secret-key-1', $api['client_id'], 'client_id entschlüsselt korrekt');
$this->assertEquals('super-signature', $api['secret'], 'secret entschlüsselt korrekt');
}
// 3. FinTS-Quelle mit PIN
$providerFints = Database::fetchOne("SELECT id FROM bank_providers WHERE verbindungstyp='FinTS' LIMIT 1");
if ($providerFints) {
$fintsId = BankSource::create([
'type' => 'fints',
'provider_id' => (int)$providerFints['id'],
'bezeichnung' => 'TestFinTS',
'blz' => '12030000',
'benutzerkennung' => 'kundennummer',
'pin' => 'g3h31m',
]);
$this->assertGreaterThan(0, $fintsId, 'FinTS-Quelle wurde angelegt');
$raw = Database::fetchOne('SELECT pin_enc FROM bank_sources WHERE id = ?', [$fintsId]);
$this->assert(str_starts_with($raw['pin_enc'], 'enc:v1:'), 'pin_enc ist verschlüsselt');
$fints = BankSource::find($fintsId);
$this->assertEquals('g3h31m', $fints['pin'], 'PIN entschlüsselt korrekt');
$this->assertEquals('12030000', $fints['blz'], 'BLZ korrekt');
// 4. updateField mit Klartext-PIN → muss verschlüsselt landen
BankSource::updateField($fintsId, 'pin', 'neue-pin');
$raw2 = Database::fetchOne('SELECT pin_enc FROM bank_sources WHERE id = ?', [$fintsId]);
$this->assert(str_starts_with($raw2['pin_enc'], 'enc:v1:'), 'updateField verschlüsselt pin');
$fints2 = BankSource::find($fintsId);
$this->assertEquals('neue-pin', $fints2['pin'], 'pin nach updateField wieder entschlüsselbar');
}
// 5. Filter all('csv') liefert nur CSV-Einträge
$allCsv = BankSource::all('csv');
foreach ($allCsv as $row) {
$this->assertEquals('csv', $row['type'], 'all(csv) liefert nur csv-Einträge');
}
// 6. Batch mit source_id anlegen funktioniert (FK)
Database::execute(
'INSERT INTO bank_import_batches (source_id, filename, row_count) VALUES (?, ?, 0)',
[$csvId, 'test-batch.csv']
);
$bid = (int)Database::lastInsertId();
$this->assertGreaterThan(0, $bid, 'Batch mit source_id angelegt');
} finally {
if ($oldKey === null) {
unset($_ENV['APP_KEY']);
} else {
$_ENV['APP_KEY'] = $oldKey;
}
Config::load();
}
}
}