OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

LicenseAssignmentTest.php

9 KB
<?php

declare(strict_types=1);

/**
 * Deckt die Zuordnung von Lizenzplätzen an Geräte und Personen ab.
 *
 * Der heikle Punkt ist `seats_used`: Bei Lizenzen aus dem Microsoft-365-
 * Abgleich gehört die Zahl der Quelle und wird bei jedem Lauf überschrieben.
 * Würde die Zuordnung sie mitführen, stritten sich zwei Stellen um denselben
 * Wert – und die lokale Zählung verlöre bei jedem Abgleich.
 *
 * Der zweite Punkt ist die Zählung selbst: Die Zuordnung zeigt auf Geräte oder
 * Personen und kann deshalb keinen Fremdschlüssel tragen. Verschwindet das
 * Ziel, bleibt eine Zeile ohne Gegenstück zurück, die weder in der Liste noch
 * in der Zahl auftauchen darf.
 */
return static function (): void {

    $wurzel  = dirname(__DIR__);
    $manager = (string)file_get_contents($wurzel . '/src/Content/LicenseManager.php');
    $ctrl    = (string)file_get_contents($wurzel . '/src/Controllers/LicenseController.php');
    $sql     = (string)file_get_contents($wurzel . '/database/migrations/219_license_assignments.sql');

    // ----------------------------------------------------------------
    // Die abgeglichene Belegung bleibt unberührt
    // ----------------------------------------------------------------

    test('Eine Zuordnung rechnet seats_used nicht fort', function () use ($manager) {
        // Weder assign() noch unassign() dürfen die gepflegte Zahl anfassen –
        // sonst überschreiben sich Abgleich und Zuordnung gegenseitig.
        foreach (['assign', 'unassign'] as $methode) {
            $start = strpos($manager, 'public function ' . $methode . '(');
            assertTrue($start !== false, "Methode {$methode} fehlt");
            $ende = strpos($manager, "\n    /**", $start);
            $body = substr($manager, $start, ($ende ?: strlen($manager)) - $start);
            assertFalse(str_contains($body, 'seats_used'), "{$methode}() fasst seats_used an");
        }
    });

    test('Die Übernahme ist abgeglichenen Lizenzen verwehrt', function () use ($ctrl) {
        // Bei ihnen käme die Zahl beim nächsten Lauf ohnehin zurück; die
        // Schaltfläche würde also etwas versprechen, das nicht hält.
        $start = strpos($ctrl, 'public function seatsFromAssignments(');
        assertTrue($start !== false, 'Die Übernahme fehlt');
        $body = substr($ctrl, $start, 1200);

        // `external_source` ist NOT NULL DEFAULT 'manual' – also nie leer. Eine
        // Prüfung auf „gesetzt" träfe deshalb auf jede Lizenz zu und verwehrte
        // die Übernahme auch dort, wo sie gerade gedacht ist.
        assertFalse(str_contains($body, "!empty(\$entry['external_source'])"), 'Die Prüfung trifft auf jede Lizenz zu');
        assertTrue(
            str_contains($body, "(\$entry['external_source'] ?? 'manual') !== 'manual'"),
            'Selbst gepflegte Lizenzen werden nicht als solche erkannt'
        );

        // Die Prüfung muss vor dem Schreiben stehen.
        $pruefung = strpos($body, "external_source");
        $schreiben = strpos($body, 'setSeatsUsed(');
        assertTrue($pruefung !== false && $schreiben !== false && $pruefung < $schreiben, 'Es wird vor der Prüfung geschrieben');
    });

    test('Die Seite unterscheidet selbst gepflegte von abgeglichenen Lizenzen', function () use ($wurzel) {
        // Dieselbe Falle wie oben: `!empty()` liefert für jede Lizenz true und
        // versteckte damit die Übernahme-Schaltfläche überall.
        $seite = (string)file_get_contents($wurzel . '/templates/licenses/show.php');
        assertTrue(
            str_contains($seite, "\$abgeglichen   = (\$e['external_source'] ?? 'manual') !== 'manual';"),
            'Die Seite erkennt selbst gepflegte Lizenzen nicht'
        );
    });

    test('Die Belegung selbst gepflegter Lizenzen lässt sich übernehmen', function () use ($wurzel) {
        $seite = (string)file_get_contents($wurzel . '/templates/licenses/show.php');
        assertTrue(str_contains($seite, '/seats-from-assignments'), 'Es gibt keinen Weg zur Übernahme');
        // Nur im Zweig für selbst gepflegte Lizenzen.
        $stelle = strpos($seite, '/seats-from-assignments');
        $davor  = substr($seite, max(0, $stelle - 700), 700);
        assertTrue(str_contains($davor, '<?php else: ?>'), 'Die Übernahme steht nicht im Zweig der selbst gepflegten Lizenzen');
    });

    // ----------------------------------------------------------------
    // Zählung und Liste laufen nicht auseinander
    // ----------------------------------------------------------------

    test('Verwaiste Zuordnungen zählen nicht mit', function () use ($manager) {
        // Liste und Zahl müssen denselben Verbund nutzen, sonst zeigt die Seite
        // „3 zugewiesen" und darunter zwei Zeilen.
        foreach (['getAssignments', 'countAssignments'] as $methode) {
            $start = strpos($manager, 'public function ' . $methode . '(');
            $body  = substr($manager, $start, 1200);
            assertTrue(str_contains($body, 'a.id IS NOT NULL OR p.id IS NOT NULL'), "{$methode}() lässt verwaiste Zeilen durch");
        }
    });

    test('Dieselbe Lizenz belegt ein Ziel nur einmal', function () use ($sql, $manager) {
        // Ein zweiter Klick darf keinen zweiten Platz erzeugen.
        assertTrue(str_contains($sql, 'UNIQUE KEY `uq_license_subject`'), 'Der Datenbank fehlt die Eindeutigkeit');
        $start = strpos($manager, 'public function assign(');
        $body  = substr($manager, $start, 2000);
        assertTrue(str_contains($body, 'return false;'), 'Eine bestehende Zuordnung wird nicht erkannt');
    });

    test('Die Zuordnung verschwindet mit ihrer Lizenz', function () use ($sql) {
        assertTrue(str_contains($sql, 'ON DELETE CASCADE'), 'Gelöschte Lizenzen lassen Zuordnungen zurück');
    });

    // ----------------------------------------------------------------
    // Eingaben
    // ----------------------------------------------------------------

    test('Nur bekannte Zieltypen werden angenommen', function () use ($manager) {
        $start = strpos($manager, 'public function assign(');
        $body  = substr($manager, $start, 2000);
        assertTrue(str_contains($body, "in_array(\$subjectType, ['asset', 'person'], true)"), 'Der Zieltyp wird nicht geprüft');
        assertTrue(str_contains($body, 'InvalidArgumentException'), 'Ein unbekannter Typ wird stillschweigend hingenommen');
    });

    test('Ein nicht vorhandenes Ziel wird abgewiesen', function () use ($manager) {
        // Sonst entstünde eine Zuordnung, die nirgends auftaucht – die Liste
        // blendet sie aus, die Zahl ebenso.
        $start = strpos($manager, 'public function assign(');
        $body  = substr($manager, $start, 2000);
        assertTrue(str_contains($body, 'Das Ziel der Zuordnung gibt es nicht.'), 'Das Ziel wird nicht geprüft');
    });

    test('Der Tabellenname für das Ziel kommt nicht aus der Eingabe', function () use ($manager) {
        // Der Name steht in der Abfrage und muss daher aus dem Code stammen.
        $start = strpos($manager, 'public function assign(');
        $body  = substr($manager, $start, 2000);
        assertTrue(
            str_contains($body, "\$tabelle = \$subjectType === 'asset' ? 'assets' : 'directory_persons';"),
            'Der Tabellenname wird nicht aus dem Code abgeleitet'
        );
    });

    // ----------------------------------------------------------------
    // Protokoll und Bedienung
    // ----------------------------------------------------------------

    test('Zuweisen und Aufheben stehen im Protokoll', function () use ($manager) {
        foreach (['license_assigned', 'license_unassigned', 'license_seats_recounted'] as $vorgang) {
            assertTrue(str_contains($manager, "'{$vorgang}'"), "Vorgang {$vorgang} wird nicht protokolliert");
        }
        // Der Eintrag nennt das Ziel, nicht nur dessen Kennung.
        assertTrue(str_contains($manager, 'subjectLabel('), 'Das Protokoll benennt das Ziel nicht');
    });

    test('Beide Richtungen sind sichtbar', function () use ($wurzel) {
        // Nur von der Lizenz aus wäre die Zuordnung halb so nützlich: Die Frage
        // lautet meist „welche Lizenzen laufen auf diesem Gerät".
        $lizenzseite = (string)file_get_contents($wurzel . '/templates/licenses/show.php');
        assertTrue(str_contains($lizenzseite, 'Zugewiesen an'), 'Die Lizenzseite zeigt die Zuordnung nicht');

        $geraeteseite = (string)file_get_contents($wurzel . '/templates/assets/show.php');
        assertTrue(str_contains($geraeteseite, '$assigned_licenses'), 'Die Geräteseite zeigt die Lizenzen nicht');

        $assetCtrl = (string)file_get_contents($wurzel . '/src/Controllers/AssetController.php');
        assertTrue(str_contains($assetCtrl, "getAssignedLicenses('asset'"), 'Die Geräteseite lädt die Lizenzen nicht');
        // Nur bei aktivem Modul und passender Berechtigung.
        assertTrue(str_contains($assetCtrl, "(\$modules['licenses'] ?? false) && \$this->can('view_licenses')"), 'Modul und Berechtigung werden nicht geprüft');
    });

    test('Die Geräte-Suche folgt den Standort-Rechten des Inventars', function () use ($ctrl) {
        // Die Suche gibt Inventar- und Seriennummern heraus.
        $start = strpos($ctrl, 'public function apiAssetSearch(');
        assertTrue($start !== false, 'Die Geräte-Suche fehlt');
        $body = substr($ctrl, $start, 1500);
        assertTrue(str_contains($body, "\$this->can('view_assets')"), 'Ohne Inventar-Recht gäbe es Treffer');
        assertTrue(str_contains($body, "locationScopeSql('view_assets'"), 'Die Standortgrenzen werden übergangen');
    });
};