feat: groups price calculator admin crud, booking/offer flow and api
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\Entity\Groups\AccommodationBooking;
|
||||
use App\Service\AccommodationBookingLinkSigner;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class AccommodationBookingLinkSignerTest extends TestCase
|
||||
{
|
||||
public function testSignThenIsValidLinkRequestSucceeds(): void
|
||||
{
|
||||
$booking = $this->bookingWithAccessLink();
|
||||
$signer = $this->createSigner();
|
||||
|
||||
$signedUrl = $signer->sign($booking);
|
||||
$request = Request::create($signedUrl);
|
||||
|
||||
self::assertTrue($signer->isValidLinkRequest($request, $booking));
|
||||
}
|
||||
|
||||
public function testTamperedQueryParamFails(): void
|
||||
{
|
||||
$booking = $this->bookingWithAccessLink();
|
||||
$signer = $this->createSigner();
|
||||
|
||||
$signedUrl = $signer->sign($booking);
|
||||
self::assertTrue($signer->isValidLinkRequest(Request::create($signedUrl), $booking));
|
||||
|
||||
// Flip the `t` value while keeping the original _hash — signature no longer matches.
|
||||
$tamperedUrl = preg_replace('/(?<=[?&]t=)\d+/', '999999999', $signedUrl);
|
||||
self::assertNotNull($tamperedUrl);
|
||||
|
||||
self::assertFalse($signer->isValidLinkRequest(Request::create($tamperedUrl), $booking));
|
||||
}
|
||||
|
||||
public function testRegeneratedLinkInvalidatesThePreviousOne(): void
|
||||
{
|
||||
$booking = $this->bookingWithAccessLink();
|
||||
$signer = $this->createSigner();
|
||||
|
||||
$signedUrl = $signer->sign($booking);
|
||||
$request = Request::create($signedUrl);
|
||||
|
||||
// Regenerating overwrites accessLinkIssuedAt — the old signed `t` no longer matches.
|
||||
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable('+1 minute'));
|
||||
|
||||
self::assertFalse($signer->isValidLinkRequest($request, $booking));
|
||||
}
|
||||
|
||||
public function testExpiredLinkFails(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable('-91 days'));
|
||||
$signer = $this->createSigner();
|
||||
|
||||
$signedUrl = $signer->sign($booking);
|
||||
$request = Request::create($signedUrl);
|
||||
|
||||
self::assertFalse($signer->isValidLinkRequest($request, $booking));
|
||||
}
|
||||
|
||||
public function testMissingAccessLinkIssuedAtFails(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$signer = $this->createSigner();
|
||||
|
||||
$request = Request::create('https://example.com/groups/booking/offer/'.$booking->getUuid().'?t=123');
|
||||
|
||||
self::assertFalse($signer->isValidLinkRequest($request, $booking));
|
||||
}
|
||||
|
||||
public function testSignThrowsWithoutAccessLinkIssuedAt(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$signer = $this->createSigner();
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
|
||||
$signer->sign($booking);
|
||||
}
|
||||
|
||||
public function testExpiresAtIsNinetyDaysAfterIssuedAt(): void
|
||||
{
|
||||
$issuedAt = new \DateTimeImmutable('2026-01-01T00:00:00+00:00');
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setAccessLinkIssuedAt($issuedAt);
|
||||
$signer = $this->createSigner();
|
||||
|
||||
self::assertSame('2026-04-01T00:00:00+00:00', $signer->expiresAt($booking)?->format(\DATE_ATOM));
|
||||
}
|
||||
|
||||
public function testSessionIsAuthorizedAfterAuthorizeSession(): void
|
||||
{
|
||||
$booking = $this->bookingWithAccessLink();
|
||||
$signer = $this->createSigner();
|
||||
$request = $this->requestWithSession();
|
||||
|
||||
self::assertFalse($signer->isSessionAuthorized($request, $booking));
|
||||
|
||||
$signer->authorizeSession($request, $booking);
|
||||
|
||||
self::assertTrue($signer->isSessionAuthorized($request, $booking));
|
||||
}
|
||||
|
||||
public function testSessionAuthorizationIsPerBooking(): void
|
||||
{
|
||||
$booking = $this->bookingWithAccessLink();
|
||||
$otherBooking = $this->bookingWithAccessLink();
|
||||
$signer = $this->createSigner();
|
||||
$request = $this->requestWithSession();
|
||||
|
||||
$signer->authorizeSession($request, $booking);
|
||||
|
||||
self::assertTrue($signer->isSessionAuthorized($request, $booking));
|
||||
self::assertFalse($signer->isSessionAuthorized($request, $otherBooking));
|
||||
}
|
||||
|
||||
public function testSessionAuthorizationIsRevokedWhenLinkIsRegenerated(): void
|
||||
{
|
||||
$booking = $this->bookingWithAccessLink();
|
||||
$signer = $this->createSigner();
|
||||
$request = $this->requestWithSession();
|
||||
|
||||
$signer->authorizeSession($request, $booking);
|
||||
self::assertTrue($signer->isSessionAuthorized($request, $booking));
|
||||
|
||||
// Regenerating the access link overwrites accessLinkIssuedAt — the
|
||||
// previously-authorized session no longer matches.
|
||||
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable('+1 minute'));
|
||||
|
||||
self::assertFalse($signer->isSessionAuthorized($request, $booking));
|
||||
}
|
||||
|
||||
public function testSessionAuthorizationFailsPastTtlEvenIfSessionMatches(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable('-91 days'));
|
||||
$signer = $this->createSigner();
|
||||
$request = $this->requestWithSession();
|
||||
|
||||
$signer->authorizeSession($request, $booking);
|
||||
|
||||
self::assertFalse($signer->isSessionAuthorized($request, $booking));
|
||||
}
|
||||
|
||||
public function testSessionAuthorizationFailsWithoutAccessLinkIssuedAt(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$signer = $this->createSigner();
|
||||
$request = $this->requestWithSession();
|
||||
|
||||
self::assertFalse($signer->isSessionAuthorized($request, $booking));
|
||||
}
|
||||
|
||||
private function requestWithSession(): Request
|
||||
{
|
||||
$request = Request::create('https://example.com/');
|
||||
$request->setSession(new Session(new MockArraySessionStorage()));
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function bookingWithAccessLink(): AccommodationBooking
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setPaxCount(10);
|
||||
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable());
|
||||
|
||||
return $booking;
|
||||
}
|
||||
|
||||
private function createSigner(): AccommodationBookingLinkSigner
|
||||
{
|
||||
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$urlGenerator
|
||||
->method('generate')
|
||||
->willReturnCallback(static fn (string $name, array $parameters) => sprintf(
|
||||
'https://example.com/groups/booking/offer/%s?t=%s',
|
||||
$parameters['uuid'],
|
||||
$parameters['t'],
|
||||
));
|
||||
|
||||
return new AccommodationBookingLinkSigner($urlGenerator, 'test-secret');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user