OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

InstallerEnvTest.php

7 KB
<?php

declare(strict_types=1);

/**
 * Deckt die Erstinstallation ab: was der Assistent in die `.env` schreibt, welche
 * Voraussetzungen er prüft und dass die Anleitungen ihn nicht aussperren.
 */
return static function (): void {

    $wurzel = dirname(__DIR__);

    /** Der Quelltext des Installers. */
    $installer = static fn (): string => (string)file_get_contents($wurzel . '/public/install.php');

    /** Der Inhalt der geschriebenen .env – der Heredoc-Block aus createEnvFile(). */
    $envVorlage = static function () use ($installer): string {
        $src = $installer();
        $von = strpos($src, '$env = <<<ENV');
        $bis = strpos($src, "\nENV;", (int)$von);
        return $von === false || $bis === false ? '' : substr($src, $von, $bis - $von);
    };

    // ----------------------------------------------------------------
    // Inhalt der erzeugten .env
    // ----------------------------------------------------------------

    test('Die erzeugte .env enthält keine Anmeldedaten des Administrators', function () use ($envVorlage, $installer) {
        // Der Administrator wird gehasht in der Datenbank angelegt; ein zusätzlicher
        // Klartext auf der Platte hätte keinen Leser und nur ein Risiko.
        $env = $envVorlage();

        assertFalse(str_contains($env, 'LOCAL_ADMIN_PASSWORD'), 'Passwort im Klartext in der .env');
        assertFalse(str_contains($env, 'LOCAL_ADMIN_EMAIL'), 'Adresse ohne Leser in der .env');
        assertFalse(str_contains($installer(), '$adminPassQ'), 'Der Wert wird weiterhin aufbereitet');
    });

    test('Der Administrator wird trotzdem angelegt', function () use ($installer) {
        // Die Werte fallen nur aus der .env, nicht aus der Installation.
        assertTrue(str_contains($installer(), 'createLocalAdmin($app[\'admin_email\'], $app[\'admin_password\']'));
    });

    test('Die .env trägt nur Schlüssel, die auch gelesen werden', function () use ($envVorlage, $wurzel) {
        $env = $envVorlage();

        preg_match_all('/^([A-Z][A-Z0-9_]+)=/m', $env, $treffer);
        assertTrue($treffer[1] !== [], 'Keine Schlüssel gefunden');

        // Gelesen wird über $_ENV in config/, bootstrap.php und im Quellbaum.
        $gelesen = '';
        foreach (['/config', '/src'] as $ordner) {
            $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($wurzel . $ordner));
            foreach ($it as $datei) {
                if ($datei->isFile() && $datei->getExtension() === 'php') {
                    $gelesen .= (string)file_get_contents($datei->getPathname());
                }
            }
        }
        $gelesen .= (string)file_get_contents($wurzel . '/public/index.php');
        $gelesen .= (string)file_get_contents($wurzel . '/bootstrap.php');

        foreach ($treffer[1] as $schluessel) {
            assertTrue(
                str_contains($gelesen, "'" . $schluessel . "'"),
                'Ohne Leser in der .env: ' . $schluessel
            );
        }
    });

    // ----------------------------------------------------------------
    // Systemvoraussetzungen
    // ----------------------------------------------------------------

    test('Der Installer prüft genau die Extensions aus composer.json', function () use ($installer, $wurzel) {
        // Weicht die Liste ab, meldet der Assistent ein installierbares System,
        // auf dem "composer install" scheitert – oder umgekehrt.
        $composer = json_decode((string)file_get_contents($wurzel . '/composer.json'), true);

        $gefordert = [];
        foreach (array_keys($composer['require'] ?? []) as $paket) {
            if (str_starts_with($paket, 'ext-')) {
                $gefordert[] = substr($paket, 4);
            }
        }
        sort($gefordert);

        $von = strpos($installer(), '$requiredExtensions = [');
        $bis = strpos($installer(), '];', (int)$von);
        preg_match_all("/'([a-z_]+)'/", substr($installer(), (int)$von, (int)$bis - (int)$von), $treffer);
        $geprueft = $treffer[1];
        sort($geprueft);

        assertSame($gefordert, $geprueft, 'Installer und composer.json fordern Unterschiedliches');
    });

    test('Die geforderte PHP-Version folgt composer.json', function () use ($installer, $wurzel) {
        $composer = json_decode((string)file_get_contents($wurzel . '/composer.json'), true);

        assertSame('>=8.2', $composer['require']['php'] ?? '');
        assertTrue(str_contains($installer(), "version_compare(PHP_VERSION, '8.2.0', '>=')"));
    });

    test('Ohne vendor/ gilt die Voraussetzung als nicht erfüllt', function () use ($installer) {
        // public/index.php lädt den Autoloader als Erstes – fehlt er, endet jeder
        // spätere Aufruf im Serverfehler statt in einer Meldung.
        assertTrue(str_contains($installer(), "is_file(BASE_PATH . '/vendor/autoload.php')"));
    });

    // ----------------------------------------------------------------
    // Sperre und Anleitungen
    // ----------------------------------------------------------------

    test('Eine vorhandene .env mit APP_KEY sperrt den Installer', function () use ($installer) {
        $src = $installer();

        assertTrue(str_contains($src, "storage/.installed"), 'Kein Marker-Zweig');
        assertTrue((bool)preg_match('/APP_KEY\\\\s\*=/', $src), 'Die .env wird nicht auf APP_KEY geprüft');
    });

    test('Die Sperrmeldung nennt die von Hand angelegte .env', function () use ($installer) {
        // Häufigster Fall in der Praxis: gar nicht installiert, nur eine .env zu früh
        // angelegt. Ohne den Hinweis wirkt die Meldung wie ein Defekt.
        $src = $installer();

        $treffer = substr_count($src, 'von Hand');
        assertTrue($treffer >= 2, 'Der Hinweis fehlt im Web- oder im CLI-Zweig');
    });

    test('Keine Anleitung legt die .env vor dem Assistenten an', function () use ($wurzel) {
        // Wer "cp .env.example .env" plus APP_KEY ausführt und danach install.php
        // aufruft, läuft in die Sperre.
        foreach (['/README.md', '/docs/INSTALLATION.md'] as $datei) {
            $text = (string)file_get_contents($wurzel . $datei);
            assertFalse(str_contains($text, 'cp .env.example .env'), 'Anleitung legt .env an: ' . $datei);
        }
    });

    test('Die Anleitungen benennen, wer die .env schreibt', function () use ($wurzel) {
        foreach (['/README.md', '/docs/INSTALLATION.md', '/.env.example'] as $datei) {
            $text = (string)file_get_contents($wurzel . $datei);
            assertTrue(
                str_contains($text, 'legt die .env')
                || str_contains($text, 'legt die `.env`')
                || str_contains($text, 'schreibt die .env'),
                'Ohne Hinweis auf den Assistenten: ' . $datei
            );
        }
    });
};