/dev/null || true" * unterdrückt wurden, meldete die Migration trotzdem Erfolg. Ergebnis: eine * Installation, in der die Belege nicht mehr auffindbar waren. * * Der Test führt scripts/intern/uploads-uebernahme.sh real aus und prüft, dass es * zusammenführt statt zu verschachteln und keine vorhandene Zieldatei überschreibt. */ class DatenverzeichnisUebernahmeTest extends TestCase { protected string $testName = 'Test 76: Übernahme Altbestand uploads/ -> data/belege'; public function run(): void { $skript = dirname(__DIR__) . '/scripts/intern/uploads-uebernahme.sh'; $this->assert(file_exists($skript), 'scripts/intern/uploads-uebernahme.sh existiert'); exec('sh -n ' . escapeshellarg($skript) . ' 2>&1', $syntax, $syntaxExit); $this->assertEquals(0, $syntaxExit, 'sh-Syntax ist gültig (' . implode(' ', $syntax) . ')'); $inhalt = (string)file_get_contents($skript); $this->assert( !preg_match('/\bmv\b[^\n]*2>\/dev\/null\s*\|\|\s*true/', $inhalt), 'Fehler beim Verschieben werden nicht unterdrückt' ); // Der manuelle Umzug muss dieselbe Logik verwenden wie der Container-Start – // sonst driftet beides auseinander. docker-entrypoint.sh wird hier bewusst // nicht geprüft: Die Datei ist nicht als Volume gemountet, im Container läge // also immer der Stand des Images vor und der Test schlüge nur an, wenn ein // Rebuild aussteht. $migration = dirname(__DIR__) . '/scripts/migrate-datenverzeichnis.sh'; $this->assert( str_contains((string)file_get_contents($migration), 'uploads-uebernahme.sh'), 'migrate-datenverzeichnis.sh verwendet scripts/intern/uploads-uebernahme.sh' ); // ── Funktionaler Lauf: Ziel enthält bereits ein Jahresverzeichnis ────── $basis = $this->tempPath(); $alt = $basis . '/uploads'; $neu = $basis . '/belege'; $logo = $basis . '/logo'; mkdir($alt . '/2026/BLG-2026-0001', 0777, true); mkdir($alt . '/2026/BLG-2026-0099', 0777, true); mkdir($alt . '/logo', 0777, true); mkdir($neu . '/2026/BLG-2026-0001', 0777, true); mkdir($logo, 0777, true); file_put_contents($alt . '/2026/BLG-2026-0001/original.pdf', 'ALT'); file_put_contents($alt . '/2026/BLG-2026-0099/original.pdf', 'NEU-IM-ALTBESTAND'); file_put_contents($alt . '/logo/logo.png', 'LOGO'); file_put_contents($neu . '/2026/BLG-2026-0001/original.pdf', 'BESTEHEND'); $befehl = 'sh ' . escapeshellarg($skript) . ' ' . escapeshellarg($alt) . ' ' . escapeshellarg($neu) . ' ' . escapeshellarg($logo) . ' 2>&1'; exec($befehl, $ausgabe, $exitCode); $ausgabeText = implode("\n", $ausgabe); $this->assert( is_file($neu . '/2026/BLG-2026-0099/original.pdf'), 'Beleg aus dem Altbestand landet im bestehenden Jahresverzeichnis' ); $this->assertEquals( 'NEU-IM-ALTBESTAND', (string)file_get_contents($neu . '/2026/BLG-2026-0099/original.pdf'), 'Übernommener Beleg ist inhaltlich unverändert' ); $this->assert( !is_dir($neu . '/2026/2026'), 'Der Baum wird nicht als data/belege/2026/2026 verschachtelt' ); $this->assertEquals( 'LOGO', (string)file_get_contents($logo . '/logo.png'), 'Das Logo wandert nach data/logo und nicht in die Belege' ); $this->assert( !is_file($neu . '/logo/logo.png'), 'Das Logo liegt nicht zusätzlich als Beleg vor' ); // Kollision: bestehende Zieldatei bleibt, Quelldatei geht nicht verloren. $this->assertEquals( 'BESTEHEND', (string)file_get_contents($neu . '/2026/BLG-2026-0001/original.pdf'), 'Vorhandene Zieldatei wird nicht überschrieben' ); $this->assert( is_file($alt . '/2026/BLG-2026-0001/original.pdf'), 'Kollidierende Quelldatei bleibt zur Prüfung liegen statt verloren zu gehen' ); $this->assert( str_contains($ausgabeText, 'übersprungen'), 'Die Kollision wird gemeldet und nicht stillschweigend übergangen' ); $this->assertEquals(1, $exitCode, 'Verbliebene Reste melden einen Fehler-Exitcode'); // ── Zweiter Lauf ohne Altbestand: nichts zu tun, sauberer Exitcode ──── $leer = $basis . '/leer'; mkdir($leer, 0777, true); exec('sh ' . escapeshellarg($skript) . ' ' . escapeshellarg($leer) . ' ' . escapeshellarg($neu) . ' ' . escapeshellarg($logo) . ' 2>&1', $zweite, $zweiterExit); $this->assertEquals(0, $zweiterExit, 'Lauf ohne Altbestand endet mit Exitcode 0'); } }