calculator = new ServiceAvailabilityCalculator(); } public function testParkingServiceIsUnavailableWhenSoldOut(): void { $parkingService = $this->createParkingService(id: 1, available: 0); $bookingDto = $this->createBookingDtoWithServices([$parkingService]); $this->assertTrue($this->calculator->isServiceUnavailable(1, $bookingDto, 0)); } public function testParkingServiceIsAvailableWhenQuotaRemains(): void { $parkingService = $this->createParkingService(id: 1, available: 5); $bookingDto = $this->createBookingDtoWithServices([$parkingService]); $this->assertFalse($this->calculator->isServiceUnavailable(1, $bookingDto, 0)); } public function testParkingSelectedByOtherParticipantCountsTowardUsage(): void { $parkingService = $this->createParkingService(id: 1, available: 1); $bookingDto = $this->createBookingDtoWithServices([$parkingService]); $otherParticipant = new ParticipantDto(); $otherParticipant->parkingService = $parkingService; $bookingDto->participants[1] = $otherParticipant; // Only one parking slot remains, and another participant already claimed it, // so it must show unavailable for the current participant (index 0) $this->assertTrue($this->calculator->isServiceUnavailable(1, $bookingDto, 0)); } public function testServiceOnBookingStopIsUnavailableDespiteUnlimitedQuota(): void { $skiPass = $this->createSkiPass(id: 1, available: null, status: Constants::STATUS_BLOCKED); $bookingDto = $this->createBookingDtoWithServices([$skiPass]); $this->assertTrue($this->calculator->isServiceUnavailable(1, $bookingDto, 0)); } public function testServiceOnBookingStopIsUnavailableDespiteRemainingQuota(): void { $skiPass = $this->createSkiPass(id: 1, available: 10, status: Constants::STATUS_BLOCKED); $bookingDto = $this->createBookingDtoWithServices([$skiPass]); $this->assertTrue($this->calculator->isServiceUnavailable(1, $bookingDto, 0)); } public function testFilterAvailableServicesRemovesSoldOutAndBlockedServices(): void { $bookable = $this->createSkiPass(id: 1, available: 5); $soldOut = $this->createSkiPass(id: 2, available: 0); $blocked = $this->createSkiPass(id: 3, available: null, status: Constants::STATUS_BLOCKED); $unlimited = $this->createSkiPass(id: 4, available: null); $bookingDto = $this->createBookingDtoWithServices([$bookable, $soldOut, $blocked, $unlimited]); $filtered = $this->calculator->filterAvailableServices( $bookingDto->travel->additionalServices, $bookingDto, 0 ); $this->assertSame([1, 4], array_keys($filtered)); } private function createParkingService(int $id, int $available): Service { $service = new Service(); $service->id = $id; $service->label = 'Parkplatz'; $service->subType = Constants::TOKEN_PARKING; $service->available = $available; return $service; } private function createSkiPass(int $id, ?int $available, string $status = Constants::STATUS_AVAILABLE): Service { $service = new Service(); $service->id = $id; $service->label = sprintf('Skipass %d', $id); $service->subType = Constants::TOKEN_SKI_PASS; $service->available = $available; $service->status = $status; return $service; } private function createBookingDtoWithServices(array $services): BookingDto { $travel = new Travel(); $indexed = []; foreach ($services as $service) { $indexed[$service->id] = $service; } $travel->additionalServices = $indexed; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants[0] = new ParticipantDto(); return $bookingDto; } }