Files
myep/tests/Service/AccommodationBookingPdfGeneratorTest.php
T

125 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Service;
use App\Entity\Groups\Accommodation;
use App\Entity\Groups\AccommodationBooking;
use App\Service\AccommodationBookingBreakdownCalculator;
use App\Service\AccommodationBookingPdfGenerator;
use PHPUnit\Framework\TestCase;
use Twig\Environment;
use Twig\Extra\Intl\IntlExtension;
use Twig\Loader\FilesystemLoader;
/**
* Renders through the real templates and dompdf — the point of this service is that the
* document survives the trip through dompdf, which a mocked Twig would not prove.
*/
class AccommodationBookingPdfGeneratorTest extends TestCase
{
public function testTheDocumentFitsOnASingleA4Page(): void
{
$pdf = $this->createGenerator($this->breakdown())->render($this->booking());
self::assertStringStartsWith('%PDF-', $pdf);
self::assertSame(1, $this->pageCount($pdf));
}
public function testTheDownloadIsAnAttachmentNamedAfterTheGroup(): void
{
$response = $this->createGenerator($this->breakdown())->createDownloadResponse($this->booking());
self::assertSame('application/pdf', $response->headers->get('Content-Type'));
self::assertStringContainsString('attachment;', (string) $response->headers->get('Content-Disposition'));
self::assertStringContainsString('buchung-2026-07-06-schulklasse-7b.pdf', (string) $response->headers->get('Content-Disposition'));
}
public function testABookingWithoutPricesCannotBeRendered(): void
{
$this->expectException(\RuntimeException::class);
$this->createGenerator(null)->render($this->booking());
}
/**
* @param array<string, mixed>|null $breakdown
*/
private function createGenerator(?array $breakdown): AccommodationBookingPdfGenerator
{
$twig = new Environment(new FilesystemLoader(__DIR__.'/../../templates'));
$twig->addExtension(new IntlExtension());
$calculator = $this->createMock(AccommodationBookingBreakdownCalculator::class);
$calculator->method('compute')->willReturn($breakdown);
return new AccommodationBookingPdfGenerator($twig, $calculator, \dirname(__DIR__, 2));
}
private function booking(): AccommodationBooking
{
$accommodation = new Accommodation();
$accommodation->setName('Haus Bergblick');
$accommodation->setMaxAdolescentAge(11);
$accommodation->setCurrency('EUR');
$booking = new AccommodationBooking();
$booking->setAccommodation($accommodation);
$booking->setGroupName('Schulklasse 7b');
$booking->setSalutation('Frau');
$booking->setFirstName('Änne');
$booking->setLastName('Müller');
$booking->setEmail('[email protected]');
$booking->setStreet('Hauptstraße 1');
$booking->setZip('50667');
$booking->setCity('Köln');
$booking->setDateFrom(new \DateTimeImmutable('2026-07-06'));
$booking->setDateTo(new \DateTimeImmutable('2026-07-11'));
$booking->setPaxCount(32);
$booking->setChildrenCount(4);
$booking->setBoardServiceLabel('Vollpension');
$booking->addAdditionalServiceSnapshot('Bettwäsche', 900, 'per_person', 5);
$booking->setRemarks('Zwei Vegetarier.');
$booking->setConfirmedAt(new \DateTimeImmutable('2026-08-18'));
return $booking;
}
/**
* @return array<string, mixed>
*/
private function breakdown(): array
{
return [
'effectivePax' => 32,
'totalPax' => 32,
'includedPax' => 25,
'nights' => 5,
'basePrice' => 250000,
'additionalPersonsPrice' => 70000,
'shortTermSurcharge' => 0,
'undersubscriptionSurcharge' => 0,
'undersubscriptionThreshold' => 40,
'boardPrice' => 480000,
'serviceDetails' => [['label' => 'Bettwäsche', 'price' => 28800]],
'servicesPrice' => 28800,
'runningCosts' => 16000,
'total' => 844800,
'currency' => 'EUR',
'discounts' => [['label' => 'Unterkunft', 'percent' => 5, 'amount' => 16000]],
'discountedTotal' => 828800,
];
}
/**
* dompdf's CPDF backend writes one /MediaBox for the page tree node and one per page,
* so the page count is the number of occurrences minus one. Reading it back this way
* avoids re-implementing the render pipeline just to reach the canvas.
*/
private function pageCount(string $pdf): int
{
return substr_count($pdf, '/MediaBox') - 1;
}
}