handler = new ParticipantAssignedRoomFieldHandler(); } public function testGetFieldName(): void { $this->assertSame('assignedRoomId', $this->handler->getFieldName()); } public function testGetDependencies(): void { $this->assertSame([], $this->handler->getDependencies()); } public function testShouldProcessReturnsTrueWhenFieldPresent(): void { $this->assertTrue($this->handler->shouldProcess(['assignedRoomId' => '1'], BookingDto::MODE_CREATE, 0)); $this->assertTrue($this->handler->shouldProcess(['assignedRoomId' => ''], BookingDto::MODE_EDIT, 5)); } public function testShouldProcessReturnsFalseWhenFieldNotPresent(): void { $this->assertFalse($this->handler->shouldProcess([], BookingDto::MODE_CREATE, 0)); $this->assertFalse($this->handler->shouldProcess(['some' => 'data'], BookingDto::MODE_EDIT, 5)); } public function testProcessFieldWithoutParticipant(): void { $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $submittedData = ['assignedRoomId' => '1']; $this->handler->processField($submittedData, $bookingDto, 0); // Should handle gracefully when participant doesn't exist $this->expectNotToPerformAssertions(); } public function testProcessFieldAssignsRoomWithAvailableCapacity(): void { $participant = new ParticipantDto(); $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $bookingDto->roomSelections = [$this->createRoomSelection(1, 'Doppelzimmer', 2, 1)]; $submittedData = ['assignedRoomId' => '1']; $this->handler->processField($submittedData, $bookingDto, 0); $this->assertSame(1, $participant->assignedRoomId); } public function testProcessFieldUnassignsParticipantWhenRoomIdIsEmpty(): void { $participant = new ParticipantDto(); $participant->assignedRoomId = 1; $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $submittedData = ['assignedRoomId' => '']; $this->handler->processField($submittedData, $bookingDto, 0); $this->assertNull($participant->assignedRoomId); } public function testProcessFieldUnassignsParticipantWhenRoomIdIsNull(): void { $participant = new ParticipantDto(); $participant->assignedRoomId = 1; $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $submittedData = ['assignedRoomId' => null]; $this->handler->processField($submittedData, $bookingDto, 0); $this->assertNull($participant->assignedRoomId); } public function testProcessFieldUnassignsHighestIndexParticipantWhenRoomAtCapacity(): void { $participant0 = new ParticipantDto(); $participant0->assignedRoomId = 1; $participant1 = new ParticipantDto(); $participant1->assignedRoomId = 1; $participant2 = new ParticipantDto(); $participant2->assignedRoomId = null; $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant0, $participant1, $participant2]; // 1x "Doppelzimmer" (capacity 2) - room is at capacity with participants 0 and 1 $bookingDto->roomSelections = [$this->createRoomSelection(1, 'Doppelzimmer', 2, 1)]; $submittedData = ['assignedRoomId' => '1']; // Participant 2 tries to select room 1 $this->handler->processField($submittedData, $bookingDto, 2); // Participant 1 (highest index with room 1) should be unassigned $this->assertSame(1, $participant0->assignedRoomId); // Still assigned (applicant protection) $this->assertNull($participant1->assignedRoomId); // Unassigned $this->assertSame(1, $participant2->assignedRoomId); // Newly assigned } public function testProcessFieldGeneratesWarningNotificationForUnassignedParticipant(): void { $participant0 = new ParticipantDto(); $participant0->assignedRoomId = 1; $participant1 = new ParticipantDto(); $participant1->assignedRoomId = 1; $participant2 = new ParticipantDto(); $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant0, $participant1, $participant2]; $bookingDto->roomSelections = [$this->createRoomSelection(1, 'Doppelzimmer', 2, 1)]; $submittedData = ['assignedRoomId' => '1']; $this->handler->processField($submittedData, $bookingDto, 2); // Participant 1 should have a warning notification $notifications = $participant1->notifications; $this->assertCount(1, $notifications); $this->assertSame('warning', $notifications[0]['type']); $this->assertStringContainsString('Doppelzimmer', $notifications[0]['message']); $this->assertStringContainsString('wurde von', $notifications[0]['message']); $this->assertStringContainsString('entfernt', $notifications[0]['message']); } public function testProcessFieldDoesNotGenerateNotificationForAssignedParticipant(): void { $participant0 = new ParticipantDto(); $participant0->assignedRoomId = 1; $participant1 = new ParticipantDto(); $participant1->assignedRoomId = 1; $participant2 = new ParticipantDto(); $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant0, $participant1, $participant2]; $bookingDto->roomSelections = [$this->createRoomSelection(1, 'Doppelzimmer', 2, 1)]; $submittedData = ['assignedRoomId' => '1']; $this->handler->processField($submittedData, $bookingDto, 2); // Participant 2 (the one being assigned) should NOT have a notification (user-initiated action) $this->assertEmpty($participant2->notifications); } public function testProcessFieldHandlesRoomChangeWithoutConflict(): void { $participant0 = new ParticipantDto(); $participant0->assignedRoomId = 1; $participant1 = new ParticipantDto(); $participant1->assignedRoomId = 1; $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant0, $participant1]; // 2x "Doppelzimmer" (capacity 2 each = 4 total beds) $bookingDto->roomSelections = [$this->createRoomSelection(1, 'Doppelzimmer', 2, 2)]; $submittedData = ['assignedRoomId' => '1']; // Participant 0 changes room (still room 1, but should not trigger conflict) $this->handler->processField($submittedData, $bookingDto, 0); // Both should remain assigned (participant 0's own slot was freed, so no conflict) $this->assertSame(1, $participant0->assignedRoomId); $this->assertSame(1, $participant1->assignedRoomId); $this->assertEmpty($participant1->notifications); } public function testProcessFieldUnassignsMultipleParticipantsWhenNeeded(): void { $participant0 = new ParticipantDto(); $participant0->assignedRoomId = 1; $participant1 = new ParticipantDto(); $participant1->assignedRoomId = 1; $participant2 = new ParticipantDto(); $participant2->assignedRoomId = 1; $participant3 = new ParticipantDto(); $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant0, $participant1, $participant2, $participant3]; // 1x "Einzelzimmer" (capacity 1) - only 1 bed available $bookingDto->roomSelections = [$this->createRoomSelection(1, 'Einzelzimmer', 1, 1)]; $submittedData = ['assignedRoomId' => '1']; // Participant 3 tries to select room 1 $this->handler->processField($submittedData, $bookingDto, 3); // All three participants with room 1 should be unassigned (highest indices first: 2, 1, 0) // But we only need to unassign enough to make space (3 participants, only 1 space needed) $this->assertNull($participant2->assignedRoomId); // Highest index, unassigned first $this->assertNull($participant1->assignedRoomId); // Second highest, unassigned second $this->assertNull($participant0->assignedRoomId); // Third highest, unassigned third $this->assertSame(1, $participant3->assignedRoomId); // Newly assigned } public function testProcessFieldHandlesNoConflictWithMultipleRoomQuantities(): void { $participant0 = new ParticipantDto(); $participant0->assignedRoomId = 1; $participant1 = new ParticipantDto(); $participant1->assignedRoomId = 1; $participant2 = new ParticipantDto(); $participant2->assignedRoomId = 1; $participant3 = new ParticipantDto(); $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant0, $participant1, $participant2, $participant3]; // 2x "Doppelzimmer" (capacity 2 each = 4 total beds) $bookingDto->roomSelections = [$this->createRoomSelection(1, 'Doppelzimmer', 2, 2)]; $submittedData = ['assignedRoomId' => '1']; // Participant 3 tries to select room 1 $this->handler->processField($submittedData, $bookingDto, 3); // All should remain assigned (4 beds available, 4 participants) $this->assertSame(1, $participant0->assignedRoomId); $this->assertSame(1, $participant1->assignedRoomId); $this->assertSame(1, $participant2->assignedRoomId); $this->assertSame(1, $participant3->assignedRoomId); $this->assertEmpty($participant0->notifications); $this->assertEmpty($participant1->notifications); $this->assertEmpty($participant2->notifications); } public function testProcessFieldHandlesComplexScenarioWithMultipleRoomTypes(): void { $participant0 = new ParticipantDto(); $participant0->assignedRoomId = 1; // Doppelzimmer $participant1 = new ParticipantDto(); $participant1->assignedRoomId = 1; // Doppelzimmer $participant2 = new ParticipantDto(); $participant2->assignedRoomId = 2; // Einzelzimmer $participant3 = new ParticipantDto(); $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant0, $participant1, $participant2, $participant3]; $bookingDto->roomSelections = [ $this->createRoomSelection(1, 'Doppelzimmer', 2, 1), // 1x Doppelzimmer (capacity 2) $this->createRoomSelection(2, 'Einzelzimmer', 1, 1), // 1x Einzelzimmer (capacity 1) ]; $submittedData = ['assignedRoomId' => '1']; // Participant 3 tries to select Doppelzimmer (already at capacity with 0 and 1) $this->handler->processField($submittedData, $bookingDto, 3); // Participant 1 (highest index with room 1) should be unassigned $this->assertSame(1, $participant0->assignedRoomId); $this->assertNull($participant1->assignedRoomId); // Unassigned $this->assertSame(2, $participant2->assignedRoomId); // Still in Einzelzimmer $this->assertSame(1, $participant3->assignedRoomId); // Newly assigned to Doppelzimmer } public function testProcessFieldHandlesEdgeCaseWhenRoomNotFoundInSelections(): void { $participant = new ParticipantDto(); $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $bookingDto->roomSelections = [$this->createRoomSelection(1, 'Doppelzimmer', 2, 1)]; // Try to assign room 999 (not in selections) $submittedData = ['assignedRoomId' => '999']; $this->handler->processField($submittedData, $bookingDto, 0); // Should still assign (no conflict detection possible, but assignment proceeds) $this->assertSame(999, $participant->assignedRoomId); } /** * Helper method to create a RoomSelectionDto for testing. */ private function createRoomSelection(int $roomId, string $label, int $capacity, int $quantity): RoomSelectionDto { $selection = new RoomSelectionDto(); $selection->roomId = $roomId; $selection->roomLabel = $label; $selection->capacity = $capacity; $selection->quantity = $quantity; return $selection; } }