143 lines
4.9 KiB
PHP
143 lines
4.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Service;
|
|
|
|
use App\BusProNet\Constants;
|
|
use App\BusProNet\Model\Booking;
|
|
use App\BusProNet\Model\Service;
|
|
use App\BusProNet\Model\Travel;
|
|
use App\Form\Model\BookingDto;
|
|
use App\Form\Model\ParticipantDto;
|
|
use App\Service\BookingEditDraftMerger;
|
|
use App\Service\ServiceAvailabilityCalculator;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Tests that draft restoration does not re-arm a selection BusPro would reject.
|
|
*
|
|
* A draft can be weeks old. Restoring a selection for a service that has since sold out or
|
|
* moved to 'Anfrage' is what makes a stuck booking stuck: the stale choice comes back on every
|
|
* re-entry and BusPro refuses the whole update again. Services the participant already holds
|
|
* still restore, because withdrawing one breaks the Leistung/Teilnehmer counts.
|
|
*/
|
|
class BookingEditDraftMergerServiceAvailabilityTest extends TestCase
|
|
{
|
|
private BookingEditDraftMerger $merger;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->merger = new BookingEditDraftMerger(new ServiceAvailabilityCalculator());
|
|
}
|
|
|
|
public function testDraftDropsSkiPassThatIsNowOnRequest(): void
|
|
{
|
|
$skiPass = $this->createSkiPass(60, 'Skipass 6 Tage', Constants::STATUS_ON_REQUEST);
|
|
$travel = $this->createTravel([60 => $skiPass]);
|
|
|
|
$participant = new ParticipantDto();
|
|
$participant->mutable = true;
|
|
|
|
$dropped = $this->apply($travel, $participant, ['skiPass' => 60], new Booking());
|
|
|
|
$this->assertNull($participant->skiPass);
|
|
$this->assertSame(['Skipass 6 Tage'], $dropped);
|
|
}
|
|
|
|
public function testDraftRestoresSkiPassThatIsStillFree(): void
|
|
{
|
|
$skiPass = $this->createSkiPass(60, 'Skipass 6 Tage');
|
|
$travel = $this->createTravel([60 => $skiPass]);
|
|
|
|
$participant = new ParticipantDto();
|
|
$participant->mutable = true;
|
|
|
|
$dropped = $this->apply($travel, $participant, ['skiPass' => 60], new Booking());
|
|
|
|
$this->assertSame(60, $participant->skiPass?->id);
|
|
$this->assertSame([], $dropped);
|
|
}
|
|
|
|
public function testDraftRestoresOnRequestSkiPassTheParticipantAlreadyHolds(): void
|
|
{
|
|
$skiPass = $this->createSkiPass(60, 'Skipass 6 Tage', Constants::STATUS_ON_REQUEST);
|
|
$travel = $this->createTravel([60 => $skiPass]);
|
|
|
|
$held = clone $skiPass;
|
|
$held->mapping = [1];
|
|
$booking = new Booking();
|
|
$booking->additionalServices = [60 => $held];
|
|
|
|
$participant = new ParticipantDto();
|
|
$participant->mutable = true;
|
|
|
|
$dropped = $this->apply($travel, $participant, ['skiPass' => 60], $booking);
|
|
|
|
$this->assertSame(60, $participant->skiPass?->id);
|
|
$this->assertSame([], $dropped);
|
|
}
|
|
|
|
public function testDraftDropsOnlyTheUnbookableEntryOfAMultiSelectField(): void
|
|
{
|
|
$bookable = $this->createCourse(70, 'Snowboardkurs');
|
|
$onRequest = $this->createCourse(71, 'Skikurs', Constants::STATUS_ON_REQUEST);
|
|
$travel = $this->createTravel([70 => $bookable, 71 => $onRequest]);
|
|
|
|
$participant = new ParticipantDto();
|
|
$participant->mutable = true;
|
|
|
|
$dropped = $this->apply($travel, $participant, ['courses' => [70, 71]], new Booking());
|
|
|
|
$this->assertSame([70], array_map(static fn (Service $s) => $s->id, $participant->courses));
|
|
$this->assertSame(['Skikurs'], $dropped);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $services
|
|
*
|
|
* @return list<string>
|
|
*/
|
|
private function apply(Travel $travel, ParticipantDto $participant, array $services, Booking $booking): array
|
|
{
|
|
$dto = new BookingDto($travel, 1);
|
|
$dto->participants = [1 => $participant];
|
|
// A non-null booking is what puts the DTO into edit mode
|
|
$dto->booking = $booking;
|
|
|
|
return $this->merger->apply($dto, 1, $participant, ['services' => $services], $travel);
|
|
}
|
|
|
|
/** @param array<int, Service> $additionalServices */
|
|
private function createTravel(array $additionalServices): Travel
|
|
{
|
|
$travel = new Travel();
|
|
$travel->additionalServices = $additionalServices;
|
|
$travel->additionalServicesMutable = true;
|
|
|
|
return $travel;
|
|
}
|
|
|
|
private function createSkiPass(int $id, string $label, string $status = Constants::STATUS_AVAILABLE): Service
|
|
{
|
|
return $this->createService($id, $label, Constants::TOKEN_SKI_PASS, $status);
|
|
}
|
|
|
|
private function createCourse(int $id, string $label, string $status = Constants::STATUS_AVAILABLE): Service
|
|
{
|
|
return $this->createService($id, $label, Constants::TOKEN_COURSES, $status);
|
|
}
|
|
|
|
private function createService(int $id, string $label, string $subType, string $status): Service
|
|
{
|
|
$service = new Service();
|
|
$service->id = $id;
|
|
$service->label = $label;
|
|
$service->subType = $subType;
|
|
$service->status = $status;
|
|
$service->category = Constants::CATEGORY_ADDITIONAL;
|
|
|
|
return $service;
|
|
}
|
|
}
|