*/ private array $subEvents; protected function setUp(): void { parent::setUp(); $this->seed(RoleSeeder::class); $this->org = Organisation::factory()->create(); $this->orgAdmin = User::factory()->create(); $this->org->users()->attach($this->orgAdmin, ['role' => 'org_admin']); $this->festival = Event::factory()->for($this->org)->festival()->create([ 'start_date' => '2026-07-10', 'end_date' => '2026-07-12', ]); $vrijdag = Event::factory()->for($this->org)->subEvent($this->festival)->create([ 'start_date' => '2026-07-10', 'end_date' => '2026-07-10', ]); $zaterdag = Event::factory()->for($this->org)->subEvent($this->festival)->create([ 'start_date' => '2026-07-11', 'end_date' => '2026-07-11', ]); $zondag = Event::factory()->for($this->org)->subEvent($this->festival)->create([ 'start_date' => '2026-07-12', 'end_date' => '2026-07-12', ]); $this->subEvents = [$vrijdag, $zaterdag, $zondag]; $pool = ArtistTimetableDevSeeder::seedOrganisationPool($this->org, 125); ArtistTimetableDevSeeder::seedForFestival($this->org, $this->festival, $this->subEvents, $pool); Sanctum::actingAs($this->orgAdmin); } public function test_engagement_event_id_is_at_festival_level(): void { // Direct DB invariant — engagement.event_id == festival.id for the // entire seeded fixture. This is the canonical model from RFC §D10/§D17. $count = \App\Models\ArtistEngagement::query() ->where('event_id', $this->festival->id) ->count(); $this->assertGreaterThan(0, $count, 'No engagements found at festival level — seeder regressed to per-sub-event scoping'); $offModelCount = \App\Models\ArtistEngagement::query() ->whereIn('event_id', collect($this->subEvents)->pluck('id')) ->count(); $this->assertSame(0, $offModelCount, 'Engagements seeded at sub-event level — Model A violated'); } public function test_performance_event_id_is_at_sub_event_level(): void { $atFestival = \App\Models\Performance::query() ->where('event_id', $this->festival->id) ->count(); $this->assertSame(0, $atFestival, 'Performances seeded at festival level — Model A violated'); $atSubEvents = \App\Models\Performance::query() ->whereIn('event_id', collect($this->subEvents)->pluck('id')) ->count(); $this->assertGreaterThan(0, $atSubEvents); } public function test_performances_index_returns_data_for_festival_with_day_filter(): void { // The exact request the SPA makes: GET /events/{festival}/performances?day={subevent} foreach ($this->subEvents as $subEvent) { $response = $this->getJson( "/api/v1/organisations/{$this->org->id}/events/{$this->festival->id}/performances?day={$subEvent->id}", ); $response->assertOk(); $payload = $response->json('data'); $this->assertIsArray($payload); $this->assertNotEmpty( $payload, "Performances index returned empty for festival={$this->festival->id} day={$subEvent->id} — controller engagement filter regressed", ); // Every returned perf should carry event_id matching the requested ?day value foreach ($payload as $perf) { $this->assertSame($subEvent->id, $perf['event_id']); } } } public function test_performances_index_unfiltered_returns_all_festival_performances(): void { // No ?day — should return every performance across all sub-events of the festival. $response = $this->getJson( "/api/v1/organisations/{$this->org->id}/events/{$this->festival->id}/performances", ); $response->assertOk(); $payload = $response->json('data'); $this->assertIsArray($payload); $this->assertNotEmpty($payload); $subEventIds = collect($this->subEvents)->pluck('id')->all(); foreach ($payload as $perf) { $this->assertContains( $perf['event_id'], $subEventIds, 'Returned performance has an event_id outside the festival sub-event set', ); } } public function test_performances_index_includes_parked_when_stage_id_null_filter(): void { $response = $this->getJson( "/api/v1/organisations/{$this->org->id}/events/{$this->festival->id}/performances?stage_id=null", ); $response->assertOk(); $payload = $response->json('data'); $this->assertNotEmpty($payload, 'Wachtrij should contain at least the seeded parked performance'); foreach ($payload as $perf) { $this->assertNull($perf['stage_id']); } } public function test_engagements_index_returns_seeded_engagements_for_festival(): void { $response = $this->getJson( "/api/v1/organisations/{$this->org->id}/events/{$this->festival->id}/engagements", ); $response->assertOk(); $payload = $response->json('data'); $this->assertIsArray($payload); $this->assertNotEmpty($payload); // Every returned engagement carries event_id = festival foreach ($payload as $engagement) { $this->assertSame($this->festival->id, $engagement['event_id']); } } public function test_stages_index_returns_seeded_stages_for_festival(): void { $response = $this->getJson( "/api/v1/organisations/{$this->org->id}/events/{$this->festival->id}/stages", ); $response->assertOk(); $payload = $response->json('data'); $this->assertIsArray($payload); $this->assertCount(5, $payload, 'Festival fixture seeds 5 stages'); // Every stage carries event_id = festival foreach ($payload as $stage) { $this->assertSame($this->festival->id, $stage['event_id']); } } }