$title, 'ccli_id' => fake()->unique()->numerify('#####'), 'author' => 'Author', ]); $label = Label::firstOrCreate(['name' => 'Intro - '.$title], ['color' => '#000000']); // Locked section with NO slides. $section = $song->sections()->create([ 'label_id' => $label->id, 'order' => 0, 'locked' => true, ]); $arrangement = $song->arrangements()->create(['name' => 'normal', 'is_default' => true]); $arrangement->arrangementSections()->create(['song_section_id' => $section->id, 'order' => 0]); return $song; } private function createNormalSong(string $title = 'Normales Lied'): Song { $song = Song::create([ 'title' => $title, 'ccli_id' => fake()->unique()->numerify('#####'), 'author' => 'Author', ]); $label = Label::firstOrCreate(['name' => 'Verse 1 - '.$title], ['color' => '#2196F3']); $section = $song->sections()->create(['label_id' => $label->id, 'order' => 0, 'locked' => false]); $section->slides()->create(['order' => 0, 'text_content' => 'Strophentext']); $arrangement = $song->arrangements()->create(['name' => 'normal', 'is_default' => true]); $arrangement->arrangementSections()->create(['song_section_id' => $section->id, 'order' => 0]); return $song; } private function testable_export_service(): PlaylistExportService { return new class extends PlaylistExportService { protected function writeProFile(string $path, string $name, array $groups, array $arrangements): void { file_put_contents($path, 'mock-pro:'.$name); } protected function writePlaylistFile(string $path, string $name, array $items, array $embeddedFiles): void { $content = 'mock-playlist:'.$name; foreach ($items as $item) { $content .= "\n".($item['name'] ?? ''); } file_put_contents($path, $content); } }; } public function test_playlist_legacy_ueberspringt_leeres_locked_lied_mit_warnung(): void { $service = Service::factory()->create(['finalized_at' => now(), 'title' => 'Mix Service']); $emptySong = $this->createEmptyLockedSong('Leeres Lied'); $normalSong = $this->createNormalSong('Normales Lied'); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $emptySong->id, 'cts_song_name' => 'Leeres Lied', 'order' => 1, ]); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $normalSong->id, 'cts_song_name' => 'Normales Lied', 'order' => 2, ]); $result = $this->testable_export_service()->generatePlaylist($service); $content = file_get_contents($result['path']); // Empty locked song skipped, normal song exported. $this->assertStringNotContainsString('Leeres Lied', $content); $this->assertStringContainsString('Normales Lied', $content); // German warning present. $this->assertContains( "Lied 'Leeres Lied' übersprungen: keine Inhaltsfolien.", $result['warnings'], ); $this->assertGreaterThanOrEqual(1, $result['skipped']); $this->cleanupTempDir($result['temp_dir']); } public function test_probundle_agenda_song_wirft_fehler_bei_leerem_locked_lied(): void { $service = Service::factory()->create(['finalized_at' => now()]); $emptySong = $this->createEmptyLockedSong('Leeres Lied'); $serviceSong = ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $emptySong->id, 'cts_song_name' => 'Leeres Lied', 'order' => 1, ]); $agendaItem = \App\Models\ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Leeres Lied', 'service_song_id' => $serviceSong->id, 'sort_order' => 1, 'is_before_event' => false, ]); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('keine Inhaltsfolien'); app(ProBundleExportService::class)->generateAgendaItemBundle($agendaItem); } public function test_probundle_agenda_song_exportiert_normales_lied(): void { $service = Service::factory()->create(['finalized_at' => now()]); $normalSong = $this->createNormalSong('Normales Lied'); $serviceSong = ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $normalSong->id, 'cts_song_name' => 'Normales Lied', 'order' => 1, ]); $agendaItem = \App\Models\ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Normales Lied', 'service_song_id' => $serviceSong->id, 'sort_order' => 1, 'is_before_event' => false, ]); $bundlePath = app(ProBundleExportService::class)->generateAgendaItemBundle($agendaItem); $this->assertFileExists($bundlePath); $this->assertGreaterThan(0, filesize($bundlePath)); @unlink($bundlePath); } private function cleanupTempDir(string $dir): void { if (! is_dir($dir)) { return; } foreach (scandir($dir) as $item) { if ($item === '.' || $item === '..') { continue; } $path = $dir.'/'.$item; is_dir($path) ? $this->cleanupTempDir($path) : unlink($path); } rmdir($dir); } }