Files
myep/tests/Form/Service/ParticipantFieldOptionsProviderEditAvailabilityTest.php
T

175 lines
6.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Form\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\Form\Service\ParticipantFieldOptionsProvider;
use App\Service\BookingPriceCalculator;
use App\Service\InsuranceManager;
use App\Service\ServiceAvailabilityCalculator;
use App\Service\ServiceLabelFormatter;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Edit mode applies the availability rules instead of bypassing them.
*
* Edit used to skip availability entirely for every category except ski passes, so a course
* that had sold out or moved to 'Anfrage' was still offered. Selecting one made BusPro refuse
* the whole update ("Status der Leistung (A) ist unterschiedlich zum Status des Teilnehmers
* (F)"), because the participants of an existing booking are fixed at status 'F'.
*
* Courses stand in for the categories that render read-only rather than dropping the option.
*/
class ParticipantFieldOptionsProviderEditAvailabilityTest extends TestCase
{
private ParticipantFieldOptionsProvider $provider;
protected function setUp(): void
{
$translator = $this->createStub(TranslatorInterface::class);
$translator->method('trans')->willReturnArgument(0);
$this->provider = new ParticipantFieldOptionsProvider(
new ServiceAvailabilityCalculator(),
$this->createStub(InsuranceManager::class),
$this->createStub(BookingPriceCalculator::class),
new ServiceLabelFormatter(),
$translator
);
}
public function testOnRequestCourseIsReadonlyInEditMode(): void
{
$course = $this->createCourse(id: 1, status: Constants::STATUS_ON_REQUEST);
$bookingDto = $this->createEditBookingDto([$course]);
$attributes = $this->getChoiceAttributes($bookingDto, $course);
$this->assertArrayHasKey('readonly', $attributes);
$this->assertSame(
'Diese Leistung ist derzeit nur auf Anfrage buchbar. Bitte kontaktiere uns.',
$attributes['data-tooltip']
);
}
public function testSoldOutCourseIsReadonlyInEditMode(): void
{
$course = $this->createCourse(id: 1, available: 0);
$bookingDto = $this->createEditBookingDto([$course]);
$attributes = $this->getChoiceAttributes($bookingDto, $course);
$this->assertArrayHasKey('readonly', $attributes);
$this->assertSame('ausgebucht', $attributes['data-tooltip']);
}
public function testOnRequestCourseStaysSelectableWhenAlreadyBooked(): void
{
$course = $this->createCourse(id: 1, status: Constants::STATUS_ON_REQUEST);
$bookingDto = $this->createEditBookingDto([$course], heldServiceIds: [1]);
$attributes = $this->getChoiceAttributes($bookingDto, $course);
$this->assertArrayNotHasKey('readonly', $attributes);
$this->assertArrayNotHasKey('data-tooltip', $attributes);
}
public function testUnlimitedCourseIsNotReadonlyInEditMode(): void
{
// A null contingent means unlimited. The previous edit branch inverted this and made
// such a service read-only for anyone who did not already hold it.
$course = $this->createCourse(id: 1, available: null);
$bookingDto = $this->createEditBookingDto([$course]);
$attributes = $this->getChoiceAttributes($bookingDto, $course);
$this->assertArrayNotHasKey('readonly', $attributes);
}
public function testOnRequestCourseStaysSelectableInCreateMode(): void
{
$course = $this->createCourse(id: 1, status: Constants::STATUS_ON_REQUEST);
$bookingDto = $this->createBookingDto([$course]);
$this->assertSame(BookingDto::MODE_CREATE, $bookingDto->getMode());
$attributes = $this->getChoiceAttributes($bookingDto, $course);
$this->assertArrayNotHasKey('readonly', $attributes);
}
/** @return array<string, mixed> */
private function getChoiceAttributes(BookingDto $bookingDto, Service $service): array
{
$options = $this->provider->getFieldOptions('courses', $bookingDto, 0);
return $options['choice_attr']($service);
}
/** @param Service[] $courses */
private function createBookingDto(array $courses): BookingDto
{
$travel = new Travel();
$travel->dateFrom = new \DateTimeImmutable('2027-01-09');
$travel->dateTo = new \DateTimeImmutable('2027-01-16');
foreach ($courses as $course) {
$travel->additionalServices[$course->id] = $course;
}
$bookingDto = new BookingDto($travel, 1);
$participant = new ParticipantDto();
$participant->index = 0;
$participant->dateOfBirth = new \DateTimeImmutable('1990-01-01');
$bookingDto->participants[0] = $participant;
return $bookingDto;
}
/**
* @param Service[] $courses
* @param list<int> $heldServiceIds IDs participant 0 already holds in the booking
*/
private function createEditBookingDto(array $courses, array $heldServiceIds = []): BookingDto
{
$bookingDto = $this->createBookingDto($courses);
$heldServices = [];
foreach ($heldServiceIds as $serviceId) {
$held = clone $bookingDto->travel->additionalServices[$serviceId];
$held->mapping = [0];
$heldServices[$serviceId] = $held;
}
// A non-null booking is what puts the DTO into edit mode
$booking = new Booking();
$booking->additionalServices = $heldServices;
$bookingDto->booking = $booking;
return $bookingDto;
}
private function createCourse(int $id, ?int $available = 10, string $status = Constants::STATUS_AVAILABLE): Service
{
$service = new Service();
$service->id = $id;
$service->label = sprintf('Kurs %d', $id);
$service->subType = Constants::TOKEN_COURSES;
$service->category = Constants::CATEGORY_ADDITIONAL;
$service->price = (float) $id;
$service->available = $available;
$service->status = $status;
return $service;
}
}