OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

AtomicFileTest.php

3 KB
<?php

declare(strict_types=1);

use OpenNIT\Core\AtomicFile;

/**
 * Deckt das atomare Schreiben von Konfigurationsdateien ab.
 *
 * Entscheidend sind die Betriebs-Garantien: bestehende Dateien werden
 * vollständig ersetzt (Rechte bleiben erhalten), es bleiben keine Tempdateien
 * zurück, und in Umgebungen ohne beschreibbares Verzeichnis greift der
 * Rückfall auf direktes Überschreiben, statt das Speichern zu brechen.
 */
return static function (): void {

    $dir = sys_get_temp_dir() . '/opennit-atomic-' . bin2hex(random_bytes(6));
    mkdir($dir, 0755, true);

    test('Neue Datei wird angelegt und ist sofort vollständig lesbar', function () use ($dir) {
        $file = $dir . '/neu.php';
        assertTrue(AtomicFile::write($file, "<?php return ['a' => 1];\n"));
        assertSame(['a' => 1], include $file);
    });

    test('Bestehende Datei wird ersetzt und behält ihre Rechte', function () use ($dir) {
        $file = $dir . '/bestehend.php';
        file_put_contents($file, "<?php return ['alt' => true];\n");
        chmod($file, 0640);

        assertTrue(AtomicFile::write($file, "<?php return ['neu' => true];\n"));
        assertSame(['neu' => true], include $file);
        // 0640 bleibt erhalten (Owner-rw ist garantiert)
        assertSame(0640, fileperms($file) & 0777);
    });

    test('Es bleiben keine Tempdateien im Verzeichnis zurück', function () use ($dir) {
        $file = $dir . '/sauber.php';
        AtomicFile::write($file, "<?php return [];\n");
        AtomicFile::write($file, "<?php return [1];\n");

        $reste = array_diff(scandir($dir), ['.', '..', 'neu.php', 'bestehend.php', 'sauber.php']);
        assertSame([], array_values($reste));
    });

    test('Ohne beschreibbares Verzeichnis greift der Rückfall auf die Datei', function () use ($dir) {
        if (function_exists('posix_geteuid') && posix_geteuid() === 0) {
            // Als root sind Verzeichnisrechte wirkungslos — Prüfung nicht möglich.
            assertTrue(true);
            return;
        }
        $sub  = $dir . '/nur-datei';
        mkdir($sub, 0755);
        $file = $sub . '/runtime.php';
        file_put_contents($file, "<?php return ['alt' => 1];\n");
        chmod($file, 0666);
        chmod($sub, 0555); // Verzeichnis schreibgeschützt, Datei beschreibbar

        $ok = AtomicFile::write($file, "<?php return ['neu' => 2];\n");
        chmod($sub, 0755);

        assertTrue($ok);
        assertSame(['neu' => 2], include $file);
    });

    test('Nicht schreibbare Datei in nicht schreibbarem Verzeichnis meldet Fehlschlag', function () use ($dir) {
        if (function_exists('posix_geteuid') && posix_geteuid() === 0) {
            assertTrue(true);
            return;
        }
        $sub  = $dir . '/gesperrt';
        mkdir($sub, 0755);
        $file = $sub . '/runtime.php';
        file_put_contents($file, "<?php return ['alt' => 1];\n");
        chmod($file, 0444);
        chmod($sub, 0555);

        $ok = AtomicFile::write($file, "<?php return ['neu' => 2];\n");
        chmod($sub, 0755);
        chmod($file, 0644);

        assertFalse($ok);
        // Die alte Datei ist unangetastet
        assertSame(['alt' => 1], include $file);
    });
};