Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Monitoring\InstanceMetrics;
/**
* Deckt die Kennzahlen ab, die ein Überwachungssystem abruft.
*
* Zwei Zusagen zählen: Die Antwort trägt immer dieselben Schlüssel – sonst
* laufen die Elemente im Überwachungssystem in einen Fehlerzustand, sobald ein
* Modul aus ist – und sie enthält keine Personendaten.
*/
return static function (): void {
/**
* Datenbank-Doppel. `$brechen` lässt jede Abfrage scheitern und stellt damit
* eine Instanz nach, der Tabellen fehlen.
*/
$db = function (array $werte = [], bool $brechen = false): object {
return new class ($werte, $brechen) extends \OpenNIT\Core\Database {
public function __construct(private array $werte, private bool $brechen)
{
// Verbindung bewusst nicht aufbauen.
}
public function fetchColumn(string $sql, array $params = [], int $column = 0): mixed
{
if ($this->brechen) {
throw new \RuntimeException('Table does not exist');
}
foreach ($this->werte as $merkmal => $wert) {
if (is_string($merkmal) && str_contains($sql, $merkmal)) {
return $wert;
}
}
return 0;
}
public function fetchOne(string $sql, array $params = []): ?array
{
if ($this->brechen) {
throw new \RuntimeException('Table does not exist');
}
return null;
}
public function fetchAll(string $sql, array $params = []): array
{
if ($this->brechen) {
throw new \RuntimeException('Table does not exist');
}
if (str_contains($sql, 'FROM cron_tasks') && str_contains($sql, 'label')) {
return [[
'name' => 'ticket_fetch', 'label' => 'Tickets abrufen', 'enabled' => 1,
'last_run_at' => date('Y-m-d H:i:s', time() - 120),
'last_status' => 'success', 'frequency_minutes' => 5,
]];
}
if (str_contains($sql, 'GROUP BY status')) {
return [
['status' => 'new', 'anzahl' => 3],
['status' => 'open', 'anzahl' => 7],
];
}
return [];
}
};
};
/** Alle Schlüssel eines Abschnitts, flach als Pfad. */
$pfade = static function (array $daten, string $prefix = ''): array {
$out = [];
foreach ($daten as $k => $v) {
$pfad = $prefix === '' ? (string)$k : $prefix . '.' . $k;
if (is_array($v) && $v !== [] && !array_is_list($v)) {
$out = array_merge($out, (function ($v, $pfad) use (&$pfade) {
return $pfade($v, $pfad);
})($v, $pfad));
} else {
$out[] = $pfad;
}
}
return $out;
};
test('Die Antwort trägt alle Abschnitte', function () use ($db) {
$alle = (new InstanceMetrics($db()))->all();
foreach ([
'tickets', 'scheduler', 'tasks', 'mail_fetch', 'mail_queue',
'backup', 'audit', 'storage', 'auth', 'expiring', 'instance',
] as $abschnitt) {
assertTrue(array_key_exists($abschnitt, $alle), 'Der Abschnitt ' . $abschnitt . ' fehlt');
}
assertTrue(array_key_exists('generated_at', $alle), 'Der Zeitstempel fehlt');
});
test('Die fünf Ticketzahlen stehen dort, wo die Vorlage sie sucht', function () use ($db) {
$alle = (new InstanceMetrics($db()))->all();
foreach (['new', 'open', 'waiting', 'solved', 'closed'] as $zustand) {
assertTrue(array_key_exists($zustand, $alle['tickets']), 'tickets.' . $zustand . ' fehlt');
}
assertSame(3, $alle['tickets']['new']);
assertSame(7, $alle['tickets']['open']);
assertSame(0, $alle['tickets']['waiting'], 'Ein fehlender Zustand zählt als 0, nicht als null');
assertSame(10, $alle['tickets']['total']);
});
test('Fehlt eine Tabelle, bleiben die Schlüssel stehen und werden null', function () use ($db, $pfade) {
$heil = (new InstanceMetrics($db()))->all();
$kaputt = (new InstanceMetrics($db([], true)))->all();
// Gleiche Form in beiden Fällen: Das Überwachungssystem darf seine
// Elemente nicht verlieren, nur weil ein Modul aus ist.
foreach (['tickets', 'scheduler', 'mail_queue', 'backup', 'audit', 'auth', 'expiring'] as $abschnitt) {
assertSame(
$pfade($heil[$abschnitt]),
$pfade($kaputt[$abschnitt]),
'Der Abschnitt ' . $abschnitt . ' verliert Schlüssel'
);
}
assertSame(null, $kaputt['tickets']['new'], 'Ohne Tabelle muss der Wert null sein');
assertSame([], $kaputt['tasks'], 'Ohne Tabelle bleibt die Aufgabenliste leer');
});
test('Die Aufgabenliste liefert, was die Erkennung braucht', function () use ($db) {
$tasks = (new InstanceMetrics($db()))->all()['tasks'];
assertSame(1, count($tasks));
foreach (['name', 'label', 'enabled', 'last_status', 'last_run_age_seconds'] as $feld) {
assertTrue(array_key_exists($feld, $tasks[0]), 'Das Feld ' . $feld . ' fehlt');
}
assertSame('ticket_fetch', $tasks[0]['name']);
assertTrue($tasks[0]['last_run_age_seconds'] >= 110, 'Das Alter wird nicht berechnet');
});
test('Ein einzelner Abschnitt lässt sich abrufen', function () use ($db) {
$m = new InstanceMetrics($db());
assertTrue(is_array($m->section('tickets')));
assertSame(null, $m->section('gibtesnicht'));
});
test('Die Antwort enthält keine Personendaten', function () use ($db) {
$roh = json_encode((new InstanceMetrics($db()))->all());
// Zähler, Alter und Zustände – nichts, was eine Person benennt.
foreach (['@', 'email', 'display_name', 'subject', 'from_name'] as $verboten) {
assertFalse(
str_contains((string)$roh, $verboten),
'Die Antwort enthält „' . $verboten . '"'
);
}
});
};