Files
crewli/api/tests/Feature/Event/EventScopesTest.php
bert.hausmans 303280286f feat: festival helper scopes and DevSeeder with full festival structure (TECH-02, TECH-03)
Fix scopeWithChildren to accept an event ID and add scopeForFestival
scope for resolving any event to its full festival context. Extend
DevSeeder with sections, time slots, and persons on the festival.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:35:01 +02:00

211 lines
7.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Event;
use App\Models\Event;
use App\Models\Organisation;
use App\Models\Scopes\OrganisationScope;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class EventScopesTest extends TestCase
{
use RefreshDatabase;
private Organisation $org;
private Organisation $otherOrg;
private Event $flatEvent;
private Event $festival;
private Event $subEvent1;
private Event $subEvent2;
private Event $subEvent3;
private Event $otherOrgEvent;
protected function setUp(): void
{
parent::setUp();
$this->org = Organisation::factory()->create();
$this->otherOrg = Organisation::factory()->create();
// Flat event (no parent, no children)
$this->flatEvent = Event::factory()->create([
'organisation_id' => $this->org->id,
'event_type' => 'event',
'parent_event_id' => null,
]);
// Festival with 3 sub-events
$this->festival = Event::factory()->festival()->create([
'organisation_id' => $this->org->id,
]);
$this->subEvent1 = Event::factory()->subEvent($this->festival)->create([
'name' => 'Vrijdag',
]);
$this->subEvent2 = Event::factory()->subEvent($this->festival)->create([
'name' => 'Zaterdag',
]);
$this->subEvent3 = Event::factory()->subEvent($this->festival)->create([
'name' => 'Zondag',
]);
// Event in a different organisation (must never appear)
$this->otherOrgEvent = Event::factory()->create([
'organisation_id' => $this->otherOrg->id,
'event_type' => 'event',
'parent_event_id' => null,
]);
}
// --- scopeTopLevel ---
public function test_scope_top_level_returns_only_events_without_parent(): void
{
$results = Event::withoutGlobalScopes()->topLevel()->pluck('id');
$this->assertTrue($results->contains($this->flatEvent->id));
$this->assertTrue($results->contains($this->festival->id));
$this->assertTrue($results->contains($this->otherOrgEvent->id));
$this->assertFalse($results->contains($this->subEvent1->id));
$this->assertFalse($results->contains($this->subEvent2->id));
$this->assertFalse($results->contains($this->subEvent3->id));
}
// --- scopeChildren ---
public function test_scope_children_returns_only_events_with_parent(): void
{
$results = Event::withoutGlobalScopes()->children()->pluck('id');
$this->assertTrue($results->contains($this->subEvent1->id));
$this->assertTrue($results->contains($this->subEvent2->id));
$this->assertTrue($results->contains($this->subEvent3->id));
$this->assertFalse($results->contains($this->flatEvent->id));
$this->assertFalse($results->contains($this->festival->id));
$this->assertFalse($results->contains($this->otherOrgEvent->id));
}
// --- scopeWithChildren ---
public function test_scope_with_children_returns_parent_and_its_children(): void
{
$results = Event::withoutGlobalScopes()
->withChildren($this->festival->id)
->pluck('id');
$this->assertCount(4, $results);
$this->assertTrue($results->contains($this->festival->id));
$this->assertTrue($results->contains($this->subEvent1->id));
$this->assertTrue($results->contains($this->subEvent2->id));
$this->assertTrue($results->contains($this->subEvent3->id));
$this->assertFalse($results->contains($this->flatEvent->id));
$this->assertFalse($results->contains($this->otherOrgEvent->id));
}
public function test_scope_with_children_for_flat_event_returns_only_self(): void
{
$results = Event::withoutGlobalScopes()
->withChildren($this->flatEvent->id)
->pluck('id');
$this->assertCount(1, $results);
$this->assertTrue($results->contains($this->flatEvent->id));
}
// --- scopeFestivals ---
public function test_scope_festivals_returns_only_festival_and_series_types(): void
{
$series = Event::factory()->series()->create([
'organisation_id' => $this->org->id,
]);
$results = Event::withoutGlobalScopes()->festivals()->pluck('id');
$this->assertTrue($results->contains($this->festival->id));
$this->assertTrue($results->contains($series->id));
$this->assertFalse($results->contains($this->flatEvent->id));
$this->assertFalse($results->contains($this->subEvent1->id));
$this->assertFalse($results->contains($this->otherOrgEvent->id));
}
// --- scopeForFestival ---
public function test_scope_for_festival_given_child_returns_parent_and_all_siblings(): void
{
$results = Event::withoutGlobalScopes()
->forFestival($this->subEvent1)
->pluck('id');
$this->assertCount(4, $results);
$this->assertTrue($results->contains($this->festival->id));
$this->assertTrue($results->contains($this->subEvent1->id));
$this->assertTrue($results->contains($this->subEvent2->id));
$this->assertTrue($results->contains($this->subEvent3->id));
}
public function test_scope_for_festival_given_parent_returns_self_and_all_children(): void
{
$results = Event::withoutGlobalScopes()
->forFestival($this->festival)
->pluck('id');
$this->assertCount(4, $results);
$this->assertTrue($results->contains($this->festival->id));
$this->assertTrue($results->contains($this->subEvent1->id));
$this->assertTrue($results->contains($this->subEvent2->id));
$this->assertTrue($results->contains($this->subEvent3->id));
}
public function test_scope_for_festival_given_flat_event_returns_only_self(): void
{
$results = Event::withoutGlobalScopes()
->forFestival($this->flatEvent)
->pluck('id');
$this->assertCount(1, $results);
$this->assertTrue($results->contains($this->flatEvent->id));
}
// --- OrganisationScope respect ---
public function test_scopes_respect_organisation_scope(): void
{
$scopedQuery = Event::withoutGlobalScopes()
->withGlobalScope('organisation', new OrganisationScope($this->org->id));
// topLevel within org should not contain other org's event
$topLevel = (clone $scopedQuery)->topLevel()->pluck('id');
$this->assertTrue($topLevel->contains($this->flatEvent->id));
$this->assertTrue($topLevel->contains($this->festival->id));
$this->assertFalse($topLevel->contains($this->otherOrgEvent->id));
// children within org
$children = (clone $scopedQuery)->children()->pluck('id');
$this->assertTrue($children->contains($this->subEvent1->id));
$this->assertFalse($children->contains($this->otherOrgEvent->id));
// withChildren within org
$withChildren = (clone $scopedQuery)->withChildren($this->festival->id)->pluck('id');
$this->assertCount(4, $withChildren);
$this->assertFalse($withChildren->contains($this->otherOrgEvent->id));
// festivals within org
$festivals = (clone $scopedQuery)->festivals()->pluck('id');
$this->assertTrue($festivals->contains($this->festival->id));
$this->assertFalse($festivals->contains($this->otherOrgEvent->id));
// forFestival within org
$forFestival = (clone $scopedQuery)->forFestival($this->subEvent1)->pluck('id');
$this->assertCount(4, $forFestival);
$this->assertFalse($forFestival->contains($this->otherOrgEvent->id));
}
}