From 8cbda3b8bc01f9394c73b0482199881f729c0bc8 Mon Sep 17 00:00:00 2001 From: Thorsten Bus Date: Mon, 2 Mar 2026 12:29:14 +0100 Subject: [PATCH] test(services): add PlaylistExportTest for .proplaylist download scenarios - Finalized service with matched songs returns .proplaylist file - Non-finalized service returns 403 - Finalized service with no songs returns 422 - Skipped songs count reported via X-Skipped-Songs header - Auth required for download endpoint --- tests/Feature/PlaylistExportTest.php | 128 +++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tests/Feature/PlaylistExportTest.php diff --git a/tests/Feature/PlaylistExportTest.php b/tests/Feature/PlaylistExportTest.php new file mode 100644 index 0000000..64b78ab --- /dev/null +++ b/tests/Feature/PlaylistExportTest.php @@ -0,0 +1,128 @@ +user = User::factory()->create(); + } + + private function createSongWithContent(string $title = 'Test Song', ?string $ccli = null): Song + { + $song = Song::create([ + 'title' => $title, + 'ccli_id' => $ccli ?? fake()->unique()->numerify('#####'), + 'author' => 'Test Author', + 'copyright_text' => 'Test Publisher', + ]); + + $verse = $song->groups()->create(['name' => 'Verse 1', 'color' => '#2196F3', 'order' => 0]); + $verse->slides()->create(['order' => 0, 'text_content' => 'First line of verse']); + + $chorus = $song->groups()->create(['name' => 'Chorus', 'color' => '#F44336', 'order' => 1]); + $chorus->slides()->create(['order' => 0, 'text_content' => 'Chorus text']); + + $arrangement = $song->arrangements()->create(['name' => 'normal', 'is_default' => true]); + $arrangement->arrangementGroups()->create(['song_group_id' => $verse->id, 'order' => 0]); + $arrangement->arrangementGroups()->create(['song_group_id' => $chorus->id, 'order' => 1]); + + return $song; + } + + public function test_download_finalisierter_service_mit_songs_gibt_proplaylist_datei(): void + { + $service = Service::factory()->create(['finalized_at' => now(), 'title' => 'Gottesdienst']); + $song = $this->createSongWithContent('Lobe den Herrn'); + + ServiceSong::create([ + 'service_id' => $service->id, + 'song_id' => $song->id, + 'cts_song_name' => 'Lobe den Herrn', + 'order' => 1, + ]); + + $response = $this->actingAs($this->user)->get(route('services.download', $service)); + + $response->assertOk(); + $contentType = $response->headers->get('content-type'); + $this->assertTrue( + in_array($contentType, ['application/octet-stream', 'application/zip']), + "Expected octet-stream or zip content-type, got: {$contentType}" + ); + $disposition = $response->headers->get('content-disposition'); + $this->assertNotNull($disposition); + $this->assertTrue( + str_contains($disposition, '.proplaylist'), + "Expected .proplaylist in content-disposition, got: {$disposition}" + ); + } + + public function test_download_nicht_finalisierter_service_gibt_403(): void + { + $service = Service::factory()->create(['finalized_at' => null]); + + $response = $this->actingAs($this->user)->getJson(route('services.download', $service)); + + $response->assertForbidden(); + } + + public function test_download_finalisierter_service_ohne_songs_gibt_422(): void + { + $service = Service::factory()->create(['finalized_at' => now()]); + + $response = $this->actingAs($this->user)->getJson(route('services.download', $service)); + + $response->assertUnprocessable() + ->assertJson(['message' => 'Keine Songs mit Inhalt zum Exportieren gefunden.']); + } + + public function test_download_mit_ungematchten_songs_setzt_skipped_header(): void + { + $service = Service::factory()->create(['finalized_at' => now()]); + $song = $this->createSongWithContent('Matched Song'); + + // Matched song + ServiceSong::create([ + 'service_id' => $service->id, + 'song_id' => $song->id, + 'cts_song_name' => 'Matched Song', + 'order' => 1, + ]); + + // Unmatched song (no song_id) + ServiceSong::create([ + 'service_id' => $service->id, + 'song_id' => null, + 'cts_song_name' => 'Unmatched Song', + 'order' => 2, + ]); + + $response = $this->actingAs($this->user)->get(route('services.download', $service)); + + $response->assertOk(); + $this->assertEquals('1', $response->headers->get('X-Skipped-Songs')); + } + + public function test_download_erfordert_authentifizierung(): void + { + $service = Service::factory()->create(['finalized_at' => now()]); + + $response = $this->getJson(route('services.download', $service)); + + $response->assertUnauthorized(); + } +}