Fix #64: E2E-Datenbestand vor jedem Lauf aufräumen und neu aufbauen
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
This commit is contained in:
parent
f4fa4f0e3a
commit
a81ec1ceba
8 changed files with 804 additions and 0 deletions
34
Changes.md
34
Changes.md
|
|
@ -1,5 +1,39 @@
|
||||||
# Änderungen
|
# Änderungen
|
||||||
|
|
||||||
|
## E2E-Datenbestand wird vor jedem Lauf aufgeräumt (04.09.2026)
|
||||||
|
|
||||||
|
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 des
|
||||||
|
Kontoblatts, und kein Bildschirmfoto eines fehlgeschlagenen Tests war mehr zu gebrauchen.
|
||||||
|
|
||||||
|
`scripts/e2e-aufraeumen.php` räumt den Bestand weg; `tests/e2e/run.sh` ruft es vor jedem
|
||||||
|
Lauf auf. Vor dem Lauf und nicht danach, weil ein abgebrochener Lauf sonst einen Bestand
|
||||||
|
hinterlässt, an den niemand mehr denkt – und weil die Daten des letzten Laufs so bis zum
|
||||||
|
nächsten für die Fehlersuche stehen bleiben. `E2E_KEIN_AUFRAEUMEN=1` lässt sie liegen,
|
||||||
|
`--probe` zählt nur, was wegfiele.
|
||||||
|
|
||||||
|
Gelöscht wird, 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 (Splitzeilen, Budgetpositionen,
|
||||||
|
Bankkonten, Kurse). Der Nutzer selbst bleibt stehen, sonst käme der nächste Lauf nicht
|
||||||
|
durch die Anmeldung.
|
||||||
|
|
||||||
|
Als Schranke gegen den Fehlgriff muss er 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.
|
||||||
|
|
||||||
|
Das Aufräumen 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. Auf einer frisch aufgeräumten Instanz fielen sie aus; vorher hing ihr Ergebnis davon
|
||||||
|
ab, wer zuletzt was hatte laufen lassen. `scripts/e2e-grundbestand.php` stellt deshalb nach
|
||||||
|
dem Aufräumen 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.
|
||||||
|
Die Suite prüft damit dasselbe wie vorher, aber auf einem Bestand, den sie kennt.
|
||||||
|
|
||||||
## E2E-Tests an den Typeahead im Kontoblatt angepasst (04.09.2026)
|
## E2E-Tests an den Typeahead im Kontoblatt angepasst (04.09.2026)
|
||||||
|
|
||||||
Zwölf E2E-Tests standen seit dem Typeahead-Umbau (Fix #41) auf Rot – nicht, weil die
|
Zwölf E2E-Tests standen seit dem Typeahead-Umbau (Fix #41) auf Rot – nicht, weil die
|
||||||
|
|
|
||||||
79
scripts/e2e-aufraeumen.php
Executable file
79
scripts/e2e-aufraeumen.php
Executable file
|
|
@ -0,0 +1,79 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Räumt den Datenbestand der E2E-Tests weg.
|
||||||
|
*
|
||||||
|
* docker compose exec -u www-data php php scripts/e2e-aufraeumen.php
|
||||||
|
*
|
||||||
|
* `tests/e2e/run.sh` ruft das vor jedem Lauf selbst auf; von Hand braucht es
|
||||||
|
* den Aufruf nur, wer zwischendurch aufräumen will.
|
||||||
|
*
|
||||||
|
* Gelöscht wird alles, was am Testnutzer hängt – der Nutzer selbst bleibt
|
||||||
|
* stehen. Welcher das ist, sagt `E2E_USER` (sonst `e2e`); er muss den
|
||||||
|
* Anzeigenamen tragen, den `scripts/e2e-benutzer.php` setzt, sonst bricht das
|
||||||
|
* Skript ab.
|
||||||
|
*
|
||||||
|
* Mit `--probe` wird nur gezählt, was wegfiele; `--still` schweigt, solange
|
||||||
|
* nichts schiefgeht.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
use Finanzmanager\Config;
|
||||||
|
use Finanzmanager\Services\E2EAufraeumer;
|
||||||
|
|
||||||
|
Config::load();
|
||||||
|
|
||||||
|
$probe = in_array('--probe', $argv, true);
|
||||||
|
$still = in_array('--still', $argv, true);
|
||||||
|
|
||||||
|
$sage = static function (string $zeile) use ($still): void {
|
||||||
|
if (!$still) {
|
||||||
|
echo $zeile . PHP_EOL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$nutzer = E2EAufraeumer::testnutzer();
|
||||||
|
|
||||||
|
if ($nutzer === null) {
|
||||||
|
// Kein Testnutzer, kein Testbestand – das ist kein Fehler, sondern der
|
||||||
|
// Zustand vor dem ersten `scripts/e2e-benutzer.php`.
|
||||||
|
$sage('Kein Testnutzer vorhanden – nichts aufzuräumen.');
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!E2EAufraeumer::istTestnutzer($nutzer)) {
|
||||||
|
fwrite(STDERR, sprintf(
|
||||||
|
"Abbruch: '%s' ist nicht als Testnutzer ausgewiesen (Anzeigename '%s' statt '%s').\n"
|
||||||
|
. "Es wurde nichts gelöscht.\n",
|
||||||
|
(string)$nutzer['benutzername'],
|
||||||
|
(string)$nutzer['anzeigename'],
|
||||||
|
E2EAufraeumer::ANZEIGENAME
|
||||||
|
));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ergebnis = E2EAufraeumer::ausfuehren((int)$nutzer['id'], $probe);
|
||||||
|
$gesamt = array_sum($ergebnis);
|
||||||
|
|
||||||
|
if ($gesamt === 0) {
|
||||||
|
$sage('Der Testbestand ist schon leer.');
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sage(sprintf(
|
||||||
|
'%s: %d Zeilen aus %d Tabellen (Testnutzer %s).',
|
||||||
|
$probe ? 'Probelauf' : 'Aufgeräumt',
|
||||||
|
$gesamt,
|
||||||
|
count($ergebnis),
|
||||||
|
(string)$nutzer['benutzername']
|
||||||
|
));
|
||||||
|
|
||||||
|
foreach ($ergebnis as $tabelle => $anzahl) {
|
||||||
|
$sage(sprintf(' %-24s %6d', $tabelle, $anzahl));
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
61
scripts/e2e-grundbestand.php
Executable file
61
scripts/e2e-grundbestand.php
Executable file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legt den Grundbestand an, auf dem die E2E-Tests aufsetzen.
|
||||||
|
*
|
||||||
|
* docker compose exec -u www-data php php scripts/e2e-grundbestand.php
|
||||||
|
*
|
||||||
|
* `tests/e2e/run.sh` ruft das nach dem Aufräumen selbst auf. Gedacht ist es für
|
||||||
|
* einen frisch aufgeräumten Bestand: Ein zweiter Aufruf legt alles noch einmal
|
||||||
|
* an, statt es zu ergänzen.
|
||||||
|
*
|
||||||
|
* Wie beim Aufräumen muss der Nutzer als Testnutzer ausgewiesen sein – sonst
|
||||||
|
* landet der Bestand in einem echten Zugang.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
use Finanzmanager\Config;
|
||||||
|
use Finanzmanager\Services\E2EAufraeumer;
|
||||||
|
use Finanzmanager\Services\E2EGrundbestand;
|
||||||
|
|
||||||
|
Config::load();
|
||||||
|
|
||||||
|
$still = in_array('--still', $argv, true);
|
||||||
|
|
||||||
|
$sage = static function (string $zeile) use ($still): void {
|
||||||
|
if (!$still) {
|
||||||
|
echo $zeile . PHP_EOL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$nutzer = E2EAufraeumer::testnutzer();
|
||||||
|
|
||||||
|
if ($nutzer === null) {
|
||||||
|
fwrite(STDERR, "Abbruch: Es gibt keinen Testnutzer. Zuerst scripts/e2e-benutzer.php.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!E2EAufraeumer::istTestnutzer($nutzer)) {
|
||||||
|
fwrite(STDERR, sprintf(
|
||||||
|
"Abbruch: '%s' ist nicht als Testnutzer ausgewiesen (Anzeigename '%s' statt '%s').\n"
|
||||||
|
. "Es wurde nichts angelegt.\n",
|
||||||
|
(string)$nutzer['benutzername'],
|
||||||
|
(string)$nutzer['anzeigename'],
|
||||||
|
E2EAufraeumer::ANZEIGENAME
|
||||||
|
));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ergebnis = E2EGrundbestand::anlegen((int)$nutzer['id']);
|
||||||
|
|
||||||
|
$sage(sprintf('Grundbestand angelegt (Testnutzer %s).', (string)$nutzer['benutzername']));
|
||||||
|
|
||||||
|
foreach ($ergebnis as $gattung => $anzahl) {
|
||||||
|
$sage(sprintf(' %-16s %6d', $gattung, $anzahl));
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
140
src/Services/E2EAufraeumer.php
Normal file
140
src/Services/E2EAufraeumer.php
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Finanzmanager\Services;
|
||||||
|
|
||||||
|
use Finanzmanager\Database;
|
||||||
|
use Finanzmanager\Models\Benutzer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Räumt den Datenbestand der E2E-Tests weg.
|
||||||
|
*
|
||||||
|
* Die Browser-Tests legen ihre Konten, Kategorien, Klassen und Buchungen bei
|
||||||
|
* jedem Lauf neu an – mit einer Zeitmarke im Namen, damit zwei Läufe sich nicht
|
||||||
|
* ins Gehege kommen. Weggeräumt hat sie bisher niemand: Nach ein paar Dutzend
|
||||||
|
* Läufen stehen im Kategoriefilter des Kontoblatts vierzig „E2E Bankprovision …“
|
||||||
|
* hintereinander, und kein Bildschirmfoto eines fehlgeschlagenen Tests ist mehr
|
||||||
|
* zu gebrauchen.
|
||||||
|
*
|
||||||
|
* Aufgeräumt wird **vor** dem Lauf, nicht danach: Ein abgebrochener Lauf
|
||||||
|
* hinterlässt sonst einen Bestand, an den niemand mehr denkt, und bis zum
|
||||||
|
* nächsten Lauf bleiben die Daten des letzten für die Fehlersuche stehen.
|
||||||
|
*
|
||||||
|
* Gelöscht wird alles, 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. Der Nutzer selbst bleibt stehen, sonst wäre der nächste
|
||||||
|
* Lauf ohne `scripts/e2e-benutzer.php` nicht anmeldefähig.
|
||||||
|
*/
|
||||||
|
class E2EAufraeumer
|
||||||
|
{
|
||||||
|
/** Der Anzeigename, den `scripts/e2e-benutzer.php` setzt. */
|
||||||
|
public const ANZEIGENAME = 'E2E-Testnutzer';
|
||||||
|
|
||||||
|
/** Der Benutzername, wenn E2E_USER nichts anderes sagt. */
|
||||||
|
public const BENUTZERNAME = 'e2e';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Tabellen mit `benutzer_id`, Kinder vor Eltern.
|
||||||
|
*
|
||||||
|
* Die Reihenfolge muss stimmen, obwohl die Fremdschlüssel ohnehin
|
||||||
|
* kaskadieren: Nur so meldet jede Tabelle die Zeilen, die sie selbst
|
||||||
|
* verloren hat, statt dass ein späterer Aufruf ins Leere läuft, weil ein
|
||||||
|
* früherer die Zeilen schon mitgenommen hat. Die Tabellen ohne eigene
|
||||||
|
* `benutzer_id` – buchung_splits, buchungsadressen, budget_positionen,
|
||||||
|
* bank_konten, kurse – hängen per CASCADE an einer von diesen.
|
||||||
|
*/
|
||||||
|
private const TABELLEN = [
|
||||||
|
'erinnerungen',
|
||||||
|
'monatsabschluesse',
|
||||||
|
'berichte',
|
||||||
|
'berichtsgruppen',
|
||||||
|
'budgets',
|
||||||
|
'abgleich_regeln',
|
||||||
|
'buchungsvorlagen',
|
||||||
|
'vertraege',
|
||||||
|
'sparziele',
|
||||||
|
'kredite',
|
||||||
|
'zahlungsauftraege',
|
||||||
|
'bank_umsaetze',
|
||||||
|
'bank_abrufe',
|
||||||
|
'bank_zugaenge',
|
||||||
|
'depot_buchungen',
|
||||||
|
'wertpapiere',
|
||||||
|
'regelmaessige_buchungen',
|
||||||
|
'buchungen',
|
||||||
|
'konten',
|
||||||
|
'kontengruppen',
|
||||||
|
'kategorien',
|
||||||
|
'klassen',
|
||||||
|
'adressen',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Testnutzer – oder null, wenn es ihn nicht gibt.
|
||||||
|
*
|
||||||
|
* @return array<string,mixed>|null
|
||||||
|
*/
|
||||||
|
public static function testnutzer(?string $benutzername = null): ?array
|
||||||
|
{
|
||||||
|
return Benutzer::findByName($benutzername ?? (getenv('E2E_USER') ?: self::BENUTZERNAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ist das wirklich der Testnutzer? Der Anzeigename ist die Schranke: Wer
|
||||||
|
* `E2E_USER` versehentlich auf seinen eigenen Zugang stellt, verliert sonst
|
||||||
|
* mit einem Aufruf seinen ganzen Datenbestand.
|
||||||
|
*
|
||||||
|
* @param array<string,mixed> $nutzer
|
||||||
|
*/
|
||||||
|
public static function istTestnutzer(array $nutzer): bool
|
||||||
|
{
|
||||||
|
return (string)($nutzer['anzeigename'] ?? '') === self::ANZEIGENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Räumt den Bestand des Testnutzers weg.
|
||||||
|
*
|
||||||
|
* @param bool $probe zählt nur, löscht nichts
|
||||||
|
* @return array<string,int> Tabelle → Anzahl der Zeilen; leere bleiben weg
|
||||||
|
*/
|
||||||
|
public static function ausfuehren(int $benutzerId, bool $probe = false): array
|
||||||
|
{
|
||||||
|
$ergebnis = [];
|
||||||
|
|
||||||
|
foreach (self::TABELLEN as $tabelle) {
|
||||||
|
$anzahl = $probe
|
||||||
|
? (int)Database::fetchValue(
|
||||||
|
'SELECT COUNT(*) FROM `' . $tabelle . '` WHERE benutzer_id = ?',
|
||||||
|
[$benutzerId],
|
||||||
|
0
|
||||||
|
)
|
||||||
|
: Database::execute(
|
||||||
|
'DELETE FROM `' . $tabelle . '` WHERE benutzer_id = ?',
|
||||||
|
[$benutzerId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($anzahl > 0) {
|
||||||
|
$ergebnis[$tabelle] = $anzahl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bankinstitute sind Stammdaten für alle und hängen an keinem Nutzer.
|
||||||
|
// Der Import aus bankinstitute.spec.js legt eigene an; sie tragen das
|
||||||
|
// Präfix, an dem sich die Testdaten erkennen lassen.
|
||||||
|
$institute = $probe
|
||||||
|
? (int)Database::fetchValue(
|
||||||
|
"SELECT COUNT(*) FROM bankinstitute WHERE institut LIKE 'E2E%'",
|
||||||
|
[],
|
||||||
|
0
|
||||||
|
)
|
||||||
|
: Database::execute("DELETE FROM bankinstitute WHERE institut LIKE 'E2E%'");
|
||||||
|
|
||||||
|
if ($institute > 0) {
|
||||||
|
$ergebnis['bankinstitute'] = $institute;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ergebnis;
|
||||||
|
}
|
||||||
|
}
|
||||||
147
src/Services/E2EGrundbestand.php
Normal file
147
src/Services/E2EGrundbestand.php
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Finanzmanager\Services;
|
||||||
|
|
||||||
|
use Finanzmanager\Database;
|
||||||
|
use Finanzmanager\Mandant;
|
||||||
|
use Finanzmanager\Models\Depotbuchung;
|
||||||
|
use Finanzmanager\Models\Buchung;
|
||||||
|
use Finanzmanager\Models\Kategorie;
|
||||||
|
use Finanzmanager\Models\Klasse;
|
||||||
|
use Finanzmanager\Models\Konto;
|
||||||
|
use Finanzmanager\Models\Wertpapier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legt den Grundbestand an, auf dem die E2E-Tests aufsetzen.
|
||||||
|
*
|
||||||
|
* Ein Teil der Browser-Tests prüft die Darstellung, nicht das Rechnen: dass ein
|
||||||
|
* Bericht eine Tabelle statt eines Platzhalters zeigt, dass Zahlenspalten
|
||||||
|
* rechtsbündig stehen, dass „Übrige Konten“ im Vermögensdiagramm eine Ebene
|
||||||
|
* tiefer führt. Solche Tests brauchen einen Bestand, legen ihn aber nicht selbst
|
||||||
|
* an – bis das Aufräumen dazukam, lebten sie von den Resten früherer Läufe.
|
||||||
|
* Genau das machte sie unzuverlässig: Was sie vorfanden, hing davon ab, wer
|
||||||
|
* zuletzt was laufen ließ.
|
||||||
|
*
|
||||||
|
* Der Bestand hier ist stattdessen festgelegt und wird vor jedem Lauf neu
|
||||||
|
* hergestellt. Die Zuschnitte sind nicht beliebig:
|
||||||
|
*
|
||||||
|
* - **Mehr Konten als Farbtöne.** `Vermoegensentwicklung::SERIEN` bestimmt, ab
|
||||||
|
* wann das Diagramm zusammenfasst; erst darüber gibt es „Übrige Konten“ und
|
||||||
|
* eine zweite Ebene zum Aufklappen.
|
||||||
|
* - **Buchungen über das ganze laufende Jahr.** Die Berichte laufen über
|
||||||
|
* „aktuelles Jahr“ und sollen bei monatlicher Aufteilung zwölf Spalten füllen.
|
||||||
|
* - **Ein Vorjahr.** Der Vergleichsbericht stellt zwei Zeiträume nebeneinander.
|
||||||
|
* - **Ein Depot mit Wertpapier und Geschäft.** `/wertpapiere` und
|
||||||
|
* `/wertpapiere/buchungen` sind sonst leere Listen ohne Zahlenspalten.
|
||||||
|
*/
|
||||||
|
class E2EGrundbestand
|
||||||
|
{
|
||||||
|
/** Am Präfix erkennt man die Zeilen wieder; [[E2EAufraeumer]] löscht sie. */
|
||||||
|
public const PRAEFIX = 'E2E Grundbestand';
|
||||||
|
|
||||||
|
/** Mehr als Vermoegensentwicklung::SERIEN, sonst fehlt „Übrige Konten“. */
|
||||||
|
public const KONTEN = Vermoegensentwicklung::SERIEN + 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stellt den Bestand her.
|
||||||
|
*
|
||||||
|
* @return array<string,int> Gattung → Anzahl
|
||||||
|
*/
|
||||||
|
public static function anlegen(int $benutzerId): array
|
||||||
|
{
|
||||||
|
Mandant::setze($benutzerId);
|
||||||
|
|
||||||
|
return (array)Database::transaktion(static function (): array {
|
||||||
|
$jahr = (int)date('Y');
|
||||||
|
$klasse = Klasse::anlegen(self::PRAEFIX . ' Privat');
|
||||||
|
|
||||||
|
$kategorien = [
|
||||||
|
'ausgabe' => Kategorie::anlegen(['name' => self::PRAEFIX . ' Einkauf', 'art' => 'ausgabe']),
|
||||||
|
'wohnen' => Kategorie::anlegen(['name' => self::PRAEFIX . ' Wohnen', 'art' => 'ausgabe']),
|
||||||
|
'einnahme' => Kategorie::anlegen(['name' => self::PRAEFIX . ' Gehalt', 'art' => 'einnahme']),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Absteigende Anfangssalden: Das Vermögensdiagramm ordnet nach
|
||||||
|
// Größe, und nur bei eindeutiger Rangfolge stehen auf Ebene 2
|
||||||
|
// verlässlich andere Konten als auf Ebene 1.
|
||||||
|
$konten = [];
|
||||||
|
for ($nr = 1; $nr <= self::KONTEN; $nr++) {
|
||||||
|
$konten[] = Konto::anlegen([
|
||||||
|
'name' => sprintf('%s Konto %02d', self::PRAEFIX, $nr),
|
||||||
|
'kontoart' => $nr % 3 === 0 ? 'sparkonto' : 'bank',
|
||||||
|
'anfangssaldo' => 10000 - $nr * 250,
|
||||||
|
'aktiv' => 1,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$depot = Konto::anlegen([
|
||||||
|
'name' => self::PRAEFIX . ' Depot',
|
||||||
|
'kontoart' => 'depot',
|
||||||
|
'anfangssaldo' => 0,
|
||||||
|
'aktiv' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$buchungen = 0;
|
||||||
|
|
||||||
|
// Jeden Monat des laufenden Jahres füllen, damit die monatliche
|
||||||
|
// Aufteilung zwölf Spalten mit Werten zeigt.
|
||||||
|
for ($monat = 1; $monat <= 12; $monat++) {
|
||||||
|
$datum = sprintf('%04d-%02d-05', $jahr, $monat);
|
||||||
|
|
||||||
|
Buchung::anlegen([
|
||||||
|
'konto_id' => $konten[0], 'datum' => $datum, 'betrag' => -49.90,
|
||||||
|
'empfaenger' => 'Supermarkt', 'kategorie_id' => $kategorien['ausgabe'],
|
||||||
|
'klasse_id' => $klasse,
|
||||||
|
]);
|
||||||
|
Buchung::anlegen([
|
||||||
|
'konto_id' => $konten[1], 'datum' => $datum, 'betrag' => -820.00,
|
||||||
|
'empfaenger' => 'Miete', 'kategorie_id' => $kategorien['wohnen'],
|
||||||
|
]);
|
||||||
|
Buchung::anlegen([
|
||||||
|
'konto_id' => $konten[0], 'datum' => $datum, 'betrag' => 2400.00,
|
||||||
|
'empfaenger' => 'Arbeitgeber', 'kategorie_id' => $kategorien['einnahme'],
|
||||||
|
]);
|
||||||
|
$buchungen += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Das Vorjahr für den Vergleichsbericht – ein Quartal genügt, er
|
||||||
|
// stellt Summen gegenüber, keine Monatsverläufe.
|
||||||
|
for ($monat = 1; $monat <= 3; $monat++) {
|
||||||
|
Buchung::anlegen([
|
||||||
|
'konto_id' => $konten[0], 'datum' => sprintf('%04d-%02d-12', $jahr - 1, $monat),
|
||||||
|
'betrag' => -60.00, 'empfaenger' => 'Supermarkt',
|
||||||
|
'kategorie_id' => $kategorien['ausgabe'],
|
||||||
|
]);
|
||||||
|
$buchungen++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wertpapier = Wertpapier::anlegen([
|
||||||
|
'name' => self::PRAEFIX . ' Aktie', 'isin' => 'DE0007236101',
|
||||||
|
'wkn' => '723610', 'art' => 'aktie', 'waehrung' => 'EUR', 'aktiv' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Depotbuchung::anlegen([
|
||||||
|
'konto_id' => $depot,
|
||||||
|
'wertpapier_id' => $wertpapier,
|
||||||
|
'wertpapiername' => self::PRAEFIX . ' Aktie',
|
||||||
|
'datum' => sprintf('%04d-02-10', $jahr),
|
||||||
|
'vorgang' => 'kauf',
|
||||||
|
'stueck' => 25,
|
||||||
|
'betrag' => 1250.00,
|
||||||
|
'gebuehren' => 9.90,
|
||||||
|
'verrechnungskonto_id' => $konten[0],
|
||||||
|
]);
|
||||||
|
$buchungen++;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'konten' => count($konten) + 1,
|
||||||
|
'kategorien' => count($kategorien),
|
||||||
|
'klassen' => 1,
|
||||||
|
'wertpapiere' => 1,
|
||||||
|
'buchungen' => $buchungen,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
274
tests/41_E2EAufraeumenTest.php
Normal file
274
tests/41_E2EAufraeumenTest.php
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
<?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'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,6 +51,57 @@ Hierher gehört nur, was einen Browser braucht: Formularabläufe, Redirects,
|
||||||
gerenderte Views, JavaScript. Rechen- und Model-Logik gehört in die PHP-Suite
|
gerenderte Views, JavaScript. Rechen- und Model-Logik gehört in die PHP-Suite
|
||||||
(`tests/NN_*.php`) – schneller und isoliert.
|
(`tests/NN_*.php`) – schneller und isoliert.
|
||||||
|
|
||||||
|
## Aufräumen
|
||||||
|
|
||||||
|
Jeder Lauf legt Konten, Kategorien, Klassen und Buchungen neu an – mit einer
|
||||||
|
Zeitmarke im Namen, damit zwei Läufe sich nicht ins Gehege kommen. Damit sich
|
||||||
|
das nicht ansammelt, räumt `run.sh` **vor** jedem Lauf auf:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -u www-data php php scripts/e2e-aufraeumen.php
|
||||||
|
docker compose exec -u www-data php php scripts/e2e-aufraeumen.php --probe # nur zählen
|
||||||
|
```
|
||||||
|
|
||||||
|
Vor dem Lauf und nicht danach, weil ein abgebrochener Lauf sonst einen Bestand
|
||||||
|
hinterlässt, an den niemand mehr denkt – und weil die Daten des letzten Laufs
|
||||||
|
so bis zum nächsten für die Fehlersuche stehen bleiben.
|
||||||
|
|
||||||
|
Gelöscht wird alles, was am Testnutzer hängt, nicht, was nach Testdaten
|
||||||
|
aussieht: Ein Namensmuster übersähe die Buchung „Supermarkt“ und träfe
|
||||||
|
irgendwann etwas Echtes. Der Nutzer selbst bleibt stehen, und er muss den
|
||||||
|
Anzeigenamen tragen, den `scripts/e2e-benutzer.php` setzt – sonst bricht das
|
||||||
|
Skript ab, statt einem echten Zugang den Bestand zu leeren.
|
||||||
|
|
||||||
|
`E2E_KEIN_AUFRAEUMEN=1` lässt den Bestand stehen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
E2E_KEIN_AUFRAEUMEN=1 tests/e2e/run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Grundbestand
|
||||||
|
|
||||||
|
Nach dem Aufräumen legt `run.sh` einen festgelegten Grundbestand an:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -u www-data php php scripts/e2e-grundbestand.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Ein Teil der Tests prüft die Darstellung, nicht das Rechnen – dass ein Bericht
|
||||||
|
eine Tabelle statt eines Platzhalters zeigt, dass Zahlenspalten rechtsbündig
|
||||||
|
stehen, dass „Übrige Konten“ eine Ebene tiefer führt. Solche Tests brauchen
|
||||||
|
einen Bestand, legen ihn aber nicht selbst an; bis zum Aufräumen lebten sie von
|
||||||
|
den Resten früherer Läufe und fanden je nach Vorgeschichte etwas anderes vor.
|
||||||
|
|
||||||
|
Die Zuschnitte in `E2EGrundbestand` sind deshalb nicht beliebig: mehr Konten als
|
||||||
|
`Vermoegensentwicklung::SERIEN` (sonst fehlt „Übrige Konten“), Buchungen in jedem
|
||||||
|
Monat des laufenden Jahres (sonst bleiben Monatsspalten leer), ein Vorjahr für
|
||||||
|
den Vergleichsbericht, ein Depot mit Wertpapiergeschäft für die Wertpapierlisten.
|
||||||
|
Wer daran etwas ändert, sieht in `tests/41_E2EAufraeumenTest.php` nach: Dort steht
|
||||||
|
zu jeder Grenze, welcher Test daran hängt.
|
||||||
|
|
||||||
|
Wer eine Spec schreibt, die eigene Daten braucht, legt sie weiterhin selbst an –
|
||||||
|
der Grundbestand trägt nur die Tests, die irgendeinen Bestand voraussetzen.
|
||||||
|
|
||||||
## Gemeinsame Helfer
|
## Gemeinsame Helfer
|
||||||
|
|
||||||
`specs/hilfen.js` liegt neben den Specs, endet aber nicht auf `.spec.js` und wird
|
`specs/hilfen.js` liegt neben den Specs, endet aber nicht auf `.spec.js` und wird
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,24 @@ IMAGE=$(sed -n 's/^ *image: *//p' "$RUNNER_DIR/docker-compose.yml" | head -n1)
|
||||||
docker image inspect "$IMAGE" >/dev/null 2>&1 \
|
docker image inspect "$IMAGE" >/dev/null 2>&1 \
|
||||||
|| skip "Playwright-Image $IMAGE nicht vorhanden (docker pull $IMAGE)"
|
|| skip "Playwright-Image $IMAGE nicht vorhanden (docker pull $IMAGE)"
|
||||||
|
|
||||||
|
# Vor dem Lauf aufräumen, nicht danach: Ein abgebrochener Lauf hinterlässt sonst
|
||||||
|
# einen Bestand, an den niemand mehr denkt – und bis hierher bleiben die Daten
|
||||||
|
# des letzten Laufs für die Fehlersuche stehen. E2E_KEIN_AUFRAEUMEN=1 lässt sie
|
||||||
|
# stehen, wer sie über den nächsten Lauf hinaus braucht.
|
||||||
|
if [ "${E2E_KEIN_AUFRAEUMEN:-0}" != "1" ]; then
|
||||||
|
PROJEKT=$(CDPATH= cd -- "$TEST_DIR/../.." && pwd)
|
||||||
|
fm() { (cd "$PROJEKT" && docker compose exec -T -u www-data php php "$@"); }
|
||||||
|
|
||||||
|
# Der Grundbestand nur nach erfolgreichem Aufräumen: Sonst stünde er ein
|
||||||
|
# zweites Mal neben dem alten und verfälschte, was die Tests vorfinden.
|
||||||
|
if fm scripts/e2e-aufraeumen.php --still; then
|
||||||
|
fm scripts/e2e-grundbestand.php --still \
|
||||||
|
|| echo "WARNUNG: Grundbestand fehlgeschlagen — Tests, die einen Bestand erwarten, fallen aus." >&2
|
||||||
|
else
|
||||||
|
echo "WARNUNG: Aufräumen fehlgeschlagen — der Lauf beginnt auf dem alten Bestand." >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Zugangsdaten des Testnutzers durchreichen; der Runner kennt nur BASE_URL.
|
# Zugangsdaten des Testnutzers durchreichen; der Runner kennt nur BASE_URL.
|
||||||
cd "$RUNNER_DIR"
|
cd "$RUNNER_DIR"
|
||||||
TEST_DIR="$TEST_DIR" BASE_URL="$BASE_URL" exec docker compose run --rm \
|
TEST_DIR="$TEST_DIR" BASE_URL="$BASE_URL" exec docker compose run --rm \
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue