withoutVite(); $user = User::factory()->create(); Service::factory()->create([ 'date' => Carbon::today()->subDay(), 'finalized_at' => null, ]); $todayService = Service::factory()->create([ 'title' => 'Gottesdienst Heute', 'date' => Carbon::today(), 'finalized_at' => null, ]); $futureService = Service::factory()->create([ 'title' => 'Gottesdienst Zukunft', 'date' => Carbon::today()->addDays(3), 'finalized_at' => null, ]); $song = Song::factory()->create(); $arrangement = SongArrangement::factory()->create([ 'song_id' => $song->id, ]); ServiceSong::create([ 'service_id' => $todayService->id, 'song_id' => $song->id, 'song_arrangement_id' => $arrangement->id, 'use_translation' => false, 'order' => 1, 'cts_song_name' => 'Song 1', 'cts_ccli_id' => '100001', ]); ServiceSong::create([ 'service_id' => $todayService->id, 'song_id' => $song->id, 'song_arrangement_id' => null, 'use_translation' => false, 'order' => 2, 'cts_song_name' => 'Song 2', 'cts_ccli_id' => '100002', ]); ServiceSong::create([ 'service_id' => $todayService->id, 'song_id' => null, 'song_arrangement_id' => null, 'use_translation' => false, 'order' => 3, 'cts_song_name' => 'Song 3', 'cts_ccli_id' => '100003', ]); ServiceSong::create([ 'service_id' => $futureService->id, 'song_id' => $song->id, 'song_arrangement_id' => $arrangement->id, 'use_translation' => false, 'order' => 1, 'cts_song_name' => 'Song 4', 'cts_ccli_id' => '100004', ]); ServiceSong::create([ 'service_id' => $futureService->id, 'song_id' => null, 'song_arrangement_id' => null, 'use_translation' => false, 'order' => 2, 'cts_song_name' => 'Song 5', 'cts_ccli_id' => '100005', ]); Slide::factory()->create([ 'service_id' => $todayService->id, 'type' => 'sermon', 'expire_date' => null, 'uploaded_at' => Carbon::today()->subDays(2), ]); Slide::factory()->create([ 'service_id' => null, 'type' => 'information', 'expire_date' => Carbon::today()->addDay(), 'uploaded_at' => Carbon::today()->subDays(3), ]); Slide::factory()->create([ 'service_id' => null, 'type' => 'information', 'expire_date' => Carbon::today()->addDays(5), 'uploaded_at' => Carbon::today()->subDays(2), ]); Slide::factory()->create([ 'service_id' => $todayService->id, 'type' => 'information', 'expire_date' => Carbon::today()->addDays(10), 'uploaded_at' => Carbon::today()->subDay(), ]); Slide::factory()->create([ 'service_id' => null, 'type' => 'information', 'expire_date' => Carbon::today()->subDay(), 'uploaded_at' => Carbon::today()->subDays(5), ]); $response = $this->actingAs($user)->get(route('services.index')); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Index') ->has('services', 2) ->where('services.0.id', $todayService->id) ->where('services.0.cts_event_id', $todayService->cts_event_id) ->where('services.0.title', 'Gottesdienst Heute') ->where('services.0.songs_total_count', 3) ->where('services.0.songs_mapped_count', 2) ->where('services.0.songs_arranged_count', 1) ->where('services.0.has_sermon_slides', true) ->where('services.0.info_slides_count', 3) ->where('services.0.agenda_slides_count', 0) ->where('services.1.id', $futureService->id) ->where('services.1.title', 'Gottesdienst Zukunft') ->where('services.1.songs_total_count', 2) ->where('services.1.songs_mapped_count', 1) ->where('services.1.songs_arranged_count', 1) ->where('services.1.has_sermon_slides', false) ->where('services.1.info_slides_count', 1) ->where('services.1.agenda_slides_count', 0) ); } public function test_service_kann_abgeschlossen_werden(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $user = User::factory()->create(); $service = Service::factory()->create([ 'finalized_at' => null, ]); // Finalize with confirmed=true to skip prerequisite check $response = $this->actingAs($user)->postJson(route('services.finalize', $service), ['confirmed' => true]); $response->assertOk(); $response->assertJson(['needs_confirmation' => false, 'success' => 'Service wurde abgeschlossen.']); $this->assertSame(now()->toDateTimeString(), $service->fresh()->finalized_at?->toDateTimeString()); } public function test_service_kann_wieder_geoeffnet_werden(): void { $user = User::factory()->create(); $service = Service::factory()->create([ 'finalized_at' => now(), ]); $response = $this->actingAs($user)->post(route('services.reopen', $service)); $response->assertRedirect(route('services.index')); $this->assertNull($service->fresh()->finalized_at); } public function test_service_edit_seite_zeigt_service_mit_songs_und_slides(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $service = Service::factory()->create([ 'title' => 'Gottesdienst', 'date' => Carbon::today()->addDays(7), 'preacher_name' => 'Pastor Mueller', 'finalized_at' => null, ]); $song = Song::factory()->create([ 'title' => 'Amazing Grace', 'ccli_id' => '4321', 'has_translation' => true, ]); $arrangement = SongArrangement::factory()->create([ 'song_id' => $song->id, 'name' => 'Normal', ]); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $song->id, 'song_arrangement_id' => $arrangement->id, 'use_translation' => true, 'order' => 1, 'cts_song_name' => 'Amazing Grace', 'cts_ccli_id' => '4321', ]); ServiceSong::create([ 'service_id' => $service->id, 'song_id' => null, 'song_arrangement_id' => null, 'use_translation' => false, 'order' => 2, 'cts_song_name' => 'Unbekannter Song', 'cts_ccli_id' => '9999', ]); Slide::factory()->create([ 'service_id' => $service->id, 'type' => 'sermon', ]); Slide::factory()->create([ 'service_id' => $service->id, 'type' => 'moderation', ]); Slide::factory()->create([ 'service_id' => null, 'type' => 'information', 'expire_date' => Carbon::today()->addDays(14), ]); $response = $this->actingAs($user)->get(route('services.edit', $service)); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Edit') ->has('service') ->where('service.title', 'Gottesdienst') ->where('service.preacher_name', 'Pastor Mueller') ->has('serviceSongs', 2) ->where('serviceSongs.0.cts_song_name', 'Amazing Grace') ->where('serviceSongs.0.song.title', 'Amazing Grace') ->where('serviceSongs.0.song.has_translation', true) ->where('serviceSongs.0.arrangement.name', 'Normal') ->where('serviceSongs.1.cts_song_name', 'Unbekannter Song') ->where('serviceSongs.1.song', null) ->has('informationSlides', 1) ->has('moderationSlides', 1) ->has('sermonSlides', 1) ); } public function test_service_edit_erfordert_authentifizierung(): void { $service = Service::factory()->create(); $response = $this->get(route('services.edit', $service)); $response->assertRedirect(route('login')); } public function test_services_index_zeigt_nur_zukuenftige_services_standardmaessig(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); Service::factory()->create([ 'date' => Carbon::today()->subDays(5), 'title' => 'Vergangener Service', ]); $todayService = Service::factory()->create([ 'date' => Carbon::today(), 'title' => 'Heutiger Service', ]); $futureService = Service::factory()->create([ 'date' => Carbon::today()->addDays(3), 'title' => 'Zukünftiger Service', ]); $response = $this->actingAs($user)->get(route('services.index')); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Index') ->has('services', 2) ->where('services.0.title', 'Heutiger Service') ->where('services.1.title', 'Zukünftiger Service') ->where('archived', false) ); } public function test_services_index_zeigt_vergangene_services_mit_archived_parameter(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $pastService1 = Service::factory()->create([ 'date' => Carbon::today()->subDays(5), 'title' => 'Vergangener Service 1', ]); $pastService2 = Service::factory()->create([ 'date' => Carbon::today()->subDays(2), 'title' => 'Vergangener Service 2', ]); Service::factory()->create([ 'date' => Carbon::today(), 'title' => 'Heutiger Service', ]); Service::factory()->create([ 'date' => Carbon::today()->addDays(3), 'title' => 'Zukünftiger Service', ]); $response = $this->actingAs($user)->get(route('services.index', ['archived' => 1])); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Index') ->has('services', 2) ->where('services.0.title', 'Vergangener Service 2') ->where('services.1.title', 'Vergangener Service 1') ->where('archived', true) ); } public function test_services_index_zaehlt_agenda_slides_fuer_nicht_song_items(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $service = Service::factory()->create([ 'title' => 'Service mit Agenda', 'date' => Carbon::today()->addDays(2), 'finalized_at' => null, ]); // Non-song agenda item WITH slides → counts $agendaItem1 = ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Predigt', 'sort_order' => 1, 'service_song_id' => null, 'is_before_event' => false, ]); Slide::factory()->create([ 'service_id' => $service->id, 'service_agenda_item_id' => $agendaItem1->id, 'type' => 'sermon', 'uploaded_at' => Carbon::today()->subDay(), ]); // Non-song agenda item WITH slides → counts $agendaItem2 = ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Hinweise', 'sort_order' => 2, 'service_song_id' => null, 'is_before_event' => false, ]); Slide::factory()->create([ 'service_id' => $service->id, 'service_agenda_item_id' => $agendaItem2->id, 'type' => 'moderation', 'uploaded_at' => Carbon::today()->subDay(), ]); // Non-song agenda item WITHOUT slides → does NOT count ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Begrüßung', 'sort_order' => 3, 'service_song_id' => null, 'is_before_event' => false, ]); // Song agenda item WITH slides → does NOT count (has service_song_id) $song = Song::factory()->create(); $serviceSong = ServiceSong::create([ 'service_id' => $service->id, 'song_id' => $song->id, 'song_arrangement_id' => null, 'use_translation' => false, 'order' => 1, 'cts_song_name' => 'Lobpreis', 'cts_ccli_id' => '55555', ]); $agendaItemSong = ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Lobpreis', 'sort_order' => 4, 'service_song_id' => $serviceSong->id, 'is_before_event' => false, ]); Slide::factory()->create([ 'service_id' => $service->id, 'service_agenda_item_id' => $agendaItemSong->id, 'type' => 'moderation', 'uploaded_at' => Carbon::today()->subDay(), ]); $response = $this->actingAs($user)->get(route('services.index')); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Index') ->has('services', 1) ->where('services.0.agenda_slides_count', 2) ); } public function test_services_index_sermon_check_nutzt_agenda_matching_wenn_konfiguriert(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $service = Service::factory()->create([ 'title' => 'Service mit Sermon Setting', 'date' => Carbon::today()->addDays(2), 'finalized_at' => null, ]); Setting::set('agenda_sermon_matching', 'Predigt*'); // Sermon agenda item with slides $sermonItem = ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Predigt: Hoffnung', 'sort_order' => 1, 'service_song_id' => null, 'is_before_event' => false, ]); Slide::factory()->create([ 'service_id' => $service->id, 'service_agenda_item_id' => $sermonItem->id, 'type' => 'sermon', 'uploaded_at' => Carbon::today()->subDay(), ]); // No old-style sermon slides directly on service (without agenda item) $response = $this->actingAs($user)->get(route('services.index')); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Index') ->has('services', 1) ->where('services.0.has_sermon_slides', true) ); } public function test_services_index_sermon_ohne_agenda_slides_zeigt_false(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $service = Service::factory()->create([ 'title' => 'Service ohne Predigtfolien', 'date' => Carbon::today()->addDays(2), 'finalized_at' => null, ]); Setting::set('agenda_sermon_matching', 'Predigt*'); // Sermon agenda item WITHOUT slides ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Predigt: Thema', 'sort_order' => 1, 'service_song_id' => null, 'is_before_event' => false, ]); $response = $this->actingAs($user)->get(route('services.index')); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Index') ->has('services', 1) ->where('services.0.has_sermon_slides', false) ); } public function test_edit_seite_liefert_leere_agenda_items_und_settings(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $service = Service::factory()->create([ 'date' => Carbon::today()->addDays(7), ]); $response = $this->actingAs($user)->get(route('services.edit', $service)); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Edit') ->has('agendaItems', 0) ->has('agendaSettings') ->where('agendaSettings.start_title', null) ->where('agendaSettings.end_title', null) ->where('agendaSettings.announcement_position', null) ->where('agendaSettings.sermon_matching', null) ); } public function test_edit_seite_liefert_agenda_items_mit_computed_flags(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $service = Service::factory()->create([ 'date' => Carbon::today()->addDays(7), ]); Setting::set('agenda_announcement_position', 'Hinweis*,Information*'); Setting::set('agenda_sermon_matching', 'Predigt*'); ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Lobpreis', 'sort_order' => 1, 'is_before_event' => false, ]); ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Hinweise und Infos', 'sort_order' => 2, 'is_before_event' => false, ]); ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Predigt: Thema', 'sort_order' => 3, 'is_before_event' => false, ]); $response = $this->actingAs($user)->get(route('services.edit', $service)); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Edit') ->has('agendaItems', 3) ->where('agendaItems.0.title', 'Lobpreis') ->where('agendaItems.0.is_announcement_position', false) ->where('agendaItems.0.is_sermon', false) ->where('agendaItems.1.title', 'Hinweise und Infos') ->where('agendaItems.1.is_announcement_position', true) ->where('agendaItems.1.is_sermon', false) ->where('agendaItems.2.title', 'Predigt: Thema') ->where('agendaItems.2.is_announcement_position', false) ->where('agendaItems.2.is_sermon', true) ); } public function test_edit_seite_filtert_agenda_items_mit_start_end_grenzen(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $service = Service::factory()->create([ 'date' => Carbon::today()->addDays(7), ]); Setting::set('agenda_start_title', 'Beginn*'); Setting::set('agenda_end_title', 'Segen*'); ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Beginn Gottesdienst', 'sort_order' => 1, 'is_before_event' => false, ]); ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Lobpreis', 'sort_order' => 2, 'is_before_event' => false, ]); ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Predigt', 'sort_order' => 3, 'is_before_event' => false, ]); ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Segen und Sendung', 'sort_order' => 4, 'is_before_event' => false, ]); $response = $this->actingAs($user)->get(route('services.edit', $service)); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Edit') ->has('agendaItems', 2) ->where('agendaItems.0.title', 'Lobpreis') ->where('agendaItems.1.title', 'Predigt') ); } public function test_edit_seite_schliesst_before_event_items_aus(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $service = Service::factory()->create([ 'date' => Carbon::today()->addDays(7), ]); ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Technik Check', 'sort_order' => 1, 'is_before_event' => true, ]); ServiceAgendaItem::factory()->create([ 'service_id' => $service->id, 'title' => 'Lobpreis', 'sort_order' => 2, 'is_before_event' => false, ]); $response = $this->actingAs($user)->get(route('services.edit', $service)); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Edit') ->has('agendaItems', 1) ->where('agendaItems.0.title', 'Lobpreis') ); } public function test_edit_seite_liefert_agenda_settings_mit_allen_vier_keys(): void { Carbon::setTestNow('2026-03-01 10:00:00'); $this->withoutVite(); $user = User::factory()->create(); $service = Service::factory()->create([ 'date' => Carbon::today()->addDays(7), ]); Setting::set('agenda_start_title', 'Beginn*'); Setting::set('agenda_end_title', 'Segen*'); Setting::set('agenda_announcement_position', 'Hinweis*'); Setting::set('agenda_sermon_matching', 'Predigt*'); $response = $this->actingAs($user)->get(route('services.edit', $service)); $response->assertOk(); $response->assertInertia( fn ($page) => $page ->component('Services/Edit') ->where('agendaSettings.start_title', 'Beginn*') ->where('agendaSettings.end_title', 'Segen*') ->where('agendaSettings.announcement_position', 'Hinweis*') ->where('agendaSettings.sermon_matching', 'Predigt*') ); } }