dolibarr.bankimport/vendor/nemiah/php-fints/lib/Tests/Fhp/FinTsTestCase.php
data 014a943f78 feat: HKEKA-Implementierung, PDF-Bugfixes, Sortierung, Umsatz-Umbenennung
- HKEKA v3/v4/v5 Segmente fuer phpFinTS implementiert (VR Bank unterstuetzt kein HKEKP)
- GetElectronicStatement Action mit Base64-Erkennung und Quittungscode
- PDF-Deduplizierung per MD5 (Bank sendet identische Saldenmitteilungen)
- Saldenmitteilungen ohne Auszugsnummer werden uebersprungen
- Datums-Validierung: 30.02. (Bank-Konvention) wird auf 28.02. korrigiert
- Numerische Sortierung fuer statement_number (CAST statt String-Sort)
- Jahr-Filter: statement_year=0 ausgeschlossen
- Menue/Button: "Kontoauszuege" -> "Umsaetze" (statements.php zeigt MT940, nicht PDFs)
- Redirect nach FinTS-Abruf auf aktuelles Jahr statt year=0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 07:10:59 +01:00

134 lines
6.3 KiB
PHP
Executable file

<?php
/** @noinspection PhpUndefinedClassInspection */
namespace Tests\Fhp;
use Fhp\Connection;
use Fhp\Options\Credentials;
use Fhp\Options\FinTsOptions;
use Fhp\Segment\HNHBK\HNHBKv3;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
abstract class FinTsTestCase extends TestCase
{
use \phpmock\phpunit\PHPMock;
public const TEST_URL = 'https://testbank.de/hbci';
public const TEST_BANK_CODE = '11223344'; // Can be overridden in sub-classes!
public const TEST_PRODUCT_NAME = '123456789ABCDEF0123456789';
public const TEST_PRODUCT_VERSION = '1.0';
public const TEST_USERNAME = 'test@user';
public const TEST_PIN = '12345';
public const TEST_TAN_MODE = '942'; // Can be overridden in sub-classes!
public const TEST_KUNDENSYSTEM_ID = 'FAKEKUNDENSYSTEMIDabcdefghij';
/** @var FinTsOptions */
protected $options;
/** @var Credentials */
protected $credentials;
/** @var Connection&MockObject */
protected $connection;
/** @var FinTsPeer */
protected $fints;
/** @var \DateTime Hard-coded time for unit tests. */
protected $now;
/** @var string[][] Series of tuples of expected request and mock response */
protected $expectedMessages;
protected function setUp(): void
{
// We mock rand() for the $randomReference generation in Fhp\Protocol\Message.
$randMock = $this->getFunctionMock('Fhp\Protocol', 'rand');
$randMock->expects($this->any())->with(1000000, 9999999)->willReturn(9999999);
// We mock time() for the timestamps in the encryption/signature headers in SicherheitsdatumUndUhrzeitV2.php.
$this->now = new \DateTime('2019-01-02 03:04:05', new \DateTimeZone('UTC'));
$timeMock = $this->getFunctionMock('Fhp\Segment\HNVSK', 'time');
$timeMock->expects($this->any())->with()->willReturn($this->now->getTimestamp());
$this->options = new FinTsOptions();
$this->options->url = static::TEST_URL;
$this->options->productName = static::TEST_PRODUCT_NAME;
$this->options->productVersion = static::TEST_PRODUCT_VERSION;
$this->options->bankCode = static::TEST_BANK_CODE;
$this->credentials = Credentials::create(static::TEST_USERNAME, static::TEST_PIN);
FinTsPeer::$mockConnection = $this->setUpConnection();
$this->fints = new FinTsPeer($this->options, $this->credentials);
}
protected function setUpConnection()
{
$this->connection = $this->createMock(Connection::class);
$this->connection->expects($this->any())->method('send')->willReturnCallback(function ($request) {
// Check that the request itself is valid wrt. to the length declared in its header.
if (preg_match('/^HNHBK:\\d+:\\d+\\+(\\d+)/', $request, $lengthMatch) === 1) {
$expectedLength = intval($lengthMatch[1]);
$this->assertSame($expectedLength, strlen($request), $request);
}
// Grab the next expected request and its mock response.
$this->assertNotEmpty($this->expectedMessages, "Expected no more requests, but got: $request");
list($expectedRequest, $mockResponse) = array_shift($this->expectedMessages);
// Check that the request matches the expectation.
if (strlen($expectedRequest) > 0 && !str_starts_with($expectedRequest, 'HNHBK')) {
// The expected request is just the inner part, so we need to unwrap the actual request. This is done in
// in a quick and hacky way, which slices everything from HNSHK's terminating delimiter to the start of
// HNSHA.
$this->assertEquals(1, preg_match("/HNSHK.*?'(.*?')HNSHA:/s", $request, $match), "For request: $request");
$request = $match[1];
}
$this->assertEquals($expectedRequest, $request);
// Send the mock response.
if (!str_starts_with($mockResponse, 'HNHBK')) {
// The mock response is just the inner part, so we need to wrap it in a fake envelope.
$mockPrefix = 'HNHBK:1:3+';
// Note: The 4242 is the message number. It's garbage and a constant, but the SUT does not verify it.
$mockMiddle = "+300+FAKEDIALOGIDabcdefghijklmnopqr+4242+FAKEDIALOGIDabcdefghijklmnopqr:2'HNVSK:998:3+PIN:2+998+1+2::"
. static::TEST_KUNDENSYSTEM_ID . "+1:20190102:030405+2:2:13:@8@00000000:5:1+280:11223344:test?@user:V:0:0+0'";
$hnvsdContent = 'HNSHK:2:4+PIN:2+' . static::TEST_TAN_MODE . '+9999999+1+1+2::'
. static::TEST_KUNDENSYSTEM_ID
. "+1+1:20190102:030405+1:999:1+6:10:19+280:11223344:test?@user:S:0:0'"
. $mockResponse . "HNSHA:10:2+9999999'";
$hnvsd = 'HNVSD:999:1+@' . strlen($hnvsdContent) . '@' . $hnvsdContent . "'";
$mockSuffix = "HNHBS:5:1+2'";
$newLength = strlen($mockPrefix) + HNHBKv3::NACHRICHTENGROESSE_LENGTH
+ strlen($mockMiddle) + strlen($hnvsd) + strlen($mockSuffix);
$newLength = str_pad($newLength, HNHBKv3::NACHRICHTENGROESSE_LENGTH, '0', STR_PAD_LEFT);
return $mockPrefix . $newLength . $mockMiddle . $hnvsd . $mockSuffix;
} else {
return $mockResponse;
}
});
return $this->connection;
}
protected function tearDown(): void
{
$this->assertAllMessagesSeen();
FinTsPeer::$mockConnection = null;
}
/**
* @param string $request Can be a full request (starting with HNHBK) or just the inner part of HNVSD, more
* precisely the slice *between* the HNSHK and HNSHA segments. Note that the latter forms a weaker expectation,
* as the SUT could be sending a wrong wrapper and we wouldn't notice.
* @param string $response Can be a full response (starting with HNHBK) or just the inner part of HNVSD, more
* precisely the slice *between* the HNSHK and HNSHA segments.
*/
protected function expectMessage(string $request, string $response)
{
$this->expectedMessages[] = [$request, $response];
}
protected function assertAllMessagesSeen()
{
$this->assertEmpty($this->expectedMessages, 'Expected requests were not received');
}
}