OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

VmMissingTest.php

9 KB
<?php

declare(strict_types=1);

/**
 * Deckt ab, dass der Hypervisor-Abgleich verschwundene VMs kennzeichnet,
 * statt sie zu loeschen – und dass nur vom Abgleich gefuehrte VMs betroffen
 * sind.
 */
return static function (): void {

    $wurzel = dirname(__DIR__);
    $src    = static fn (string $datei): string => (string)file_get_contents($wurzel . $datei);

    // ----------------------------------------------------------------
    // Abgleich
    // ----------------------------------------------------------------

    test('Der Abgleich loescht nichts von selbst', function () use ($src) {
        // Gekennzeichnet wird, entschieden wird vom Betreiber.
        $code = $src('/src/Hypervisor/HypervisorSyncService.php');
        $von  = strpos($code, 'private function markiereVermisste');
        $block = substr($code, (int)$von, 1800);

        assertTrue(str_contains($block, 'missing_since = NOW()'), 'Keine Kennzeichnung');
        assertFalse((bool)preg_match('/\bDELETE\s+FROM\s+vm_vms\b/i', $block), 'Abgleich loescht VMs');
    });

    test('Von Hand angelegte VMs bleiben unberuehrt', function () use ($src) {
        // Ohne hypervisor_vm_id fuehrt der Abgleich die VM nicht – sie kann
        // also auch nicht vermisst werden.
        $code = $src('/src/Hypervisor/HypervisorSyncService.php');
        $von  = strpos($code, 'private function markiereVermisste');
        $block = substr($code, (int)$von, 1800);

        assertTrue(str_contains($block, "hypervisor_vm_id IS NOT NULL AND hypervisor_vm_id <> ''"), 'Nicht gefuehrte VMs einbezogen');
        // Beide Zweige – mit und ohne gemeldete VMs – nutzen dieselbe Einschraenkung.
        assertSame(2, substr_count($block, '{$gefuehrt}'), 'Einschraenkung fehlt in einem Zweig');
    });

    test('Eine wieder aufgetauchte VM verliert die Kennzeichnung', function () use ($src) {
        $code = $src('/src/Hypervisor/HypervisorSyncService.php');

        // Im Abgleich selbst …
        $von   = strpos($code, 'private function markiereVermisste');
        $block = substr($code, (int)$von, 1800);
        assertTrue(str_contains($block, 'missing_since = NULL'), 'Kennzeichnung bleibt haengen');

        // … und im Aktualisieren einer gemeldeten VM.
        $upsert = substr($code, (int)strpos($code, 'private function upsertVm'), 1600);
        assertTrue(str_contains($upsert, 'missing_since=NULL'), 'Gemeldete VM bleibt gekennzeichnet');
    });

    test('Der Zeitpunkt bleibt der erste Lauf ohne Meldung', function () use ($src) {
        // Sonst stuende dort immer "heute" und man saehe nie, wie lange
        // eine VM schon fehlt.
        $code = $src('/src/Hypervisor/HypervisorSyncService.php');
        $von  = strpos($code, 'private function markiereVermisste');
        $block = substr($code, (int)$von, 1800);

        assertSame(2, substr_count($block, 'missing_since IS NULL'), 'Zeitpunkt wird ueberschrieben');
    });

    test('Meldet der Hypervisor gar nichts mehr, fehlen alle', function () use ($src) {
        // Der Sonderfall braucht einen eigenen Zweig, weil NOT IN () kein
        // gueltiges SQL ist.
        $code = $src('/src/Hypervisor/HypervisorSyncService.php');
        $von  = strpos($code, 'private function markiereVermisste');
        $block = substr($code, (int)$von, 1800);

        assertTrue(str_contains($block, 'if ($gesehen === [])'), 'Leere Meldung nicht abgefangen');
    });

    test('Ein fehlgeschlagener Abgleich kennzeichnet nichts', function () use ($src) {
        // Ein Verbindungsfehler wuerde sonst schlagartig alle VMs als
        // verschwunden ausweisen.
        $code = $src('/src/Hypervisor/HypervisorSyncService.php');
        $von  = strpos($code, 'public function syncHost');
        // Der aeussere Fehlerzweig – erkennbar am Schreiben des Fehlerstatus.
        $bis  = strpos($code, "'UPDATE vm_hosts SET sync_status=?, sync_error=?", (int)$von);
        $erfolg = substr($code, (int)$von, (int)$bis - (int)$von);

        assertTrue(str_contains($erfolg, 'markiereVermisste'), 'Kennzeichnung nicht im Erfolgsfall');

        // Im Fehlerzweig kommt sie nicht vor.
        $fehler = substr($code, (int)$bis, 900);
        assertFalse(str_contains($fehler, 'markiereVermisste'), 'Kennzeichnung im Fehlerfall');
        assertTrue(str_contains($fehler, "'vms_missing'  => 0"), 'Fehlerfall meldet eine Zahl');
    });

    test('Erst wird abgeglichen, dann gekennzeichnet', function () use ($src) {
        $code = $src('/src/Hypervisor/HypervisorSyncService.php');
        $von  = strpos($code, 'public function syncHost');
        $block = substr($code, (int)$von, 2600);

        $upsert = strpos($block, '$this->upsertVm(');
        $mark   = strpos($block, 'markiereVermisste');
        assertTrue($upsert !== false && $mark !== false, 'Eine Haelfte fehlt');
        assertTrue($upsert < $mark, 'Kennzeichnung vor dem Abgleich');
    });

    // ----------------------------------------------------------------
    // Aufraeumen
    // ----------------------------------------------------------------

    test('Aufgeraeumt wird nur, was auch vermisst wird', function () use ($src) {
        // Eine mitgeschickte ID einer laufenden VM darf nichts loeschen.
        $code = $src('/src/Content/VmManager.php');
        $von  = strpos($code, 'public function deleteMissingVms');
        $block = substr($code, (int)$von, 1400);

        assertTrue(str_contains($block, 'missing_since IS NOT NULL AND id IN'), 'Auswahl ungeprueft');
        assertTrue(str_contains($block, '$this->deleteVm('), 'Kein Audit-Eintrag je VM');
    });

    test('Die Auswahl wird auf ganze Zahlen eingegrenzt', function () use ($src) {
        $code = $src('/src/Content/VmManager.php');
        $von  = strpos($code, 'public function deleteMissingVms');
        $block = substr($code, (int)$von, 700);

        assertTrue(str_contains($block, "array_map('intval', \$ids)"), 'Keine Typumwandlung');
        assertTrue(str_contains($block, 'if ($ids === [])'), 'Leere Auswahl nicht abgefangen');
    });

    // ----------------------------------------------------------------
    // Oberflaeche
    // ----------------------------------------------------------------

    test('Die Aufraeum-Route steht vor der {id}-Route', function () use ($src) {
        // Sonst matcht der Router {id}=missing.
        $routen = $src('/src/Core/Application.php');

        $missing = strpos($routen, "'/vms/missing'");
        $byId    = strpos($routen, "'/vms/{id}'");
        assertTrue($missing !== false && $byId !== false, 'Eine Route fehlt');
        assertTrue($missing < $byId, 'Reihenfolge vertauscht');

        assertTrue(str_contains($routen, "'/vms/missing/cleanup'"), 'Keine Aufraeum-Route');
    });

    test('Das Aufraeumen verlangt das Recht zu aendern', function () use ($src) {
        $routen = $src('/src/Core/Application.php');
        $von    = strpos($routen, "'/vms/missing/cleanup'");
        $zeile  = substr($routen, (int)$von, 220);
        assertTrue(str_contains($zeile, "'capability' => 'manage_vms'"), 'Nur Lese-Recht verlangt');

        $ctrl = $src('/src/Controllers/VmController.php');
        $von  = strpos($ctrl, 'public function vmsMissingCleanup');
        assertTrue(str_contains(substr($ctrl, (int)$von, 400), "can('manage_vms')"), 'Controller prueft nicht');
    });

    test('Die Seite traegt den CSRF-Token und fragt nach', function () use ($src, $wurzel) {
        assertTrue(is_file($wurzel . '/templates/vms/missing.php'), 'Keine Seite');
        $tpl = $src('/templates/vms/missing.php');

        assertSame(substr_count($tpl, '<form'), substr_count($tpl, 'name="_csrf"'), 'Formular ohne Token');
        assertTrue(str_contains($tpl, 'confirm('), 'Loescht ohne Rueckfrage');
        // Der Unterschied zwischen OpenNIT und Hypervisor wird benannt.
        assertTrue(str_contains($tpl, 'nicht auf dem Hypervisor'), 'Wirkung nicht erklaert');
    });

    test('Die Seite nutzt nur zentrale Komponenten', function () use ($src) {
        // Die vml-Klassen sind an den Style-Block von index.php gebunden und
        // waeren hier ungestylt.
        $tpl = $src('/templates/vms/missing.php');

        foreach (['vml-wrap', 'vml-sub', 'vml-muted', 'vml-mono'] as $klasse) {
            assertFalse(str_contains($tpl, 'class="' . $klasse), "Seitengebundene Klasse: {$klasse}");
        }
        assertTrue(str_contains($tpl, 'table-wrapper'), 'Keine zentrale Tabelle');
        assertTrue(str_contains($tpl, 'status-pill'), 'Keine zentrale Auszeichnung');
    });

    test('Der Hinweis erscheint nur, wenn es etwas aufzuraeumen gibt', function () use ($src) {
        $tpl = $src('/templates/vms/index.php');
        $von = strpos($tpl, "(\$missing_count ?? 0) > 0");
        assertTrue($von !== false, 'Kein bedingter Einstieg');
        assertTrue(str_contains(substr($tpl, (int)$von, 400), '/vms/missing'), 'Kein Verweis');

        $ctrl = $src('/src/Controllers/VmController.php');
        assertTrue(str_contains($ctrl, "'missing_count' => \$mgr->countMissingVms()"), 'Zahl wird nicht geliefert');
    });

    test('Die Migration ist wiederholbar', function () use ($wurzel) {
        $sql = (string)file_get_contents($wurzel . '/database/migrations/214_vm_missing_since.sql');

        assertTrue(str_contains($sql, 'ADD COLUMN `missing_since`'), 'Spalte fehlt');
        assertTrue(str_contains($sql, 'ADD KEY `idx_vmvm_missing`'), 'Kein Index');
        assertFalse((bool)preg_match('/\bINSERT\s+INTO\b/i', $sql), 'Nacktes INSERT');
        assertFalse(str_contains($sql, 'TRUNCATE'), 'Zerstoerendes Statement');
    });
};