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
This commit is contained in:
parent
747d2c3c07
commit
8cbda3b8bc
128
tests/Feature/PlaylistExportTest.php
Normal file
128
tests/Feature/PlaylistExportTest.php
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceSong;
|
||||||
|
use App\Models\Song;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
final class PlaylistExportTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private User $user;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue