Finanzmanager/tests/42_UebermittlungsprotokollTest.php
fds2610-bot 3caf1dcdca Fix #67: Übermittlungsprotokoll für Überweisungen und Bankkommunikation speichern
Der gesamte FinTS-Dialog (gesendete und empfangene Meldungen) wird je Durchlauf
in bank_protokolle gespeichert, im Erfolgs- wie im Fehlerfall. Im Zahlungsverkehr
öffnet der Status das Protokoll der Überweisung als modalen Dialog; unter
Online-Banking › Protokoll gibt es das Gesamtprotokoll mit Filter nach Bank,
Datum und Freitext.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 07:48:10 +02:00

148 lines
6.2 KiB
PHP
Raw 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 Finanzmanager\Database;
use Finanzmanager\Models\Bankprotokoll;
use Finanzmanager\Models\Bankzugang;
use Finanzmanager\Models\Konto;
use Finanzmanager\Models\Zahlungsauftrag;
use Finanzmanager\Services\BankProtokollLogger;
use Finanzmanager\Services\FinTsUeberweisung;
/**
* Übermittlungsprotokoll: Nachweis gegenüber der Bank.
*
* Geprüft wird, dass der Dialog mit der Bank gespeichert wird – im Erfolgs- wie
* im Fehlerfall –, dass er zur Überweisung wiederzufinden ist und dass das
* Gesamtprotokoll nach Bank, Datum und Freitext filtert. Für den Fehlerfall geht
* die Überweisung an eine Adresse auf dem eigenen Rechner, auf der niemand
* lauscht: Es verlässt nichts den Container.
*/
class UebermittlungsprotokollTest extends TestCase
{
protected string $testName = 'Test 42: Übermittlungsprotokoll';
private int $giro = 0;
private int $zugang = 0;
private int $zweiterZugang = 0;
public function run(): void
{
$this->mandantAnlegen();
$this->stammdaten();
$this->derMitschnittSammeltGesendetUndEmpfangen();
$this->einProtokollHaengtAmAuftrag();
$this->einFehlgeschlagenerVersuchHinterlaesstEinProtokoll();
$this->dasGesamtprotokollFiltert();
}
private function stammdaten(): void
{
$this->giro = Konto::anlegen([
'name' => 'Protokoll-Giro', 'kontoart' => 'bank', 'anfangssaldo' => 0, 'aktiv' => 1,
]);
$zugang = static fn(string $name): array => [
'name' => $name,
'bankname' => $name . ' AG',
'blz' => '12030000',
'fints_url' => 'https://banking.example.org/fints',
'benutzerkennung' => 'testkunde',
'pin' => 'geheim1234',
'abrufzeitraum' => 'letzter_abruf',
'aktiv' => 1,
];
$this->zugang = Bankzugang::anlegen($zugang('Protokoll-Bank'));
$this->zweiterZugang = Bankzugang::anlegen($zugang('Andere-Bank'));
}
private function auftrag(): int
{
return Zahlungsauftrag::anlegen([
'konto_id' => $this->giro,
'zugang_id' => $this->zugang,
'empfaenger' => 'Finanzamt Musterstadt',
'iban' => 'DE02120300000000202051',
'betrag' => 42.5,
]);
}
private function derMitschnittSammeltGesendetUndEmpfangen(): void
{
$logger = new BankProtokollLogger();
$logger->debug('> HNHBK:1:3+000000000123');
$logger->debug("< HNHBK:1:3+000000000456 Ümlaut\xE4");
$logger->vermerke('Ergebnis: angenommen');
$text = $logger->text();
$this->assert(str_contains($text, '> HNHBK:1:3+000000000123'), 'Die gesendete Meldung steht im Mitschnitt');
$this->assert(str_contains($text, '< HNHBK:1:3+000000000456'), 'Die empfangene Meldung steht im Mitschnitt');
$this->assert(str_contains($text, 'Ergebnis: angenommen'), 'Auch die eigenen Vermerke stehen darin');
$this->assert(mb_check_encoding($text, 'UTF-8'), 'Der Mitschnitt ist gültiges UTF-8, auch nach Latin-1');
}
private function einProtokollHaengtAmAuftrag(): void
{
$auftragId = $this->auftrag();
Bankprotokoll::speichern($this->zugang, 'ueberweisung', 'offen', 'TAN nötig', '> Anfrage', $auftragId);
Bankprotokoll::speichern($this->zugang, 'ueberweisung', 'erfolg', 'angenommen', '< Antwort', $auftragId);
$eintraege = Bankprotokoll::fuerAuftrag($auftragId);
$this->assertEquals(2, count($eintraege), 'Beide Durchläufe gehören zur Überweisung');
$this->assertEquals('offen', $eintraege[0]['status'], 'Der älteste Durchlauf steht zuerst');
$this->assertEquals('< Antwort', $eintraege[1]['protokoll'], 'Das Protokoll bleibt vollständig erhalten');
$liste = Zahlungsauftrag::alle();
$this->assertEquals(2, (int)$liste[0]['protokolle'], 'Die Liste zählt die Protokolle je Überweisung');
}
private function einFehlgeschlagenerVersuchHinterlaesstEinProtokoll(): void
{
// Auf dem eigenen Rechner lauscht niemand – der Versuch scheitert sofort.
Database::execute(
'UPDATE bank_zugaenge SET fints_url = ? WHERE id = ?',
['http://127.0.0.1:9/fints', $this->zugang]
);
$auftragId = $this->auftrag();
$ergebnis = FinTsUeberweisung::senden($auftragId);
FinTsUeberweisung::abbrechen();
$this->assertEquals('fehler', $ergebnis['status'], 'Ohne erreichbare Bank scheitert das Senden');
$eintraege = Bankprotokoll::fuerAuftrag($auftragId);
$this->assertEquals(1, count($eintraege), 'Auch der Fehlerfall wird protokolliert');
$this->assertEquals('fehler', $eintraege[0]['status'], 'Der Eintrag trägt den Status „fehler“');
$this->assert(
str_contains((string)$eintraege[0]['protokoll'], 'Finanzamt Musterstadt'),
'Das Protokoll nennt die Überweisung'
);
$this->assert(
!str_contains((string)$eintraege[0]['protokoll'], 'geheim1234'),
'Die PIN steht nicht im Protokoll'
);
}
private function dasGesamtprotokollFiltert(): void
{
Bankprotokoll::speichern($this->zweiterZugang, 'abruf', 'erfolg', 'Umsätze geholt', 'Kontoauszug Nummer 77');
$alle = Bankprotokoll::suchen();
$this->assertGreaterThan(3, count($alle), 'Das Gesamtprotokoll führt alle Durchläufe');
$andere = Bankprotokoll::suchen(['zugang_id' => $this->zweiterZugang]);
$this->assertEquals(1, count($andere), 'Der Bankfilter zeigt nur diese Bank');
$treffer = Bankprotokoll::suchen(['text' => 'Nummer 77']);
$this->assertEquals(1, count($treffer), 'Die Freitextsuche findet den Wortlaut der Meldungen');
$heute = date('Y-m-d');
$this->assertGreaterThan(0, count(Bankprotokoll::suchen(['von' => $heute, 'bis' => $heute])), 'Heute ist im Zeitraum');
$this->assertEquals(0, count(Bankprotokoll::suchen(['bis' => '2000-01-01'])), 'Ein Zeitraum in der Vergangenheit ist leer');
$this->assertEquals(0, count(Bankprotokoll::suchen(['text' => '100%'])), 'Sonderzeichen der Suche sind wörtlich gemeint');
}
}