user = User::factory()->create(); }); test('finalize ohne voraussetzungen gibt warnungen zurueck', function () { $service = Service::factory()->create(['finalized_at' => null]); // Song without match or arrangement ServiceSong::create([ 'service_id' => $service->id, 'song_id' => null, 'song_arrangement_id' => null, 'use_translation' => false, 'order' => 1, 'cts_song_name' => 'Test Song', 'cts_ccli_id' => '12345', ]); $response = $this->actingAs($this->user) ->postJson(route('services.finalize', $service), ['confirmed' => false]); $response->assertOk() ->assertJson([ 'needs_confirmation' => true, ]); $data = $response->json(); expect($data['warnings'])->toHaveCount(3) ->and($data['warnings'][0])->toContain('Songs sind zugeordnet') ->and($data['warnings'][1])->toContain('Arrangement') ->and($data['warnings'][2])->toContain('Predigtfolien'); // Not finalized yet expect($service->fresh()->finalized_at)->toBeNull(); }); test('finalize mit confirmed=true trotz warnungen finalisiert service', function () { $service = Service::factory()->create(['finalized_at' => null]); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => null, 'song_arrangement_id' => null, 'use_translation' => false, 'order' => 1, 'cts_song_name' => 'Test Song', 'cts_ccli_id' => '12345', ]); $response = $this->actingAs($this->user) ->postJson(route('services.finalize', $service), ['confirmed' => true]); $response->assertOk() ->assertJson([ 'needs_confirmation' => false, 'success' => 'Service wurde abgeschlossen.', ]); expect($service->fresh()->finalized_at)->not->toBeNull(); }); test('finalize ohne warnungen finalisiert direkt', function () { $service = Service::factory()->create(['finalized_at' => null]); $song = Song::factory()->create(); $arrangement = SongArrangement::factory()->create(['song_id' => $song->id]); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $song->id, 'song_arrangement_id' => $arrangement->id, 'use_translation' => false, 'order' => 1, 'cts_song_name' => 'Test Song', 'cts_ccli_id' => '12345', ]); Slide::factory()->create([ 'service_id' => $service->id, 'type' => 'sermon', ]); $response = $this->actingAs($this->user) ->postJson(route('services.finalize', $service), ['confirmed' => false]); $response->assertOk() ->assertJson([ 'needs_confirmation' => false, 'success' => 'Service wurde abgeschlossen.', ]); expect($service->fresh()->finalized_at)->not->toBeNull(); }); test('finalize warnt bei fehlenden song-zuordnungen', function () { $service = Service::factory()->create(['finalized_at' => null]); $song = Song::factory()->create(); $arrangement = SongArrangement::factory()->create(['song_id' => $song->id]); // One matched, one unmatched ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $song->id, 'song_arrangement_id' => $arrangement->id, 'use_translation' => false, 'order' => 1, 'cts_song_name' => 'Matched', 'cts_ccli_id' => '111', ]); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => null, 'song_arrangement_id' => null, 'use_translation' => false, 'order' => 2, 'cts_song_name' => 'Unmatched', 'cts_ccli_id' => '222', ]); Slide::factory()->create([ 'service_id' => $service->id, 'type' => 'sermon', ]); $response = $this->actingAs($this->user) ->postJson(route('services.finalize', $service), ['confirmed' => false]); $data = $response->json(); expect($data['needs_confirmation'])->toBeTrue() ->and($data['warnings'])->toContain('Nur 1 von 2 Songs sind zugeordnet.') ->and($data['warnings'])->toContain('Nur 1 von 2 Songs haben ein Arrangement.'); }); test('finalize warnt bei fehlenden predigtfolien', function () { $service = Service::factory()->create(['finalized_at' => null]); $song = Song::factory()->create(); $arrangement = SongArrangement::factory()->create(['song_id' => $song->id]); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $song->id, 'song_arrangement_id' => $arrangement->id, 'use_translation' => false, 'order' => 1, 'cts_song_name' => 'Song', 'cts_ccli_id' => '111', ]); // No sermon slides $response = $this->actingAs($this->user) ->postJson(route('services.finalize', $service), ['confirmed' => false]); $data = $response->json(); expect($data['needs_confirmation'])->toBeTrue() ->and($data['warnings'])->toContain('Es wurden keine Predigtfolien hochgeladen.'); }); test('reopen setzt finalized_at zurueck', function () { $service = Service::factory()->create(['finalized_at' => now()]); $response = $this->actingAs($this->user) ->post(route('services.reopen', $service)); $response->assertRedirect(route('services.index')); expect($service->fresh()->finalized_at)->toBeNull(); }); test('download gibt placeholder nachricht zurueck', function () { $service = Service::factory()->create(['finalized_at' => now()]); $response = $this->actingAs($this->user) ->getJson(route('services.download', $service)); $response->assertOk() ->assertJson([ 'message' => 'Die Show-Erstellung wird in einem zukünftigen Update verfügbar sein.', ]); }); test('finalize erfordert authentifizierung', function () { $service = Service::factory()->create(); $response = $this->postJson(route('services.finalize', $service)); $response->assertUnauthorized(); }); test('download erfordert authentifizierung', function () { $service = Service::factory()->create(); $response = $this->getJson(route('services.download', $service)); $response->assertUnauthorized(); }); test('service model isReadyToFinalize accessor', function () { $service = Service::factory()->create(['finalized_at' => null]); // No songs, no sermon slides → only sermon warning expect($service->is_ready_to_finalize)->toBeFalse(); Slide::factory()->create([ 'service_id' => $service->id, 'type' => 'sermon', ]); // Refresh to clear cached relations $service->refresh(); // No songs, has sermon slides → ready (0/0 songs is not a warning) expect($service->is_ready_to_finalize)->toBeTrue(); }); test('finalization status mit service ohne songs warnt nur bei predigtfolien', function () { $service = Service::factory()->create(['finalized_at' => null]); $status = $service->finalizationStatus(); // No songs: no song warnings. Only sermon slides warning. expect($status['ready'])->toBeFalse() ->and($status['warnings'])->toHaveCount(1) ->and($status['warnings'][0])->toContain('Predigtfolien'); });