Merge pull request 'Fix #195 + #198: run-tests.sh-Commit-Gate und fehlender View-Import in suppliers/list.php' (#197) from fds2610-bot/Steuerkram:main into main
Reviewed-on: https://codeberg.org/fds2610/Steuerkram/pulls/197
This commit is contained in:
commit
941f69b5e3
9 changed files with 237 additions and 44 deletions
|
|
@ -56,9 +56,9 @@ Tests liegen in `tests/` und folgen dem Namensschema `NN_FunktionsnameTest.php`.
|
||||||
Jede Testklasse erweitert `TestCase` und implementiert `run()`.
|
Jede Testklasse erweitert `TestCase` und implementiert `run()`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose exec -u www-data web php tests/run.php # alle Tests ausführen
|
docker compose exec -u www-data web php tests/run.php # alle Tests ausführen
|
||||||
./scripts/run-tests.sh # Tests + automatische Issue-Erstellung bei Fehlern
|
docker compose exec -u www-data web ./scripts/run-tests.sh # Tests + automatische Issue-Erstellung bei Fehlern
|
||||||
./scripts/run-tests.sh 42 # Tests; bei Fehlern Kommentar in Issue #42
|
docker compose exec -u www-data web ./scripts/run-tests.sh 42 # Tests; bei Fehlern Kommentar in Issue #42
|
||||||
```
|
```
|
||||||
|
|
||||||
**Immer `-u www-data`:** ohne die Option laufen die Tests als root und legen
|
**Immer `-u www-data`:** ohne die Option laufen die Tests als root und legen
|
||||||
|
|
|
||||||
12
Changes.md
12
Changes.md
|
|
@ -2,6 +2,18 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-08-21 – Lieferantenliste (/suppliers) lief in HTTP 500: fehlender View-Import (#198)
|
||||||
|
|
||||||
|
- **`src/Views/suppliers/list.php`:** Die View rief `View::formatDate()` ohne `use Steuerkram\View;` auf. Views werden per `include` eingebunden und laufen dadurch im globalen Namespace – das `use` des einbindenden Controllers gilt dort nicht, der Aufruf ging also auf `\View::formatDate()` und die Seite starb mit `Class "View" not found`. Sichtbar wurde das erst mit dem ersten Lieferanten, weil die Zeile in der `foreach`-Schleife steht. Import ergänzt; alle übrigen Views hatten ihn bereits.
|
||||||
|
- **`tests/99_ViewKlassenImportTest.php`** (neu): prüft alle Views in `src/Views/` statisch – jeder unqualifizierte `Klasse::`-Aufruf muss entweder per `use` importiert sein oder eine global existierende Klasse (`DateTime`, `PDO`, …) meinen. Fängt fehlende Importe ab, bevor sie im Betrieb auffallen.
|
||||||
|
- **`tests/e2e/specs/rechnung-export.spec.js`:** klickte noch `button[onclick^="showAddLineDialog"]`; der Inline-Handler-Umbau aus #160 hat daraus `data-action="show-add-line-dialog"` gemacht – fünf E2E-Tests liefen deshalb in den Timeout. Selektor nachgezogen.
|
||||||
|
|
||||||
|
## 2026-08-21 – run-tests.sh: Commit-Gate meldete fälschlich Erfolg bei fehlgeschlagenen Tests (#195)
|
||||||
|
|
||||||
|
- **`scripts/run-tests.sh`:** Die Auswertung von `php tests/run.php --json` hing an `python3`, das weder im Container noch auf dem Host installiert ist – der Fallback `|| echo "0"` verwandelte jeden Parse-Fehler in „0 fehlgeschlagene Tests", das Commit-Gate griff also nie. Erfolg/Misserfolg hängt jetzt direkt am Exit-Code von `php tests/run.php --json`; Detailauswertung (`failed`, `failures`) läuft über `php -r` statt Python. Lässt sich die JSON-Ausgabe nicht auswerten, bricht das Skript mit einem Fehler ab statt fälschlich Erfolg zu melden. Der zuvor ungenutzte `EXIT_CODE` (verworfen durch ein voranstehendes `|| true`) wird jetzt tatsächlich ausgewertet. Die JSON-Payloads für die automatische Issue-Erstellung werden ebenfalls per `php -r` statt Python gebaut (Body-Text jetzt mit echten Zeilenumbrüchen statt literalem `\n`).
|
||||||
|
- **`tests/run.php` / `tests/TestCase.php`:** `--json`-Modus gab bisher zusätzlich zur JSON-Ausgabe die komplette Fortschrittsanzeige (✓/✗ je Assertion) auf STDOUT aus – dadurch war die JSON-Ausgabe nie für sich allein parsbar, unabhängig vom Python-Problem. `TestCase::setQuiet()` unterdrückt diese Ausgabe im `--json`-Modus; PHP-Warnungen/-Notices einzelner Tests gehen in diesem Modus zusätzlich per `ini_set('display_errors', 'stderr')` nach STDERR statt STDOUT.
|
||||||
|
- **`CLAUDE.md`:** Aufrufzeilen für `./scripts/run-tests.sh` auf `docker compose exec -u www-data web ./scripts/run-tests.sh` korrigiert – das Skript läuft nur im Container, auf dem Host ist kein PHP installiert.
|
||||||
|
|
||||||
## 2026-08-21 – SMTP-SSRF-Schutz: IP-Pinning gegen DNS-Rebinding, Admin-Pflicht für SMTP-Einstellungen (#193)
|
## 2026-08-21 – SMTP-SSRF-Schutz: IP-Pinning gegen DNS-Rebinding, Admin-Pflicht für SMTP-Einstellungen (#193)
|
||||||
|
|
||||||
- **`src/Services/SsrfGuard.php`:** neue `resolvePinnedHost(string $host): string[]` – wie `assertPublicHost()`, gibt aber zusätzlich die geprüfte(n) IP-Adresse(n) zurück, analog zu `resolvePinned()` für https-URLs. `assertPublicHost()` ruft sie jetzt intern auf.
|
- **`src/Services/SsrfGuard.php`:** neue `resolvePinnedHost(string $host): string[]` – wie `assertPublicHost()`, gibt aber zusätzlich die geprüfte(n) IP-Adresse(n) zurück, analog zu `resolvePinned()` für https-URLs. `assertPublicHost()` ruft sie jetzt intern auf.
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Steuerkram Test-Runner mit automatischer Issue-Erstellung
|
# Steuerkram Test-Runner mit automatischer Issue-Erstellung
|
||||||
#
|
#
|
||||||
# Verwendung:
|
# Verwendung (im Container):
|
||||||
# ./scripts/run-tests.sh [ISSUE_NUMMER]
|
# docker compose exec -u www-data web ./scripts/run-tests.sh [ISSUE_NUMMER]
|
||||||
#
|
#
|
||||||
# Parameter:
|
# Parameter:
|
||||||
# ISSUE_NUMMER (optional) Nummer des aktuell bearbeiteten Issues,
|
# ISSUE_NUMMER (optional) Nummer des aktuell bearbeiteten Issues,
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
# Umgebungsvariablen:
|
# Umgebungsvariablen:
|
||||||
# CODEBERG_TOKEN Codeberg-API-Token (für Issue-Erstellung)
|
# CODEBERG_TOKEN Codeberg-API-Token (für Issue-Erstellung)
|
||||||
#
|
#
|
||||||
# Exit-Code: 0 = alle Tests OK, 1 = Fehler gefunden
|
# Exit-Code: 0 = alle Tests OK, 1 = Fehler gefunden (oder Auswertung fehlgeschlagen)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
@ -39,22 +39,42 @@ fi
|
||||||
# ── Tests ausführen ──────────────────────────────────────────
|
# ── Tests ausführen ──────────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "Führe Tests aus..."
|
echo "Führe Tests aus..."
|
||||||
JSON_OUTPUT=$($PHP tests/run.php --json 2>&1) || true
|
# STDERR wird bewusst NICHT mitgefangen: tests/run.php schreibt im --json-Modus
|
||||||
EXIT_CODE=$?
|
# etwaige PHP-Warnungen/-Notices dorthin, damit STDOUT ausschließlich das JSON
|
||||||
|
# enthält (siehe tests/run.php). STDERR landet direkt im Terminal.
|
||||||
|
JSON_OUTPUT=$($PHP tests/run.php --json) && EXIT_CODE=0 || EXIT_CODE=$?
|
||||||
|
|
||||||
# Auch die lesbare Ausgabe zeigen
|
# Auch die lesbare Ausgabe zeigen
|
||||||
$PHP tests/run.php || true
|
$PHP tests/run.php || true
|
||||||
|
|
||||||
# ── Ergebnis auswerten ───────────────────────────────────────
|
# ── Ergebnis auswerten ───────────────────────────────────────
|
||||||
FAILED=$(echo "$JSON_OUTPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('failed',0))" 2>/dev/null || echo "0")
|
if [ "$EXIT_CODE" -eq 0 ]; then
|
||||||
FAILURES_JSON=$(echo "$JSON_OUTPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d.get('failures',[])))" 2>/dev/null || echo "[]")
|
|
||||||
|
|
||||||
if [ "$FAILED" = "0" ] || [ "$FAILED" = "" ]; then
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✓ Alle Tests bestanden. Commit kann fortgesetzt werden."
|
echo "✓ Alle Tests bestanden. Commit kann fortgesetzt werden."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# tests/run.php meldet Fehlschläge (oder einen sonstigen Fehler) über den
|
||||||
|
# Exit-Code. Die Details holen wir uns per PHP aus dem JSON – lässt sich die
|
||||||
|
# Ausgabe nicht auswerten, bricht das Skript hier ab (set -e), statt
|
||||||
|
# fälschlich Erfolg zu melden.
|
||||||
|
FAILED=$(echo "$JSON_OUTPUT" | php -r '
|
||||||
|
$d = json_decode(stream_get_contents(STDIN), true);
|
||||||
|
if (!is_array($d) || !array_key_exists("failed", $d)) {
|
||||||
|
fwrite(STDERR, "Ungültige JSON-Ausgabe von tests/run.php --json\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo $d["failed"];
|
||||||
|
')
|
||||||
|
FAILURES_JSON=$(echo "$JSON_OUTPUT" | php -r '
|
||||||
|
$d = json_decode(stream_get_contents(STDIN), true);
|
||||||
|
if (!is_array($d) || !array_key_exists("failures", $d)) {
|
||||||
|
fwrite(STDERR, "Ungültige JSON-Ausgabe von tests/run.php --json\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo json_encode($d["failures"]);
|
||||||
|
')
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✗ ${FAILED} Test(s) fehlgeschlagen."
|
echo "✗ ${FAILED} Test(s) fehlgeschlagen."
|
||||||
|
|
||||||
|
|
@ -63,7 +83,10 @@ if [ -z "${CODEBERG_TOKEN:-}" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Hinweis: CODEBERG_TOKEN nicht gesetzt – keine automatischen Issues erstellt."
|
echo "Hinweis: CODEBERG_TOKEN nicht gesetzt – keine automatischen Issues erstellt."
|
||||||
echo "Fehlgeschlagene Tests:"
|
echo "Fehlgeschlagene Tests:"
|
||||||
echo "$FAILURES_JSON" | python3 -c "import json,sys; [print(' •', f) for f in json.load(sys.stdin)]" 2>/dev/null || true
|
echo "$FAILURES_JSON" | php -r '
|
||||||
|
$d = json_decode(stream_get_contents(STDIN), true);
|
||||||
|
foreach ((is_array($d) ? $d : []) as $f) { echo " • {$f}\n"; }
|
||||||
|
'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -79,51 +102,83 @@ while IFS= read -r failure; do
|
||||||
# Titel kürzen (max. 100 Zeichen)
|
# Titel kürzen (max. 100 Zeichen)
|
||||||
TITLE="[BUG] Test fehlgeschlagen: ${failure:0:80}"
|
TITLE="[BUG] Test fehlgeschlagen: ${failure:0:80}"
|
||||||
|
|
||||||
# Body zusammenbauen
|
# Body zusammenbauen (mit echten Zeilenumbrüchen, kein "\n"-Text)
|
||||||
BODY="## Automatisch erkannter Testfehler\n\n"
|
BODY_LINES=(
|
||||||
BODY+="**Fehlermeldung:** \`${failure}\`\n\n"
|
"## Automatisch erkannter Testfehler"
|
||||||
BODY+="**Erkannt am:** $(date '+%Y-%m-%d %H:%M:%S')\n\n"
|
""
|
||||||
BODY+="**Commit:** $(git rev-parse --short HEAD 2>/dev/null || echo 'unbekannt')\n\n"
|
"**Fehlermeldung:** \`${failure}\`"
|
||||||
|
""
|
||||||
|
"**Erkannt am:** $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
""
|
||||||
|
"**Commit:** $(git rev-parse --short HEAD 2>/dev/null || echo 'unbekannt')"
|
||||||
|
""
|
||||||
|
)
|
||||||
if [ -n "$CURRENT_ISSUE" ]; then
|
if [ -n "$CURRENT_ISSUE" ]; then
|
||||||
BODY+="Dieser Fehler wurde beim Bearbeiten von Issue #${CURRENT_ISSUE} entdeckt.\n\n"
|
BODY_LINES+=("Dieser Fehler wurde beim Bearbeiten von Issue #${CURRENT_ISSUE} entdeckt." "")
|
||||||
fi
|
fi
|
||||||
|
BODY_LINES+=(
|
||||||
|
"### Schritte zur Reproduktion"
|
||||||
|
"1. \`docker compose exec -u www-data web php tests/run.php\` ausführen"
|
||||||
|
"2. Fehler tritt im oben genannten Test auf"
|
||||||
|
""
|
||||||
|
"_Dieser Issue wurde automatisch durch den Test-Runner erstellt._"
|
||||||
|
"_Das Label 'automate' wurde bewusst NICHT gesetzt (Human-in-the-loop)._"
|
||||||
|
)
|
||||||
|
BODY=$(printf '%s\n' "${BODY_LINES[@]}")
|
||||||
|
|
||||||
BODY+="### Schritte zur Reproduktion\n"
|
PAYLOAD=$(TITLE="$TITLE" BODY="$BODY" php -r '
|
||||||
BODY+="1. \`docker compose exec -u www-data web php tests/run.php\` ausführen\n"
|
echo json_encode([
|
||||||
BODY+="2. Fehler tritt im oben genannten Test auf\n\n"
|
"title" => getenv("TITLE"),
|
||||||
BODY+="_Dieser Issue wurde automatisch durch den Test-Runner erstellt._\n"
|
"body" => getenv("BODY"),
|
||||||
BODY+="_Das Label 'automate' wurde bewusst NICHT gesetzt (Human-in-the-loop)._"
|
"labels" => [],
|
||||||
|
]);
|
||||||
|
')
|
||||||
|
|
||||||
RESPONSE=$(curl -s -X POST \
|
RESPONSE=$(curl -s -X POST \
|
||||||
-H "Authorization: token $CODEBERG_TOKEN" \
|
-H "Authorization: token $CODEBERG_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$(python3 -c "import json; print(json.dumps({'title': '${TITLE}', 'body': '${BODY}', 'labels': []}))")" \
|
-d "$PAYLOAD" \
|
||||||
"${API_BASE}/repos/${REPO}/issues")
|
"${API_BASE}/repos/${REPO}/issues")
|
||||||
|
|
||||||
ISSUE_NR=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('number','?'))" 2>/dev/null || echo "?")
|
ISSUE_NR=$(echo "$RESPONSE" | php -r '
|
||||||
|
$d = json_decode(stream_get_contents(STDIN), true);
|
||||||
|
echo (is_array($d) && isset($d["number"])) ? $d["number"] : "?";
|
||||||
|
')
|
||||||
CREATED_ISSUES+=("#${ISSUE_NR}")
|
CREATED_ISSUES+=("#${ISSUE_NR}")
|
||||||
echo " → Issue #${ISSUE_NR} erstellt: ${failure:0:60}..."
|
echo " → Issue #${ISSUE_NR} erstellt: ${failure:0:60}..."
|
||||||
|
|
||||||
done < <(echo "$FAILURES_JSON" | python3 -c "import json,sys; [print(f) for f in json.load(sys.stdin)]" 2>/dev/null)
|
done < <(echo "$FAILURES_JSON" | php -r '
|
||||||
|
$d = json_decode(stream_get_contents(STDIN), true);
|
||||||
|
foreach ((is_array($d) ? $d : []) as $f) { echo $f, "\n"; }
|
||||||
|
')
|
||||||
|
|
||||||
# ── Kommentar im aktuellen Issue hinterlassen ─────────────────
|
# ── Kommentar im aktuellen Issue hinterlassen ─────────────────
|
||||||
if [ -n "$CURRENT_ISSUE" ] && [ ${#CREATED_ISSUES[@]} -gt 0 ]; then
|
if [ -n "$CURRENT_ISSUE" ] && [ ${#CREATED_ISSUES[@]} -gt 0 ]; then
|
||||||
ISSUE_LINKS=$(printf '%s, ' "${CREATED_ISSUES[@]}")
|
ISSUE_LINKS=$(printf '%s, ' "${CREATED_ISSUES[@]}")
|
||||||
ISSUE_LINKS="${ISSUE_LINKS%, }"
|
ISSUE_LINKS="${ISSUE_LINKS%, }"
|
||||||
|
|
||||||
COMMENT="### Automatische Testläufe\n\n"
|
COMMENT_LINES=(
|
||||||
COMMENT+="Beim Bearbeiten dieses Issues wurden **${FAILED} Test(s)** fehlgeschlagen.\n\n"
|
"### Automatische Testläufe"
|
||||||
COMMENT+="Folgende Bug-Issues wurden automatisch erstellt (ohne 'automate'-Label – bitte manuell prüfen):\n\n"
|
""
|
||||||
|
"Beim Bearbeiten dieses Issues wurden **${FAILED} Test(s)** fehlgeschlagen."
|
||||||
|
""
|
||||||
|
"Folgende Bug-Issues wurden automatisch erstellt (ohne 'automate'-Label – bitte manuell prüfen):"
|
||||||
|
""
|
||||||
|
)
|
||||||
for issue in "${CREATED_ISSUES[@]}"; do
|
for issue in "${CREATED_ISSUES[@]}"; do
|
||||||
COMMENT+="- ${issue}\n"
|
COMMENT_LINES+=("- ${issue}")
|
||||||
done
|
done
|
||||||
COMMENT+="\n_Commit: $(git rev-parse --short HEAD 2>/dev/null || echo 'unbekannt')_"
|
COMMENT_LINES+=("" "_Commit: $(git rev-parse --short HEAD 2>/dev/null || echo 'unbekannt')_")
|
||||||
|
COMMENT=$(printf '%s\n' "${COMMENT_LINES[@]}")
|
||||||
|
|
||||||
|
PAYLOAD=$(COMMENT="$COMMENT" php -r '
|
||||||
|
echo json_encode(["body" => getenv("COMMENT")]);
|
||||||
|
')
|
||||||
|
|
||||||
curl -s -X POST \
|
curl -s -X POST \
|
||||||
-H "Authorization: token $CODEBERG_TOKEN" \
|
-H "Authorization: token $CODEBERG_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$(python3 -c "import json; print(json.dumps({'body': '${COMMENT}'}))")" \
|
-d "$PAYLOAD" \
|
||||||
"${API_BASE}/repos/${REPO}/issues/${CURRENT_ISSUE}/comments" > /dev/null
|
"${API_BASE}/repos/${REPO}/issues/${CURRENT_ISSUE}/comments" > /dev/null
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
<?php use Steuerkram\View; ?>
|
||||||
|
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
1-260802
|
1-260821
|
||||||
|
|
|
||||||
96
tests/99_ViewKlassenImportTest.php
Normal file
96
tests/99_ViewKlassenImportTest.php
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test 99: Klassen-Importe in den Views (Fix für /suppliers, HTTP 500)
|
||||||
|
*
|
||||||
|
* Views werden per include eingebunden und laufen dadurch im globalen
|
||||||
|
* Namespace – das `use Steuerkram\View;` des einbindenden Controllers gilt
|
||||||
|
* dort nicht. Fehlt der Import in der View selbst, wird `View::formatDate()`
|
||||||
|
* zu `\View::formatDate()` und die Seite stirbt mit "Class View not found".
|
||||||
|
*
|
||||||
|
* Der Fehler bleibt so lange unsichtbar, wie die betroffene Zeile nicht
|
||||||
|
* ausgeführt wird (in suppliers/list.php stand sie in einer foreach-Schleife,
|
||||||
|
* schlug also erst mit dem ersten Lieferanten zu). Dieser Test prüft alle
|
||||||
|
* Views statisch, damit ein fehlender Import nicht erst im Betrieb auffällt.
|
||||||
|
*/
|
||||||
|
class ViewKlassenImportTest extends TestCase
|
||||||
|
{
|
||||||
|
protected string $testName = 'Test 99: Klassen-Importe in den Views';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$viewDir = realpath(__DIR__ . '/../src/Views');
|
||||||
|
$this->assert($viewDir !== false, 'src/Views/ existiert');
|
||||||
|
|
||||||
|
$dateien = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($viewDir));
|
||||||
|
$geprueft = 0;
|
||||||
|
$fehler = [];
|
||||||
|
|
||||||
|
foreach ($dateien as $datei) {
|
||||||
|
if (!$datei->isFile() || $datei->getExtension() !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$inhalt = file_get_contents($datei->getPathname());
|
||||||
|
$relativ = str_replace($viewDir . '/', '', $datei->getPathname());
|
||||||
|
$geprueft++;
|
||||||
|
|
||||||
|
foreach ($this->unaufgeloesteKlassen($inhalt) as $klasse) {
|
||||||
|
$fehler[] = "$relativ verwendet $klasse:: ohne Import";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertGreaterThan(20, $geprueft, 'Es wurden Views eingelesen');
|
||||||
|
$this->assert(
|
||||||
|
$fehler === [],
|
||||||
|
'Jede View importiert die Klassen, die sie statisch aufruft'
|
||||||
|
. ($fehler === [] ? '' : ': ' . implode('; ', $fehler))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert die in $inhalt statisch aufgerufenen Klassennamen, die weder
|
||||||
|
* global existieren noch per use-Statement importiert sind.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function unaufgeloesteKlassen(string $inhalt): array
|
||||||
|
{
|
||||||
|
preg_match_all(
|
||||||
|
'/\buse\s+([A-Za-z0-9_\\\\]+)(?:\s+as\s+([A-Za-z0-9_]+))?\s*;/i',
|
||||||
|
$inhalt,
|
||||||
|
$useTreffer,
|
||||||
|
PREG_SET_ORDER
|
||||||
|
);
|
||||||
|
|
||||||
|
$importiert = [];
|
||||||
|
foreach ($useTreffer as $treffer) {
|
||||||
|
$teile = explode('\\', $treffer[1]);
|
||||||
|
$name = $treffer[2] ?? end($teile);
|
||||||
|
$importiert[$name] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aufrufe wie View::formatDate(), aber nicht \Steuerkram\View::,
|
||||||
|
// $obj::, self::/static::/parent::
|
||||||
|
preg_match_all('/(?<![\\\\$\w>])([A-Z][A-Za-z0-9_]*)\s*::/', $inhalt, $aufrufe);
|
||||||
|
|
||||||
|
$offen = [];
|
||||||
|
foreach (array_unique($aufrufe[1]) as $klasse) {
|
||||||
|
if (isset($importiert[$klasse])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Global existierende Klassen (DateTime, PDO, …) sind unkritisch.
|
||||||
|
if (class_exists($klasse, false) || interface_exists($klasse, false) || enum_exists($klasse, false)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (class_exists('\\' . $klasse) || interface_exists('\\' . $klasse)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$offen[] = $klasse;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $offen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,14 @@ abstract class TestCase
|
||||||
private static int $totalFailed = 0;
|
private static int $totalFailed = 0;
|
||||||
private static array $allFailures = [];
|
private static array $allFailures = [];
|
||||||
|
|
||||||
|
/** Unterdrückt die Fortschrittsausgabe (z. B. im --json-Modus von tests/run.php). */
|
||||||
|
private static bool $quiet = false;
|
||||||
|
|
||||||
|
public static function setQuiet(bool $quiet): void
|
||||||
|
{
|
||||||
|
self::$quiet = $quiet;
|
||||||
|
}
|
||||||
|
|
||||||
protected int $passed = 0;
|
protected int $passed = 0;
|
||||||
protected int $failed = 0;
|
protected int $failed = 0;
|
||||||
protected array $failures = [];
|
protected array $failures = [];
|
||||||
|
|
@ -95,6 +103,8 @@ abstract class TestCase
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!self::isInsideCleanupRoot($path)) {
|
if (!self::isInsideCleanupRoot($path)) {
|
||||||
|
// Nicht durch $quiet unterdrückt: Test 79 prüft diese Meldung gezielt
|
||||||
|
// über ob_start()/ob_get_clean() ihrer eigenen TestCase-Instanz.
|
||||||
echo " \e[33m!\e[0m Nicht aufgeräumt (außerhalb der Testverzeichnisse): {$path}\n";
|
echo " \e[33m!\e[0m Nicht aufgeräumt (außerhalb der Testverzeichnisse): {$path}\n";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -145,11 +155,15 @@ abstract class TestCase
|
||||||
{
|
{
|
||||||
if ($condition) {
|
if ($condition) {
|
||||||
$this->passed++;
|
$this->passed++;
|
||||||
echo " \e[32m✓\e[0m {$message}\n";
|
if (!self::$quiet) {
|
||||||
|
echo " \e[32m✓\e[0m {$message}\n";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->failed++;
|
$this->failed++;
|
||||||
$this->failures[] = "[{$this->testName}] {$message}";
|
$this->failures[] = "[{$this->testName}] {$message}";
|
||||||
echo " \e[31m✗\e[0m {$message}\n";
|
if (!self::$quiet) {
|
||||||
|
echo " \e[31m✗\e[0m {$message}\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +199,9 @@ abstract class TestCase
|
||||||
|
|
||||||
public function execute(): array
|
public function execute(): array
|
||||||
{
|
{
|
||||||
echo "\n\e[1m{$this->testName}\e[0m\n";
|
if (!self::$quiet) {
|
||||||
|
echo "\n\e[1m{$this->testName}\e[0m\n";
|
||||||
|
}
|
||||||
$this->setUp();
|
$this->setUp();
|
||||||
try {
|
try {
|
||||||
$this->run();
|
$this->run();
|
||||||
|
|
@ -193,15 +209,19 @@ abstract class TestCase
|
||||||
$this->failed++;
|
$this->failed++;
|
||||||
$msg = "[{$this->testName}] Ausnahme: " . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
|
$msg = "[{$this->testName}] Ausnahme: " . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
|
||||||
$this->failures[] = $msg;
|
$this->failures[] = $msg;
|
||||||
echo " \e[31m✗\e[0m Unerwartete Ausnahme: " . $e->getMessage() . "\n";
|
if (!self::$quiet) {
|
||||||
|
echo " \e[31m✗\e[0m Unerwartete Ausnahme: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
$this->tearDown();
|
$this->tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
$status = $this->failed === 0
|
if (!self::$quiet) {
|
||||||
? "\e[32mBestanden ({$this->passed}/{$this->passed})\e[0m"
|
$status = $this->failed === 0
|
||||||
: "\e[31m{$this->failed} Fehler, {$this->passed} OK\e[0m";
|
? "\e[32mBestanden ({$this->passed}/{$this->passed})\e[0m"
|
||||||
echo " → {$status}\n";
|
: "\e[31m{$this->failed} Fehler, {$this->passed} OK\e[0m";
|
||||||
|
echo " → {$status}\n";
|
||||||
|
}
|
||||||
|
|
||||||
self::$totalPassed += $this->passed;
|
self::$totalPassed += $this->passed;
|
||||||
self::$totalFailed += $this->failed;
|
self::$totalFailed += $this->failed;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ async function entwurfAnlegen(page, { tag, einzelpreis = '100.00', ustSatz = '19
|
||||||
await page.fill('#betreff', tag)
|
await page.fill('#betreff', tag)
|
||||||
|
|
||||||
// Position über den "+"-Button und das Popup hinzufügen.
|
// Position über den "+"-Button und das Popup hinzufügen.
|
||||||
await page.click('button[onclick^="showAddLineDialog"]')
|
await page.click('button[data-action="show-add-line-dialog"]')
|
||||||
await page.click('#add-line-popup >> text=Neue Position')
|
await page.click('#add-line-popup >> text=Neue Position')
|
||||||
|
|
||||||
const zeile = page.locator('#invoice-lines-body tr').last()
|
const zeile = page.locator('#invoice-lines-body tr').last()
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,14 @@ require __DIR__ . '/TestCase.php';
|
||||||
\Steuerkram\Config::load();
|
\Steuerkram\Config::load();
|
||||||
|
|
||||||
$jsonMode = in_array('--json', $argv ?? [], true);
|
$jsonMode = in_array('--json', $argv ?? [], true);
|
||||||
|
TestCase::setQuiet($jsonMode);
|
||||||
|
|
||||||
|
// PHP-Warnungen/-Notices einzelner Tests (z. B. absichtlich provozierte
|
||||||
|
// Fehlerfälle) dürfen die JSON-Ausgabe auf STDOUT nicht verunreinigen –
|
||||||
|
// scripts/run-tests.sh wertet sie sonst nicht mehr auswertbar aus.
|
||||||
|
if ($jsonMode) {
|
||||||
|
ini_set('display_errors', 'stderr');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erfasst den Bestand unterhalb der Datenverzeichnisse.
|
* Erfasst den Bestand unterhalb der Datenverzeichnisse.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue