Files
myep/tests/Form/Service/ParticipantAssignedRoomFieldHandlerTest.php

342 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Form\Service;
use App\BusProNet\Model\Travel;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Form\Model\RoomSelectionDto;
use App\Form\Service\ParticipantAssignedRoomFieldHandler;
use PHPUnit\Framework\TestCase;
class ParticipantAssignedRoomFieldHandlerTest extends TestCase
{
private ParticipantAssignedRoomFieldHandler $handler;
protected function setUp(): void
{
$this->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);
// Get first notification (notifications are keyed by hash, not numeric index)
$firstNotification = reset($notifications);
$this->assertSame('warning', $firstNotification['type']);
$this->assertStringContainsString('Doppelzimmer', $firstNotification['message']);
$this->assertStringContainsString('wurde von', $firstNotification['message']);
$this->assertStringContainsString('entfernt', $firstNotification['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->id = $roomId;
$selection->label = $label;
$selection->capacity = $capacity;
$selection->quantity = $quantity;
return $selection;
}
}