seed(RoleSeeder::class); $this->organisation = Organisation::factory()->create(); $this->volunteerCrowdType = CrowdType::factory()->systemType('VOLUNTEER')->create([ 'organisation_id' => $this->organisation->id, ]); $this->event = Event::factory()->create([ 'organisation_id' => $this->organisation->id, 'status' => 'published', ]); $this->user = User::factory()->create(); $this->person = Person::factory()->approved()->create([ 'event_id' => $this->event->id, 'crowd_type_id' => $this->volunteerCrowdType->id, 'user_id' => $this->user->id, 'email' => $this->user->email, ]); } public function test_portal_me_returns_upcoming_shift_for_approved_person(): void { $futureDate = now()->addDays(7)->toDateString(); $timeSlot = TimeSlot::factory()->create([ 'event_id' => $this->event->id, 'date' => $futureDate, 'start_time' => '18:00:00', 'end_time' => '02:00:00', ]); $section = FestivalSection::factory()->create([ 'event_id' => $this->event->id, 'name' => 'Bar Hoofdpodium', ]); $location = Location::factory()->create([ 'event_id' => $this->event->id, 'name' => 'Festivalterrein', ]); $shift = Shift::factory()->create([ 'festival_section_id' => $section->id, 'time_slot_id' => $timeSlot->id, 'location_id' => $location->id, 'title' => 'Tapper', ]); ShiftAssignment::factory()->approved()->create([ 'shift_id' => $shift->id, 'person_id' => $this->person->id, 'time_slot_id' => $timeSlot->id, ]); Sanctum::actingAs($this->user); $response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}"); $response->assertOk(); $response->assertJsonPath('data.upcoming_shift.date', $futureDate); $response->assertJsonPath('data.upcoming_shift.time', '18:00 - 02:00'); $response->assertJsonPath('data.upcoming_shift.title', 'Tapper'); $response->assertJsonPath('data.upcoming_shift.section', 'Bar Hoofdpodium'); $response->assertJsonPath('data.upcoming_shift.location', 'Festivalterrein'); } public function test_portal_me_returns_null_upcoming_shift_when_no_future_shifts(): void { // Create a past shift assignment $pastDate = now()->subDays(7)->toDateString(); $timeSlot = TimeSlot::factory()->create([ 'event_id' => $this->event->id, 'date' => $pastDate, 'start_time' => '18:00:00', 'end_time' => '02:00:00', ]); $section = FestivalSection::factory()->create([ 'event_id' => $this->event->id, ]); $shift = Shift::factory()->create([ 'festival_section_id' => $section->id, 'time_slot_id' => $timeSlot->id, 'title' => 'Tapper', ]); ShiftAssignment::factory()->approved()->create([ 'shift_id' => $shift->id, 'person_id' => $this->person->id, 'time_slot_id' => $timeSlot->id, ]); Sanctum::actingAs($this->user); $response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}"); $response->assertOk(); $response->assertJsonPath('data.upcoming_shift', null); } public function test_portal_me_returns_null_upcoming_shift_for_pending_person(): void { $pendingPerson = Person::factory()->create([ 'event_id' => $this->event->id, 'crowd_type_id' => $this->volunteerCrowdType->id, 'user_id' => null, 'status' => 'pending', ]); $pendingUser = User::factory()->create(['email' => $pendingPerson->email]); $pendingPerson->user_id = $pendingUser->id; $pendingPerson->save(); $futureDate = now()->addDays(7)->toDateString(); $timeSlot = TimeSlot::factory()->create([ 'event_id' => $this->event->id, 'date' => $futureDate, ]); $section = FestivalSection::factory()->create([ 'event_id' => $this->event->id, ]); $shift = Shift::factory()->create([ 'festival_section_id' => $section->id, 'time_slot_id' => $timeSlot->id, ]); // Assignment is pending_approval, not approved ShiftAssignment::factory()->create([ 'shift_id' => $shift->id, 'person_id' => $pendingPerson->id, 'time_slot_id' => $timeSlot->id, 'status' => ShiftAssignmentStatus::PENDING_APPROVAL, ]); Sanctum::actingAs($pendingUser); $response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}"); $response->assertOk(); $response->assertJsonPath('data.upcoming_shift', null); } public function test_portal_me_returns_nearest_future_shift(): void { $nearDate = now()->addDays(3)->toDateString(); $farDate = now()->addDays(10)->toDateString(); $nearTimeSlot = TimeSlot::factory()->create([ 'event_id' => $this->event->id, 'date' => $nearDate, 'start_time' => '10:00:00', 'end_time' => '18:00:00', ]); $farTimeSlot = TimeSlot::factory()->create([ 'event_id' => $this->event->id, 'date' => $farDate, 'start_time' => '18:00:00', 'end_time' => '02:00:00', ]); $section = FestivalSection::factory()->create([ 'event_id' => $this->event->id, ]); $nearShift = Shift::factory()->create([ 'festival_section_id' => $section->id, 'time_slot_id' => $nearTimeSlot->id, 'title' => 'Ochtend Runner', ]); $farShift = Shift::factory()->create([ 'festival_section_id' => $section->id, 'time_slot_id' => $farTimeSlot->id, 'title' => 'Avond Tapper', ]); ShiftAssignment::factory()->approved()->create([ 'shift_id' => $farShift->id, 'person_id' => $this->person->id, 'time_slot_id' => $farTimeSlot->id, ]); ShiftAssignment::factory()->approved()->create([ 'shift_id' => $nearShift->id, 'person_id' => $this->person->id, 'time_slot_id' => $nearTimeSlot->id, ]); Sanctum::actingAs($this->user); $response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}"); $response->assertOk(); $response->assertJsonPath('data.upcoming_shift.title', 'Ochtend Runner'); $response->assertJsonPath('data.upcoming_shift.date', $nearDate); } public function test_portal_me_ignores_cancelled_shift_assignments(): void { $futureDate = now()->addDays(7)->toDateString(); $timeSlot = TimeSlot::factory()->create([ 'event_id' => $this->event->id, 'date' => $futureDate, ]); $section = FestivalSection::factory()->create([ 'event_id' => $this->event->id, ]); $shift = Shift::factory()->create([ 'festival_section_id' => $section->id, 'time_slot_id' => $timeSlot->id, ]); ShiftAssignment::factory()->create([ 'shift_id' => $shift->id, 'person_id' => $this->person->id, 'time_slot_id' => $timeSlot->id, 'status' => ShiftAssignmentStatus::CANCELLED, ]); Sanctum::actingAs($this->user); $response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}"); $response->assertOk(); $response->assertJsonPath('data.upcoming_shift', null); } }