Die Browser-Tests legten ihre Konten, Kategorien und Buchungen bei jedem Lauf neu an, weggeräumt hat sie niemand: Zuletzt standen über dreitausend Zeilen Testdaten in der Datenbank – vierzig „E2E Bankprovision …“ hintereinander im Kategoriefilter, und kein Bildschirmfoto eines fehlgeschlagenen Tests war mehr zu gebrauchen. scripts/e2e-aufraeumen.php räumt weg, was am Testnutzer hängt – nicht, was nach Testdaten aussieht. Ein Namensmuster übersähe die Buchung „Supermarkt“ und träfe irgendwann etwas Echtes; die Fremdschlüssel dagegen sagen genau, was zu diesem Nutzer gehört, und nehmen über ihre Kaskaden auch die Zeilen ohne eigene benutzer_id mit. Der Nutzer selbst bleibt stehen, und er muss den Anzeigenamen tragen, den scripts/e2e-benutzer.php setzt: Wer E2E_USER versehentlich auf seinen eigenen Zugang stellt, bekommt einen Abbruch statt eines leeren Datenbestands. Aufgerufen wird es vor dem Lauf, nicht danach – ein abgebrochener Lauf hinterlässt sonst einen Bestand, an den niemand mehr denkt, und so bleiben die Daten des letzten Laufs bis zum nächsten für die Fehlersuche stehen. Das deckte auf, dass sieben Tests auf den Resten früherer Läufe standen: Die Auswertungen prüfen, dass ein Bericht eine Tabelle statt eines Platzhalters zeigt, das Cockpit, dass „Übrige Konten“ eine Ebene tiefer führt – Daten dafür legte keiner von ihnen an. scripts/e2e-grundbestand.php stellt deshalb einen festgelegten Bestand her, dessen Zuschnitte an genau diesen Tests hängen: mehr Konten als Vermoegensentwicklung::SERIEN, Buchungen in jedem Monat des laufenden Jahres, ein Vorjahr für den Vergleichsbericht, ein Depot mit Wertpapiergeschäft. Test 41 prüft jede dieser Grenzen einzeln und nennt den Test, der daran hängt. 87 E2E-Tests grün, in 1,3 statt 2,7 Minuten – die Listen rendern keine Tausenden Altzeilen mehr. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQ57wQWZ4xKHcKmp26bzgr
274 lines
10 KiB
PHP
274 lines
10 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use Finanzmanager\Database;
|
||
use Finanzmanager\Mandant;
|
||
use Finanzmanager\Models\Benutzer;
|
||
use Finanzmanager\Models\Buchung;
|
||
use Finanzmanager\Models\Kategorie;
|
||
use Finanzmanager\Models\Klasse;
|
||
use Finanzmanager\Models\Konto;
|
||
use Finanzmanager\Services\E2EAufraeumer;
|
||
use Finanzmanager\Services\E2EGrundbestand;
|
||
use Finanzmanager\Services\Vermoegensentwicklung;
|
||
|
||
/**
|
||
* Fix #64: Den E2E-Datenbestand aufräumen und neu aufbauen.
|
||
*
|
||
* Geprüft wird beides: dass wirklich alles wegkommt, was am Testnutzer hängt –
|
||
* einschließlich der Zeilen, die nur über einen Fremdschlüssel daran hängen –
|
||
* und dass ein zweiter Nutzer daneben unangetastet bleibt. Der zweite Teil ist
|
||
* der wichtigere: Das Skript läuft künftig automatisch, und wer es auf dem
|
||
* eigenen Rechner mit echten Daten anstößt, darf davon nichts merken.
|
||
*/
|
||
class E2EAufraeumenTest extends TestCase
|
||
{
|
||
protected string $testName = 'Test 41: E2E-Datenbestand aufräumen und aufbauen';
|
||
|
||
public function run(): void
|
||
{
|
||
$this->erkenntDenTestnutzer();
|
||
$this->raeumtDenBestandWeg();
|
||
$this->kaskadeNimmtDieSplitzeilenMit();
|
||
$this->fremderBestandBleibtStehen();
|
||
$this->probelaufLoeschtNichts();
|
||
$this->grundbestandTraegtDieDarstellungstests();
|
||
$this->grundbestandGehtBeimAufraeumenWiederWeg();
|
||
}
|
||
|
||
/** Legt einen Nutzer an und macht ihn zum Mandanten. */
|
||
private function nutzerAnlegen(string $anzeigename): int
|
||
{
|
||
$id = Benutzer::anlegen(
|
||
'aufraeum_' . bin2hex(random_bytes(4)),
|
||
'testpasswort-' . bin2hex(random_bytes(4)),
|
||
$anzeigename
|
||
);
|
||
Mandant::setze($id);
|
||
|
||
return $id;
|
||
}
|
||
|
||
/** Ein Konto mit einer Buchung darauf; liefert die Kennung der Buchung. */
|
||
private function bestandAnlegen(string $marke): int
|
||
{
|
||
$konto = Konto::anlegen([
|
||
'name' => 'E2E Giro ' . $marke, 'kontoart' => 'bank', 'anfangssaldo' => 0, 'aktiv' => 1,
|
||
]);
|
||
|
||
return Buchung::anlegen([
|
||
'konto_id' => $konto, 'datum' => '2026-04-01', 'betrag' => -12.30,
|
||
'empfaenger' => 'Supermarkt',
|
||
]);
|
||
}
|
||
|
||
private function erkenntDenTestnutzer(): void
|
||
{
|
||
$this->assert(
|
||
E2EAufraeumer::istTestnutzer(['anzeigename' => E2EAufraeumer::ANZEIGENAME]),
|
||
'Der Anzeigename des Testnutzers wird erkannt'
|
||
);
|
||
|
||
// Die Schranke gegen den Fehlgriff: Wer E2E_USER auf seinen eigenen
|
||
// Zugang stellt, soll nichts verlieren.
|
||
$this->assert(
|
||
!E2EAufraeumer::istTestnutzer(['anzeigename' => 'Felix']),
|
||
'Ein gewöhnlicher Nutzer gilt nicht als Testnutzer'
|
||
);
|
||
$this->assert(
|
||
!E2EAufraeumer::istTestnutzer([]),
|
||
'Ohne Anzeigename gilt niemand als Testnutzer'
|
||
);
|
||
}
|
||
|
||
private function raeumtDenBestandWeg(): void
|
||
{
|
||
$nutzer = $this->nutzerAnlegen(E2EAufraeumer::ANZEIGENAME);
|
||
$this->bestandAnlegen('111111');
|
||
Kategorie::anlegen(['name' => 'E2E Einkauf 111111', 'art' => 'ausgabe']);
|
||
Klasse::anlegen('E2E Privat 111111');
|
||
|
||
$ergebnis = E2EAufraeumer::ausfuehren($nutzer);
|
||
|
||
$this->assertEquals(1, $ergebnis['konten'] ?? 0, 'Das Konto ist mitgezählt');
|
||
$this->assertEquals(1, $ergebnis['buchungen'] ?? 0, 'Die Buchung ist mitgezählt');
|
||
$this->assertEquals(1, $ergebnis['kategorien'] ?? 0, 'Die Kategorie ist mitgezählt');
|
||
$this->assertEquals(1, $ergebnis['klassen'] ?? 0, 'Die Klasse ist mitgezählt');
|
||
|
||
$this->assertEquals(
|
||
0,
|
||
(int)Database::fetchValue('SELECT COUNT(*) FROM konten WHERE benutzer_id = ?', [$nutzer], 0),
|
||
'Nach dem Aufräumen steht kein Konto mehr'
|
||
);
|
||
$this->assertEquals(
|
||
0,
|
||
(int)Database::fetchValue('SELECT COUNT(*) FROM buchungen WHERE benutzer_id = ?', [$nutzer], 0),
|
||
'Nach dem Aufräumen steht keine Buchung mehr'
|
||
);
|
||
|
||
// Der Nutzer selbst bleibt: Ohne ihn käme der nächste Lauf nicht durch
|
||
// die Anmeldung.
|
||
$this->assertNotEmpty(Benutzer::find($nutzer), 'Der Testnutzer selbst bleibt stehen');
|
||
}
|
||
|
||
private function kaskadeNimmtDieSplitzeilenMit(): void
|
||
{
|
||
$nutzer = $this->nutzerAnlegen(E2EAufraeumer::ANZEIGENAME);
|
||
$konto = Konto::anlegen([
|
||
'name' => 'E2E Giro 222222', 'kontoart' => 'bank', 'anfangssaldo' => 0, 'aktiv' => 1,
|
||
]);
|
||
$kategorie = Kategorie::anlegen(['name' => 'E2E Einkauf 222222', 'art' => 'ausgabe']);
|
||
|
||
$buchung = Buchung::anlegen(
|
||
['konto_id' => $konto, 'datum' => '2026-04-02', 'betrag' => -90.0, 'empfaenger' => 'Aufzuteilen'],
|
||
[
|
||
['kategorie_id' => $kategorie, 'betrag' => -50.0, 'notiz' => 'Teil eins'],
|
||
['kategorie_id' => $kategorie, 'betrag' => -40.0, 'notiz' => 'Teil zwei'],
|
||
]
|
||
);
|
||
|
||
$this->assertEquals(2, count(Buchung::splits($buchung)), 'Die Splitzeilen stehen zunächst');
|
||
|
||
E2EAufraeumer::ausfuehren($nutzer);
|
||
|
||
// buchung_splits trägt keine benutzer_id – die Zeilen gehen nur mit,
|
||
// weil der Fremdschlüssel kaskadiert.
|
||
$this->assertEquals(
|
||
0,
|
||
(int)Database::fetchValue(
|
||
'SELECT COUNT(*) FROM buchung_splits WHERE buchung_id = ?',
|
||
[$buchung],
|
||
0
|
||
),
|
||
'Die Splitzeilen gehen über die Kaskade mit'
|
||
);
|
||
}
|
||
|
||
private function fremderBestandBleibtStehen(): void
|
||
{
|
||
$fremd = $this->nutzerAnlegen('Felix');
|
||
$this->bestandAnlegen('333333');
|
||
$fremdeKategorie = Kategorie::anlegen(['name' => 'Lebensmittel', 'art' => 'ausgabe']);
|
||
|
||
$test = $this->nutzerAnlegen(E2EAufraeumer::ANZEIGENAME);
|
||
$this->bestandAnlegen('444444');
|
||
|
||
E2EAufraeumer::ausfuehren($test);
|
||
|
||
$this->assertEquals(
|
||
1,
|
||
(int)Database::fetchValue('SELECT COUNT(*) FROM konten WHERE benutzer_id = ?', [$fremd], 0),
|
||
'Das Konto des fremden Nutzers steht noch'
|
||
);
|
||
$this->assertEquals(
|
||
1,
|
||
(int)Database::fetchValue('SELECT COUNT(*) FROM buchungen WHERE benutzer_id = ?', [$fremd], 0),
|
||
'Die Buchung des fremden Nutzers steht noch'
|
||
);
|
||
$this->assertNotEmpty(
|
||
Database::fetchOne('SELECT * FROM kategorien WHERE id = ?', [$fremdeKategorie]),
|
||
'Die Kategorie des fremden Nutzers steht noch'
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Der Grundbestand ist kein beliebiger Datenhaufen: Jeder Zuschnitt hängt an
|
||
* einem Test, der ihn braucht. Bleibt er hinter einer dieser Grenzen zurück,
|
||
* fällt der zugehörige Browser-Test aus – und zwar mit einer Meldung über
|
||
* eine fehlende Tabellenspalte, die den Grund nicht verrät.
|
||
*/
|
||
private function grundbestandTraegtDieDarstellungstests(): void
|
||
{
|
||
$nutzer = $this->nutzerAnlegen(E2EAufraeumer::ANZEIGENAME);
|
||
$jahr = (int)date('Y');
|
||
|
||
E2EGrundbestand::anlegen($nutzer);
|
||
|
||
$konten = (int)Database::fetchValue(
|
||
'SELECT COUNT(*) FROM konten WHERE benutzer_id = ?',
|
||
[$nutzer],
|
||
0
|
||
);
|
||
$this->assertGreaterThan(
|
||
Vermoegensentwicklung::SERIEN,
|
||
$konten,
|
||
'Mehr Konten als Farbtöne – sonst gibt es kein „Übrige Konten“ zum Aufklappen'
|
||
);
|
||
|
||
// Zwölf Monatsspalten füllen sich nur, wenn in jedem Monat etwas steht.
|
||
$monate = (int)Database::fetchValue(
|
||
'SELECT COUNT(DISTINCT MONTH(datum)) FROM buchungen
|
||
WHERE benutzer_id = ? AND YEAR(datum) = ?',
|
||
[$nutzer, $jahr],
|
||
0
|
||
);
|
||
$this->assertEquals(12, $monate, 'Jeder Monat des laufenden Jahres trägt Buchungen');
|
||
|
||
$this->assertGreaterThan(
|
||
0,
|
||
(int)Database::fetchValue(
|
||
'SELECT COUNT(*) FROM buchungen WHERE benutzer_id = ? AND YEAR(datum) = ?',
|
||
[$nutzer, $jahr - 1],
|
||
0
|
||
),
|
||
'Das Vorjahr ist besetzt – der Vergleichsbericht stellt zwei Zeiträume nebeneinander'
|
||
);
|
||
|
||
$this->assertGreaterThan(
|
||
0,
|
||
(int)Database::fetchValue(
|
||
'SELECT COUNT(*) FROM depot_buchungen WHERE benutzer_id = ?',
|
||
[$nutzer],
|
||
0
|
||
),
|
||
'Ein Wertpapiergeschäft steht – sonst sind /wertpapiere/buchungen eine leere Liste'
|
||
);
|
||
|
||
// Ohne Kategorie bliebe der Summenbericht ein Platzhalter.
|
||
$this->assertGreaterThan(
|
||
0,
|
||
(int)Database::fetchValue(
|
||
'SELECT COUNT(*) FROM buchungen WHERE benutzer_id = ? AND kategorie_id IS NOT NULL',
|
||
[$nutzer],
|
||
0
|
||
),
|
||
'Die Buchungen tragen Kategorien'
|
||
);
|
||
}
|
||
|
||
private function grundbestandGehtBeimAufraeumenWiederWeg(): void
|
||
{
|
||
$nutzer = $this->nutzerAnlegen(E2EAufraeumer::ANZEIGENAME);
|
||
E2EGrundbestand::anlegen($nutzer);
|
||
|
||
E2EAufraeumer::ausfuehren($nutzer);
|
||
|
||
foreach (['konten', 'buchungen', 'kategorien', 'wertpapiere', 'depot_buchungen'] as $tabelle) {
|
||
$this->assertEquals(
|
||
0,
|
||
(int)Database::fetchValue(
|
||
'SELECT COUNT(*) FROM `' . $tabelle . '` WHERE benutzer_id = ?',
|
||
[$nutzer],
|
||
0
|
||
),
|
||
'Der Grundbestand ist aus ' . $tabelle . ' wieder verschwunden'
|
||
);
|
||
}
|
||
}
|
||
|
||
private function probelaufLoeschtNichts(): void
|
||
{
|
||
$nutzer = $this->nutzerAnlegen(E2EAufraeumer::ANZEIGENAME);
|
||
$this->bestandAnlegen('555555');
|
||
|
||
$probe = E2EAufraeumer::ausfuehren($nutzer, true);
|
||
|
||
$this->assertEquals(1, $probe['konten'] ?? 0, 'Der Probelauf zählt das Konto');
|
||
$this->assertEquals(
|
||
1,
|
||
(int)Database::fetchValue('SELECT COUNT(*) FROM konten WHERE benutzer_id = ?', [$nutzer], 0),
|
||
'Der Probelauf lässt das Konto stehen'
|
||
);
|
||
}
|
||
}
|