feat: offer contact validation flow

This commit is contained in:
2026-09-16 10:40:01 +02:00
parent 5d7ffd7d58
commit 12fe4d8e15
15 changed files with 1219 additions and 554 deletions
@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace App\Tests\Controller\Groups\Offer;
use App\Controller\Groups\Offer\ViewController;
use App\Entity\Groups\Accommodation;
use App\Entity\Groups\AccommodationBooking;
use App\Enum\Groups\AccommodationBookingStatus;
use App\Model\AccommodationBookingContext;
use App\Repository\Groups\AccommodationBookingRepository;
use App\Service\AccommodationBookingLinkSigner;
use App\Service\AccommodationBookingService;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
class ViewControllerTest extends TestCase
{
/**
* The status is re-read on every view, so an authorized session is no free pass: a
* booking pushed back into Entwurf stops showing the offer from that moment on.
*/
#[DataProvider('statusesTheCustomerMustNotSee')]
public function testRendersUnavailableForABookingTheCustomerMustNotSee(AccommodationBookingStatus $status): void
{
$booking = new AccommodationBooking();
$booking->setStatus($status);
$booking->setAccommodation(new Accommodation());
$bookingRepository = $this->createStub(AccommodationBookingRepository::class);
$bookingRepository->method('findOneBy')->willReturn($booking);
$linkSigner = $this->createStub(AccommodationBookingLinkSigner::class);
$linkSigner->method('isSessionAuthorized')->willReturn(true);
$bookingService = $this->createMock(AccommodationBookingService::class);
$bookingService->expects(self::never())->method('createOfferContext');
$controller = new TestableOfferViewController($bookingRepository, $linkSigner, $bookingService);
$request = Request::create('/groups/booking/offer/'.$booking->getUuid().'/view');
$request->setSession(new Session(new MockArraySessionStorage()));
$response = $controller->index($booking->getUuid(), $request);
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
self::assertSame('groups/offer/unavailable.html.twig', $controller->renderedView);
}
/**
* @return iterable<string, array{AccommodationBookingStatus}>
*/
public static function statusesTheCustomerMustNotSee(): iterable
{
yield 'draft' => [AccommodationBookingStatus::Draft];
yield 'requested' => [AccommodationBookingStatus::Requested];
yield 'discarded' => [AccommodationBookingStatus::Discarded];
}
public function testRendersOfferWhenSessionAuthorized(): void
{
$booking = new AccommodationBooking();
$booking->setStatus(AccommodationBookingStatus::Open);
$booking->setAccommodation(new Accommodation());
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
$bookingRepository->method('findOneBy')->with(['uuid' => $booking->getUuid()])->willReturn($booking);
$linkSigner = $this->createStub(AccommodationBookingLinkSigner::class);
$linkSigner->method('isSessionAuthorized')->willReturn(true);
$ctx = new AccommodationBookingContext(accommodation: $booking->getAccommodation(), priceBreakdown: ['total' => 1000]);
$bookingService = $this->createMock(AccommodationBookingService::class);
$bookingService->method('createOfferContext')->with($booking, $booking->getAccommodation())->willReturn($ctx);
$bookingService->expects(self::never())->method('acceptBooking');
$controller = new TestableOfferViewController($bookingRepository, $linkSigner, $bookingService);
$request = Request::create('/groups/booking/offer/'.$booking->getUuid().'/view');
$request->setSession(new Session(new MockArraySessionStorage()));
$response = $controller->index($booking->getUuid(), $request);
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
self::assertSame('groups/offer/view.html.twig', $controller->renderedView);
self::assertSame($booking, $controller->renderedParameters['booking']);
self::assertSame(['total' => 1000], $controller->renderedParameters['priceBreakdown']);
self::assertSame($ctx, $controller->renderedParameters['ctx']);
}
public function testRendersUnavailableWhenSessionNotAuthorized(): void
{
$booking = new AccommodationBooking();
$bookingRepository = $this->createStub(AccommodationBookingRepository::class);
$bookingRepository->method('findOneBy')->willReturn($booking);
$linkSigner = $this->createStub(AccommodationBookingLinkSigner::class);
$linkSigner->method('isSessionAuthorized')->willReturn(false);
$controller = new TestableOfferViewController(
$bookingRepository,
$linkSigner,
$this->createStub(AccommodationBookingService::class),
);
$response = $controller->index($booking->getUuid(), Request::create('/groups/booking/offer/'.$booking->getUuid().'/view'));
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
self::assertSame('groups/offer/unavailable.html.twig', $controller->renderedView);
}
}
final class TestableOfferViewController extends ViewController
{
public ?string $renderedView = null;
/** @var array<string, mixed> */
public array $renderedParameters = [];
protected function render(string $view, array $parameters = [], ?Response $response = null): Response
{
$this->renderedView = $view;
$this->renderedParameters = $parameters;
return $response ?? new Response('<html></html>');
}
}