OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

InstanceMetricsTest.php

6 KB
<?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 . '"'
            );
        }
    });
};