Docker Compose mit drei Services (nginx auf Port 8000), Migrationsmechanik, Update- und Backup-Skript. Anwendung: Router mit CSRF-Prüfung, Anmeldung und Ersteinrichtung, zentraler Einstellungsdialog mit Reitern, ein- und ausschaltbares Debug-Log mit Maskierung von Zugangsdaten. Layout nach Vorbild des FinanzManagers: feste Navigation links, Werkzeugleiste oben, Tab-Leiste; Farbschema hell/dunkel/System. Tests: 51 PHP-Prüfungen und 7 Playwright-Tests, alle grün.
93 lines
3.3 KiB
PHP
93 lines
3.3 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use Finanzmanager\Config;
|
||
use Finanzmanager\Services\DebugLog;
|
||
|
||
class DebugLogTest extends TestCase
|
||
{
|
||
protected string $testName = 'Test 04: Debug-Log';
|
||
|
||
private string $urspruenglichesVerzeichnis = '';
|
||
|
||
public function run(): void
|
||
{
|
||
$this->urspruenglichesVerzeichnis = (string)Config::get('log_dir');
|
||
|
||
$verzeichnis = $this->tempPfad('_logs');
|
||
mkdir($verzeichnis, 0775, true);
|
||
Config::set('log_dir', $verzeichnis);
|
||
|
||
try {
|
||
$this->ausgeschaltetSchreibtNichts($verzeichnis);
|
||
$this->eingeschaltetSchreibt($verzeichnis);
|
||
$this->maskiertGeheimnisse();
|
||
$this->fehlerImmerProtokolliert($verzeichnis);
|
||
} finally {
|
||
Config::set('log_dir', $this->urspruenglichesVerzeichnis);
|
||
DebugLog::setAktiv(false);
|
||
}
|
||
}
|
||
|
||
private function ausgeschaltetSchreibtNichts(string $verzeichnis): void
|
||
{
|
||
DebugLog::setAktiv(false);
|
||
DebugLog::schreibe('test', 'darf nicht auftauchen');
|
||
|
||
$this->assert(
|
||
glob($verzeichnis . '/debug-*.log') === [],
|
||
'Bei ausgeschaltetem Log entsteht keine Datei'
|
||
);
|
||
}
|
||
|
||
private function eingeschaltetSchreibt(string $verzeichnis): void
|
||
{
|
||
DebugLog::setAktiv(true);
|
||
DebugLog::schreibe('test', 'eine Nachricht', ['zahl' => 42]);
|
||
|
||
$pfad = DebugLog::pfad();
|
||
$this->assert(file_exists($pfad), 'Eingeschaltet entsteht eine Logdatei');
|
||
|
||
$inhalt = (string)file_get_contents($pfad);
|
||
$this->assert(str_contains($inhalt, 'eine Nachricht'), 'Die Nachricht steht im Log');
|
||
$this->assert(str_contains($inhalt, '"zahl":42'), 'Der Kontext steht im Log');
|
||
$this->assert(str_contains($inhalt, '[test]'), 'Die Kategorie steht im Log');
|
||
}
|
||
|
||
private function maskiertGeheimnisse(): void
|
||
{
|
||
$maskiert = DebugLog::maskiere([
|
||
'benutzer' => 'felix',
|
||
'pin' => '123456',
|
||
'passwort' => 'streng geheim',
|
||
'daten' => ['api_key' => 'abc', 'betrag' => 12.5],
|
||
]);
|
||
|
||
$this->assertEquals('felix', $maskiert['benutzer'], 'Harmlose Werte bleiben lesbar');
|
||
$this->assertEquals('***', $maskiert['pin'], 'PIN wird maskiert');
|
||
$this->assertEquals('***', $maskiert['passwort'], 'Passwort wird maskiert');
|
||
$this->assertEquals('***', $maskiert['daten']['api_key'], 'Verschachtelter API-Key wird maskiert');
|
||
$this->assertEquals(12.5, $maskiert['daten']['betrag'], 'Betrag bleibt unverändert');
|
||
|
||
// Der Weg über schreibe() muss ebenfalls maskieren – sonst landet eine
|
||
// Banking-PIN über einen SQL-Parameter im Klartext im Log.
|
||
DebugLog::setAktiv(true);
|
||
DebugLog::schreibe('test', 'mit Geheimnis', ['pin' => '999888']);
|
||
|
||
$inhalt = (string)file_get_contents(DebugLog::pfad());
|
||
$this->assert(!str_contains($inhalt, '999888'), 'Die PIN taucht nicht im Log auf');
|
||
}
|
||
|
||
private function fehlerImmerProtokolliert(string $verzeichnis): void
|
||
{
|
||
DebugLog::setAktiv(false);
|
||
DebugLog::fehler(new RuntimeException('Testfehler'));
|
||
|
||
$inhalt = (string)file_get_contents(DebugLog::pfad());
|
||
$this->assert(
|
||
str_contains($inhalt, 'Testfehler'),
|
||
'Ausnahmen werden auch bei ausgeschaltetem Log protokolliert'
|
||
);
|
||
}
|
||
}
|