tempPath('.csv'); file_put_contents($path, mb_convert_encoding($utf8Content, 'ISO-8859-1', 'UTF-8')); return $path; } public function run(): void { $import = new \Steuerkram\Services\FintsInstituteImport(); // ── Vollständige, realistische Datei ─────────────────────────── $csv = "Kreditinstitute mit PIN/TAN-Zugang Stand: 20.05.2026\n" . "BLZ;Institut;Ort;BIC;PIN/TAN-Zugang URL;DDV;RDH-1\n" . "10030000;Sparkasse Köln Bonn;Köln;COLSDE33XXX;https://banking.spk.de/fints;1;1\n" . "10030000;Sparkasse Köln Bonn Filiale;Bonn;COLSDE33XXX;https://banking.spk.de/fints;1;1\n" . "12030000;\"Oldenburgische\nLandesbank\";Oldenburg;OLBODEH2XXX;https://fints.oldenburgischelandesbank.de;0;1\n" . "99999999;Kleine Bank ohne Zugang;Nirgendwo;;kein PIN/TAN-Zugang;0;0\n" . "88888888;;;;;;\n" . ";;;;;;\n" . "88888889;;;;;;\n"; $tmp = $this->makeIso88591Csv($csv); $result = $import->analyze($tmp, 'blz-aktuell-csv-data.csv'); $this->assertEquals(4, $result['zeilen'], 'Vier verwertbare Zeilen (Leerzeilen am Ende ausgeschlossen)'); $this->assertEquals(3, $result['blz_anzahl'], 'Drei verschiedene Bankleitzahlen (10030000 doppelt)'); $this->assertEquals(3, $result['mit_zugangspunkt'], 'Drei Zeilen mit echtem PIN/TAN-Zugangspunkt (die vierte hat nur Text statt Adresse)'); $this->assertEquals('20.05.2026', $result['stand'], 'Stand aus der Titelzeile erkannt'); $names = array_column($result['rows'], 'name'); $this->assert( (bool)preg_grep('/Oldenburgische.*Landesbank/s', $names), 'Mehrzeiliger, in Anführungszeichen stehender Institutsname wird als eine Zeile gelesen' ); $koeln = $result['rows'][0]; $this->assertEquals('Köln', mb_substr($koeln['ort'], 0, 4), 'ISO-8859-1 → UTF-8: Umlaut in "Köln" korrekt dekodiert'); $ohneZugang = array_values(array_filter($result['rows'], fn($r) => $r['blz'] === '99999999'))[0]; $this->assert($ohneZugang['pin_tan_url'] === null, 'Text statt Adresse in der PIN/TAN-Spalte wird nicht als URL übernommen'); $this->assert( !in_array('88888888', array_column($result['rows'], 'blz'), true) && !in_array('88888889', array_column($result['rows'], 'blz'), true), 'Zeilen ohne Institutsnamen (trailing Leerzeilen) werden übersprungen' ); // ── Fehlender Pflicht-Header bricht den Import ab ────────────── $csvOhneHeader = "Nur irgendein Text;ohne;Header\n1;2;3\n"; $tmpBad = $this->makeIso88591Csv($csvOhneHeader); $exceptionThrown = false; try { $import->analyze($tmpBad, 'kaputt.csv'); } catch (\RuntimeException $e) { $exceptionThrown = true; } $this->assert($exceptionThrown, 'Fehlen BLZ/Institut-Header, wird der Import mit RuntimeException abgebrochen'); // ── Models\Bankinstitut: replaceAll / search / findAccessPointByBlz ── \Steuerkram\Models\Bankinstitut::replaceAll($result['rows'], [ 'filename' => 'blz-aktuell-csv-data.csv', 'stand' => $result['stand'], 'zeilen' => $result['zeilen'], 'blz_anzahl' => $result['blz_anzahl'], 'mit_zugangspunkt' => $result['mit_zugangspunkt'], ]); $this->assertEquals(4, \Steuerkram\Models\Bankinstitut::count(), 'replaceAll() hat vier Zeilen übernommen'); $treffer = \Steuerkram\Models\Bankinstitut::findAccessPointByBlz('10030000'); $this->assert($treffer !== null && $treffer['pin_tan_url'] === 'https://banking.spk.de/fints', 'findAccessPointByBlz() liefert Zugangspunkt trotz doppelter BLZ'); $suchtreffer = \Steuerkram\Models\Bankinstitut::search('1822', 10); $this->assertEquals(0, count($suchtreffer), 'Suche nach "1822" findet keinen Treffer, wenn weder BLZ noch Name/Ort/BIC passen'); $suchtreffer2 = \Steuerkram\Models\Bankinstitut::search('Köln', 10); $this->assertGreaterThan(0, count($suchtreffer2), 'Suche über den Namen findet Treffer'); $meta = \Steuerkram\Models\Bankinstitut::importMeta(); $this->assertEquals('20.05.2026', $meta['stand'] ?? null, 'importMeta() liefert den zuletzt importierten Stand'); // ── Ein zweiter Import ersetzt den Bestand vollständig ───────── \Steuerkram\Models\Bankinstitut::replaceAll( [['blz' => '11111111', 'name' => 'Nur noch dieses Institut', 'ort' => 'X', 'bic' => '', 'pin_tan_url' => null]], ['filename' => 'neu.csv', 'stand' => null, 'zeilen' => 1, 'blz_anzahl' => 1, 'mit_zugangspunkt' => 0] ); $this->assertEquals(1, \Steuerkram\Models\Bankinstitut::count(), 'Ein erneuter Import ersetzt den gesamten Bestand statt ihn zu ergänzen'); } }