$title, 'ccli_id' => fake()->unique()->numerify('#####'), 'author' => 'Test Author', 'copyright_text' => 'Test Publisher', ]); $label = Label::firstOrCreate( ['name' => 'Verse - '.$title], ['color' => '#2196F3'], ); $section = $song->sections()->create(['label_id' => $label->id, 'order' => 0]); $section->slides()->create(['order' => 0, 'text_content' => 'Erste Zeile']); $arrangement = $song->arrangements()->create(['name' => 'normal', 'is_default' => true]); $arrangement->arrangementSections()->create(['song_section_id' => $section->id, 'order' => 0]); return $song; } private function buildProbundleZip(string $proContent, array $mediaFiles = []): string { $tmpPath = tempnam(sys_get_temp_dir(), 'test-probundle-').'.probundle'; $zip = new ZipArchive(); $zip->open($tmpPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); $zip->addFromString('song.pro', $proContent); foreach ($mediaFiles as $name => $content) { $zip->addFromString($name, $content); } $zip->close(); return $tmpPath; } private function cleanupTempDir(string $dir): void { if (! is_dir($dir)) { return; } $items = scandir($dir); if ($items === false) { return; } foreach ($items as $item) { if ($item === '.' || $item === '..') { continue; } $path = $dir.'/'.$item; is_dir($path) ? $this->cleanupTempDir($path) : unlink($path); } rmdir($dir); } public function test_probundle_prefix_wird_korrekt_injiziert(): void { $user = User::factory()->create(); $service = Service::factory()->create([ 'title' => 'Bundle Test Service', 'date' => now(), ]); $song = $this->createSongWithContent('Test Song'); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $song->id, 'cts_song_name' => 'Test Song', 'order' => 1, ]); $proContent = 'dummy-pro-content'; $mediaContent = 'dummy-media-bytes'; $bundlePath = $this->buildProbundleZip($proContent, ['background.jpg' => $mediaContent]); $storedPath = 'export-pro-files/intro.probundle'; Storage::disk('local')->put($storedPath, file_get_contents($bundlePath)); unlink($bundlePath); ExportProFile::create([ 'type' => 'prefix', 'original_name' => 'intro.probundle', 'stored_path' => $storedPath, 'order' => 1, ]); $result = app(PlaylistExportService::class)->generatePlaylist($service); $playlist = ProPlaylistReader::read($result['path']); $entries = $playlist->getEntries(); $entryNames = array_map(fn ($e) => $e->getName(), $entries); $this->assertContains('intro', $entryNames, 'Expected prefix presentation entry named "intro"'); $songPos = array_search('Test Song', $entryNames, true); $prefixPos = array_search('intro', $entryNames, true); $this->assertNotFalse($prefixPos, 'Prefix entry not found'); $this->assertNotFalse($songPos, 'Song entry not found'); $this->assertLessThan($songPos, $prefixPos, 'Prefix should appear before song'); $embeddedFiles = $playlist->getEmbeddedFiles(); $this->assertArrayHasKey('PREFIX_1_intro.pro', $embeddedFiles, 'Inner .pro should be embedded as PREFIX_1_intro.pro'); $this->assertSame($proContent, $embeddedFiles['PREFIX_1_intro.pro']); $this->assertArrayHasKey('background.jpg', $embeddedFiles, 'Media file from .probundle should be embedded'); $this->assertSame($mediaContent, $embeddedFiles['background.jpg']); $this->cleanupTempDir($result['temp_dir']); } public function test_probundle_postfix_wird_nach_songs_injiziert(): void { $service = Service::factory()->create([ 'title' => 'Postfix Bundle Service', 'date' => now(), ]); $song = $this->createSongWithContent('Worship Song'); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $song->id, 'cts_song_name' => 'Worship Song', 'order' => 1, ]); $bundlePath = $this->buildProbundleZip('outro-pro-content', ['outro-media.jpg' => 'outro-media-bytes']); $storedPath = 'export-pro-files/outro.probundle'; Storage::disk('local')->put($storedPath, file_get_contents($bundlePath)); unlink($bundlePath); ExportProFile::create([ 'type' => 'postfix', 'original_name' => 'outro.probundle', 'stored_path' => $storedPath, 'order' => 1, ]); $result = app(PlaylistExportService::class)->generatePlaylist($service); $playlist = ProPlaylistReader::read($result['path']); $entries = $playlist->getEntries(); $entryNames = array_map(fn ($e) => $e->getName(), $entries); $songPos = array_search('Worship Song', $entryNames, true); $postfixPos = array_search('outro', $entryNames, true); $this->assertNotFalse($postfixPos, 'Postfix entry not found'); $this->assertNotFalse($songPos, 'Song entry not found'); $this->assertLessThan($postfixPos, $songPos, 'Song should appear before postfix'); $embeddedFiles = $playlist->getEmbeddedFiles(); $this->assertArrayHasKey('POSTFIX_1_outro.pro', $embeddedFiles); $this->assertSame('outro-pro-content', $embeddedFiles['POSTFIX_1_outro.pro']); $this->assertArrayHasKey('outro-media.jpg', $embeddedFiles); $this->cleanupTempDir($result['temp_dir']); } public function test_probundle_ohne_pro_eintrag_wird_uebersprungen(): void { $service = Service::factory()->create([ 'title' => 'Skip Bundle Service', 'date' => now(), ]); $song = $this->createSongWithContent('Einziger Song'); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $song->id, 'cts_song_name' => 'Einziger Song', 'order' => 1, ]); $tmpPath = tempnam(sys_get_temp_dir(), 'test-probundle-').'.probundle'; $zip = new ZipArchive(); $zip->open($tmpPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); $zip->addFromString('media.jpg', 'media-only'); $zip->close(); $storedPath = 'export-pro-files/broken.probundle'; Storage::disk('local')->put($storedPath, file_get_contents($tmpPath)); unlink($tmpPath); ExportProFile::create([ 'type' => 'prefix', 'original_name' => 'broken.probundle', 'stored_path' => $storedPath, 'order' => 1, ]); $result = app(PlaylistExportService::class)->generatePlaylist($service); $playlist = ProPlaylistReader::read($result['path']); $entryNames = array_map(fn ($e) => $e->getName(), $playlist->getEntries()); $this->assertNotContains('broken', $entryNames); $this->assertContains('Einziger Song', $entryNames); $this->cleanupTempDir($result['temp_dir']); } public function test_plain_pro_prefix_weiterhin_korrekt(): void { $service = Service::factory()->create([ 'title' => 'Plain Pro Service', 'date' => now(), ]); $song = $this->createSongWithContent('Lied'); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $song->id, 'cts_song_name' => 'Lied', 'order' => 1, ]); $storedPath = 'export-pro-files/welcome.pro'; Storage::disk('local')->put($storedPath, 'welcome-pro-bytes'); ExportProFile::create([ 'type' => 'prefix', 'original_name' => 'welcome.pro', 'stored_path' => $storedPath, 'order' => 1, ]); $result = app(PlaylistExportService::class)->generatePlaylist($service); $playlist = ProPlaylistReader::read($result['path']); $entries = $playlist->getEntries(); $entryNames = array_map(fn ($e) => $e->getName(), $entries); $this->assertContains('welcome', $entryNames); $prefixPos = array_search('welcome', $entryNames, true); $songPos = array_search('Lied', $entryNames, true); $this->assertLessThan($songPos, $prefixPos, 'Plain .pro prefix should appear before song'); $embeddedFiles = $playlist->getEmbeddedFiles(); $this->assertArrayHasKey('PREFIX_1_welcome.pro', $embeddedFiles); $this->assertSame('welcome-pro-bytes', $embeddedFiles['PREFIX_1_welcome.pro']); $this->cleanupTempDir($result['temp_dir']); } }