Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Controllers\Admin\StatusAdminController;
use OpenNIT\Status\StatusManager;
/**
* Deckt das rückwirkende Auffüllen der Verlaufsanzeige ab.
*
* Die Balken unter den Diensten zeigen 90 Tage; ohne erfasste Tage bleiben sie
* leer. Die Aktion trägt diese Tage als „betriebsbereit" nach – und lässt dabei
* alles unangetastet, was bereits erfasst ist: Eine protokollierte Störung ist
* Teil der Historie und darf nicht nachträglich grün werden.
*/
return static function (): void {
/** Der Rumpf der Aktion. */
$aktion = static function (): string {
$src = (string)file_get_contents(
dirname(__DIR__) . '/src/Controllers/Admin/StatusAdminController.php'
);
$von = strpos($src, 'public function backfillHistory');
return $von !== false ? substr($src, $von, 2200) : '';
};
// ----------------------------------------------------------------
// Erreichbarkeit
// ----------------------------------------------------------------
test('Die Aktion liegt im Backend und verlangt das Admin-Recht', function () use ($aktion) {
assertTrue(method_exists(StatusAdminController::class, 'backfillHistory'), 'Die Aktion fehlt');
$app = (string)file_get_contents(dirname(__DIR__) . '/src/Core/Application.php');
assertTrue(
str_contains($app, "'/admin/statuspage/history/backfill'"),
'Die Route fehlt'
);
assertTrue(str_contains($aktion(), "authorize('admin_statuspage')"), 'Keine Rechteprüfung');
});
test('Der Zeitraum entspricht der öffentlichen Anzeige', function () {
// Die Seite zeigt getUptimeHistory(90); die Aktion muss denselben
// Zeitraum füllen, sonst bleibt ein Teil des Balkens leer.
$ctrl = (string)file_get_contents(dirname(__DIR__) . '/src/Controllers/Admin/StatusAdminController.php');
$page = (string)file_get_contents(dirname(__DIR__) . '/src/Controllers/StatusController.php');
assertTrue(str_contains($ctrl, 'private const HISTORY_DAYS = 90;'), 'Kein fester Zeitraum');
assertTrue(str_contains($page, 'getUptimeHistory(90)'), 'Die Anzeige nutzt einen anderen Zeitraum');
});
// ----------------------------------------------------------------
// Was geschrieben wird
// ----------------------------------------------------------------
test('Vorhandene Tage bleiben unverändert', function () use ($aktion) {
// Der Doppelschlüssel (service_id, day) trifft erfasste Tage; die
// Zuweisung auf sich selbst lässt die Zeile dann in Ruhe.
$body = $aktion();
assertTrue(
str_contains($body, 'ON DUPLICATE KEY UPDATE service_id = status_service_history.service_id'),
'Erfasste Tage würden überschrieben'
);
assertFalse(str_contains($body, 'DELETE FROM status_service_history'), 'Die Aktion löscht Historie');
assertFalse(str_contains($body, 'UPDATE status_service_history SET'), 'Die Aktion ändert erfasste Tage');
});
test('Es entsteht genau ein Tag je Schritt, rückwärts bis heute', function () use ($aktion) {
$body = $aktion();
assertTrue(
str_contains($body, 'for ($vorTagen = self::HISTORY_DAYS - 1; $vorTagen >= 0; $vorTagen--)'),
'Der Zeitraum wird anders durchlaufen'
);
// 89 Tage zurück bis 0 = heute → 90 Tage einschließlich heute.
assertTrue(str_contains($body, 'DATE_SUB(CURDATE(), INTERVAL ? DAY)'), 'Kein Tagesbezug');
});
test('Eingetragen wird betriebsbereit mit Schwere 0', function () use ($aktion) {
assertTrue(str_contains($aktion(), "'operational', 0"), 'Es wird kein grüner Tag geschrieben');
});
test('Ohne Dienste passiert nichts', function () use ($aktion) {
$body = $aktion();
$leer = strpos($body, 'if (empty($dienste))');
assertTrue($leer !== false, 'Der Fall wird nicht behandelt');
assertTrue($leer < strpos($body, 'INSERT INTO status_service_history'), 'Es wird trotzdem geschrieben');
});
test('Der Vorgang wird protokolliert und der Cache verworfen', function () use ($aktion) {
$body = $aktion();
assertTrue(str_contains($body, "'status_history_backfilled'"), 'Kein Audit-Eintrag');
assertTrue(str_contains($body, 'StatusManager::bustPublicCache()'), 'Die öffentliche Seite bliebe im Cache');
});
// ----------------------------------------------------------------
// Die Regel selbst
// ----------------------------------------------------------------
/** Bildet nach, was ein Tag nach dem Auffüllen zeigt. */
$auffuellen = static fn (?string $erfasst): string => $erfasst ?? 'operational';
test('Ein leerer Tag wird grün', function () use ($auffuellen) {
assertSame('operational', $auffuellen(null));
});
test('Ein Tag mit Störung behält seine Störung', function () use ($auffuellen) {
foreach (['degraded', 'partial_outage', 'major_outage'] as $stand) {
assertSame($stand, $auffuellen($stand), 'Überschrieben: ' . $stand);
}
});
// ----------------------------------------------------------------
// Gemeinsame Cache-Logik
// ----------------------------------------------------------------
test('Beide Controller verwerfen den Cache über dieselbe Stelle', function () {
// Zwei Kopien des Pfades wären eine Quelle für stille Abweichung.
assertTrue(method_exists(StatusManager::class, 'bustPublicCache'));
foreach ([
'/src/Controllers/StatusController.php',
'/src/Controllers/Admin/StatusAdminController.php',
] as $datei) {
$src = (string)file_get_contents(dirname(__DIR__) . $datei);
assertTrue(str_contains($src, 'StatusManager::bustPublicCache()'), 'Nicht zentral: ' . $datei);
assertFalse(
str_contains($src, "/storage/cache/' . \$k . '.html'"),
'Eigener Cache-Pfad in ' . $datei
);
}
});
// ----------------------------------------------------------------
// Oberfläche
// ----------------------------------------------------------------
test('Der Knopf steht im Backend und fragt nach', function () {
$tpl = (string)file_get_contents(dirname(__DIR__) . '/templates/admin/statuspage/settings.php');
assertTrue(str_contains($tpl, 'action="/admin/statuspage/history/backfill"'), 'Kein Formular');
assertTrue(str_contains($tpl, 'name="_csrf"'), 'Ohne CSRF-Feld');
assertTrue(str_contains($tpl, 'onsubmit="return confirm('), 'Keine Rückfrage');
assertTrue(str_contains($tpl, '<i class="fas fa-history"></i>'), 'Icon fehlt oder ohne Stil-Präfix');
});
test('Das Formular liegt nicht im Einstellungs-Formular', function () {
// Verschachtelte Formulare sind ungültig; der Browser würde eines
// davon verwerfen.
$tpl = (string)file_get_contents(dirname(__DIR__) . '/templates/admin/statuspage/settings.php');
$ende = strpos($tpl, '</form>');
$aktion = strpos($tpl, 'action="/admin/statuspage/history/backfill"');
assertTrue($ende !== false && $aktion !== false);
assertTrue($aktion > $ende, 'Das Formular steht innerhalb des Einstellungs-Formulars');
});
test('Das Icon ist der Emoji-Umschaltung bekannt', function () {
$map = (string)file_get_contents(dirname(__DIR__) . '/src/Core/IconMap.php');
assertTrue(str_contains($map, "'fa-history'"), 'Nicht in der IconMap');
});
};