150 lines
5.5 KiB
PHP
150 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Repository;
|
|
|
|
use App\Entity\Booking;
|
|
use App\Entity\Resource;
|
|
use App\Entity\ResourceType;
|
|
use App\Entity\User;
|
|
use App\Repository\BookingRepository;
|
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
|
|
/**
|
|
* Hits the real database, because the behaviour under test is the database's: the unique
|
|
* constraint is what actually prevents double-booking.
|
|
*
|
|
* Each test runs inside a transaction that is rolled back afterwards.
|
|
*/
|
|
class BookingRepositoryTest extends KernelTestCase
|
|
{
|
|
private EntityManagerInterface $em;
|
|
private BookingRepository $bookings;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
self::bootKernel();
|
|
$this->em = self::getContainer()->get(EntityManagerInterface::class);
|
|
$this->bookings = self::getContainer()->get(BookingRepository::class);
|
|
$this->em->getConnection()->beginTransaction();
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
$this->em->getConnection()->rollBack();
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function testTheSameResourceCannotBeBookedTwiceOnOneDay(): void
|
|
{
|
|
$desk = $this->resource('D1');
|
|
$date = new \DateTimeImmutable('2026-10-01');
|
|
|
|
$this->persist(new Booking($this->user('[email protected]'), $desk, $date));
|
|
$this->em->flush();
|
|
|
|
$this->persist(new Booking($this->user('[email protected]'), $desk, $date));
|
|
|
|
// The controller catches exactly this and re-renders the fragment with an inline error,
|
|
// rather than pre-checking, which would be racy.
|
|
$this->expectException(UniqueConstraintViolationException::class);
|
|
$this->em->flush();
|
|
}
|
|
|
|
public function testTheSameResourceOnAnotherDayIsFine(): void
|
|
{
|
|
$desk = $this->resource('D1');
|
|
$user = $this->user('[email protected]');
|
|
|
|
$this->persist(new Booking($user, $desk, new \DateTimeImmutable('2026-10-01')));
|
|
$this->persist(new Booking($user, $desk, new \DateTimeImmutable('2026-10-02')));
|
|
$this->em->flush();
|
|
|
|
self::assertCount(1, $this->bookings->findForDate(new \DateTimeImmutable('2026-10-01')));
|
|
}
|
|
|
|
public function testATimeComponentDoesNotCreateASecondSlotForTheSameDay(): void
|
|
{
|
|
$desk = $this->resource('D1');
|
|
|
|
$this->persist(new Booking($this->user('[email protected]'), $desk, new \DateTimeImmutable('2026-10-01 08:00')));
|
|
$this->em->flush();
|
|
|
|
$this->persist(new Booking($this->user('[email protected]'), $desk, new \DateTimeImmutable('2026-10-01 17:30')));
|
|
|
|
$this->expectException(UniqueConstraintViolationException::class);
|
|
$this->em->flush();
|
|
}
|
|
|
|
public function testFindForDateCarriesTheBookingHolderForTheTeamView(): void
|
|
{
|
|
$this->persist(new Booking($this->user('[email protected]', 'Anna'), $this->resource('D1'), new \DateTimeImmutable('2026-10-01')));
|
|
$this->em->flush();
|
|
$this->em->clear();
|
|
|
|
$bookings = $this->bookings->findForDate(new \DateTimeImmutable('2026-10-01'));
|
|
|
|
self::assertCount(1, $bookings);
|
|
self::assertSame('Anna', $bookings[0]->getUser()->getName());
|
|
self::assertSame('D1', $bookings[0]->getResource()->getLabel());
|
|
}
|
|
|
|
public function testCountPerDateInRangeIsKeyedByDay(): void
|
|
{
|
|
$user = $this->user('[email protected]');
|
|
$this->persist(new Booking($user, $this->resource('D1'), new \DateTimeImmutable('2026-10-01')));
|
|
$this->persist(new Booking($this->user('[email protected]'), $this->resource('D2'), new \DateTimeImmutable('2026-10-01')));
|
|
$this->persist(new Booking($user, $this->resource('P1', ResourceType::PARKING), new \DateTimeImmutable('2026-10-03')));
|
|
$this->em->flush();
|
|
|
|
$counts = $this->bookings->countPerDateInRange(new \DateTimeImmutable('2026-10-01'), new \DateTimeImmutable('2026-10-31'));
|
|
|
|
self::assertSame(['2026-10-01' => 2, '2026-10-03' => 1], $counts);
|
|
}
|
|
|
|
public function testCountPerDateIgnoresDaysOutsideTheWindow(): void
|
|
{
|
|
$this->persist(new Booking($this->user('[email protected]'), $this->resource('D1'), new \DateTimeImmutable('2026-11-05')));
|
|
$this->em->flush();
|
|
|
|
self::assertSame([], $this->bookings->countPerDateInRange(new \DateTimeImmutable('2026-10-01'), new \DateTimeImmutable('2026-10-31')));
|
|
}
|
|
|
|
public function testFindForUserInRangeOnlyReturnsThatPersonsBookings(): void
|
|
{
|
|
$anna = $this->user('[email protected]');
|
|
$this->persist(new Booking($anna, $this->resource('D1'), new \DateTimeImmutable('2026-10-01')));
|
|
$this->persist(new Booking($this->user('[email protected]'), $this->resource('D2'), new \DateTimeImmutable('2026-10-01')));
|
|
$this->em->flush();
|
|
|
|
$mine = $this->bookings->findForUserInRange($anna, new \DateTimeImmutable('2026-10-01'), new \DateTimeImmutable('2026-10-31'));
|
|
|
|
self::assertCount(1, $mine);
|
|
self::assertSame('D1', $mine[0]->getResource()->getLabel());
|
|
}
|
|
|
|
private function persist(object $entity): void
|
|
{
|
|
$this->em->persist($entity);
|
|
}
|
|
|
|
private function user(string $email, string $name = 'Test'): User
|
|
{
|
|
$user = new User($email, $name);
|
|
$this->em->persist($user);
|
|
|
|
return $user;
|
|
}
|
|
|
|
private function resource(string $label, ResourceType $type = ResourceType::DESK): Resource
|
|
{
|
|
$resource = new Resource($type, $label);
|
|
$this->em->persist($resource);
|
|
|
|
return $resource;
|
|
}
|
|
}
|