- 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)
129 lines
5.8 KiB
PHP
129 lines
5.8 KiB
PHP
<?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();
|
||
}
|
||
}
|
||
}
|