OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

ZabbixTemplateTest.php

6 KB
<?php

declare(strict_types=1);

use OpenNIT\Controllers\Admin\MonitoringAdminController;

/**
 * Deckt die Zabbix-Vorlage ab, die sich unter *Alerts → Media types → Import*
 * einspielen lässt.
 *
 * Eine Vorlage, die Zabbix beim Import abweist, ist schlimmer als keine – der
 * Aufbau wird deshalb hier festgehalten, samt der Merkmale, an denen sich ein
 * verrutschtes YAML zeigt.
 */
return static function (): void {

    /** Erzeugt die Vorlage ohne App-Bootstrap. */
    $yaml = static function (
        string $url = 'https://doku.example.test/monitoring/webhook/zabbix',
        string $token = 'abc123'
    ): string {
        $c = new \ReflectionClass(MonitoringAdminController::class);
        $m = $c->getMethod('zabbixTemplateYaml');
        $m->setAccessible(true);
        return (string)$m->invoke($c->newInstanceWithoutConstructor(), $url, $token);
    };

    test('Der Kopf nennt eine Fassung, die jeder neuere Stand einspielt', function () use ($yaml) {
        $text = $yaml();
        assertTrue(str_contains($text, 'zabbix_export:'), 'Der Wurzelknoten fehlt');
        // Zabbix spielt eine ältere Fassung in einen neueren Stand ein,
        // umgekehrt nicht – deshalb die niedrigste noch gepflegte.
        assertTrue(str_contains($text, "  version: '6.0'"), 'Die Fassung fehlt oder ist zu hoch');
        assertTrue(str_contains($text, '  media_types:'), 'Der Abschnitt der Media-Types fehlt');
        assertTrue(str_contains($text, '      type: WEBHOOK'), 'Der Typ fehlt');
    });

    test('Adresse und Schlüssel stehen bereits darin', function () use ($yaml) {
        $text = $yaml('https://doku.example.test/monitoring/webhook/zabbix', 'deadbeef');
        assertTrue(str_contains($text, "          value: 'https://doku.example.test/monitoring/webhook/zabbix'"));
        assertTrue(str_contains($text, "          value: 'deadbeef'"));
    });

    test('Jede Zabbix-Marke steht in Hochkommata', function () use ($yaml) {
        // Eine geschweifte Klammer am Anfang eines Wertes wäre für YAML der
        // Beginn einer Zuordnung – der Import bräche ab.
        foreach (explode("\n", $yaml()) as $zeile) {
            if (!str_contains($zeile, '{')) {
                continue;
            }
            if (!str_starts_with(ltrim($zeile), 'value:')) {
                continue; // Skript und Beschreibung stehen als Block
            }
            assertTrue(
                (bool)preg_match("/^\s*value: '.*'$/", $zeile),
                'Ungeschützter Wert: ' . trim($zeile)
            );
        }
    });

    test('Ein Hochkomma im Schlüssel bricht die Vorlage nicht', function () use ($yaml) {
        // YAML verdoppelt es; sonst endete die Zeichenkette mitten im Wert.
        assertTrue(str_contains($yaml('https://x.test/w', "a'b"), "value: 'a''b'"));
    });

    test('Die Vorlage führt die Parameter, die der Endpunkt erwartet', function () use ($yaml) {
        $text = $yaml();
        foreach ([
            'event_id', 'host', 'hostname', 'message', 'severity',
            'status', 'trigger_name', 'url', 'webhook_token', 'webhook_url',
        ] as $name) {
            assertTrue(str_contains($text, "        - name: '" . $name . "'"), 'Parameter fehlt: ' . $name);
        }
        // Sprachunabhängig, wie in der Anleitung empfohlen.
        assertTrue(str_contains($text, "          value: '{EVENT.VALUE}'"));
    });

    test('Das Skript beginnt ohne zusätzliche Einrückung im Block', function () use ($yaml) {
        $text   = $yaml();
        $anfang = strpos($text, '      script: |');
        assertTrue($anfang !== false, 'Der Skript-Block fehlt');

        $erste = explode("\n", substr($text, $anfang))[1];
        // Acht Zeichen Einrückung: der Block liegt zwei Stufen unter `script`.
        assertSame('        var p = JSON.parse(value);', $erste);
    });

    test('Die Vorlage bringt Nachrichtenvorlagen mit', function () use ($yaml) {
        // Ohne sie weist Zabbix eine Operation mit „Default message" ab – die
        // Action liesse sich dann nicht speichern.
        $text = $yaml();
        assertTrue(str_contains($text, '      message_templates:'), 'Der Abschnitt fehlt');
        foreach (['PROBLEM', 'RECOVERY', 'UPDATE'] as $modus) {
            assertTrue(
                str_contains($text, '          operation_mode: ' . $modus),
                'Nachrichtenvorlage fehlt: ' . $modus
            );
        }
        assertSame(3, substr_count($text, '        - event_source: TRIGGERS'));
    });

    test('Ohne Schlüssel gibt es keine Vorlage', function () {
        // Eine Vorlage mit leerem Feld sähe eingerichtet aus, ohne es zu sein.
        $src = (string)file_get_contents(
            dirname(__DIR__) . '/src/Controllers/Admin/MonitoringAdminController.php'
        );
        $start = strpos($src, 'public function zabbixTemplate(');
        assertTrue($start !== false, 'Der Download fehlt');
        $rumpf = substr($src, $start, 1200);

        assertTrue(str_contains($rumpf, "if (\$token === '')"), 'Der leere Schlüssel wird nicht abgefangen');
        assertTrue(str_contains($rumpf, '$this->authorize(\'admin_monitoring\')'), 'Der Download ist nicht geschützt');
        assertTrue(str_contains($rumpf, 'Content-Disposition'), 'Die Datei wird nicht als Download ausgeliefert');
        // Der Schlüssel verlässt die Anwendung – das gehört ins Protokoll,
        // der Schlüssel selbst aber nicht.
        assertTrue(str_contains($rumpf, '$this->audit()->log('), 'Der Download steht nicht im Protokoll');
        assertFalse(str_contains($rumpf, "'token' => \$token"), 'Der Schlüssel darf nicht ins Protokoll');
    });

    test('Der Download hängt an einer Route', function () {
        $src = (string)file_get_contents(dirname(__DIR__) . '/src/Core/Application.php');
        assertTrue(
            str_contains($src, "'/admin/monitoring/zabbix-template'"),
            'Die Route der Vorlage fehlt'
        );
    });
};